xref: /original-bsd/usr.bin/passwd/passwd.c (revision 7d595439)
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
8 char copyright[] =
9 "@(#) Copyright (c) 1983 Regents of the University of California.\n\
10  All rights reserved.\n";
11 #endif not lint
12 
13 #ifndef lint
14 static char sccsid[] = "@(#)passwd.c	4.26 (Berkeley) 11/17/86";
15 #endif not lint
16 
17 /*
18  * Modify a field in the password file (either
19  * password, login shell, or gecos field).
20  * This program should be suid with an owner
21  * with write permission on /etc/passwd.
22  */
23 #include <sys/types.h>
24 #include <sys/file.h>
25 #include <sys/time.h>
26 #include <sys/resource.h>
27 
28 #include <stdio.h>
29 #include <signal.h>
30 #include <pwd.h>
31 #include <ndbm.h>
32 #include <errno.h>
33 #include <strings.h>
34 #include <ctype.h>
35 
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 
42 char	temp[] = "/etc/ptmp";
43 char	passwd[] = "/etc/passwd";
44 char	*getpass();
45 char	*getlogin();
46 char	*getfingerinfo();
47 char	*getloginshell();
48 char	*getnewpasswd();
49 char	*malloc();
50 char	*getusershell();
51 extern	int errno;
52 
53 main(argc, argv)
54 	char *argv[];
55 {
56 	struct passwd *pwd;
57 	char *cp, *uname, *progname;
58 	int fd, u, dochfn, dochsh, err;
59 	FILE *tf;
60 	DBM *dp;
61 
62 	if ((progname = rindex(argv[0], '/')) == NULL)
63 		progname = argv[0];
64 	else
65 		progname++;
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 	}
93 	if (!dochfn && !dochsh) {
94 		if (strcmp(progname, "chfn") == 0)
95 			dochfn = 1;
96 		else if (strcmp(progname, "chsh") == 0)
97 			dochsh = 1;
98 	}
99 	if (argc < 1) {
100 		if ((uname = getlogin()) == NULL) {
101 			fprintf(stderr, "Usage: %s [-f] [-s] [user]\n", progname);
102 			exit(1);
103 		}
104 		printf("Changing %s for %s.\n",
105 		    dochfn ? "finger information" :
106 			dochsh ? "login shell" : "password",
107 		    uname);
108 	} else
109 		uname = *argv++;
110 	pwd = getpwnam(uname);
111 	if (pwd == NULL) {
112 		fprintf(stderr, "passwd: %s: unknown user.\n", uname);
113 		exit(1);
114 	}
115 	u = getuid();
116 	if (u != 0 && u != pwd->pw_uid) {
117 		printf("Permission denied.\n");
118 		exit(1);
119 	}
120 	if (dochfn)
121 		cp = getfingerinfo(pwd);
122 	else if (dochsh)
123 		cp = getloginshell(pwd, u, *argv);
124 	else
125 		cp = getnewpasswd(pwd, u);
126 	(void) signal(SIGHUP, SIG_IGN);
127 	(void) signal(SIGINT, SIG_IGN);
128 	(void) signal(SIGQUIT, SIG_IGN);
129 	(void) signal(SIGTSTP, SIG_IGN);
130 	(void) umask(0);
131 	fd = open(temp, O_WRONLY|O_CREAT|O_EXCL, 0644);
132 	if (fd < 0) {
133 		err = errno;
134 
135 		fprintf(stderr, "passwd: ");
136 		if (err == EEXIST)
137 			fprintf(stderr, "password file busy - try again.\n");
138 		else {
139 			errno = err;
140 			perror(temp);
141 		}
142 		exit(1);
143 	}
144 	if ((tf = fdopen(fd, "w")) == NULL) {
145 		fprintf(stderr, "passwd: fdopen failed?\n");
146 		exit(1);
147 	}
148 	if ((dp = dbm_open(passwd, O_RDWR, 0644)) == NULL) {
149 		err = errno;
150 		fprintf(stderr, "Warning: dbm_open failed: ");
151 		errno = err;
152 		perror(passwd);
153 	} else if (flock(dp->dbm_dirf, LOCK_EX) < 0) {
154 		perror("Warning: lock failed");
155 		dbm_close(dp);
156 		dp = NULL;
157 	}
158 	unlimit(RLIMIT_CPU);
159 	unlimit(RLIMIT_FSIZE);
160 	/*
161 	 * Copy passwd to temp, replacing matching lines
162 	 * with new password.
163 	 */
164 	while ((pwd = getpwent()) != NULL) {
165 		if (strcmp(pwd->pw_name, uname) == 0) {
166 			if (u && u != pwd->pw_uid) {
167 				fprintf(stderr, "passwd: permission denied.\n");
168 				goto out;
169 			}
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] == '*')	/* ??? */
177 				pwd->pw_gecos++;
178 			replace(dp, pwd);
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();
190 	if (dp != NULL && dbm_error(dp))
191 		fprintf(stderr, "Warning: dbm_store failed\n");
192 	(void) fflush(tf);
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);
199 	if (dp != NULL)
200 		dbm_close(dp);
201 	if (rename(temp, passwd) < 0) {
202 		perror("passwd: rename");
203 	out:
204 		(void) unlink(temp);
205 		exit(1);
206 	}
207 	exit(0);
208 }
209 
210 unlimit(lim)
211 {
212 	struct rlimit rlim;
213 
214 	rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
215 	(void) setrlimit(lim, &rlim);
216 }
217 
218 /*
219  * Replace the password entry in the dbm data base with pwd.
220  */
221 replace(dp, pwd)
222 	DBM *dp;
223 	struct passwd *pwd;
224 {
225 	datum key, content;
226 	register char *cp, *tp;
227 	char buf[BUFSIZ];
228 
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);
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);
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);
250 	dbm_store(dp, key, content, DBM_REPLACE);
251 	key.dptr = (char *)&pwd->pw_uid;
252 	key.dsize = sizeof (int);
253 	dbm_store(dp, key, content, DBM_REPLACE);
254 }
255 
256 char *
257 getnewpasswd(pwd, u)
258 	register struct passwd *pwd;
259 	int u;
260 {
261 	char saltc[2];
262 	long salt;
263 	int i, insist = 0, ok, flags;
264 	int c, pwlen;
265 	static char pwbuf[10];
266 	long time();
267 	char *crypt(), *pw, *p;
268 
269 	if (pwd->pw_passwd[0] && u != 0) {
270 		(void) strcpy(pwbuf, getpass("Old password:"));
271 		pw = crypt(pwbuf, pwd->pw_passwd);
272 		if (strcmp(pw, pwd->pw_passwd) != 0) {
273 			printf("Sorry.\n");
274 			exit(1);
275 		}
276 	}
277 tryagain:
278 	(void) strcpy(pwbuf, getpass("New password:"));
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 	}
320 	(void) time(&salt);
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 
335 char *
336 getloginshell(pwd, u, arg)
337 	struct passwd *pwd;
338 	int u;
339 	char *arg;
340 {
341 	static char newshell[BUFSIZ];
342 	char *cp, *valid, *getusershell();
343 
344 	if (pwd->pw_shell == 0 || *pwd->pw_shell == '\0')
345 		pwd->pw_shell = DEFSHELL;
346 	if (u != 0) {
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);
355 	}
356 	if (arg != 0) {
357 		(void) strncpy(newshell, arg, sizeof newshell - 1);
358 		newshell[sizeof newshell - 1] = '\0';
359 	} else {
360 		printf("Old shell: %s\nNew shell: ", pwd->pw_shell);
361 		(void)fgets(newshell, sizeof (newshell) - 1, stdin);
362 		cp = index(newshell, '\n');
363 		if (cp)
364 			*cp = '\0';
365 		if (newshell[0] == 0) {
366 			puts("Login shell unchanged.");
367 			exit(1);
368 		}
369 	}
370 	/*
371 	 * Allow user to give shell name w/o preceding pathname.
372 	 */
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 			}
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 			}
395 		} while (strcmp(newshell, cp) != 0);
396 	} else {
397 		valid = newshell;
398 	}
399 	if (strcmp(valid, pwd->pw_shell) == 0) {
400 		puts("Login shell unchanged.");
401 		exit(1);
402 	}
403 	if (access(valid, X_OK) < 0) {
404 		printf("%s is unavailable.\n", valid);
405 		exit(1);
406 	}
407 	if (strcmp(valid, DEFSHELL) == 0)
408 		valid[0] = '\0';
409 	return (valid);
410 }
411 
412 struct 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  */
422 char *
423 getfingerinfo(pwd)
424 	struct passwd *pwd;
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);
432 	printf("Default values are printed inside of '[]'.\n");
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.
458 	 * Remove hyphens.
459 	 */
460 	do {
461 		printf("Office Phone (Ex: 6426000) [%s]: ",
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);
467 	} while (illegal_input(in_str) || not_all_digits(in_str));
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  */
498 illegal_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  */
538 remove_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  */
551 not_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 
564 /*
565  * Deal with Berkeley buildings.  Abbreviating Cory to C and Evans to E.
566  * Correction changes "str".
567  *
568  * Returns 1 if incorrect room format.
569  *
570  * Note: this function assumes that the newline has been removed from str.
571  */
572 illegal_building(str)
573 	register char *str;
574 {
575 	int length = strlen(str);
576 	register char *ptr;
577 
578 	/*
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.
582 	 */
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';
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  */
609 struct default_values *
610 get_defaults(str)
611 	char *str;
612 {
613 	struct default_values *answer;
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  */
653 special_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);
695 }
696