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