xref: /openbsd-src/lib/libutil/passwd.c (revision db3296cf5c1dd9058ceecc3a29fe4aaa0bd26000)
1 /*	$OpenBSD: passwd.c,v 1.42 2003/06/26 16:34:42 deraadt 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 #if defined(LIBC_SCCS) && !defined(lint)
33 static const char rcsid[] = "$OpenBSD: passwd.c,v 1.42 2003/06/26 16:34:42 deraadt Exp $";
34 #endif /* LIBC_SCCS and not lint */
35 
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <sys/time.h>
39 #include <sys/resource.h>
40 #include <sys/wait.h>
41 
42 #include <fcntl.h>
43 #include <unistd.h>
44 #include <stdlib.h>
45 #include <stdio.h>
46 #include <string.h>
47 #include <ctype.h>
48 #include <pwd.h>
49 #include <err.h>
50 #include <errno.h>
51 #include <paths.h>
52 #include <signal.h>
53 #include <limits.h>
54 
55 #include "util.h"
56 
57 #define NUM_OPTIONS	2	/* Number of hardcoded defaults */
58 
59 static void	pw_cont(int sig);
60 
61 static const char options[NUM_OPTIONS][2][80] = {
62 	{"localcipher", "blowfish,4"},
63 	{"ypcipher", "old"}
64 };
65 
66 static char pw_defdir[] = "/etc";
67 static char *pw_dir = pw_defdir;
68 static char *pw_lck;
69 
70 static void trim_whitespace(char *);
71 static int read_line(FILE *, char *, int);
72 static const char *pw_default(const char *);
73 
74 /* Removes head and/or tail spaces. */
75 static void
76 trim_whitespace(char *line)
77 {
78 	char   *p;
79 
80 	/* Remove leading spaces */
81 	p = line;
82 	while (isspace(*p))
83 		p++;
84 	(void) memmove(line, p, strlen(p) + 1);
85 
86 	/* Remove trailing spaces */
87 	p = line + strlen(line) - 1;
88 	while (isspace(*p))
89 		p--;
90 	*(p + 1) = '\0';
91 }
92 
93 
94 /* Get one line, remove spaces from front and tail */
95 static int
96 read_line(FILE *fp, char *line, int max)
97 {
98 	char   *p;
99 
100 	/* Read one line of config */
101 	if (fgets(line, max, fp) == 0)
102 		return 0;
103 	if (!(p = strchr(line, '\n'))) {
104 		warnx("line too long");
105 		return 0;
106 	}
107 	*p = '\0';
108 
109 	/* Remove comments */
110 	if ((p = strchr(line, '#')))
111 		*p = '\0';
112 
113 	trim_whitespace(line);
114 	return 1;
115 }
116 
117 
118 static const char *
119 pw_default(const char *option)
120 {
121 	int	i;
122 
123 	for (i = 0; i < NUM_OPTIONS; i++)
124 		if (!strcmp(options[i][0], option))
125 			return options[i][1];
126 	return NULL;
127 }
128 
129 char *
130 pw_file(const char *nm)
131 {
132 	const char *p = strrchr(nm, '/');
133 	char *new_nm;
134 
135 	if (p)
136 		p++;
137 	else
138 		p = nm;
139 
140 	if (asprintf(&new_nm, "%s/%s", pw_dir, p) == -1)
141 		return NULL;
142 	return new_nm;
143 }
144 
145 
146 /*
147  * Retrieve password information from the /etc/passwd.conf file,
148  * at the moment this is only for choosing the cipher to use.
149  * It could easily be used for other authentication methods as
150  * well.
151  */
152 void
153 pw_getconf(char *data, size_t max, const char *key, const char *option)
154 {
155 	FILE   *fp;
156 	char    line[LINE_MAX];
157 	static char result[LINE_MAX];
158 	char   *p;
159 	int	got = 0;
160 	int	found = 0;
161 
162 	result[0] = '\0';
163 
164 	p = pw_file(_PATH_PASSWDCONF);
165 	if (!p || (fp = fopen(p, "r")) == NULL) {
166 		if (p)
167 			free(p);
168 		if ((p = (char *)pw_default(option))) {
169 			strncpy(data, p, max - 1);
170 			data[max - 1] = '\0';
171 		} else
172 			data[0] = '\0';
173 		return;
174 	}
175 	free(p);
176 
177 	while (!found && (got || read_line(fp, line, LINE_MAX))) {
178 		got = 0;
179 		if (strncmp(key, line, strlen(key)) ||
180 		    line[strlen(key)] != ':')
181 			continue;
182 
183 		/* Now we found our specified key */
184 		while (read_line(fp, line, LINE_MAX)) {
185 			char   *p2;
186 
187 			/* Leaving key field */
188 			if (strchr(line, ':')) {
189 				got = 1;
190 				break;
191 			}
192 			p2 = line;
193 			if (!(p = strsep(&p2, "=")) || p2 == NULL)
194 				continue;
195 			trim_whitespace(p);
196 			if (!strncmp(p, option, strlen(option))) {
197 				trim_whitespace(p2);
198 				strlcpy(result, p2, sizeof result);
199 				found = 1;
200 				break;
201 			}
202 		}
203 	}
204 	fclose(fp);
205 
206 	/*
207 	 * If we got no result and were looking for a default
208 	 * value, try hard coded defaults.
209 	 */
210 
211 	if (!strlen(result) && !strcmp(key,"default") &&
212 	    (p = (char *)pw_default(option)))
213 		strncpy(data, p, max - 1);
214 	else
215 		strncpy(data, result, max - 1);
216 	data[max - 1] = '\0';
217 }
218 
219 
220 void
221 pw_setdir(const char *dir)
222 {
223 	char *p;
224 
225 	if (strcmp (dir, pw_dir) == 0)
226 		return;
227 	if (pw_dir != pw_defdir)
228 		free(pw_dir);
229 	pw_dir = strdup(dir);
230 	if (pw_lck) {
231 		p = pw_file(pw_lck);
232 		free(pw_lck);
233 		pw_lck = p;
234 	}
235 }
236 
237 
238 int
239 pw_lock(int retries)
240 {
241 	int i, fd;
242 	mode_t old_mode;
243 	int save_errno;
244 
245 	if (!pw_lck)
246 		return (-1);
247 	/* Acquire the lock file.  */
248 	old_mode = umask(0);
249 	fd = open(pw_lck, O_WRONLY|O_CREAT|O_EXCL, 0600);
250 	for (i = 0; i < retries && fd < 0 && errno == EEXIST; i++) {
251 		sleep(1);
252 		fd = open(pw_lck, O_WRONLY|O_CREAT|O_EXCL, 0600);
253 	}
254 	save_errno = errno;
255 	if (fd != -1 && fcntl(fd, F_SETFD, 1) == -1) {
256 		close(fd);
257 		fd = -1;
258 	}
259 	(void) umask(old_mode);
260 	errno = save_errno;
261 	return (fd);
262 }
263 
264 int
265 pw_mkdb(char *username, int flags)
266 {
267 	int pstat, ac;
268 	pid_t pid;
269 	char *av[8];
270 	struct stat sb;
271 
272 	if (pw_lck == NULL)
273 		return(-1);
274 
275 	/* A zero length passwd file is never ok */
276 	if (stat(pw_lck, &sb) == 0 && sb.st_size == 0) {
277 		warnx("%s is zero length", pw_lck);
278 		return (-1);
279 	}
280 
281 	ac = 0;
282 	av[ac++] = "pwd_mkdb";
283 	av[ac++] = "-d";
284 	av[ac++] = pw_dir;
285 	if (flags & _PASSWORD_SECUREONLY)
286 		av[ac++] = "-s";
287 	else if (!(flags & _PASSWORD_OMITV7))
288 		av[ac++] = "-p";
289 	if (username) {
290 		av[ac++] = "-u";
291 		av[ac++] = username;
292 	}
293 	av[ac++] = pw_lck;
294 	av[ac] = NULL;
295 
296 	pid = vfork();
297 	if (pid == -1)
298 		return (-1);
299 	if (pid == 0) {
300 		if (pw_lck)
301 			execv(_PATH_PWD_MKDB, av);
302 		_exit(1);
303 	}
304 	pid = waitpid(pid, &pstat, 0);
305 	if (pid == -1 || !WIFEXITED(pstat) || WEXITSTATUS(pstat) != 0)
306 		return (-1);
307 	return (0);
308 }
309 
310 int
311 pw_abort(void)
312 {
313 	return (pw_lck ? unlink(pw_lck) : -1);
314 }
315 
316 /* Everything below this point is intended for the convenience of programs
317  * which allow a user to interactively edit the passwd file.  Errors in the
318  * routines below will cause the process to abort. */
319 
320 static pid_t editpid = -1;
321 
322 static void
323 pw_cont(int signo)
324 {
325 	int save_errno = errno;
326 
327 	if (editpid != -1)
328 		kill(editpid, signo);
329 	errno = save_errno;
330 }
331 
332 void
333 pw_init(void)
334 {
335 	struct rlimit rlim;
336 
337 	/* Unlimited resource limits. */
338 	rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
339 	(void)setrlimit(RLIMIT_CPU, &rlim);
340 	(void)setrlimit(RLIMIT_FSIZE, &rlim);
341 	(void)setrlimit(RLIMIT_STACK, &rlim);
342 	(void)setrlimit(RLIMIT_DATA, &rlim);
343 	(void)setrlimit(RLIMIT_RSS, &rlim);
344 
345 	/* Don't drop core (not really necessary, but GP's). */
346 	rlim.rlim_cur = rlim.rlim_max = 0;
347 	(void)setrlimit(RLIMIT_CORE, &rlim);
348 
349 	/* Turn off signals. */
350 	(void)signal(SIGALRM, SIG_IGN);
351 	(void)signal(SIGHUP, SIG_IGN);
352 	(void)signal(SIGINT, SIG_IGN);
353 	(void)signal(SIGPIPE, SIG_IGN);
354 	(void)signal(SIGQUIT, SIG_IGN);
355 	(void)signal(SIGTERM, SIG_IGN);
356 	(void)signal(SIGCONT, pw_cont);
357 
358 	if (!pw_lck)
359 		pw_lck = pw_file(_PATH_MASTERPASSWD_LOCK);
360 }
361 
362 void
363 pw_edit(int notsetuid, const char *filename)
364 {
365 	int pstat;
366 	char *p;
367 	char * volatile editor;
368 	char *argp[] = {"sh", "-c", NULL, NULL};
369 
370 	if (!filename) {
371 		filename = pw_lck;
372 		if (!filename)
373 			return;
374 	}
375 
376 	if ((editor = getenv("EDITOR")) == NULL)
377 		editor = _PATH_VI;
378 
379 	if (asprintf(&p, "%s %s", editor, filename) == -1)
380 		return;
381 	argp[2] = p;
382 
383 	switch (editpid = vfork()) {
384 	case -1:			/* error */
385 		free(p);
386 		return;
387 	case 0:				/* child */
388 		if (notsetuid) {
389 			setgid(getgid());
390 			setuid(getuid());
391 		}
392 		execv(_PATH_BSHELL, argp);
393 		_exit(127);
394 	}
395 
396 	free(p);
397 	for (;;) {
398 		editpid = waitpid(editpid, (int *)&pstat, WUNTRACED);
399 		if (editpid == -1)
400 			pw_error(editor, 1, 1);
401 		else if (WIFSTOPPED(pstat))
402 			raise(WSTOPSIG(pstat));
403 		else if (WIFEXITED(pstat) && WEXITSTATUS(pstat) == 0)
404 			break;
405 		else
406 			pw_error(editor, 1, 1);
407 	}
408 	editpid = -1;
409 }
410 
411 void
412 pw_prompt(void)
413 {
414 	int first, c;
415 
416 	(void)printf("re-edit the password file? [y]: ");
417 	(void)fflush(stdout);
418 	first = c = getchar();
419 	while (c != '\n' && c != EOF)
420 		c = getchar();
421 	if (first == 'n')
422 		pw_error(NULL, 0, 0);
423 }
424 
425 void
426 pw_copy(int ffd, int tfd, struct passwd *pw)
427 {
428 	FILE   *from, *to;
429 	int	done;
430 	char   *p, buf[8192];
431 	char   *master = pw_file(_PATH_MASTERPASSWD);
432 
433 	if (!master)
434 		pw_error(NULL, 0, 1);
435 	if (!(from = fdopen(ffd, "r")))
436 		pw_error(master, 1, 1);
437 	if (!(to = fdopen(tfd, "w")))
438 		pw_error(pw_lck ? pw_lck : NULL, pw_lck ? 1 : 0, 1);
439 
440 	for (done = 0; fgets(buf, sizeof(buf), from);) {
441 		if (!strchr(buf, '\n')) {
442 			warnx("%s: line too long", master);
443 			pw_error(NULL, 0, 1);
444 		}
445 		if (done) {
446 			(void)fprintf(to, "%s", buf);
447 			if (ferror(to))
448 				goto err;
449 			continue;
450 		}
451 		if (!(p = strchr(buf, ':'))) {
452 			warnx("%s: corrupted entry", master);
453 			pw_error(NULL, 0, 1);
454 		}
455 		*p = '\0';
456 		if (strcmp(buf, pw->pw_name)) {
457 			*p = ':';
458 			(void)fprintf(to, "%s", buf);
459 			if (ferror(to))
460 				goto err;
461 			continue;
462 		}
463 		(void)fprintf(to, "%s:%s:%u:%u:%s:%d:%d:%s:%s:%s\n",
464 		    pw->pw_name, pw->pw_passwd, (u_int)pw->pw_uid,
465 		    (u_int)pw->pw_gid, pw->pw_class, pw->pw_change,
466 		    pw->pw_expire, pw->pw_gecos, pw->pw_dir,
467 		    pw->pw_shell);
468 		done = 1;
469 		if (ferror(to))
470 			goto err;
471 	}
472 	if (!done)
473 		(void)fprintf(to, "%s:%s:%d:%d:%s:%d:%d:%s:%s:%s\n",
474 		    pw->pw_name, pw->pw_passwd, pw->pw_uid, pw->pw_gid,
475 		    pw->pw_class, pw->pw_change, pw->pw_expire, pw->pw_gecos,
476 		    pw->pw_dir, pw->pw_shell);
477 
478 	if (ferror(to))
479 err:
480 		pw_error(NULL, 0, 1);
481 	free(master);
482 	(void)fclose(to);
483 }
484 
485 int
486 pw_scan(char *bp, struct passwd *pw, int *flags)
487 {
488 	u_long id;
489 	int root;
490 	char *p, *sh, *p2;
491 
492 	if (flags != (int *)NULL)
493 		*flags = 0;
494 
495 	if (!(p = strsep(&bp, ":")) || *p == '\0')	/* login */
496 		goto fmt;
497 	pw->pw_name = p;
498 	root = !strcmp(pw->pw_name, "root");
499 
500 	if (!(pw->pw_passwd = strsep(&bp, ":")))	/* passwd */
501 		goto fmt;
502 
503 	if (!(p = strsep(&bp, ":")))			/* uid */
504 		goto fmt;
505 	id = strtoul(p, &p2, 10);
506 	if (root && id) {
507 		warnx("root uid should be 0");
508 		return (0);
509 	}
510 	if (*p2 != '\0') {
511 		warnx("illegal uid field");
512 		return (0);
513 	}
514 	if (id > UID_MAX) {
515 		/* errno is set to ERANGE by strtoul(3) */
516 		warnx("uid greater than %u", UID_MAX-1);
517 		return (0);
518 	}
519 	pw->pw_uid = (uid_t)id;
520 	if ((*p == '\0') && (flags != (int *)NULL))
521 		*flags |= _PASSWORD_NOUID;
522 
523 	if (!(p = strsep(&bp, ":")))			/* gid */
524 		goto fmt;
525 	id = strtoul(p, &p2, 10);
526 	if (*p2 != '\0') {
527 		warnx("illegal gid field");
528 		return (0);
529 	}
530 	if (id > UID_MAX) {
531 		/* errno is set to ERANGE by strtoul(3) */
532 		warnx("gid greater than %u", UID_MAX-1);
533 		return (0);
534 	}
535 	pw->pw_gid = (gid_t)id;
536 	if ((*p == '\0') && (flags != (int *)NULL))
537 		*flags |= _PASSWORD_NOGID;
538 
539 	pw->pw_class = strsep(&bp, ":");		/* class */
540 	if (!(p = strsep(&bp, ":")))			/* change */
541 		goto fmt;
542 	pw->pw_change = atol(p);
543 	if ((*p == '\0') && (flags != (int *)NULL))
544 		*flags |= _PASSWORD_NOCHG;
545 	if (!(p = strsep(&bp, ":")))			/* expire */
546 		goto fmt;
547 	pw->pw_expire = atol(p);
548 	if ((*p == '\0') && (flags != (int *)NULL))
549 		*flags |= _PASSWORD_NOEXP;
550 	pw->pw_gecos = strsep(&bp, ":");		/* gecos */
551 	pw->pw_dir = strsep(&bp, ":");			/* directory */
552 	if (!(pw->pw_shell = strsep(&bp, ":")))		/* shell */
553 		goto fmt;
554 
555 	p = pw->pw_shell;
556 	if (root && *p) {				/* empty == /bin/sh */
557 		for (setusershell();;) {
558 			if (!(sh = getusershell())) {
559 				warnx("warning, unknown root shell");
560 				break;
561 			}
562 			if (!strcmp(p, sh))
563 				break;
564 		}
565 		endusershell();
566 	}
567 
568 	if ((p = strsep(&bp, ":"))) {			/* too many */
569 fmt:		warnx("corrupted entry");
570 		return (0);
571 	}
572 
573 	return (1);
574 }
575 
576 __dead void
577 pw_error(const char *name, int err, int eval)
578 {
579 	char   *master = pw_file(_PATH_MASTERPASSWD);
580 
581 	if (err) {
582 		if (name)
583 			warn("%s", name);
584 		else
585 			warn(NULL);
586 	}
587 	if (master) {
588 		warnx("%s: unchanged", master);
589 		free(master);
590 	}
591 
592 	pw_abort();
593 	exit(eval);
594 }
595