passwd -> chpass
[unix-history] / usr / src / usr.bin / chpass / chpass.c
CommitLineData
59f1a2c1 1/*-
8b35ab41
KB
2 * Copyright (c) 1988 The Regents of the University of California.
3 * All rights reserved.
4 *
59f1a2c1 5 * %sccs.include.redist.c%
8b35ab41
KB
6 */
7
8#ifndef lint
9char copyright[] =
10"@(#) Copyright (c) 1988 The Regents of the University of California.\n\
11 All rights reserved.\n";
12#endif /* not lint */
13
14#ifndef lint
59f1a2c1 15static char sccsid[] = "@(#)chpass.c 5.15 (Berkeley) %G%";
8b35ab41
KB
16#endif /* not lint */
17
18#include <sys/param.h>
19#include <sys/file.h>
20#include <sys/stat.h>
21#include <sys/signal.h>
22#include <sys/time.h>
23#include <sys/resource.h>
b7eb8299 24#include <pwd.h>
8b35ab41
KB
25#include <errno.h>
26#include <stdio.h>
27#include <ctype.h>
59f1a2c1 28#include <string.h>
435e8dff
KB
29#include "chpass.h"
30#include "pathnames.h"
8b35ab41
KB
31
32char e1[] = ": ";
33char e2[] = ":,";
34
8557219a
KB
35int p_change(), p_class(), p_expire(), p_gecos(), p_gid(), p_hdir();
36int p_login(), p_passwd(), p_shell(), p_uid();
8b35ab41
KB
37
38struct entry list[] = {
39 { "Login", p_login, 1, 5, e1, },
8557219a 40 { "Password", p_passwd, 1, 8, e1, },
8b35ab41
KB
41 { "Uid", p_uid, 1, 3, e1, },
42 { "Gid", p_gid, 1, 3, e1, },
43 { "Class", p_class, 1, 5, e1, },
44 { "Change", p_change, 1, 6, NULL, },
45 { "Expire", p_expire, 1, 6, NULL, },
b7eb8299 46#define E_NAME 7
ef97d022 47 { "Full Name", p_gecos, 0, 9, e2, },
b7eb8299 48#define E_BPHONE 8
ef97d022 49 { "Office Phone", p_gecos, 0, 12, e2, },
b7eb8299 50#define E_HPHONE 9
ef97d022 51 { "Home Phone", p_gecos, 0, 10, e2, },
b7eb8299 52#define E_LOCATE 10
ef97d022 53 { "Location", p_gecos, 0, 8, e2, },
8b35ab41 54 { "Home directory", p_hdir, 1, 14, e1, },
1e1166d4 55#define E_SHELL 12
8b35ab41
KB
56 { "Shell", p_shell, 0, 5, e1, },
57 { NULL, 0, },
58};
59
b7eb8299 60uid_t uid;
8b35ab41
KB
61
62main(argc, argv)
63 int argc;
64 char **argv;
65{
b7eb8299
KB
66 extern int errno, optind;
67 extern char *optarg;
8b35ab41 68 register char *p;
b7eb8299 69 struct passwd lpw, *pw;
8b35ab41
KB
70 struct rlimit rlim;
71 FILE *temp_fp;
b7eb8299 72 int aflag, ch, fd;
34dda72c 73 char *fend, *newsh, *passwd, *temp, *tend;
8b35ab41
KB
74 char from[MAXPATHLEN], to[MAXPATHLEN];
75 char *getusershell();
76
8b35ab41 77 uid = getuid();
b7eb8299 78 aflag = 0;
34dda72c
KB
79 newsh = NULL;
80 while ((ch = getopt(argc, argv, "a:s:")) != EOF)
b7eb8299
KB
81 switch(ch) {
82 case 'a':
34dda72c
KB
83 if (uid)
84 baduser();
b7eb8299
KB
85 loadpw(optarg, pw = &lpw);
86 aflag = 1;
87 break;
34dda72c
KB
88 case 's':
89 newsh = optarg;
90 /* protect p_field -- it thinks NULL is /bin/sh */
91 if (!*newsh)
92 usage();
93 break;
b7eb8299
KB
94 case '?':
95 default:
96 usage();
8b35ab41 97 }
b7eb8299
KB
98 argc -= optind;
99 argv += optind;
100
101 if (!aflag)
102 switch(argc) {
103 case 0:
104 if (!(pw = getpwuid(uid))) {
105 (void)fprintf(stderr,
106 "chpass: unknown user: uid %u\n", uid);
107 exit(1);
108 }
109 break;
110 case 1:
111 if (!(pw = getpwnam(*argv))) {
112 (void)fprintf(stderr,
113 "chpass: unknown user %s.\n", *argv);
114 exit(1);
115 }
34dda72c
KB
116 if (uid && uid != pw->pw_uid)
117 baduser();
b7eb8299
KB
118 break;
119 default:
120 usage();
8b35ab41 121 }
8b35ab41
KB
122
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
128 rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
129 (void)setrlimit(RLIMIT_CPU, &rlim);
130 (void)setrlimit(RLIMIT_FSIZE, &rlim);
131
132 (void)umask(0);
133
134 temp = _PATH_PTMP;
135 if ((fd = open(temp, O_WRONLY|O_CREAT|O_EXCL, 0600)) < 0) {
136 if (errno == EEXIST) {
b7eb8299 137 (void)fprintf(stderr,
8b35ab41
KB
138 "chpass: password file busy -- try again later.\n");
139 exit(1);
140 }
7df40547
KB
141 (void)fprintf(stderr, "chpass: %s: %s; ",
142 temp, strerror(errno));
8b35ab41
KB
143 goto bad;
144 }
145 if (!(temp_fp = fdopen(fd, "w"))) {
7df40547 146 (void)fprintf(stderr, "chpass: can't write %s; ", temp);
8b35ab41
KB
147 goto bad;
148 }
149
34dda72c
KB
150 if (newsh) {
151 if (p_shell(newsh, pw, (struct entry *)NULL))
152 goto bad;
153 }
154 else if (!aflag && !info(pw))
8b35ab41
KB
155 goto bad;
156
8b35ab41
KB
157 /* root should have a 0 uid and a reasonable shell */
158 if (!strcmp(pw->pw_name, "root")) {
159 if (pw->pw_uid) {
b7eb8299 160 (void)fprintf(stderr, "chpass: root uid should be 0.");
8b35ab41
KB
161 exit(1);
162 }
163 setusershell();
164 for (;;)
165 if (!(p = getusershell())) {
b7eb8299 166 (void)fprintf(stderr,
8b35ab41
KB
167 "chpass: warning, unknown root shell.");
168 break;
169 }
170 else if (!strcmp(pw->pw_shell, p))
171 break;
172 }
173
174 passwd = _PATH_MASTERPASSWD;
175 if (!freopen(passwd, "r", stdin)) {
7df40547 176 (void)fprintf(stderr, "chpass: can't read %s; ", passwd);
8b35ab41
KB
177 goto bad;
178 }
179 if (!copy(pw, temp_fp))
180 goto bad;
181
182 (void)fclose(temp_fp);
183 (void)fclose(stdin);
184
185 switch(fork()) {
186 case 0:
187 break;
188 case -1:
7df40547 189 (void)fprintf(stderr, "chpass: can't fork; ");
8b35ab41
KB
190 goto bad;
191 /* NOTREACHED */
192 default:
193 exit(0);
194 /* NOTREACHED */
195 }
196
197 if (makedb(temp)) {
7df40547
KB
198 (void)fprintf(stderr, "chpass: mkpasswd failed; ");
199bad: (void)fprintf(stderr, "%s unchanged.\n", _PATH_MASTERPASSWD);
8b35ab41
KB
200 (void)unlink(temp);
201 exit(1);
202 }
203
204 /*
205 * possible race; have to rename four files, and someone could slip
206 * in between them. LOCK_EX and rename the ``passwd.dir'' file first
207 * so that getpwent(3) can't slip in; the lock should never fail and
208 * it's unclear what to do if it does. Rename ``ptmp'' last so that
209 * passwd/vipw/chpass can't slip in.
210 */
211 (void)setpriority(PRIO_PROCESS, 0, -20);
212 fend = strcpy(from, temp) + strlen(temp);
1f982253 213 tend = strcpy(to, _PATH_PASSWD) + strlen(_PATH_PASSWD);
8b35ab41
KB
214 bcopy(".dir", fend, 5);
215 bcopy(".dir", tend, 5);
216 if ((fd = open(from, O_RDONLY, 0)) >= 0)
217 (void)flock(fd, LOCK_EX);
218 /* here we go... */
219 (void)rename(from, to);
220 bcopy(".pag", fend, 5);
221 bcopy(".pag", tend, 5);
222 (void)rename(from, to);
223 bcopy(".orig", fend, 6);
224 (void)rename(from, _PATH_PASSWD);
225 (void)rename(temp, passwd);
226 /* done! */
227 exit(0);
228}
229
230info(pw)
231 struct passwd *pw;
232{
8b35ab41
KB
233 struct stat begin, end;
234 FILE *fp;
235 int fd, rval;
7df40547 236 char *tfile;
8b35ab41 237
435e8dff 238 tfile = _PATH_TMP;
8b35ab41 239 if ((fd = mkstemp(tfile)) == -1 || !(fp = fdopen(fd, "w+"))) {
b7eb8299 240 (void)fprintf(stderr, "chpass: no temporary file");
8b35ab41
KB
241 return(0);
242 }
243
1e1166d4
KB
244 /*
245 * if print doesn't print out a shell field, make it restricted.
246 * Not particularly pretty, but print is the routine that checks
247 * to see if the user can change their shell.
248 */
249 if (!print(fp, pw))
250 list[E_SHELL].restricted = 1;
8b35ab41
KB
251 (void)fflush(fp);
252
3e71cb13
KB
253 /*
254 * give the file to the real user; setuid permissions
255 * are discarded in edit()
256 */
257 (void)fchown(fd, getuid(), getgid());
8b35ab41 258
7df40547
KB
259 for (rval = 0;;) {
260 (void)fstat(fd, &begin);
261 if (edit(tfile)) {
262 (void)fprintf(stderr, "chpass: edit failed; ");
263 break;
264 }
265 (void)fstat(fd, &end);
266 if (begin.st_mtime == end.st_mtime) {
267 (void)fprintf(stderr, "chpass: no changes made; ");
268 break;
269 }
270 (void)rewind(fp);
271 if (check(fp, pw)) {
272 rval = 1;
273 break;
274 }
275 (void)fflush(stderr);
276 if (prompt())
277 break;
8b35ab41 278 }
7df40547
KB
279 (void)fclose(fp);
280 (void)unlink(tfile);
281 return(rval);
282}
283
284check(fp, pw)
285 FILE *fp;
286 struct passwd *pw;
287{
288 register struct entry *ep;
289 register char *p;
290 static char buf[1024];
291
8b35ab41 292 while (fgets(buf, sizeof(buf), fp)) {
7df40547 293 if (!buf[0] || buf[0] == '#')
8b35ab41
KB
294 continue;
295 if (!(p = index(buf, '\n'))) {
7df40547 296 (void)fprintf(stderr, "chpass: line too long.\n");
8b35ab41
KB
297 return(0);
298 }
299 *p = '\0';
7df40547
KB
300 for (ep = list;; ++ep) {
301 if (!ep->prompt) {
302 (void)fprintf(stderr,
303 "chpass: unrecognized field.\n");
304 return(0);
305 }
8b35ab41 306 if (!strncasecmp(buf, ep->prompt, ep->len)) {
1e1166d4
KB
307 if (ep->restricted && uid) {
308 (void)fprintf(stderr,
309 "chpass: you may not change the %s field.\n",
310 ep->prompt);
311 return(0);
312 }
8b35ab41 313 if (!(p = index(buf, ':'))) {
b7eb8299 314 (void)fprintf(stderr,
7df40547 315 "chpass: line corrupted.\n");
8b35ab41
KB
316 return(0);
317 }
318 while (isspace(*++p));
319 if (ep->except && strpbrk(p, ep->except)) {
b7eb8299 320 (void)fprintf(stderr,
1e1166d4 321 "chpass: illegal character in the \"%s\" field.\n",
8b35ab41
KB
322 ep->prompt);
323 return(0);
324 }
325 if ((ep->func)(p, pw, ep))
326 return(0);
327 break;
328 }
7df40547 329 }
8b35ab41 330 }
b7eb8299
KB
331 /*
332 * special checks...
333 *
334 * there has to be a limit on the size of the gecos fields,
335 * otherwise getpwent(3) can choke.
336 * ``if I swallow anything evil, put your fingers down my throat...''
337 * -- The Who
338 */
339 if (strlen(list[E_NAME].save) + strlen(list[E_BPHONE].save) +
340 strlen(list[E_HPHONE].save) + strlen(list[E_LOCATE].save)
341 > 512) {
342 (void)fprintf(stderr, "chpass: gecos field too large.\n");
343 exit(1);
344 }
345 (void)sprintf(pw->pw_gecos = buf, "%s,%s,%s,%s",
346 list[E_NAME].save, list[E_LOCATE].save, list[E_BPHONE].save,
347 list[E_HPHONE].save);
8b35ab41
KB
348 return(1);
349}
350
351copy(pw, fp)
352 struct passwd *pw;
353 FILE *fp;
354{
355 register int done;
356 register char *p;
357 char buf[1024];
358
359 for (done = 0; fgets(buf, sizeof(buf), stdin);) {
360 /* skip lines that are too big */
361 if (!index(buf, '\n')) {
7df40547 362 (void)fprintf(stderr, "chpass: line too long; ");
8b35ab41
KB
363 return(0);
364 }
365 if (done) {
b7eb8299 366 (void)fprintf(fp, "%s", buf);
8b35ab41
KB
367 continue;
368 }
369 if (!(p = index(buf, ':'))) {
7df40547 370 (void)fprintf(stderr, "chpass: corrupted entry; ");
8b35ab41
KB
371 return(0);
372 }
373 *p = '\0';
374 if (strcmp(buf, pw->pw_name)) {
375 *p = ':';
b7eb8299 376 (void)fprintf(fp, "%s", buf);
8b35ab41
KB
377 continue;
378 }
b7eb8299 379 (void)fprintf(fp, "%s:%s:%d:%d:%s:%ld:%ld:%s:%s:%s\n",
8b35ab41
KB
380 pw->pw_name, pw->pw_passwd, pw->pw_uid, pw->pw_gid,
381 pw->pw_class, pw->pw_change, pw->pw_expire, pw->pw_gecos,
382 pw->pw_dir, pw->pw_shell);
383 done = 1;
384 }
385 if (!done)
b7eb8299 386 (void)fprintf(fp, "%s:%s:%d:%d:%s:%ld:%ld:%s:%s:%s\n",
8557219a 387 pw->pw_name, pw->pw_passwd, pw->pw_uid, pw->pw_gid,
8b35ab41
KB
388 pw->pw_class, pw->pw_change, pw->pw_expire, pw->pw_gecos,
389 pw->pw_dir, pw->pw_shell);
390 return(1);
391}
392
393makedb(file)
394 char *file;
395{
396 int status, pid, w;
397
398 if (!(pid = vfork())) {
399 execl(_PATH_MKPASSWD, "mkpasswd", "-p", file, NULL);
fb62dd1a 400 (void)fprintf(stderr, "chpass: can't find \"mkpasswd\".\n");
8b35ab41
KB
401 _exit(127);
402 }
403 while ((w = wait(&status)) != pid && w != -1);
404 return(w == -1 || status);
405}
406
407edit(file)
408 char *file;
409{
410 int status, pid, w;
411 char *p, *editor, *getenv();
412
413 if (editor = getenv("EDITOR")) {
414 if (p = rindex(editor, '/'))
415 ++p;
416 else
417 p = editor;
418 }
419 else
420 p = editor = "vi";
421 if (!(pid = vfork())) {
3e71cb13 422 (void)setgid(getgid());
9e0035c0 423 (void)setuid(getuid());
8b35ab41 424 execlp(editor, p, file, NULL);
fb62dd1a 425 (void)fprintf(stderr, "chpass: can't find \"%s\".\n", editor);
8b35ab41
KB
426 _exit(127);
427 }
428 while ((w = wait(&status)) != pid && w != -1);
429 return(w == -1 || status);
430}
b7eb8299
KB
431
432loadpw(arg, pw)
433 char *arg;
434 register struct passwd *pw;
435{
436 register char *cp;
59f1a2c1 437 char *bp = arg;
b7eb8299
KB
438 long atol();
439 char *strsep();
440
59f1a2c1
MT
441 pw->pw_name = strsep(&bp, ":");
442 pw->pw_passwd = strsep(&bp, ":");
443 if (!(cp = strsep(&bp, ":")))
b7eb8299
KB
444 goto bad;
445 pw->pw_uid = atoi(cp);
59f1a2c1 446 if (!(cp = strsep(&bp, ":")))
b7eb8299
KB
447 goto bad;
448 pw->pw_gid = atoi(cp);
59f1a2c1
MT
449 pw->pw_class = strsep(&bp, ":");
450 if (!(cp = strsep(&bp, ":")))
b7eb8299
KB
451 goto bad;
452 pw->pw_change = atol(cp);
59f1a2c1 453 if (!(cp = strsep(&bp, ":")))
b7eb8299
KB
454 goto bad;
455 pw->pw_expire = atol(cp);
59f1a2c1
MT
456 pw->pw_gecos = strsep(&bp, ":");
457 pw->pw_dir = strsep(&bp, ":");
458 pw->pw_shell = strsep(&bp, ":");
459 if (!pw->pw_shell || strsep(&bp, ":")) {
b7eb8299
KB
460bad: (void)fprintf(stderr, "chpass: bad password list.\n");
461 exit(1);
462 }
463}
464
7df40547
KB
465prompt()
466{
467 register int c;
468
469 for (;;) {
470 (void)printf("re-edit the password file? [y]: ");
471 (void)fflush(stdout);
472 c = getchar();
473 if (c != EOF && c != (int)'\n')
474 while (getchar() != (int)'\n');
475 return(c == (int)'n');
476 }
477 /* NOTREACHED */
478}
479
34dda72c
KB
480baduser()
481{
482 (void)fprintf(stderr, "chpass: %s\n", strerror(EACCES));
483 exit(1);
484}
485
b7eb8299
KB
486usage()
487{
34dda72c 488 (void)fprintf(stderr, "usage: chpass [-a list] [-s shell] [user]\n");
b7eb8299
KB
489 exit(1);
490}