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