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