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