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