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