xref: /openbsd-src/lib/libutil/passwd.c (revision a28daedfc357b214be5c701aa8ba8adb29a7f1c2)
1 /*	$OpenBSD: passwd.c,v 1.49 2006/12/20 23:07:36 ray Exp $	*/
2 
3 /*
4  * Copyright (c) 1987, 1993, 1994, 1995
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <sys/time.h>
35 #include <sys/resource.h>
36 #include <sys/wait.h>
37 
38 #include <fcntl.h>
39 #include <unistd.h>
40 #include <stdlib.h>
41 #include <stdio.h>
42 #include <string.h>
43 #include <ctype.h>
44 #include <pwd.h>
45 #include <err.h>
46 #include <errno.h>
47 #include <paths.h>
48 #include <signal.h>
49 #include <limits.h>
50 
51 #include "util.h"
52 
53 static char pw_defdir[] = "/etc";
54 static char *pw_dir = pw_defdir;
55 static char *pw_lck;
56 
57 char *
58 pw_file(const char *nm)
59 {
60 	const char *p = strrchr(nm, '/');
61 	char *new_nm;
62 
63 	if (p)
64 		p++;
65 	else
66 		p = nm;
67 
68 	if (asprintf(&new_nm, "%s/%s", pw_dir, p) == -1)
69 		return NULL;
70 	return new_nm;
71 }
72 
73 void
74 pw_setdir(const char *dir)
75 {
76 	char *p;
77 
78 	if (strcmp (dir, pw_dir) == 0)
79 		return;
80 	if (pw_dir != pw_defdir)
81 		free(pw_dir);
82 	pw_dir = strdup(dir);
83 	if (pw_lck) {
84 		p = pw_file(pw_lck);
85 		free(pw_lck);
86 		pw_lck = p;
87 	}
88 }
89 
90 
91 int
92 pw_lock(int retries)
93 {
94 	int i, fd;
95 	mode_t old_mode;
96 	int save_errno;
97 
98 	if (!pw_lck) {
99 		errno = EINVAL;
100 		return (-1);
101 	}
102 	/* Acquire the lock file.  */
103 	old_mode = umask(0);
104 	fd = open(pw_lck, O_WRONLY|O_CREAT|O_EXCL, 0600);
105 	for (i = 0; i < retries && fd < 0 && errno == EEXIST; i++) {
106 		sleep(1);
107 		fd = open(pw_lck, O_WRONLY|O_CREAT|O_EXCL, 0600);
108 	}
109 	save_errno = errno;
110 	if (fd != -1 && fcntl(fd, F_SETFD, 1) == -1) {
111 		close(fd);
112 		fd = -1;
113 	}
114 	(void) umask(old_mode);
115 	errno = save_errno;
116 	return (fd);
117 }
118 
119 int
120 pw_mkdb(char *username, int flags)
121 {
122 	int pstat, ac;
123 	pid_t pid;
124 	char *av[8];
125 	struct stat sb;
126 
127 	if (pw_lck == NULL)
128 		return(-1);
129 
130 	/* A zero length passwd file is never ok */
131 	if (stat(pw_lck, &sb) == 0 && sb.st_size == 0) {
132 		warnx("%s is zero length", pw_lck);
133 		return (-1);
134 	}
135 
136 	ac = 0;
137 	av[ac++] = "pwd_mkdb";
138 	av[ac++] = "-d";
139 	av[ac++] = pw_dir;
140 	if (flags & _PASSWORD_SECUREONLY)
141 		av[ac++] = "-s";
142 	else if (!(flags & _PASSWORD_OMITV7))
143 		av[ac++] = "-p";
144 	if (username) {
145 		av[ac++] = "-u";
146 		av[ac++] = username;
147 	}
148 	av[ac++] = pw_lck;
149 	av[ac] = NULL;
150 
151 	pid = vfork();
152 	if (pid == -1)
153 		return (-1);
154 	if (pid == 0) {
155 		if (pw_lck)
156 			execv(_PATH_PWD_MKDB, av);
157 		_exit(1);
158 	}
159 	pid = waitpid(pid, &pstat, 0);
160 	if (pid == -1 || !WIFEXITED(pstat) || WEXITSTATUS(pstat) != 0)
161 		return (-1);
162 	return (0);
163 }
164 
165 int
166 pw_abort(void)
167 {
168 	return (pw_lck ? unlink(pw_lck) : -1);
169 }
170 
171 /* Everything below this point is intended for the convenience of programs
172  * which allow a user to interactively edit the passwd file.  Errors in the
173  * routines below will cause the process to abort. */
174 
175 static pid_t editpid = -1;
176 
177 static void
178 pw_cont(int signo)
179 {
180 	int save_errno = errno;
181 
182 	if (editpid != -1)
183 		kill(editpid, signo);
184 	errno = save_errno;
185 }
186 
187 void
188 pw_init(void)
189 {
190 	struct rlimit rlim;
191 
192 	/* Unlimited resource limits. */
193 	rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
194 	(void)setrlimit(RLIMIT_CPU, &rlim);
195 	(void)setrlimit(RLIMIT_FSIZE, &rlim);
196 	(void)setrlimit(RLIMIT_STACK, &rlim);
197 	(void)setrlimit(RLIMIT_DATA, &rlim);
198 	(void)setrlimit(RLIMIT_RSS, &rlim);
199 
200 	/* Don't drop core (not really necessary, but GP's). */
201 	rlim.rlim_cur = rlim.rlim_max = 0;
202 	(void)setrlimit(RLIMIT_CORE, &rlim);
203 
204 	/* Turn off signals. */
205 	(void)signal(SIGALRM, SIG_IGN);
206 	(void)signal(SIGHUP, SIG_IGN);
207 	(void)signal(SIGINT, SIG_IGN);
208 	(void)signal(SIGPIPE, SIG_IGN);
209 	(void)signal(SIGQUIT, SIG_IGN);
210 	(void)signal(SIGTERM, SIG_IGN);
211 	(void)signal(SIGCONT, pw_cont);
212 
213 	if (!pw_lck)
214 		pw_lck = pw_file(_PATH_MASTERPASSWD_LOCK);
215 }
216 
217 void
218 pw_edit(int notsetuid, const char *filename)
219 {
220 	int pstat;
221 	char *p;
222 	char * volatile editor;
223 	char *argp[] = {"sh", "-c", NULL, NULL};
224 
225 	if (!filename) {
226 		filename = pw_lck;
227 		if (!filename)
228 			return;
229 	}
230 
231 	if ((editor = getenv("EDITOR")) == NULL)
232 		editor = _PATH_VI;
233 
234 	if (asprintf(&p, "%s %s", editor, filename) == -1)
235 		return;
236 	argp[2] = p;
237 
238 	switch (editpid = vfork()) {
239 	case -1:			/* error */
240 		free(p);
241 		return;
242 	case 0:				/* child */
243 		if (notsetuid) {
244 			setgid(getgid());
245 			setuid(getuid());
246 		}
247 		execv(_PATH_BSHELL, argp);
248 		_exit(127);
249 	}
250 
251 	free(p);
252 	for (;;) {
253 		editpid = waitpid(editpid, (int *)&pstat, WUNTRACED);
254 		if (editpid == -1)
255 			pw_error(editor, 1, 1);
256 		else if (WIFSTOPPED(pstat))
257 			raise(WSTOPSIG(pstat));
258 		else if (WIFEXITED(pstat) && WEXITSTATUS(pstat) == 0)
259 			break;
260 		else
261 			pw_error(editor, 1, 1);
262 	}
263 	editpid = -1;
264 }
265 
266 void
267 pw_prompt(void)
268 {
269 	int first, c;
270 
271 	(void)printf("re-edit the password file? [y]: ");
272 	(void)fflush(stdout);
273 	first = c = getchar();
274 	while (c != '\n' && c != EOF)
275 		c = getchar();
276 	switch (first) {
277 	case EOF:
278 		putchar('\n');
279 		/* FALLTHROUGH */
280 	case 'n':
281 	case 'N':
282 		pw_error(NULL, 0, 0);
283 		break;
284 	}
285 }
286 
287 static int
288 pw_equal(const struct passwd *pw1, const struct passwd *pw2)
289 {
290 	return (strcmp(pw1->pw_name, pw2->pw_name) == 0 &&
291 	    pw1->pw_uid == pw2->pw_uid &&
292 	    pw1->pw_gid == pw2->pw_gid &&
293 	    strcmp(pw1->pw_class, pw2->pw_class) == 0 &&
294 	    pw1->pw_change == pw2->pw_change &&
295 	    pw1->pw_expire == pw2->pw_expire &&
296 	    strcmp(pw1->pw_gecos, pw2->pw_gecos) == 0 &&
297 	    strcmp(pw1->pw_dir, pw2->pw_dir) == 0 &&
298 	    strcmp(pw1->pw_shell, pw2->pw_shell) == 0);
299 }
300 
301 void
302 pw_copy(int ffd, int tfd, const struct passwd *pw, const struct passwd *opw)
303 {
304 	struct passwd tpw;
305 	FILE   *from, *to;
306 	int	done;
307 	char   *p, *ep, buf[8192];
308 	char   *master = pw_file(_PATH_MASTERPASSWD);
309 
310 	if (!master)
311 		pw_error(NULL, 0, 1);
312 	if (!(from = fdopen(ffd, "r")))
313 		pw_error(master, 1, 1);
314 	if (!(to = fdopen(tfd, "w")))
315 		pw_error(pw_lck ? pw_lck : NULL, pw_lck ? 1 : 0, 1);
316 
317 	for (done = 0; fgets(buf, (int)sizeof(buf), from);) {
318 		if ((ep = strchr(buf, '\n')) == NULL) {
319 			warnx("%s: line too long", master);
320 			pw_error(NULL, 0, 1);
321 		}
322 		if (done) {
323 			(void)fprintf(to, "%s", buf);
324 			if (ferror(to))
325 				goto fail;
326 			continue;
327 		}
328 		if (!(p = strchr(buf, ':'))) {
329 			warnx("%s: corrupted entry", master);
330 			pw_error(NULL, 0, 1);
331 		}
332 		*p = '\0';
333 		if (strcmp(buf, opw ? opw->pw_name : pw->pw_name)) {
334 			*p = ':';
335 			(void)fprintf(to, "%s", buf);
336 			if (ferror(to))
337 				goto fail;
338 			continue;
339 		}
340 		if (opw != NULL) {
341 			*p = ':';
342 			*ep = '\0';
343 			if (!pw_scan(buf, &tpw, NULL))
344 				pw_error(NULL, 0, 1);
345 			if (!pw_equal(&tpw, opw)) {
346 				warnx("%s: inconsistent entry", master);
347 				pw_error(NULL, 0, 1);
348 			}
349 		}
350 		(void)fprintf(to, "%s:%s:%u:%u:%s:%d:%d:%s:%s:%s\n",
351 		    pw->pw_name, pw->pw_passwd, (u_int)pw->pw_uid,
352 		    (u_int)pw->pw_gid, pw->pw_class, pw->pw_change,
353 		    pw->pw_expire, pw->pw_gecos, pw->pw_dir,
354 		    pw->pw_shell);
355 		done = 1;
356 		if (ferror(to))
357 			goto fail;
358 	}
359 	if (!done)
360 		(void)fprintf(to, "%s:%s:%d:%d:%s:%d:%d:%s:%s:%s\n",
361 		    pw->pw_name, pw->pw_passwd, pw->pw_uid, pw->pw_gid,
362 		    pw->pw_class, pw->pw_change, pw->pw_expire, pw->pw_gecos,
363 		    pw->pw_dir, pw->pw_shell);
364 
365 	if (ferror(to))
366 fail:
367 		pw_error(NULL, 0, 1);
368 	free(master);
369 	(void)fclose(to);
370 }
371 
372 int
373 pw_scan(char *bp, struct passwd *pw, int *flags)
374 {
375 	u_long id;
376 	int root;
377 	char *p, *sh, *p2;
378 
379 	if (flags != (int *)NULL)
380 		*flags = 0;
381 
382 	if (!(p = strsep(&bp, ":")) || *p == '\0')	/* login */
383 		goto fmt;
384 	pw->pw_name = p;
385 	root = !strcmp(pw->pw_name, "root");
386 
387 	if (!(pw->pw_passwd = strsep(&bp, ":")))	/* passwd */
388 		goto fmt;
389 
390 	if (!(p = strsep(&bp, ":")))			/* uid */
391 		goto fmt;
392 	id = strtoul(p, &p2, 10);
393 	if (root && id) {
394 		warnx("root uid should be 0");
395 		return (0);
396 	}
397 	if (*p2 != '\0') {
398 		warnx("illegal uid field");
399 		return (0);
400 	}
401 	if (id > UID_MAX) {
402 		/* errno is set to ERANGE by strtoul(3) */
403 		warnx("uid greater than %u", UID_MAX-1);
404 		return (0);
405 	}
406 	pw->pw_uid = (uid_t)id;
407 	if ((*p == '\0') && (flags != (int *)NULL))
408 		*flags |= _PASSWORD_NOUID;
409 
410 	if (!(p = strsep(&bp, ":")))			/* gid */
411 		goto fmt;
412 	id = strtoul(p, &p2, 10);
413 	if (*p2 != '\0') {
414 		warnx("illegal gid field");
415 		return (0);
416 	}
417 	if (id > UID_MAX) {
418 		/* errno is set to ERANGE by strtoul(3) */
419 		warnx("gid greater than %u", UID_MAX-1);
420 		return (0);
421 	}
422 	pw->pw_gid = (gid_t)id;
423 	if ((*p == '\0') && (flags != (int *)NULL))
424 		*flags |= _PASSWORD_NOGID;
425 
426 	pw->pw_class = strsep(&bp, ":");		/* class */
427 	if (!(p = strsep(&bp, ":")))			/* change */
428 		goto fmt;
429 	pw->pw_change = atol(p);
430 	if ((*p == '\0') && (flags != (int *)NULL))
431 		*flags |= _PASSWORD_NOCHG;
432 	if (!(p = strsep(&bp, ":")))			/* expire */
433 		goto fmt;
434 	pw->pw_expire = atol(p);
435 	if ((*p == '\0') && (flags != (int *)NULL))
436 		*flags |= _PASSWORD_NOEXP;
437 	pw->pw_gecos = strsep(&bp, ":");		/* gecos */
438 	pw->pw_dir = strsep(&bp, ":");			/* directory */
439 	if (!(pw->pw_shell = strsep(&bp, ":")))		/* shell */
440 		goto fmt;
441 
442 	p = pw->pw_shell;
443 	if (root && *p) {				/* empty == /bin/sh */
444 		for (setusershell();;) {
445 			if (!(sh = getusershell())) {
446 				warnx("warning, unknown root shell");
447 				break;
448 			}
449 			if (!strcmp(p, sh))
450 				break;
451 		}
452 		endusershell();
453 	}
454 
455 	if ((p = strsep(&bp, ":"))) {			/* too many */
456 fmt:		warnx("corrupted entry");
457 		return (0);
458 	}
459 
460 	return (1);
461 }
462 
463 __dead void
464 pw_error(const char *name, int error, int eval)
465 {
466 	char   *master = pw_file(_PATH_MASTERPASSWD);
467 
468 	if (error) {
469 		if (name)
470 			warn("%s", name);
471 		else
472 			warn(NULL);
473 	}
474 	if (master) {
475 		warnx("%s: unchanged", master);
476 		free(master);
477 	}
478 
479 	pw_abort();
480 	exit(eval);
481 }
482