ticket-2618-patches-20031207
[openafs.git] / src / kauth / kpasswd.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  * 
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 /* These two needed for rxgen output to work */
11 #include <afsconfig.h>
12 #include <afs/param.h>
13
14 RCSID
15     ("$Header$");
16
17 #include <afs/stds.h>
18 #include <sys/types.h>
19 #ifdef  AFS_AIX32_ENV
20 #include <signal.h>
21 #endif
22
23 #include <rx/xdr.h>
24
25 #include <lock.h>
26 #include <ubik.h>
27
28 #include <stdio.h>
29 #ifndef AFS_NT40_ENV
30 #include <pwd.h>
31 #endif
32 #ifdef HAVE_STRING_H
33 #include <string.h>
34 #else
35 #ifdef HAVE_STRINGS_H
36 #include <strings.h>
37 #endif
38 #endif
39 #include <signal.h>
40 #include <afs/com_err.h>
41 #include <afs/auth.h>
42 #include <afs/cellconfig.h>
43 #include <afs/cmd.h>
44 #include "kauth.h"
45 #include "kautils.h"
46 #ifndef AFS_NT40_ENV
47 #include <unistd.h>
48 #endif
49 #include <limits.h>
50
51
52 /* This code borrowed heavily from the log program.  Here is the intro comment
53  * for that program: */
54
55 /*
56         log -- tell the Andrew Cache Manager your password
57         5 June 1985
58         modified February 1986
59
60         Further modified in August 1987 to understand cell IDs.
61  */
62
63 /* Current Usage:
64      kpasswd [user [password] [newpassword]] [-c cellname] [-servers <hostlist>]
65
66      where:
67        principal is of the form 'name' or 'name@cell' which provides the
68           cellname.  See the -c option below.
69        password is the user's password.  This form is NOT recommended for
70           interactive users.
71        newpassword is the new password.  This form is NOT recommended for
72           interactive users.
73        -c identifies cellname as the cell in which authentication is to take
74           place.
75        -servers allows the explicit specification of the hosts providing
76           authentication services for the cell being used for authentication.
77           This is a debugging option and will disappear.
78  */
79
80 /* The following code to make use of libcmd.a also stolen from klog.c. */
81
82 int CommandProc();
83
84 static int zero_argc;
85 static char **zero_argv;
86 extern int init_child(), give_to_child(), terminate_child();
87
88 #ifdef AFS_NT40_ENV
89 struct passwd {
90     char *pw_name;
91 };
92 char userName[128];
93 DWORD userNameLen;
94 #endif
95
96 main(argc, argv, envp)
97      int argc;
98      char *argv[];
99      char **envp;
100 {
101     struct cmd_syndesc *ts;
102     afs_int32 code;
103
104 #ifdef  AFS_AIX32_ENV
105     /*
106      * The following signal action for AIX is necessary so that in case of a 
107      * crash (i.e. core is generated) we can include the user's data section 
108      * in the core dump. Unfortunately, by default, only a partial core is
109      * generated which, in many cases, isn't too useful.
110      */
111     struct sigaction nsa;
112
113     sigemptyset(&nsa.sa_mask);
114     nsa.sa_handler = SIG_DFL;
115     nsa.sa_flags = SA_FULLDUMP;
116     sigaction(SIGSEGV, &nsa, NULL);
117 #endif
118
119     zero_argc = argc;
120     zero_argv = argv;
121
122     init_child(*argv);
123     ts = cmd_CreateSyntax(NULL, CommandProc, 0, "change user's password");
124
125 #define aXFLAG 0
126 #define aPRINCIPAL 1
127 #define aPASSWORD 2
128 #define aNEWPASSWORD 3
129 #define aCELL 4
130 #define aSERVERS 5
131 #define aPIPE 6
132
133     cmd_AddParm(ts, "-x", CMD_FLAG, CMD_OPTIONAL, "(obsolete, noop)");
134     cmd_AddParm(ts, "-principal", CMD_SINGLE, CMD_OPTIONAL, "user name");
135     cmd_AddParm(ts, "-password", CMD_SINGLE, CMD_OPTIONAL, "user's password");
136     cmd_AddParm(ts, "-newpassword", CMD_SINGLE, CMD_OPTIONAL,
137                 "user's new password");
138     cmd_AddParm(ts, "-cell", CMD_SINGLE, CMD_OPTIONAL, "cell name");
139     cmd_AddParm(ts, "-servers", CMD_LIST, CMD_OPTIONAL,
140                 "explicit list of servers");
141     cmd_AddParm(ts, "-pipe", CMD_FLAG, CMD_OPTIONAL, "silent operation");
142
143     code = cmd_Dispatch(argc, argv);
144     exit(code != 0);
145 }
146
147
148 static void
149 getpipepass(gpbuf, len)
150      char *gpbuf;
151      int len;
152 {
153     /* read a password from stdin, stop on \n or eof */
154     register int i, tc;
155     memset(gpbuf, 0, len);
156     for (i = 0; i < len; i++) {
157         tc = fgetc(stdin);
158         if (tc == '\n' || tc == EOF)
159             break;
160         gpbuf[i] = tc;
161     }
162     return;
163 }
164
165 static afs_int32
166 read_pass(passwd, len, prompt, verify)
167      char *passwd;
168      int len;
169      char *prompt;
170      int verify;
171 {
172     afs_int32 code;
173     code = read_pw_string(passwd, len, prompt, verify);
174     if (code == -1) {
175         getpipepass(passwd, len);
176         return 0;
177     }
178     return code;
179 }
180
181 static int
182 password_ok(newpw, insist)
183      char *newpw;
184      int *insist;
185 {
186     if (insist == 0) {
187         /* see if it is reasonable, but don't get so obnoxious */
188         /* FIXME: null pointer derefence!!! */
189         (*insist)++;            /* so we don't get called again */
190         if (strlen(newpw) < 6)
191             return 0;
192     }
193     return 1;                   /* lie about it */
194 }
195
196 static char rn[] = "kpasswd";   /* Routine name */
197 static int Pipe = 0;            /* reading from a pipe */
198
199 #if TIMEOUT
200 int
201 timedout()
202 {
203     if (!Pipe)
204         fprintf(stderr, "%s: timed out\n", rn);
205     exit(1);
206 }
207 #endif
208
209 char passwd[BUFSIZ], npasswd[BUFSIZ], verify[BUFSIZ];
210 CommandProc(as, arock)
211      char *arock;
212      struct cmd_syndesc *as;
213 {
214     char name[MAXKTCNAMELEN];
215     char instance[MAXKTCNAMELEN];
216     char cell[MAXKTCREALMLEN];
217     char realm[MAXKTCREALMLEN];
218     afs_int32 serverList[MAXSERVERS];
219     char *lcell;                /* local cellname */
220     int code;
221     int i;
222
223     struct ubik_client *conn = 0;
224     struct ktc_encryptionKey key;
225     struct ktc_encryptionKey mitkey;
226     struct ktc_encryptionKey newkey;
227     struct ktc_encryptionKey newmitkey;
228
229     struct ktc_token token;
230
231     struct passwd pwent;
232     struct passwd *pw = &pwent;
233
234     int insist;                 /* insist on good password quality */
235     int lexplicit = 0;          /* servers specified explicitly */
236     int local;                  /* explicit cell is same a local cell */
237     int foundPassword = 0;      /*Not yet, anyway */
238     int foundNewPassword = 0;   /*Not yet, anyway */
239     int foundExplicitCell = 0;  /*Not yet, anyway */
240 #ifdef DEFAULT_MITV4_STRINGTOKEY
241     int dess2k = 1;
242 #elif DEFAULT_AFS_STRINGTOKEY
243     int dess2k = 0;
244 #else
245     int dess2k = -1;
246 #endif
247
248     /* blow away command line arguments */
249     for (i = 1; i < zero_argc; i++)
250         memset(zero_argv[i], 0, strlen(zero_argv[i]));
251     zero_argc = 0;
252
253     /* first determine quiet flag based on -pipe switch */
254     Pipe = (as->parms[aPIPE].items ? 1 : 0);
255
256 #if TIMEOUT
257     signal(SIGALRM, timedout);
258     alarm(30);
259 #endif
260
261     code = ka_Init(0);
262     if (code || !(lcell = ka_LocalCell())) {
263 #ifndef AFS_FREELANCE_CLIENT
264         if (!Pipe)
265             com_err(rn, code, "Can't get local cell name!");
266         exit(1);
267 #endif
268     }
269
270     code = rx_Init(0);
271     if (code) {
272         if (!Pipe)
273             com_err(rn, code, "Failed to initialize Rx");
274         exit(1);
275     }
276
277     strcpy(instance, "");
278
279     /* Parse our arguments. */
280
281     if (as->parms[aCELL].items) {
282         /*
283          * cell name explicitly mentioned; take it in if no other cell name
284          * has already been specified and if the name actually appears.  If
285          * the given cell name differs from our own, we don't do a lookup.
286          */
287         foundExplicitCell = 1;
288         strncpy(realm, as->parms[aCELL].items->data, sizeof(realm));
289     }
290
291     if (as->parms[aSERVERS].items) {
292         /* explicit server list */
293         int i;
294         struct cmd_item *ip;
295         char *ap[MAXSERVERS + 2];
296
297         for (ip = as->parms[aSERVERS].items, i = 2; ip; ip = ip->next, i++)
298             ap[i] = ip->data;
299         ap[0] = "";
300         ap[1] = "-servers";
301         code = ubik_ParseClientList(i, ap, serverList);
302         if (code) {
303             if (!Pipe)
304                 com_err(rn, code, "could not parse server list");
305             return code;
306         }
307         lexplicit = 1;
308     }
309
310     if (as->parms[aPRINCIPAL].items) {
311         ka_ParseLoginName(as->parms[aPRINCIPAL].items->data, name, instance,
312                           cell);
313         if (strlen(instance) > 0)
314             if (!Pipe)
315                 fprintf(stderr,
316                         "Non-null instance (%s) may cause strange behavior.\n",
317                         instance);
318         if (strlen(cell) > 0) {
319             if (foundExplicitCell) {
320                 if (!Pipe)
321                     fprintf(stderr,
322                             "%s: May not specify an explicit cell twice.\n",
323                             rn);
324                 return -1;
325             }
326             foundExplicitCell = 1;
327             strncpy(realm, cell, sizeof(realm));
328         }
329         pw->pw_name = name;
330     } else {
331         /* No explicit name provided: use Unix uid. */
332 #ifdef AFS_NT40_ENV
333         userNameLen = 128;
334         if (GetUserName(userName, &userNameLen) == 0) {
335             if (!Pipe) {
336                 fprintf(stderr,
337                         "Can't figure out your name in local cell %s from your user id.\n",
338                         lcell);
339                 fprintf(stderr, "Try providing the user name.\n");
340             }
341             exit(1);
342         }
343         pw->pw_name = userName;
344 #else
345         pw = getpwuid(getuid());
346         if (pw == 0) {
347             if (!Pipe) {
348                 fprintf(stderr,
349                         "Can't figure out your name in local cell %s from your user id.\n",
350                         lcell);
351                 fprintf(stderr, "Try providing the user name.\n");
352             }
353             exit(1);
354         }
355 #endif
356     }
357
358     if (as->parms[aPASSWORD].items) {
359         /*
360          * Current argument is the desired password string.  Remember it in
361          * our local buffer, and zero out the argument string - anyone can
362          * see it there with ps!
363          */
364         foundPassword = 1;
365         strncpy(passwd, as->parms[aPASSWORD].items->data, sizeof(passwd));
366         memset(as->parms[aPASSWORD].items->data, 0,
367                strlen(as->parms[aPASSWORD].items->data));
368     }
369
370     if (as->parms[aNEWPASSWORD].items) {
371         /*
372          * Current argument is the new password string.  Remember it in
373          * our local buffer, and zero out the argument string - anyone can
374          * see it there with ps!
375          */
376         foundNewPassword = 1;
377         strncpy(npasswd, as->parms[aNEWPASSWORD].items->data,
378                 sizeof(npasswd));
379         memset(as->parms[aNEWPASSWORD].items->data, 0,
380                strlen(as->parms[aNEWPASSWORD].items->data));
381     }
382 #ifdef AFS_FREELANCE_CLIENT
383     if (!foundExplicitCell && !lcell) {
384         if (!Pipe)
385             com_err(rn, code, "no cell name provided");
386         exit(1);
387     }
388 #else
389     if (!foundExplicitCell)
390         strcpy(realm, lcell);
391 #endif /* freelance */
392
393     if (code = ka_CellToRealm(realm, realm, &local)) {
394         if (!Pipe)
395             com_err(rn, code, "Can't convert cell to realm");
396         exit(1);
397     }
398     lcstring(cell, realm, sizeof(cell));
399
400     ka_PrintUserID("Changing password for '", pw->pw_name, instance, "'");
401     printf(" in cell '%s'.\n", cell);
402
403     /* Get the password if it wasn't provided. */
404     if (!foundPassword) {
405         if (Pipe)
406             getpipepass(passwd, sizeof(passwd));
407         else {
408             code = read_pass(passwd, sizeof(passwd), "Old password: ", 0);
409             if (code || (strlen(passwd) == 0)) {
410                 if (code)
411                     code = KAREADPW;
412                 memset(&mitkey, 0, sizeof(mitkey));
413                 memset(&key, 0, sizeof(key));
414                 memset(passwd, 0, sizeof(passwd));
415                 if (code)
416                     com_err(rn, code, "reading password");
417                 exit(1);
418             }
419         }
420     }
421     ka_StringToKey(passwd, realm, &key);
422     des_string_to_key(passwd, &mitkey);
423     give_to_child(passwd);
424
425     /* Get new password if it wasn't provided. */
426     insist = 0;
427     if (!foundNewPassword) {
428         if (Pipe)
429             getpipepass(npasswd, sizeof(npasswd));
430         else {
431             do {
432                 code =
433                     read_pass(npasswd, sizeof(npasswd),
434                               "New password (RETURN to abort): ", 0);
435                 if (code || (strlen(npasswd) == 0)) {
436                     if (code)
437                         code = KAREADPW;
438                     goto no_change;
439
440                 }
441             } while (password_bad(npasswd));
442
443             code =
444                 read_pass(verify, sizeof(verify), "Retype new password: ", 0);
445             if (code) {
446                 code = KAREADPW;
447                 goto no_change;
448             }
449             if (strcmp(verify, npasswd) != 0) {
450                 printf("Mismatch - ");
451                 goto no_change;
452             }
453             memset(verify, 0, sizeof(verify));
454         }
455     }
456     if (code = password_bad(npasswd)) { /* assmt here! */
457         goto no_change_no_msg;
458     }
459 #if TRUNCATEPASSWORD
460     if (strlen(npasswd) > 8) {
461         npasswd[8] = 0;
462         fprintf(stderr,
463                 "%s: password too long, only the first 8 chars will be used.\n",
464                 rn);
465     } else
466         npasswd[8] = 0;         /* in case the password was exactly 8 chars long */
467 #endif
468     ka_StringToKey(npasswd, realm, &newkey);
469     des_string_to_key(npasswd, &newmitkey);
470     memset(npasswd, 0, sizeof(npasswd));
471
472     if (lexplicit)
473         ka_ExplicitCell(realm, serverList);
474
475     /* Get an connection to kaserver's admin service in desired cell.  Set the
476      * lifetime above the time uncertainty so that badly skewed clocks are
477      * reported when the ticket is decrypted.  Then give us 10 seconds to
478      * actually get our work done if the clocks are skewed by only 14:59.
479      * NOTE: Kerberos lifetime encoding will round this up to next 5 minute
480      * interval, namely 20 minutes. */
481
482 #define ADMIN_LIFETIME (KTC_TIME_UNCERTAINTY+1)
483
484     code =
485         ka_GetAdminToken(pw->pw_name, instance, realm, &key, ADMIN_LIFETIME,
486                          &token, /*!new */ 0);
487     if (code == KABADREQUEST) {
488         code =
489             ka_GetAdminToken(pw->pw_name, instance, realm, &mitkey,
490                              ADMIN_LIFETIME, &token, /*!new */ 0);
491         if ((code == KABADREQUEST) && (strlen(passwd) > 8)) {
492             /* try with only the first 8 characters incase they set their password
493              * with an old style passwd program. */
494             char pass8[9];
495             strncpy(pass8, passwd, 8);
496             pass8[8] = 0;
497             ka_StringToKey(pass8, realm, &key);
498             memset(pass8, 0, sizeof(pass8));
499             memset(passwd, 0, sizeof(passwd));
500             code = ka_GetAdminToken(pw->pw_name, instance, realm, &key, ADMIN_LIFETIME, &token, /*!new */
501                                     0);
502 #ifdef notdef
503             /* the folks in testing really *hate* this message */
504             if (code == 0) {
505                 fprintf(stderr,
506                         "Warning: only the first 8 characters of your old password were significant.\n");
507             }
508 #endif
509             if (code == 0) {
510                 if (dess2k == -1)
511                     dess2k = 0;
512             }
513         } else {
514             if (dess2k == -1)
515                 dess2k = 1;
516         }
517     } else {
518         if (dess2k == -1)
519             dess2k = 0;
520     }
521
522     memset(&mitkey, 0, sizeof(mitkey));
523     memset(&key, 0, sizeof(key));
524     if (code == KAUBIKCALL)
525         com_err(rn, code, "(Authentication Server unavailable, try later)");
526     else if (code) {
527         if (code == KABADREQUEST)
528             fprintf(stderr, "%s: Incorrect old password.\n", rn);
529         else
530             com_err(rn, code, "so couldn't change password");
531     } else {
532         code =
533             ka_AuthServerConn(realm, KA_MAINTENANCE_SERVICE, &token, &conn);
534         if (code)
535             com_err(rn, code, "contacting Admin Server");
536         else {
537             if (dess2k == 1)
538                 code =
539                     ka_ChangePassword(pw->pw_name, instance, conn, 0,
540                                       &newmitkey);
541             else
542                 code =
543                     ka_ChangePassword(pw->pw_name, instance, conn, 0,
544                                       &newkey);
545             memset(&newkey, 0, sizeof(newkey));
546             memset(&newmitkey, 0, sizeof(newmitkey));
547             if (code) {
548                 char *reason;
549                 reason = (char *)error_message(code);
550                 fprintf(stderr, "%s: Password was not changed because %s\n",
551                         rn, reason);
552             } else
553                 printf("Password changed.\n\n");
554         }
555     }
556     memset(&newkey, 0, sizeof(newkey));
557     memset(&newmitkey, 0, sizeof(newmitkey));
558
559     /* Might need to close down the ubik_Client connection */
560     if (conn) {
561         ubik_ClientDestroy(conn);
562         conn = 0;
563     }
564     rx_Finalize();
565     terminate_child();
566     exit(code);
567
568   no_change:                    /* yuck, yuck, yuck */
569     if (code)
570         com_err(rn, code, "getting new password");
571   no_change_no_msg:
572     memset(&key, 0, sizeof(key));
573     memset(npasswd, 0, sizeof(npasswd));
574     printf("Password for '%s' in cell '%s' unchanged.\n\n", pw->pw_name,
575            cell);
576     terminate_child();
577     exit(code ? code : 1);
578 }