added depend label
[unix-history] / usr / src / usr.bin / passwd / passwd.c
CommitLineData
1f978f4c
KM
1/*
2 * Copyright (c) 1983 Regents of the University of California.
3 * All rights reserved. The Berkeley software License Agreement
4 * specifies the terms and conditions for redistribution.
5 */
6
7#ifndef lint
8char copyright[] =
9"@(#) Copyright (c) 1983 Regents of the University of California.\n\
10 All rights reserved.\n";
11#endif not lint
12
252e456d 13#ifndef lint
920e234e 14static char sccsid[] = "@(#)passwd.c 4.26 (Berkeley) %G%";
1f978f4c 15#endif not lint
252e456d
SL
16
17/*
844df0cd
RC
18 * Modify a field in the password file (either
19 * password, login shell, or gecos field).
9a59f452
SL
20 * This program should be suid with an owner
21 * with write permission on /etc/passwd.
252e456d 22 */
6afaba66 23#include <sys/types.h>
252e456d 24#include <sys/file.h>
844df0cd
RC
25#include <sys/time.h>
26#include <sys/resource.h>
252e456d
SL
27
28#include <stdio.h>
29#include <signal.h>
30#include <pwd.h>
9ae20471 31#include <ndbm.h>
252e456d 32#include <errno.h>
844df0cd
RC
33#include <strings.h>
34#include <ctype.h>
252e456d 35
6afaba66
KM
36/*
37 * This should be the first thing returned from a getloginshells()
38 * but too many programs know that it is /bin/sh.
39 */
40#define DEFSHELL "/bin/sh"
41
8d3b95af 42char temp[] = "/etc/ptmp";
252e456d 43char passwd[] = "/etc/passwd";
252e456d
SL
44char *getpass();
45char *getlogin();
844df0cd
RC
46char *getfingerinfo();
47char *getloginshell();
48char *getnewpasswd();
bd94915c 49char *malloc();
6afaba66 50char *getusershell();
252e456d
SL
51extern int errno;
52
53main(argc, argv)
54 char *argv[];
55{
844df0cd 56 struct passwd *pwd;
a1b0286d 57 char *cp, *uname, *progname;
bd94915c 58 int fd, u, dochfn, dochsh, err;
252e456d 59 FILE *tf;
9ae20471 60 DBM *dp;
252e456d 61
98abc60f 62 if ((progname = rindex(argv[0], '/')) == NULL)
a1b0286d
RC
63 progname = argv[0];
64 else
65 progname++;
844df0cd
RC
66 dochfn = 0, dochsh = 0;
67 argc--, argv++;
68 while (argc > 0 && argv[0][0] == '-') {
69 for (cp = &argv[0][1]; *cp; cp++) switch (*cp) {
70
71 case 'f':
72 if (dochsh)
73 goto bad;
74 dochfn = 1;
75 break;
76
77 case 's':
78 if (dochfn) {
79 bad:
80 fprintf(stderr,
81 "passwd: Only one of -f and -s allowed.\n");
82 exit(1);
83 }
84 dochsh = 1;
85 break;
86
87 default:
88 fprintf(stderr, "passwd: -%c: unknown option.\n", *cp);
89 exit(1);
90 }
91 argc--, argv++;
92 }
a1b0286d
RC
93 if (!dochfn && !dochsh) {
94 if (strcmp(progname, "chfn") == 0)
95 dochfn = 1;
96 else if (strcmp(progname, "chsh") == 0)
97 dochsh = 1;
98 }
844df0cd 99 if (argc < 1) {
252e456d 100 if ((uname = getlogin()) == NULL) {
a1b0286d 101 fprintf(stderr, "Usage: %s [-f] [-s] [user]\n", progname);
252e456d
SL
102 exit(1);
103 }
844df0cd
RC
104 printf("Changing %s for %s.\n",
105 dochfn ? "finger information" :
106 dochsh ? "login shell" : "password",
107 uname);
252e456d 108 } else
98abc60f 109 uname = *argv++;
8d3b95af 110 pwd = getpwnam(uname);
05e78391 111 if (pwd == NULL) {
844df0cd 112 fprintf(stderr, "passwd: %s: unknown user.\n", uname);
05e78391
JB
113 exit(1);
114 }
252e456d 115 u = getuid();
05e78391 116 if (u != 0 && u != pwd->pw_uid) {
252e456d
SL
117 printf("Permission denied.\n");
118 exit(1);
119 }
844df0cd 120 if (dochfn)
bd94915c 121 cp = getfingerinfo(pwd);
844df0cd 122 else if (dochsh)
98abc60f 123 cp = getloginshell(pwd, u, *argv);
844df0cd
RC
124 else
125 cp = getnewpasswd(pwd, u);
bd94915c
KM
126 (void) signal(SIGHUP, SIG_IGN);
127 (void) signal(SIGINT, SIG_IGN);
128 (void) signal(SIGQUIT, SIG_IGN);
129 (void) signal(SIGTSTP, SIG_IGN);
643bb829 130 (void) umask(0);
9a59f452 131 fd = open(temp, O_WRONLY|O_CREAT|O_EXCL, 0644);
252e456d 132 if (fd < 0) {
ba9597f7
RE
133 err = errno;
134
252e456d 135 fprintf(stderr, "passwd: ");
ba9597f7 136 if (err == EEXIST)
252e456d 137 fprintf(stderr, "password file busy - try again.\n");
ba9597f7
RE
138 else {
139 errno = err;
252e456d 140 perror(temp);
ba9597f7 141 }
252e456d
SL
142 exit(1);
143 }
252e456d
SL
144 if ((tf = fdopen(fd, "w")) == NULL) {
145 fprintf(stderr, "passwd: fdopen failed?\n");
146 exit(1);
147 }
844df0cd 148 if ((dp = dbm_open(passwd, O_RDWR, 0644)) == NULL) {
ba9597f7 149 err = errno;
844df0cd 150 fprintf(stderr, "Warning: dbm_open failed: ");
ba9597f7 151 errno = err;
9ae20471 152 perror(passwd);
844df0cd 153 } else if (flock(dp->dbm_dirf, LOCK_EX) < 0) {
9ae20471 154 perror("Warning: lock failed");
844df0cd 155 dbm_close(dp);
9ae20471
RC
156 dp = NULL;
157 }
844df0cd
RC
158 unlimit(RLIMIT_CPU);
159 unlimit(RLIMIT_FSIZE);
252e456d
SL
160 /*
161 * Copy passwd to temp, replacing matching lines
162 * with new password.
163 */
164 while ((pwd = getpwent()) != NULL) {
8d3b95af 165 if (strcmp(pwd->pw_name, uname) == 0) {
252e456d
SL
166 if (u && u != pwd->pw_uid) {
167 fprintf(stderr, "passwd: permission denied.\n");
8d3b95af 168 goto out;
252e456d 169 }
844df0cd
RC
170 if (dochfn)
171 pwd->pw_gecos = cp;
172 else if (dochsh)
173 pwd->pw_shell = cp;
174 else
175 pwd->pw_passwd = cp;
176 if (pwd->pw_gecos[0] == '*') /* ??? */
252e456d 177 pwd->pw_gecos++;
9ae20471 178 replace(dp, pwd);
252e456d
SL
179 }
180 fprintf(tf,"%s:%s:%d:%d:%s:%s:%s\n",
181 pwd->pw_name,
182 pwd->pw_passwd,
183 pwd->pw_uid,
184 pwd->pw_gid,
185 pwd->pw_gecos,
186 pwd->pw_dir,
187 pwd->pw_shell);
188 }
189 endpwent();
844df0cd
RC
190 if (dp != NULL && dbm_error(dp))
191 fprintf(stderr, "Warning: dbm_store failed\n");
bd94915c 192 (void) fflush(tf);
183d82a9
RE
193 if (ferror(tf)) {
194 fprintf(stderr, "Warning: %s write error, %s not updated\n",
195 temp, passwd);
196 goto out;
197 }
198 (void) fclose(tf);
eb635a62
KM
199 if (dp != NULL)
200 dbm_close(dp);
9ae20471 201 if (rename(temp, passwd) < 0) {
183d82a9 202 perror("passwd: rename");
9ae20471 203 out:
bd94915c 204 (void) unlink(temp);
9ae20471
RC
205 exit(1);
206 }
207 exit(0);
8d3b95af
RC
208}
209
844df0cd
RC
210unlimit(lim)
211{
212 struct rlimit rlim;
213
214 rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
215 (void) setrlimit(lim, &rlim);
216}
217
9ae20471
RC
218/*
219 * Replace the password entry in the dbm data base with pwd.
220 */
221replace(dp, pwd)
222 DBM *dp;
223 struct passwd *pwd;
8d3b95af 224{
9ae20471
RC
225 datum key, content;
226 register char *cp, *tp;
227 char buf[BUFSIZ];
8d3b95af 228
9ae20471
RC
229 if (dp == NULL)
230 return;
231
232 cp = buf;
233#define COMPACT(e) tp = pwd->pw_/**/e; while (*cp++ = *tp++);
234 COMPACT(name);
235 COMPACT(passwd);
3c92851f
RC
236 bcopy((char *)&pwd->pw_uid, cp, sizeof (int));
237 cp += sizeof (int);
238 bcopy((char *)&pwd->pw_gid, cp, sizeof (int));
239 cp += sizeof (int);
240 bcopy((char *)&pwd->pw_quota, cp, sizeof (int));
241 cp += sizeof (int);
9ae20471
RC
242 COMPACT(comment);
243 COMPACT(gecos);
244 COMPACT(dir);
245 COMPACT(shell);
246 content.dptr = buf;
247 content.dsize = cp - buf;
248 key.dptr = pwd->pw_name;
249 key.dsize = strlen(pwd->pw_name);
844df0cd 250 dbm_store(dp, key, content, DBM_REPLACE);
9ae20471
RC
251 key.dptr = (char *)&pwd->pw_uid;
252 key.dsize = sizeof (int);
844df0cd
RC
253 dbm_store(dp, key, content, DBM_REPLACE);
254}
255
256char *
257getnewpasswd(pwd, u)
258 register struct passwd *pwd;
259 int u;
260{
261 char saltc[2];
bd94915c 262 long salt;
844df0cd
RC
263 int i, insist = 0, ok, flags;
264 int c, pwlen;
265 static char pwbuf[10];
bd94915c 266 long time();
844df0cd
RC
267 char *crypt(), *pw, *p;
268
269 if (pwd->pw_passwd[0] && u != 0) {
bd94915c 270 (void) strcpy(pwbuf, getpass("Old password:"));
844df0cd
RC
271 pw = crypt(pwbuf, pwd->pw_passwd);
272 if (strcmp(pw, pwd->pw_passwd) != 0) {
273 printf("Sorry.\n");
274 exit(1);
275 }
276 }
277tryagain:
bd94915c 278 (void) strcpy(pwbuf, getpass("New password:"));
844df0cd
RC
279 pwlen = strlen(pwbuf);
280 if (pwlen == 0) {
281 printf("Password unchanged.\n");
282 exit(1);
283 }
284 /*
285 * Insure password is of reasonable length and
286 * composition. If we really wanted to make things
287 * sticky, we could check the dictionary for common
288 * words, but then things would really be slow.
289 */
290 ok = 0;
291 flags = 0;
292 p = pwbuf;
293 while (c = *p++) {
294 if (c >= 'a' && c <= 'z')
295 flags |= 2;
296 else if (c >= 'A' && c <= 'Z')
297 flags |= 4;
298 else if (c >= '0' && c <= '9')
299 flags |= 1;
300 else
301 flags |= 8;
302 }
303 if (flags >= 7 && pwlen >= 4)
304 ok = 1;
305 if ((flags == 2 || flags == 4) && pwlen >= 6)
306 ok = 1;
307 if ((flags == 3 || flags == 5 || flags == 6) && pwlen >= 5)
308 ok = 1;
309 if (!ok && insist < 2) {
310 printf("Please use %s.\n", flags == 1 ?
311 "at least one non-numeric character" :
312 "a longer password");
313 insist++;
314 goto tryagain;
315 }
316 if (strcmp(pwbuf, getpass("Retype new password:")) != 0) {
317 printf("Mismatch - password unchanged.\n");
318 exit(1);
319 }
bd94915c 320 (void) time(&salt);
844df0cd
RC
321 salt = 9 * getpid();
322 saltc[0] = salt & 077;
323 saltc[1] = (salt>>6) & 077;
324 for (i = 0; i < 2; i++) {
325 c = saltc[i] + '.';
326 if (c > '9')
327 c += 7;
328 if (c > 'Z')
329 c += 6;
330 saltc[i] = c;
331 }
332 return (crypt(pwbuf, saltc));
333}
334
844df0cd 335char *
98abc60f 336getloginshell(pwd, u, arg)
844df0cd
RC
337 struct passwd *pwd;
338 int u;
98abc60f 339 char *arg;
844df0cd 340{
6afaba66
KM
341 static char newshell[BUFSIZ];
342 char *cp, *valid, *getusershell();
844df0cd
RC
343
344 if (pwd->pw_shell == 0 || *pwd->pw_shell == '\0')
345 pwd->pw_shell = DEFSHELL;
7bffa124 346 if (u != 0) {
920e234e
KB
347 do {
348 valid = getusershell();
349 if (valid == NULL) {
350 printf("Cannot change from restricted shell %s\n",
351 pwd->pw_shell);
352 exit(1);
353 }
354 } while (strcmp(pwd->pw_shell, valid) != 0);
7bffa124 355 }
98abc60f 356 if (arg != 0) {
bd94915c 357 (void) strncpy(newshell, arg, sizeof newshell - 1);
920e234e 358 newshell[sizeof newshell - 1] = '\0';
98abc60f
EW
359 } else {
360 printf("Old shell: %s\nNew shell: ", pwd->pw_shell);
bd94915c 361 (void)fgets(newshell, sizeof (newshell) - 1, stdin);
98abc60f
EW
362 cp = index(newshell, '\n');
363 if (cp)
364 *cp = '\0';
920e234e
KB
365 if (newshell[0] == 0) {
366 puts("Login shell unchanged.");
367 exit(1);
368 }
844df0cd
RC
369 }
370 /*
371 * Allow user to give shell name w/o preceding pathname.
372 */
920e234e
KB
373 if (u != 0 || newshell[0] != '/') {
374 endusershell();
375 do {
376 valid = getusershell();
377 if (valid == 0) {
378 if (u == 0) {
379 valid = newshell;
380 break;
381 }
382 printf("%s is unacceptable as a new shell.\n",
383 newshell);
384 exit(1);
385 }
6afaba66
KM
386 if (newshell[0] == '/') {
387 cp = valid;
388 } else {
389 cp = rindex(valid, '/');
390 if (cp == 0)
391 cp = valid;
392 else
393 cp++;
394 }
920e234e
KB
395 } while (strcmp(newshell, cp) != 0);
396 } else {
397 valid = newshell;
844df0cd 398 }
920e234e
KB
399 if (strcmp(valid, pwd->pw_shell) == 0) {
400 puts("Login shell unchanged.");
844df0cd
RC
401 exit(1);
402 }
6afaba66
KM
403 if (access(valid, X_OK) < 0) {
404 printf("%s is unavailable.\n", valid);
405 exit(1);
bd94915c 406 }
6afaba66
KM
407 if (strcmp(valid, DEFSHELL) == 0)
408 valid[0] = '\0';
409 return (valid);
bd94915c 410}
844df0cd
RC
411
412struct default_values {
413 char *name;
414 char *office_num;
415 char *office_phone;
416 char *home_phone;
417};
418
419/*
420 * Get name, room number, school phone, and home phone.
421 */
422char *
bd94915c 423getfingerinfo(pwd)
844df0cd 424 struct passwd *pwd;
844df0cd
RC
425{
426 char in_str[BUFSIZ];
427 struct default_values *defaults, *get_defaults();
428 static char answer[4*BUFSIZ];
429
430 answer[0] = '\0';
431 defaults = get_defaults(pwd->pw_gecos);
b194f32a 432 printf("Default values are printed inside of '[]'.\n");
844df0cd
RC
433 printf("To accept the default, type <return>.\n");
434 printf("To have a blank entry, type the word 'none'.\n");
435 /*
436 * Get name.
437 */
438 do {
439 printf("\nName [%s]: ", defaults->name);
440 (void) fgets(in_str, BUFSIZ, stdin);
441 if (special_case(in_str, defaults->name))
442 break;
443 } while (illegal_input(in_str));
444 (void) strcpy(answer, in_str);
445 /*
446 * Get room number.
447 */
448 do {
449 printf("Room number (Exs: 597E or 197C) [%s]: ",
450 defaults->office_num);
451 (void) fgets(in_str, BUFSIZ, stdin);
452 if (special_case(in_str, defaults->office_num))
453 break;
454 } while (illegal_input(in_str) || illegal_building(in_str));
455 (void) strcat(strcat(answer, ","), in_str);
456 /*
457 * Get office phone number.
008e3485 458 * Remove hyphens.
844df0cd
RC
459 */
460 do {
008e3485 461 printf("Office Phone (Ex: 6426000) [%s]: ",
844df0cd
RC
462 defaults->office_phone);
463 (void) fgets(in_str, BUFSIZ, stdin);
464 if (special_case(in_str, defaults->office_phone))
465 break;
466 remove_hyphens(in_str);
008e3485 467 } while (illegal_input(in_str) || not_all_digits(in_str));
844df0cd
RC
468 (void) strcat(strcat(answer, ","), in_str);
469 /*
470 * Get home phone number.
471 * Remove hyphens if present.
472 */
473 do {
474 printf("Home Phone (Ex: 9875432) [%s]: ", defaults->home_phone);
475 (void) fgets(in_str, BUFSIZ, stdin);
476 if (special_case(in_str, defaults->home_phone))
477 break;
478 remove_hyphens(in_str);
479 } while (illegal_input(in_str) || not_all_digits(in_str));
480 (void) strcat(strcat(answer, ","), in_str);
481 if (strcmp(answer, pwd->pw_gecos) == 0) {
482 printf("Finger information unchanged.\n");
483 exit(1);
484 }
485 return (answer);
486}
487
488/*
489 * Prints an error message if a ':' or a newline is found in the string.
490 * A message is also printed if the input string is too long.
491 * The password file uses :'s as seperators, and are not allowed in the "gcos"
492 * field. Newlines serve as delimiters between users in the password file,
493 * and so, those too, are checked for. (I don't think that it is possible to
494 * type them in, but better safe than sorry)
495 *
496 * Returns '1' if a colon or newline is found or the input line is too long.
497 */
498illegal_input(input_str)
499 char *input_str;
500{
501 char *ptr;
502 int error_flag = 0;
503 int length = strlen(input_str);
504
505 if (index(input_str, ':')) {
506 printf("':' is not allowed.\n");
507 error_flag = 1;
508 }
509 if (input_str[length-1] != '\n') {
510 /* the newline and the '\0' eat up two characters */
511 printf("Maximum number of characters allowed is %d\n",
512 BUFSIZ-2);
513 /* flush the rest of the input line */
514 while (getchar() != '\n')
515 /* void */;
516 error_flag = 1;
517 }
518 /*
519 * Delete newline by shortening string by 1.
520 */
521 input_str[length-1] = '\0';
522 /*
523 * Don't allow control characters, etc in input string.
524 */
525 for (ptr=input_str; *ptr != '\0'; ptr++) {
526 if ((int) *ptr < 040) {
527 printf("Control characters are not allowed.\n");
528 error_flag = 1;
529 break;
530 }
531 }
532 return (error_flag);
533}
534
535/*
536 * Removes '-'s from the input string.
537 */
538remove_hyphens(str)
539 char *str;
540{
541 char *hyphen;
542
543 while ((hyphen = index(str, '-')) != NULL)
544 (void) strcpy(hyphen, hyphen+1);
545}
546
547/*
548 * Checks to see if 'str' contains only digits (0-9). If not, then
549 * an error message is printed and '1' is returned.
550 */
551not_all_digits(str)
552 char *str;
553{
554 char *ptr;
555
556 for (ptr = str; *ptr != '\0'; ++ptr)
557 if (!isdigit(*ptr)) {
558 printf("Phone numbers can only contain digits.\n");
559 return (1);
560 }
561 return (0);
562}
563
844df0cd 564/*
5700acfe 565 * Deal with Berkeley buildings. Abbreviating Cory to C and Evans to E.
844df0cd 566 * Correction changes "str".
844df0cd
RC
567 *
568 * Returns 1 if incorrect room format.
569 *
570 * Note: this function assumes that the newline has been removed from str.
571 */
572illegal_building(str)
5700acfe 573 register char *str;
844df0cd
RC
574{
575 int length = strlen(str);
5700acfe 576 register char *ptr;
844df0cd
RC
577
578 /*
5700acfe
EW
579 * If the string is [Ee]vans or [Cc]ory or ends in
580 * [ \t0-9][Ee]vans or [ \t0-9M][Cc]ory, then contract the name
581 * into 'E' or 'C', as the case may be, and delete leading blanks.
844df0cd 582 */
5700acfe
EW
583 if (length >= 5 && strcmp(ptr = str + length - 4, "vans") == 0 &&
584 (*--ptr == 'e' || *ptr == 'E') &&
585 (--ptr < str || isspace(*ptr) || isdigit(*ptr))) {
586 for (; ptr > str && isspace(*ptr); ptr--)
587 ;
588 ptr++;
589 *ptr++ = 'E';
590 *ptr = '\0';
591 } else
592 if (length >= 4 && strcmp(ptr = str + length - 3, "ory") == 0 &&
593 (*--ptr == 'c' || *ptr == 'C') &&
594 (--ptr < str || *ptr == 'M' || isspace(*ptr) || isdigit(*ptr))) {
595 for (; ptr > str && isspace(*ptr); ptr--)
596 ;
597 ptr++;
598 *ptr++ = 'C';
599 *ptr = '\0';
844df0cd
RC
600 }
601 return (0);
602}
603
604/*
605 * get_defaults picks apart "str" and returns a structure points.
606 * "str" contains up to 4 fields separated by commas.
607 * Any field that is missing is set to blank.
608 */
609struct default_values *
610get_defaults(str)
611 char *str;
612{
613 struct default_values *answer;
844df0cd
RC
614
615 answer = (struct default_values *)
616 malloc((unsigned)sizeof(struct default_values));
617 if (answer == (struct default_values *) NULL) {
618 fprintf(stderr,
619 "\nUnable to allocate storage in get_defaults!\n");
620 exit(1);
621 }
622 /*
623 * Values if no corresponding string in "str".
624 */
625 answer->name = str;
626 answer->office_num = "";
627 answer->office_phone = "";
628 answer->home_phone = "";
629 str = index(answer->name, ',');
630 if (str == 0)
631 return (answer);
632 *str = '\0';
633 answer->office_num = str + 1;
634 str = index(answer->office_num, ',');
635 if (str == 0)
636 return (answer);
637 *str = '\0';
638 answer->office_phone = str + 1;
639 str = index(answer->office_phone, ',');
640 if (str == 0)
641 return (answer);
642 *str = '\0';
643 answer->home_phone = str + 1;
644 return (answer);
645}
646
647/*
648 * special_case returns true when either the default is accepted
649 * (str = '\n'), or when 'none' is typed. 'none' is accepted in
650 * either upper or lower case (or any combination). 'str' is modified
651 * in these two cases.
652 */
653special_case(str,default_str)
654 char *str, *default_str;
655{
656 static char word[] = "none\n";
657 char *ptr, *wordptr;
658
659 /*
660 * If the default is accepted, then change the old string do the
661 * default string.
662 */
663 if (*str == '\n') {
664 (void) strcpy(str, default_str);
665 return (1);
666 }
667 /*
668 * Check to see if str is 'none'. (It is questionable if case
669 * insensitivity is worth the hair).
670 */
671 wordptr = word-1;
672 for (ptr = str; *ptr != '\0'; ++ptr) {
673 ++wordptr;
674 if (*wordptr == '\0') /* then words are different sizes */
675 return (0);
676 if (*ptr == *wordptr)
677 continue;
678 if (isupper(*ptr) && (tolower(*ptr) == *wordptr))
679 continue;
680 /*
681 * At this point we have a mismatch, so we return
682 */
683 return (0);
684 }
685 /*
686 * Make sure that words are the same length.
687 */
688 if (*(wordptr+1) != '\0')
689 return (0);
690 /*
691 * Change 'str' to be the null string
692 */
693 *str = '\0';
694 return (1);
252e456d 695}