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