d6e0d6bdda0d34f4f3dd6a39622ed6bd34cbbcd6
[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 newkey;
202
203     struct ktc_token    token;
204
205     struct passwd pwent;
206     struct passwd *pw = &pwent;
207
208     int insist;                         /* insist on good password quality */
209     int lexplicit=0;                    /* servers specified explicitly */
210     int local;                          /* explicit cell is same a local cell */
211     int foundPassword = 0;              /*Not yet, anyway*/
212     int foundNewPassword = 0;           /*Not yet, anyway*/
213     int foundExplicitCell = 0;          /*Not yet, anyway*/
214
215     /* blow away command line arguments */
216     for (i=1; i<zero_argc; i++) bzero (zero_argv[i], strlen(zero_argv[i]));
217     zero_argc = 0;
218
219     /* first determine quiet flag based on -pipe switch */
220     Pipe = (as->parms[aPIPE].items ? 1 : 0);
221
222 #if TIMEOUT
223     signal(SIGALRM, timedout);
224     alarm (30);
225 #endif
226
227     code = ka_Init(0);
228     if (code ||
229         !(lcell = ka_LocalCell())) {
230         if (!Pipe) com_err (rn, code , "Can't get local cell name!");
231         exit (1);
232     }
233
234     code = rx_Init(0);
235     if (code) {
236         if (!Pipe) com_err (rn, code , "Failed to initialize Rx");
237         exit (1);
238     }
239
240     strcpy (instance, "");
241
242     /* Parse our arguments. */
243
244     if (as->parms[aCELL].items) {
245         /*
246          * cell name explicitly mentioned; take it in if no other cell name
247          * has already been specified and if the name actually appears.  If
248          * the given cell name differs from our own, we don't do a lookup.
249          */
250         foundExplicitCell = 1;
251         strncpy (realm, as->parms[aCELL].items->data, sizeof(realm));
252     }
253
254     if (as->parms[aSERVERS].items) {
255         /* explicit server list */
256         int i;
257         struct cmd_item *ip;
258         char *ap[MAXSERVERS+2];
259
260         for (ip = as->parms[aSERVERS].items, i=2; ip; ip=ip->next, i++)
261             ap[i] = ip->data;
262         ap[0] = "";
263         ap[1] = "-servers";
264         code = ubik_ParseClientList(i, ap, serverList);
265         if (code) {
266             if (!Pipe) com_err (rn, code, "could not parse server list");
267             return code;
268         }
269         lexplicit = 1;
270     }
271
272     if (as->parms[aPRINCIPAL].items) {
273         ka_ParseLoginName (as->parms[aPRINCIPAL].items->data,
274                            name, instance, cell);
275         if (strlen (instance) > 0)
276             if (!Pipe)
277                 fprintf (stderr, "Non-null instance (%s) may cause strange behavior.\n",
278                          instance);
279         if (strlen(cell) > 0) {
280             if (foundExplicitCell) {
281                 if (!Pipe)
282                     fprintf (stderr, "%s: May not specify an explicit cell twice.\n", rn);
283                 return -1;
284             }
285             foundExplicitCell = 1;
286             strncpy (realm, cell, sizeof(realm));
287         }
288         pw->pw_name = name;
289     } else {
290         /* No explicit name provided: use Unix uid. */
291 #ifdef AFS_NT40_ENV
292       userNameLen = 128;
293       if (GetUserName(userName, &userNameLen) == 0) {
294         if (!Pipe) {
295           fprintf (stderr, "Can't figure out your name in local cell %s from your user id.\n", lcell);
296           fprintf (stderr, "Try providing the user name.\n");
297         }
298         exit (1);
299       }
300       pw->pw_name = userName;
301 #else   
302         pw = getpwuid(getuid());
303         if (pw == 0) {
304             if (!Pipe) {
305                 fprintf (stderr, "Can't figure out your name in local cell %s from your user id.\n", lcell);
306                 fprintf (stderr, "Try providing the user name.\n");
307             }
308             exit (1);
309         }
310 #endif
311     } 
312
313     if (as->parms[aPASSWORD].items) {
314         /*
315          * Current argument is the desired password string.  Remember it in
316          * our local buffer, and zero out the argument string - anyone can
317          * see it there with ps!
318          */
319         foundPassword = 1;
320         strncpy (passwd, as->parms[aPASSWORD].items->data, sizeof(passwd));
321         bzero (as->parms[aPASSWORD].items->data,
322                strlen(as->parms[aPASSWORD].items->data));
323     }
324
325     if (as->parms[aNEWPASSWORD].items) {
326         /*
327          * Current argument is the new password string.  Remember it in
328          * our local buffer, and zero out the argument string - anyone can
329          * see it there with ps!
330          */
331         foundNewPassword = 1;
332         strncpy (npasswd, as->parms[aNEWPASSWORD].items->data,
333                  sizeof(npasswd));
334         bzero (as->parms[aNEWPASSWORD].items->data,
335                strlen(as->parms[aNEWPASSWORD].items->data));
336     }
337
338     if (!foundExplicitCell) strcpy (realm, lcell);
339     if (code = ka_CellToRealm (realm, realm, &local)) {
340         if (!Pipe) com_err (rn, code, "Can't convert cell to realm");
341         exit (1);
342     }
343     lcstring (cell, realm, sizeof(cell));
344
345     ka_PrintUserID ("Changing password for '", pw->pw_name, instance, "'");
346     printf (" in cell '%s'.\n", cell);
347
348     /* Get the password if it wasn't provided. */
349     if (!foundPassword) {
350         if (Pipe) getpipepass (passwd, sizeof(passwd));
351         else {
352             code = read_pass (passwd, sizeof(passwd), "Old password: ", 0);
353             if (code || (strlen (passwd) == 0)) {
354                 if (code) code = KAREADPW;
355                 bzero (&key, sizeof(key));
356                 bzero (passwd, sizeof(passwd));
357                 if (code) com_err (rn, code, "reading password");
358                 exit (1);
359             }
360         }
361     } 
362     ka_StringToKey (passwd, realm, &key);
363     give_to_child(passwd);
364
365     /* Get new password if it wasn't provided. */
366     insist = 0;
367     if (!foundNewPassword) {
368         if (Pipe)
369            getpipepass (npasswd, sizeof(npasswd));
370         else {
371             do {
372                 code = read_pass (npasswd, sizeof(npasswd),
373                                   "New password (RETURN to abort): ", 0);
374                 if (code || (strlen (npasswd) == 0)) {
375                     if (code) 
376                        code = KAREADPW;
377                     goto no_change;
378
379                 }
380             } while (password_bad (npasswd));
381
382             code = read_pass (verify, sizeof(verify),
383                                   "Retype new password: ", 0);
384             if (code) {
385               code = KAREADPW;
386               goto no_change;
387             }
388             if (strcmp (verify, npasswd) != 0) {
389               printf ("Mismatch - ");
390               goto no_change;
391             }
392             bzero (verify, sizeof(verify));
393           }
394       }
395     if (code = password_bad (npasswd)) {  /* assmt here! */
396         goto no_change_no_msg;
397     }
398
399 #if TRUNCATEPASSWORD
400     if (strlen(npasswd) > 8) {
401         npasswd[8] = 0;
402         fprintf(stderr,
403                 "%s: password too long, only the first 8 chars will be used.\n",
404                 rn);
405     }
406     else
407         npasswd[8] = 0; /* in case the password was exactly 8 chars long */
408 #endif
409     ka_StringToKey (npasswd, realm, &newkey);
410     bzero (npasswd, sizeof(npasswd));
411
412     if (lexplicit) ka_ExplicitCell (realm, serverList);
413
414     /* Get an connection to kaserver's admin service in desired cell.  Set the
415      * lifetime above the time uncertainty so that badly skewed clocks are
416      * reported when the ticket is decrypted.  Then give us 10 seconds to
417      * actually get our work done if the clocks are skewed by only 14:59.
418      * NOTE: Kerberos lifetime encoding will round this up to next 5 minute
419      * interval, namely 20 minutes. */
420
421 #define ADMIN_LIFETIME (KTC_TIME_UNCERTAINTY+1)
422
423     code = ka_GetAdminToken (pw->pw_name, instance, realm,
424                              &key, ADMIN_LIFETIME, &token, /*!new*/0);
425
426     if ((code == KABADREQUEST) && (strlen (passwd) > 8)) {
427         /* try with only the first 8 characters incase they set their password
428          * with an old style passwd program. */
429         char pass8[9];
430         strncpy (pass8, passwd, 8);
431         pass8[8] = 0;
432         ka_StringToKey (pass8, realm, &key);
433         bzero (pass8, sizeof(pass8));
434         bzero (passwd, sizeof(passwd));
435         code = ka_GetAdminToken (pw->pw_name, instance, realm,
436                                  &key, ADMIN_LIFETIME, &token, /*!new*/0);
437 #ifdef notdef
438         /* the folks in testing really *hate* this message */
439         if (code == 0) {
440             fprintf (stderr, "Warning: only the first 8 characters of your old password were significant.\n");
441         }
442 #endif
443     }   
444
445     bzero (&key, sizeof(key));
446     if (code == KAUBIKCALL) com_err (rn, code, "(Authentication Server unavailable, try later)");
447     else if (code) {
448         if (code == KABADREQUEST)
449             fprintf(stderr, "%s: Incorrect old password.\n", rn);
450         else
451             com_err (rn, code, "so couldn't change password");
452     }
453     else {
454         code = ka_AuthServerConn (realm, KA_MAINTENANCE_SERVICE, &token, &conn);
455         if (code) com_err (rn, code, "contacting Admin Server");
456         else {
457             code = ka_ChangePassword (pw->pw_name, instance, conn, 0, &newkey);
458             bzero (&newkey, sizeof(newkey));
459             if (code) {
460               char * reason;
461               reason = (char *) error_message(code);
462               fprintf (stderr, "%s: Password was not changed because %s\n", rn, reason);
463             }
464             else printf("Password changed.\n\n");
465         }
466     }
467     bzero (&newkey, sizeof(newkey));
468
469     /* Might need to close down the ubik_Client connection */
470     if (conn) {
471         ubik_ClientDestroy(conn);
472         conn = 0;
473     }
474     rx_Finalize();
475     terminate_child();
476     exit (code);
477
478   no_change:                       /* yuck, yuck, yuck */
479     if (code) com_err (rn, code, "getting new password");
480  no_change_no_msg:
481     bzero (&key, sizeof(key));
482     bzero (npasswd, sizeof(npasswd));
483     printf("Password for '%s' in cell '%s' unchanged.\n\n", pw->pw_name, cell);
484     terminate_child();
485     exit (code ? code : 1);
486 }