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