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