fetch interface pointer with packet
[unix-history] / usr / src / usr.bin / passwd / passwd.c
CommitLineData
252e456d 1#ifndef lint
008e3485 2static char sccsid[] = "@(#)passwd.c 4.18 (Berkeley) 85/08/28";
252e456d
SL
3#endif
4
5/*
844df0cd
RC
6 * Modify a field in the password file (either
7 * password, login shell, or gecos field).
9a59f452
SL
8 * This program should be suid with an owner
9 * with write permission on /etc/passwd.
252e456d 10 */
844df0cd 11#include <sys/types.h>
252e456d 12#include <sys/file.h>
844df0cd
RC
13#include <sys/time.h>
14#include <sys/resource.h>
252e456d
SL
15
16#include <stdio.h>
17#include <signal.h>
18#include <pwd.h>
9ae20471 19#include <ndbm.h>
252e456d 20#include <errno.h>
844df0cd
RC
21#include <strings.h>
22#include <ctype.h>
252e456d 23
8d3b95af 24char temp[] = "/etc/ptmp";
252e456d 25char passwd[] = "/etc/passwd";
252e456d
SL
26char *getpass();
27char *getlogin();
844df0cd
RC
28char *getfingerinfo();
29char *getloginshell();
30char *getnewpasswd();
252e456d
SL
31extern int errno;
32
33main(argc, argv)
34 char *argv[];
35{
844df0cd 36 struct passwd *pwd;
a1b0286d 37 char *cp, *uname, *progname;
ba9597f7 38 int fd, i, u, dochfn, dochsh, err;
252e456d 39 FILE *tf;
9ae20471 40 DBM *dp;
252e456d 41
98abc60f 42 if ((progname = rindex(argv[0], '/')) == NULL)
a1b0286d
RC
43 progname = argv[0];
44 else
45 progname++;
844df0cd
RC
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 }
a1b0286d
RC
73 if (!dochfn && !dochsh) {
74 if (strcmp(progname, "chfn") == 0)
75 dochfn = 1;
76 else if (strcmp(progname, "chsh") == 0)
77 dochsh = 1;
78 }
844df0cd 79 if (argc < 1) {
252e456d 80 if ((uname = getlogin()) == NULL) {
a1b0286d 81 fprintf(stderr, "Usage: %s [-f] [-s] [user]\n", progname);
252e456d
SL
82 exit(1);
83 }
844df0cd
RC
84 printf("Changing %s for %s.\n",
85 dochfn ? "finger information" :
86 dochsh ? "login shell" : "password",
87 uname);
252e456d 88 } else
98abc60f 89 uname = *argv++;
8d3b95af 90 pwd = getpwnam(uname);
05e78391 91 if (pwd == NULL) {
844df0cd 92 fprintf(stderr, "passwd: %s: unknown user.\n", uname);
05e78391
JB
93 exit(1);
94 }
252e456d 95 u = getuid();
05e78391 96 if (u != 0 && u != pwd->pw_uid) {
252e456d
SL
97 printf("Permission denied.\n");
98 exit(1);
99 }
844df0cd
RC
100 if (dochfn)
101 cp = getfingerinfo(pwd, u);
102 else if (dochsh)
98abc60f 103 cp = getloginshell(pwd, u, *argv);
844df0cd
RC
104 else
105 cp = getnewpasswd(pwd, u);
252e456d
SL
106 signal(SIGHUP, SIG_IGN);
107 signal(SIGINT, SIG_IGN);
108 signal(SIGQUIT, SIG_IGN);
dfcb5d54 109 signal(SIGTSTP, SIG_IGN);
643bb829 110 (void) umask(0);
9a59f452 111 fd = open(temp, O_WRONLY|O_CREAT|O_EXCL, 0644);
252e456d 112 if (fd < 0) {
ba9597f7
RE
113 err = errno;
114
252e456d 115 fprintf(stderr, "passwd: ");
ba9597f7 116 if (err == EEXIST)
252e456d 117 fprintf(stderr, "password file busy - try again.\n");
ba9597f7
RE
118 else {
119 errno = err;
252e456d 120 perror(temp);
ba9597f7 121 }
252e456d
SL
122 exit(1);
123 }
252e456d
SL
124 if ((tf = fdopen(fd, "w")) == NULL) {
125 fprintf(stderr, "passwd: fdopen failed?\n");
126 exit(1);
127 }
844df0cd 128 if ((dp = dbm_open(passwd, O_RDWR, 0644)) == NULL) {
ba9597f7 129 err = errno;
844df0cd 130 fprintf(stderr, "Warning: dbm_open failed: ");
ba9597f7 131 errno = err;
9ae20471 132 perror(passwd);
844df0cd 133 } else if (flock(dp->dbm_dirf, LOCK_EX) < 0) {
9ae20471 134 perror("Warning: lock failed");
844df0cd 135 dbm_close(dp);
9ae20471
RC
136 dp = NULL;
137 }
844df0cd
RC
138 unlimit(RLIMIT_CPU);
139 unlimit(RLIMIT_FSIZE);
252e456d
SL
140 /*
141 * Copy passwd to temp, replacing matching lines
142 * with new password.
143 */
144 while ((pwd = getpwent()) != NULL) {
8d3b95af 145 if (strcmp(pwd->pw_name, uname) == 0) {
252e456d
SL
146 if (u && u != pwd->pw_uid) {
147 fprintf(stderr, "passwd: permission denied.\n");
8d3b95af 148 goto out;
252e456d 149 }
844df0cd
RC
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] == '*') /* ??? */
252e456d 157 pwd->pw_gecos++;
9ae20471 158 replace(dp, pwd);
252e456d
SL
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();
844df0cd
RC
170 if (dp != NULL && dbm_error(dp))
171 fprintf(stderr, "Warning: dbm_store failed\n");
0fd3b352 172 fflush(tf);
183d82a9
RE
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);
844df0cd 179 dbm_close(dp);
9ae20471 180 if (rename(temp, passwd) < 0) {
183d82a9 181 perror("passwd: rename");
9ae20471
RC
182 out:
183 unlink(temp);
184 exit(1);
185 }
186 exit(0);
8d3b95af
RC
187}
188
844df0cd
RC
189unlimit(lim)
190{
191 struct rlimit rlim;
192
193 rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
194 (void) setrlimit(lim, &rlim);
195}
196
9ae20471
RC
197/*
198 * Replace the password entry in the dbm data base with pwd.
199 */
200replace(dp, pwd)
201 DBM *dp;
202 struct passwd *pwd;
8d3b95af 203{
9ae20471
RC
204 datum key, content;
205 register char *cp, *tp;
206 char buf[BUFSIZ];
8d3b95af 207
9ae20471
RC
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);
3c92851f
RC
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);
9ae20471
RC
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);
844df0cd 229 dbm_store(dp, key, content, DBM_REPLACE);
9ae20471
RC
230 key.dptr = (char *)&pwd->pw_uid;
231 key.dsize = sizeof (int);
844df0cd
RC
232 dbm_store(dp, key, content, DBM_REPLACE);
233}
234
235char *
236getnewpasswd(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 }
255tryagain:
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]
314char *okshells[] =
315 { "/bin/sh", "/bin/csh", "/bin/oldcsh", "/bin/newcsh", "/usr/new/csh", 0 };
316
317char *
98abc60f 318getloginshell(pwd, u, arg)
844df0cd
RC
319 struct passwd *pwd;
320 int u;
98abc60f 321 char *arg;
844df0cd
RC
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;
98abc60f
EW
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 }
844df0cd
RC
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
376struct 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 */
386char *
387getfingerinfo(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.
008e3485 423 * Remove hyphens.
844df0cd
RC
424 */
425 do {
008e3485 426 printf("Office Phone (Ex: 6426000) [%s]: ",
844df0cd
RC
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);
008e3485 432 } while (illegal_input(in_str) || not_all_digits(in_str));
844df0cd
RC
433 (void) strcat(strcat(answer, ","), in_str);
434 /*
435 * Get home phone number.
436 * Remove hyphens if present.
437 */
438 do {
439 printf("Home Phone (Ex: 9875432) [%s]: ", defaults->home_phone);
440 (void) fgets(in_str, BUFSIZ, stdin);
441 if (special_case(in_str, defaults->home_phone))
442 break;
443 remove_hyphens(in_str);
444 } while (illegal_input(in_str) || not_all_digits(in_str));
445 (void) strcat(strcat(answer, ","), in_str);
446 if (strcmp(answer, pwd->pw_gecos) == 0) {
447 printf("Finger information unchanged.\n");
448 exit(1);
449 }
450 return (answer);
451}
452
453/*
454 * Prints an error message if a ':' or a newline is found in the string.
455 * A message is also printed if the input string is too long.
456 * The password file uses :'s as seperators, and are not allowed in the "gcos"
457 * field. Newlines serve as delimiters between users in the password file,
458 * and so, those too, are checked for. (I don't think that it is possible to
459 * type them in, but better safe than sorry)
460 *
461 * Returns '1' if a colon or newline is found or the input line is too long.
462 */
463illegal_input(input_str)
464 char *input_str;
465{
466 char *ptr;
467 int error_flag = 0;
468 int length = strlen(input_str);
469
470 if (index(input_str, ':')) {
471 printf("':' is not allowed.\n");
472 error_flag = 1;
473 }
474 if (input_str[length-1] != '\n') {
475 /* the newline and the '\0' eat up two characters */
476 printf("Maximum number of characters allowed is %d\n",
477 BUFSIZ-2);
478 /* flush the rest of the input line */
479 while (getchar() != '\n')
480 /* void */;
481 error_flag = 1;
482 }
483 /*
484 * Delete newline by shortening string by 1.
485 */
486 input_str[length-1] = '\0';
487 /*
488 * Don't allow control characters, etc in input string.
489 */
490 for (ptr=input_str; *ptr != '\0'; ptr++) {
491 if ((int) *ptr < 040) {
492 printf("Control characters are not allowed.\n");
493 error_flag = 1;
494 break;
495 }
496 }
497 return (error_flag);
498}
499
500/*
501 * Removes '-'s from the input string.
502 */
503remove_hyphens(str)
504 char *str;
505{
506 char *hyphen;
507
508 while ((hyphen = index(str, '-')) != NULL)
509 (void) strcpy(hyphen, hyphen+1);
510}
511
512/*
513 * Checks to see if 'str' contains only digits (0-9). If not, then
514 * an error message is printed and '1' is returned.
515 */
516not_all_digits(str)
517 char *str;
518{
519 char *ptr;
520
521 for (ptr = str; *ptr != '\0'; ++ptr)
522 if (!isdigit(*ptr)) {
523 printf("Phone numbers can only contain digits.\n");
524 return (1);
525 }
526 return (0);
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;
579 char *malloc();
580
581 answer = (struct default_values *)
582 malloc((unsigned)sizeof(struct default_values));
583 if (answer == (struct default_values *) NULL) {
584 fprintf(stderr,
585 "\nUnable to allocate storage in get_defaults!\n");
586 exit(1);
587 }
588 /*
589 * Values if no corresponding string in "str".
590 */
591 answer->name = str;
592 answer->office_num = "";
593 answer->office_phone = "";
594 answer->home_phone = "";
595 str = index(answer->name, ',');
596 if (str == 0)
597 return (answer);
598 *str = '\0';
599 answer->office_num = str + 1;
600 str = index(answer->office_num, ',');
601 if (str == 0)
602 return (answer);
603 *str = '\0';
604 answer->office_phone = str + 1;
605 str = index(answer->office_phone, ',');
606 if (str == 0)
607 return (answer);
608 *str = '\0';
609 answer->home_phone = str + 1;
610 return (answer);
611}
612
613/*
614 * special_case returns true when either the default is accepted
615 * (str = '\n'), or when 'none' is typed. 'none' is accepted in
616 * either upper or lower case (or any combination). 'str' is modified
617 * in these two cases.
618 */
619special_case(str,default_str)
620 char *str, *default_str;
621{
622 static char word[] = "none\n";
623 char *ptr, *wordptr;
624
625 /*
626 * If the default is accepted, then change the old string do the
627 * default string.
628 */
629 if (*str == '\n') {
630 (void) strcpy(str, default_str);
631 return (1);
632 }
633 /*
634 * Check to see if str is 'none'. (It is questionable if case
635 * insensitivity is worth the hair).
636 */
637 wordptr = word-1;
638 for (ptr = str; *ptr != '\0'; ++ptr) {
639 ++wordptr;
640 if (*wordptr == '\0') /* then words are different sizes */
641 return (0);
642 if (*ptr == *wordptr)
643 continue;
644 if (isupper(*ptr) && (tolower(*ptr) == *wordptr))
645 continue;
646 /*
647 * At this point we have a mismatch, so we return
648 */
649 return (0);
650 }
651 /*
652 * Make sure that words are the same length.
653 */
654 if (*(wordptr+1) != '\0')
655 return (0);
656 /*
657 * Change 'str' to be the null string
658 */
659 *str = '\0';
660 return (1);
252e456d 661}