xref: /openbsd-src/lib/libutil/passwd.c (revision 2badd5e3f47d2d4252969cd98d7042b4e701b5ac)
1 /*	$OpenBSD: passwd.c,v 1.27 2001/08/16 18:24:32 millert 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.27 2001/08/16 18:24:32 millert 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, secureonly)
277 	char *username;
278 	int secureonly;
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 (secureonly)
299 		av[ac++] = "-s";
300 	else
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 
340 	if (editpid != -1)
341 		kill(editpid, sig);
342 }
343 
344 void
345 pw_init()
346 {
347 	struct rlimit rlim;
348 
349 	/* Unlimited resource limits. */
350 	rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
351 	(void)setrlimit(RLIMIT_CPU, &rlim);
352 	(void)setrlimit(RLIMIT_FSIZE, &rlim);
353 	(void)setrlimit(RLIMIT_STACK, &rlim);
354 	(void)setrlimit(RLIMIT_DATA, &rlim);
355 	(void)setrlimit(RLIMIT_RSS, &rlim);
356 
357 	/* Don't drop core (not really necessary, but GP's). */
358 	rlim.rlim_cur = rlim.rlim_max = 0;
359 	(void)setrlimit(RLIMIT_CORE, &rlim);
360 
361 	/* Turn off signals. */
362 	(void)signal(SIGALRM, SIG_IGN);
363 	(void)signal(SIGHUP, SIG_IGN);
364 	(void)signal(SIGINT, SIG_IGN);
365 	(void)signal(SIGPIPE, SIG_IGN);
366 	(void)signal(SIGQUIT, SIG_IGN);
367 	(void)signal(SIGTERM, SIG_IGN);
368 	(void)signal(SIGCONT, pw_cont);
369 
370 	if (!pw_lck)
371 		pw_lck = pw_file(_PATH_MASTERPASSWD_LOCK);
372 }
373 
374 void
375 pw_edit(notsetuid, filename)
376 	int notsetuid;
377 	const char *filename;
378 {
379 	int pstat;
380 	char *p, *editor;
381 	char *argp[] = {"sh", "-c", NULL, NULL};
382 
383 #ifdef __GNUC__
384 	(void)&editor;
385 #endif
386 	if (!filename) {
387 		filename = pw_lck;
388 		if (!filename)
389 			return;
390 	}
391 
392 	if ((editor = getenv("EDITOR")) == NULL)
393 		editor = _PATH_VI;
394 
395 	p = malloc(strlen(editor) + 1 + strlen(filename) + 1);
396 	if (p == NULL)
397 		return;
398 	sprintf(p, "%s %s", editor, filename);
399 	argp[2] = p;
400 
401 	switch (editpid = vfork()) {
402 	case -1:			/* error */
403 		free(p);
404 		return;
405 	case 0:				/* child */
406 		if (notsetuid) {
407 			setgid(getgid());
408 			setuid(getuid());
409 		}
410 		execv(_PATH_BSHELL, argp);
411 		_exit(127);
412 	}
413 
414 	free(p);
415 	for (;;) {
416 		editpid = waitpid(editpid, (int *)&pstat, WUNTRACED);
417 		if (editpid == -1)
418 			pw_error(editor, 1, 1);
419 		else if (WIFSTOPPED(pstat))
420 			raise(WSTOPSIG(pstat));
421 		else if (WIFEXITED(pstat) && WEXITSTATUS(pstat) == 0)
422 			break;
423 		else
424 			pw_error(editor, 1, 1);
425 	}
426 	editpid = -1;
427 }
428 
429 void
430 pw_prompt()
431 {
432 	int first, c;
433 
434 	(void)printf("re-edit the password file? [y]: ");
435 	(void)fflush(stdout);
436 	first = c = getchar();
437 	while (c != '\n' && c != EOF)
438 		c = getchar();
439 	if (first == 'n')
440 		pw_error(NULL, 0, 0);
441 }
442 
443 void
444 pw_copy(ffd, tfd, pw)
445 	int ffd, tfd;
446 	struct passwd *pw;
447 {
448 	FILE   *from, *to;
449 	int	done;
450 	char   *p, buf[8192];
451 	char   *master = pw_file(_PATH_MASTERPASSWD);
452 
453 	if (!master)
454 		pw_error(NULL, 0, 1);
455 	if (!(from = fdopen(ffd, "r")))
456 		pw_error(master, 1, 1);
457 	if (!(to = fdopen(tfd, "w")))
458 		pw_error(pw_lck ? pw_lck : NULL, pw_lck ? 1 : 0, 1);
459 
460 	for (done = 0; fgets(buf, sizeof(buf), from);) {
461 		if (!strchr(buf, '\n')) {
462 			warnx("%s: line too long", master);
463 			pw_error(NULL, 0, 1);
464 		}
465 		if (done) {
466 			(void)fprintf(to, "%s", buf);
467 			if (ferror(to))
468 				goto err;
469 			continue;
470 		}
471 		if (!(p = strchr(buf, ':'))) {
472 			warnx("%s: corrupted entry", master);
473 			pw_error(NULL, 0, 1);
474 		}
475 		*p = '\0';
476 		if (strcmp(buf, pw->pw_name)) {
477 			*p = ':';
478 			(void)fprintf(to, "%s", buf);
479 			if (ferror(to))
480 				goto err;
481 			continue;
482 		}
483 		(void)fprintf(to, "%s:%s:%d:%d:%s:%d:%d:%s:%s:%s\n",
484 		    pw->pw_name, pw->pw_passwd, pw->pw_uid, pw->pw_gid,
485 		    pw->pw_class, pw->pw_change, pw->pw_expire, pw->pw_gecos,
486 		    pw->pw_dir, pw->pw_shell);
487 		done = 1;
488 		if (ferror(to))
489 			goto err;
490 	}
491 	if (!done)
492 		(void)fprintf(to, "%s:%s:%d:%d:%s:%d:%d:%s:%s:%s\n",
493 		    pw->pw_name, pw->pw_passwd, pw->pw_uid, pw->pw_gid,
494 		    pw->pw_class, pw->pw_change, pw->pw_expire, pw->pw_gecos,
495 		    pw->pw_dir, pw->pw_shell);
496 
497 	if (ferror(to))
498 err:
499 	pw_error(NULL, 0, 1);
500 	free(master);
501 	(void)fclose(to);
502 }
503 
504 int
505 pw_scan(bp, pw, flags)
506 	char *bp;
507 	struct passwd *pw;
508 	int *flags;
509 {
510 	u_long id;
511 	int root;
512 	char *p, *sh, *p2;
513 
514 	if (flags != (int *)NULL)
515 		*flags = 0;
516 
517 	if (!(pw->pw_name = strsep(&bp, ":")))		/* login */
518 		goto fmt;
519 	root = !strcmp(pw->pw_name, "root");
520 
521 	if (!(pw->pw_passwd = strsep(&bp, ":")))	/* passwd */
522 		goto fmt;
523 
524 	if (!(p = strsep(&bp, ":")))			/* uid */
525 		goto fmt;
526 	id = strtoul(p, &p2, 10);
527 	if (root && id) {
528 		warnx("root uid should be 0");
529 		return (0);
530 	}
531 	if (*p2 != '\0') {
532 		warnx("illegal uid field");
533 		return (0);
534 	}
535 	if (id > UID_MAX) {
536 		/* errno is set to ERANGE by strtoul(3) */
537 		warnx("uid greater than %u", UID_MAX-1);
538 		return (0);
539 	}
540 	pw->pw_uid = (uid_t)id;
541 	if ((*p == '\0') && (flags != (int *)NULL))
542 		*flags |= _PASSWORD_NOUID;
543 
544 	if (!(p = strsep(&bp, ":")))			/* gid */
545 		goto fmt;
546 	id = strtoul(p, &p2, 10);
547 	if (*p2 != '\0') {
548 		warnx("illegal gid field");
549 		return (0);
550 	}
551 	if (id > UID_MAX) {
552 		/* errno is set to ERANGE by strtoul(3) */
553 		warnx("gid greater than %u", UID_MAX-1);
554 		return (0);
555 	}
556 	pw->pw_gid = (gid_t)id;
557 	if ((*p == '\0') && (flags != (int *)NULL))
558 		*flags |= _PASSWORD_NOGID;
559 
560 	pw->pw_class = strsep(&bp, ":");		/* class */
561 	if (!(p = strsep(&bp, ":")))			/* change */
562 		goto fmt;
563 	pw->pw_change = atol(p);
564 	if ((*p == '\0') && (flags != (int *)NULL))
565 		*flags |= _PASSWORD_NOCHG;
566 	if (!(p = strsep(&bp, ":")))			/* expire */
567 		goto fmt;
568 	pw->pw_expire = atol(p);
569 	if ((*p == '\0') && (flags != (int *)NULL))
570 		*flags |= _PASSWORD_NOEXP;
571 	pw->pw_gecos = strsep(&bp, ":");		/* gecos */
572 	pw->pw_dir = strsep(&bp, ":");			/* directory */
573 	if (!(pw->pw_shell = strsep(&bp, ":")))		/* shell */
574 		goto fmt;
575 
576 	p = pw->pw_shell;
577 	if (root && *p) {				/* empty == /bin/sh */
578 		for (setusershell();;) {
579 			if (!(sh = getusershell())) {
580 				warnx("warning, unknown root shell");
581 				break;
582 			}
583 			if (!strcmp(p, sh))
584 				break;
585 		}
586 		endusershell();
587 	}
588 
589 	if ((p = strsep(&bp, ":"))) {			/* too many */
590 fmt:		warnx("corrupted entry");
591 		return (0);
592 	}
593 
594 	return (1);
595 }
596 
597 void
598 pw_error(name, err, eval)
599 	const char *name;
600 	int err, eval;
601 {
602 	char   *master = pw_file(_PATH_MASTERPASSWD);
603 
604 	if (err) {
605 		if (name)
606 			warn("%s", name);
607 		else
608 			warn(NULL);
609 	}
610 	if (master)
611 		warnx("%s: unchanged", master);
612 	pw_abort();
613 	exit(eval);
614 }
615