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