correctly document change/expire/class fields.
[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
25static char sccsid[] = "@(#)chpass.c 5.1 (Berkeley) %G%";
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>
34#include </usr/src/include/pwd.h>
35#include <errno.h>
36#include <stdio.h>
37#include <ctype.h>
38#include <chpass.h>
39#include <strings.h>
40
41char e1[] = ": ";
42char e2[] = ":,";
43
44int p_login(), p_uid(), p_gid(), p_hdir(), p_shell(), p_change(), p_class();
45int p_expire(), p_save();
46
47struct entry list[] = {
48 { "Login", p_login, 1, 5, e1, },
49 { "Uid", p_uid, 1, 3, e1, },
50 { "Gid", p_gid, 1, 3, e1, },
51 { "Class", p_class, 1, 5, e1, },
52 { "Change", p_change, 1, 6, NULL, },
53 { "Expire", p_expire, 1, 6, NULL, },
54#define E_NAME 6
55 { "Full Name", p_save, 0, 9, e2, },
56#define E_BPHONE 7
57 { "Office Phone", p_save, 0, 12, e2, },
58#define E_HPHONE 8
59 { "Home Phone", p_save, 0, 10, e2, },
60#define E_LOCATE 9
61 { "Location", p_save, 0, 8, e2, },
62 { "Home directory", p_hdir, 1, 14, e1, },
63 { "Shell", p_shell, 0, 5, e1, },
64 { NULL, 0, },
65};
66
67uid_t euid, uid;
68
69main(argc, argv)
70 int argc;
71 char **argv;
72{
73 extern int errno;
74 register char *p;
75 struct passwd *pw;
76 struct rlimit rlim;
77 FILE *temp_fp;
78 int fd;
79 char *fend, *passwd, *temp, *tend, buf[1024];
80 char from[MAXPATHLEN], to[MAXPATHLEN];
81 char *getusershell();
82
83 euid = geteuid();
84 uid = getuid();
85 switch(--argc) {
86 case 0:
87 if (!(pw = getpwuid(uid))) {
88 fprintf(stderr, "chpass: unknown user: uid %u\n", uid);
89 exit(1);
90 }
91 break;
92 case 1:
93 if (!(pw = getpwnam(argv[1]))) {
94 fprintf(stderr, "chpass: unknown user %s.\n", argv[1]);
95 exit(1);
96 }
97 if (uid && uid != pw->pw_uid) {
98 fprintf(stderr, "chpass: %s\n", strerror(EACCES));
99 exit(1);
100 }
101 break;
102 default:
103 fprintf(stderr, "usage: chpass [user]\n");
104 exit(1);
105 }
106
107 (void)signal(SIGHUP, SIG_IGN);
108 (void)signal(SIGINT, SIG_IGN);
109 (void)signal(SIGQUIT, SIG_IGN);
110 (void)signal(SIGTSTP, SIG_IGN);
111
112 rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
113 (void)setrlimit(RLIMIT_CPU, &rlim);
114 (void)setrlimit(RLIMIT_FSIZE, &rlim);
115
116 (void)umask(0);
117
118 temp = _PATH_PTMP;
119 if ((fd = open(temp, O_WRONLY|O_CREAT|O_EXCL, 0600)) < 0) {
120 if (errno == EEXIST) {
121 fprintf(stderr,
122 "chpass: password file busy -- try again later.\n");
123 exit(1);
124 }
125 fprintf(stderr, "chpass: %s: %s", temp, strerror(errno));
126 goto bad;
127 }
128 if (!(temp_fp = fdopen(fd, "w"))) {
129 fprintf(stderr, "chpass: can't write %s", temp);
130 goto bad;
131 }
132
133 if (!info(pw))
134 goto bad;
135
136 /*
137 * special checks...
138 *
139 * there has to be a limit on the size of the gecos fields,
140 * otherwise getpwent(3) can choke.
141 * ``if I swallow anything evil, put your fingers down my throat...''
142 * -- The Who
143 */
144 if (strlen(list[E_NAME].save) + strlen(list[E_BPHONE].save) +
145 strlen(list[E_HPHONE].save) + strlen(list[E_LOCATE].save)
146 > 512) {
147 fprintf(stderr, "chpass: gecos field too large.\n");
148 exit(1);
149 }
150 (void)sprintf(pw->pw_gecos = buf, "%s,%s,%s,%s",
151 list[E_NAME].save, list[E_BPHONE].save, list[E_HPHONE].save,
152 list[E_LOCATE].save);
153
154 /* root should have a 0 uid and a reasonable shell */
155 if (!strcmp(pw->pw_name, "root")) {
156 if (pw->pw_uid) {
157 fprintf(stderr, "chpass: root uid should be 0.");
158 exit(1);
159 }
160 setusershell();
161 for (;;)
162 if (!(p = getusershell())) {
163 fprintf(stderr,
164 "chpass: warning, unknown root shell.");
165 break;
166 }
167 else if (!strcmp(pw->pw_shell, p))
168 break;
169 }
170
171 passwd = _PATH_MASTERPASSWD;
172 if (!freopen(passwd, "r", stdin)) {
173 fprintf(stderr, "chpass: can't read %s", passwd);
174 goto bad;
175 }
176 if (!copy(pw, temp_fp))
177 goto bad;
178
179 (void)fclose(temp_fp);
180 (void)fclose(stdin);
181
182 switch(fork()) {
183 case 0:
184 break;
185 case -1:
186 fprintf(stderr, "chpass: can't fork");
187 goto bad;
188 /* NOTREACHED */
189 default:
190 exit(0);
191 /* NOTREACHED */
192 }
193
194 if (makedb(temp)) {
195 fprintf(stderr, "chpass: mkpasswd failed");
196bad: fprintf(stderr, "; information unchanged.\n");
197 (void)unlink(temp);
198 exit(1);
199 }
200
201 /*
202 * possible race; have to rename four files, and someone could slip
203 * in between them. LOCK_EX and rename the ``passwd.dir'' file first
204 * so that getpwent(3) can't slip in; the lock should never fail and
205 * it's unclear what to do if it does. Rename ``ptmp'' last so that
206 * passwd/vipw/chpass can't slip in.
207 */
208 (void)setpriority(PRIO_PROCESS, 0, -20);
209 fend = strcpy(from, temp) + strlen(temp);
210 tend = strcpy(to, passwd) + strlen(passwd);
211 bcopy(".dir", fend, 5);
212 bcopy(".dir", tend, 5);
213 if ((fd = open(from, O_RDONLY, 0)) >= 0)
214 (void)flock(fd, LOCK_EX);
215 /* here we go... */
216 (void)rename(from, to);
217 bcopy(".pag", fend, 5);
218 bcopy(".pag", tend, 5);
219 (void)rename(from, to);
220 bcopy(".orig", fend, 6);
221 (void)rename(from, _PATH_PASSWD);
222 (void)rename(temp, passwd);
223 /* done! */
224 exit(0);
225}
226
227info(pw)
228 struct passwd *pw;
229{
230 register struct entry *ep;
231 register char *p;
232 struct stat begin, end;
233 FILE *fp;
234 int fd, rval;
235 char *tfile, buf[1024], *getenv();
236
237 tfile = "/tmp/passwd.XXXXXX";
238 if ((fd = mkstemp(tfile)) == -1 || !(fp = fdopen(fd, "w+"))) {
239 fprintf(stderr, "chpass: no temporary file");
240 return(0);
241 }
242
243 print(fp, pw);
244 (void)fflush(fp);
245
246 (void)fstat(fd, &begin);
247 rval = edit(tfile);
248 (void)unlink(tfile);
249
250 if (rval) {
251 fprintf(stderr, "chpass: edit failed");
252 return(0);
253 }
254 (void)fstat(fd, &end);
255 if (begin.st_mtime == end.st_mtime) {
256 fprintf(stderr, "chpass: no changes made");
257 return(0);
258 }
259 (void)rewind(fp);
260 while (fgets(buf, sizeof(buf), fp)) {
261 if (!buf[0])
262 continue;
263 if (!(p = index(buf, '\n'))) {
264 fprintf(stderr, "chpass: line too long");
265 return(0);
266 }
267 *p = '\0';
268 for (ep = list; ep->prompt; ++ep)
269 if (!strncasecmp(buf, ep->prompt, ep->len)) {
270 if (ep->restricted && euid)
271 continue;
272 if (!(p = index(buf, ':'))) {
273 fprintf(stderr,
274 "chpass: line corrupted");
275 return(0);
276 }
277 while (isspace(*++p));
278 if (ep->except && strpbrk(p, ep->except)) {
279 fprintf(stderr,
280 "chpass: illegal character in the \"%s\" field",
281 ep->prompt);
282 return(0);
283 }
284 if ((ep->func)(p, pw, ep))
285 return(0);
286 break;
287 }
288 }
289 (void)fclose(fp);
290 return(1);
291}
292
293copy(pw, fp)
294 struct passwd *pw;
295 FILE *fp;
296{
297 register int done;
298 register char *p;
299 char buf[1024];
300
301 for (done = 0; fgets(buf, sizeof(buf), stdin);) {
302 /* skip lines that are too big */
303 if (!index(buf, '\n')) {
304 fprintf(stderr, "chpass: line too long");
305 return(0);
306 }
307 if (done) {
308 fprintf(fp, "%s", buf);
309 continue;
310 }
311 if (!(p = index(buf, ':'))) {
312 fprintf(stderr, "chpass: corrupted entry");
313 return(0);
314 }
315 *p = '\0';
316 if (strcmp(buf, pw->pw_name)) {
317 *p = ':';
318 fprintf(fp, "%s", buf);
319 continue;
320 }
321 fprintf(fp, "%s:%s:%d:%d:%s:%ld:%ld:%s:%s:%s\n",
322 pw->pw_name, pw->pw_passwd, pw->pw_uid, pw->pw_gid,
323 pw->pw_class, pw->pw_change, pw->pw_expire, pw->pw_gecos,
324 pw->pw_dir, pw->pw_shell);
325 done = 1;
326 }
327 if (!done)
328 fprintf(fp, "%s:%s:%d:%d:%s:%ld:%ld:%s:%s:%s\n",
329 pw->pw_name, "NOLOGIN", pw->pw_uid, pw->pw_gid,
330 pw->pw_class, pw->pw_change, pw->pw_expire, pw->pw_gecos,
331 pw->pw_dir, pw->pw_shell);
332 return(1);
333}
334
335makedb(file)
336 char *file;
337{
338 int status, pid, w;
339
340 if (!(pid = vfork())) {
341 execl(_PATH_MKPASSWD, "mkpasswd", "-p", file, NULL);
342 _exit(127);
343 }
344 while ((w = wait(&status)) != pid && w != -1);
345 return(w == -1 || status);
346}
347
348edit(file)
349 char *file;
350{
351 int status, pid, w;
352 char *p, *editor, *getenv();
353
354 if (editor = getenv("EDITOR")) {
355 if (p = rindex(editor, '/'))
356 ++p;
357 else
358 p = editor;
359 }
360 else
361 p = editor = "vi";
362 if (!(pid = vfork())) {
363 execlp(editor, p, file, NULL);
364 _exit(127);
365 }
366 while ((w = wait(&status)) != pid && w != -1);
367 return(w == -1 || status);
368}