xref: /original-bsd/usr.bin/passwd/passwd.c (revision 2301fdfb)
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.32 (Berkeley) 01/21/88";
15 #endif not lint
16 
17 /*
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.
21  */
22 #include <sys/types.h>
23 #include <sys/file.h>
24 #include <sys/stat.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 #define	DICT		"/usr/dict/words"
43 #define	PASSWD		"/etc/passwd"
44 #define	PTEMP		"/etc/ptmp"
45 
46 #define	EOS		'\0';
47 
48 main(argc, argv)
49 	int	argc;
50 	char	**argv;
51 {
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 	}
72 	else
73 		umsg = "usage: passwd [-fs] [username]\n";
74 
75 	while ((ch = getopt(argc, argv, "fs")) != EOF)
76 		switch((char)ch) {
77 		case 'f':
78 			if (dochsh)
79 				goto usage;
80 			dochfn = 1;
81 			break;
82 		case 's':
83 			if (dochfn)
84 				goto usage;
85 			dochsh = 1;
86 			break;
87 		case '?':
88 		default:
89 usage:			fputs(umsg, stderr);
90 			exit(1);
91 		}
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);
104 	}
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 		}
111 	}
112 	if (uid && uid != pwd->pw_uid) {
113 		fputs("Permission denied.\n", stderr);
114 		exit(1);
115 	}
116 	printf("Changing %s for %s.\n", dochfn ? "finger information" : dochsh ? "login shell" : "password", uname);
117 	if (dochfn)
118 		cp = getfingerinfo(pwd);
119 	else if (dochsh)
120 		cp = getloginshell(pwd, uid);
121 	else
122 		cp = getnewpasswd(pwd, uid);
123 	(void) signal(SIGHUP, SIG_IGN);
124 	(void) signal(SIGINT, SIG_IGN);
125 	(void) signal(SIGQUIT, SIG_IGN);
126 	(void) signal(SIGTSTP, SIG_IGN);
127 	(void) umask(0);
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);
131 		else {
132 			fprintf(stderr, "%s: %s: ", progname, PTEMP);
133 			perror((char *)NULL);
134 		}
135 		exit(1);
136 	}
137 	if ((tf = fdopen(fd, "w")) == NULL) {
138 		fprintf(stderr, "%s: fdopen failed.\n", progname);
139 		exit(1);
140 	}
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) {
146 		perror("Warning: lock failed");
147 		dbm_close(dp);
148 		dp = NULL;
149 	}
150 	unlimit(RLIMIT_CPU);
151 	unlimit(RLIMIT_FSIZE);
152 	/*
153 	 * Copy passwd to temp, replacing matching lines
154 	 * with new password.
155 	 */
156 	while ((pwd = getpwent()) != NULL) {
157 		if (!strcmp(pwd->pw_name, uname)) {
158 			if (uid && uid != pwd->pw_uid) {
159 				fprintf(stderr, "%s: permission denied.\n", progname);
160 				goto out;
161 			}
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] == '*')	/* ??? */
169 				pwd->pw_gecos++;
170 			replace(dp, pwd);
171 		}
172 		fprintf(tf, "%s:%s:%d:%d:%s:%s:%s\n",
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();
182 	if (dp && dbm_error(dp))
183 		fputs("Warning: dbm_store failed.\n", stderr);
184 	(void) fflush(tf);
185 	if (ferror(tf)) {
186 		fprintf(stderr, "Warning: %s write error, %s not updated.\n",
187 		    PTEMP, PASSWD);
188 		goto out;
189 	}
190 	(void)fclose(tf);
191 	if (dp != NULL)
192 		dbm_close(dp);
193 	if (rename(PTEMP, PASSWD) < 0) {
194 		perror(progname);
195 	out:
196 		(void)unlink(PTEMP);
197 		exit(1);
198 	}
199 	exit(0);
200 }
201 
202 unlimit(lim)
203 	int	lim;
204 {
205 	struct rlimit rlim;
206 
207 	rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
208 	(void)setrlimit(lim, &rlim);
209 }
210 
211 /*
212  * Replace the password entry in the dbm data base with pwd.
213  */
214 replace(dp, pwd)
215 	DBM *dp;
216 	struct passwd *pwd;
217 {
218 	datum key, content;
219 	register char *cp, *tp;
220 	char buf[BUFSIZ];
221 
222 	if (dp == NULL)
223 		return;
224 
225 	cp = buf;
226 #define	COMPACT(e)	tp = pwd->e; while (*cp++ = *tp++);
227 	COMPACT(pw_name);
228 	COMPACT(pw_passwd);
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);
235 	COMPACT(pw_comment);
236 	COMPACT(pw_gecos);
237 	COMPACT(pw_dir);
238 	COMPACT(pw_shell);
239 	content.dptr = buf;
240 	content.dsize = cp - buf;
241 	key.dptr = pwd->pw_name;
242 	key.dsize = strlen(pwd->pw_name);
243 	dbm_store(dp, key, content, DBM_REPLACE);
244 	key.dptr = (char *)&pwd->pw_uid;
245 	key.dsize = sizeof (int);
246 	dbm_store(dp, key, content, DBM_REPLACE);
247 }
248 
249 char *
250 getnewpasswd(pwd, u)
251 	register struct passwd *pwd;
252 	uid_t u;
253 {
254 	time_t	salt, time();
255 	int	c, i, insist;
256 	char	*pw, pwbuf[10], pwcopy[10], saltc[2],
257 		*crypt(), *getpass();
258 
259 	if (pwd->pw_passwd[0] && u != 0) {
260 		(void)strcpy(pwbuf, getpass("Old password:"));
261 		pw = crypt(pwbuf, pwd->pw_passwd);
262 		if (strcmp(pw, pwd->pw_passwd) != 0) {
263 			puts("Sorry.");
264 			exit(1);
265 		}
266 	}
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;
279 		if (strlen(pwbuf) <= 4)
280 			puts("Please enter a longer password.");
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 		}
287 	}
288 	if (strcmp(pwbuf, getpass("Retype new password:"))) {
289 		puts("Mismatch - password unchanged.");
290 		exit(1);
291 	}
292 	(void)time(&salt);
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 	}
304 	return(crypt(pwbuf, saltc));
305 }
306 
307 char *
308 getloginshell(pwd, u)
309 	struct passwd *pwd;
310 	uid_t u;
311 {
312 	static char newshell[BUFSIZ];
313 	char *cp, *valid, *getusershell();
314 
315 	if (pwd->pw_shell == 0 || *pwd->pw_shell == '\0')
316 		pwd->pw_shell = DEFSHELL;
317 	if (u != 0) {
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);
326 	}
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);
335 	}
336 	/*
337 	 * Allow user to give shell name w/o preceding pathname.
338 	 */
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 			}
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 			}
361 		} while (strcmp(newshell, cp) != 0);
362 	}
363 	else
364 		valid = newshell;
365 	if (strcmp(valid, pwd->pw_shell) == 0) {
366 		puts("Login shell unchanged.");
367 		exit(1);
368 	}
369 	if (access(valid, X_OK) < 0) {
370 		printf("%s is unavailable.\n", valid);
371 		exit(1);
372 	}
373 	if (strcmp(valid, DEFSHELL) == 0)
374 		valid[0] = '\0';
375 	return (valid);
376 }
377 
378 struct 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  */
388 char *
389 getfingerinfo(pwd)
390 	struct passwd *pwd;
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);
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'.");
401 	/*
402 	 * Get name.
403 	 */
404 	do {
405 		printf("\nName [%s]: ", defaults->name);
406 		(void) fgets(in_str, BUFSIZ - 1, stdin);
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);
417 		(void) fgets(in_str, BUFSIZ - 1, stdin);
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.
424 	 * Remove hyphens.
425 	 */
426 	do {
427 		printf("Office Phone (Ex: 6426000) [%s]: ",
428 			defaults->office_phone);
429 		(void) fgets(in_str, BUFSIZ - 1, stdin);
430 		if (special_case(in_str, defaults->office_phone))
431 			break;
432 		remove_hyphens(in_str);
433 	} while (illegal_input(in_str) || not_all_digits(in_str));
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);
441 		(void) fgets(in_str, BUFSIZ - 1, stdin);
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) {
448 		puts("Finger information unchanged.");
449 		exit(1);
450 	}
451 	return (answer);
452 }
453 
454 /*
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
461  * type them in, but better safe than sorry)
462  *
463  * Returns '1' if a colon, comma or newline is found or the input line is
464  * too long.
465  */
466 illegal_input(input_str)
467 	char *input_str;
468 {
469 	char *ptr;
470 	int error_flag = 0;
471 	int length = strlen(input_str);
472 
473 	if (strpbrk(input_str, ",:")) {
474 		puts("':' and ',' are not allowed.");
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 	 */
493 	for (ptr = input_str; *ptr; ptr++)
494 		if (!isprint(*ptr)) {
495 			puts("Control characters are not allowed.");
496 			error_flag = 1;
497 			break;
498 		}
499 	return (error_flag);
500 }
501 
502 /*
503  * Removes '-'s from the input string.
504  */
505 remove_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  */
518 not_all_digits(str)
519 	register char *str;
520 {
521 	for (; *str; ++str)
522 		if (!isdigit(*str)) {
523 			puts("Phone numbers may only contain digits.");
524 			return(1);
525 		}
526 	return(0);
527 }
528 
529 /*
530  * Deal with Berkeley buildings.  Abbreviating Cory to C and Evans to E.
531  * Correction changes "str".
532  *
533  * Returns 1 if incorrect room format.
534  *
535  * Note: this function assumes that the newline has been removed from str.
536  */
537 illegal_building(str)
538 	register char *str;
539 {
540 	int length = strlen(str);
541 	register char *ptr;
542 
543 	/*
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.
547 	 */
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';
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  */
574 struct default_values *
575 get_defaults(str)
576 	char *str;
577 {
578 	struct default_values *answer;
579 	char	*malloc();
580 
581 	answer = (struct default_values *)
582 		malloc((unsigned)sizeof(struct default_values));
583 	if (answer == (struct default_values *) NULL) {
584 		fputs("\nUnable to allocate storage in get_defaults!\n", stderr);
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  */
618 special_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);
660 }
661