xref: /openbsd-src/lib/libutil/passwd.c (revision 62a742911104f98b9185b2c6b6007d9b1c36396c)
1 /*	$OpenBSD: passwd.c,v 1.20 1998/11/16 07:10:32 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.20 1998/11/16 07:10:32 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 	(void) umask(old_mode);
267 	errno = save_errno;
268 	return (fd);
269 }
270 
271 int
272 pw_mkdb()
273 {
274 	int pstat;
275 	pid_t pid;
276 	struct stat sb;
277 
278 	/* A zero length passwd file is never ok */
279 	if (pw_lck && stat(pw_lck, &sb) == 0) {
280 		if (sb.st_size == 0) {
281 			warnx("%s is zero length", pw_lck);
282 			return (-1);
283 		}
284 	}
285 
286 	pid = vfork();
287 	if (pid == -1)
288 		return (-1);
289 	if (pid == 0) {
290 		if (pw_lck)
291 			execl(_PATH_PWD_MKDB, "pwd_mkdb", "-p", "-d", pw_dir,
292 			    pw_lck, NULL);
293 		_exit(1);
294 	}
295 	pid = waitpid(pid, &pstat, 0);
296 	if (pid == -1 || !WIFEXITED(pstat) || WEXITSTATUS(pstat) != 0)
297 		return (-1);
298 	return (0);
299 }
300 
301 int
302 pw_abort()
303 {
304 	return (pw_lck ? unlink(pw_lck) : -1);
305 }
306 
307 /* Everything below this point is intended for the convenience of programs
308  * which allow a user to interactively edit the passwd file.  Errors in the
309  * routines below will cause the process to abort. */
310 
311 static pid_t editpid = -1;
312 
313 static void
314 pw_cont(sig)
315 	int sig;
316 {
317 
318 	if (editpid != -1)
319 		kill(editpid, sig);
320 }
321 
322 void
323 pw_init()
324 {
325 	struct rlimit rlim;
326 
327 	/* Unlimited resource limits. */
328 	rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
329 	(void)setrlimit(RLIMIT_CPU, &rlim);
330 	(void)setrlimit(RLIMIT_FSIZE, &rlim);
331 	(void)setrlimit(RLIMIT_STACK, &rlim);
332 	(void)setrlimit(RLIMIT_DATA, &rlim);
333 	(void)setrlimit(RLIMIT_RSS, &rlim);
334 
335 	/* Don't drop core (not really necessary, but GP's). */
336 	rlim.rlim_cur = rlim.rlim_max = 0;
337 	(void)setrlimit(RLIMIT_CORE, &rlim);
338 
339 	/* Turn off signals. */
340 	(void)signal(SIGALRM, SIG_IGN);
341 	(void)signal(SIGHUP, SIG_IGN);
342 	(void)signal(SIGINT, SIG_IGN);
343 	(void)signal(SIGPIPE, SIG_IGN);
344 	(void)signal(SIGQUIT, SIG_IGN);
345 	(void)signal(SIGTERM, SIG_IGN);
346 	(void)signal(SIGCONT, pw_cont);
347 
348 	if (!pw_lck)
349 		pw_lck = pw_file(_PATH_MASTERPASSWD_LOCK);
350 }
351 
352 void
353 pw_edit(notsetuid, filename)
354 	int notsetuid;
355 	const char *filename;
356 {
357 	int pstat;
358 	char *p, *editor;
359 	char *argp[] = {"sh", "-c", NULL, NULL};
360 
361 #ifdef __GNUC__
362 	(void)&editor;
363 #endif
364 	if (!filename) {
365 		filename = pw_lck;
366 		if (!filename)
367 			return;
368 	}
369 
370 	if ((editor = getenv("EDITOR")) == NULL)
371 		editor = _PATH_VI;
372 
373 	p = malloc(strlen(editor) + 1 + strlen(filename) + 1);
374 	if (p == NULL)
375 		return;
376 	sprintf(p, "%s %s", editor, filename);
377 	argp[2] = p;
378 
379 	switch (editpid = vfork()) {
380 	case -1:			/* error */
381 		free(p);
382 		return;
383 	case 0:				/* child */
384 		if (notsetuid) {
385 			setgid(getgid());
386 			setuid(getuid());
387 		}
388 		execv(_PATH_BSHELL, argp);
389 		_exit(127);
390 	}
391 
392 	free(p);
393 	for (;;) {
394 		editpid = waitpid(editpid, (int *)&pstat, WUNTRACED);
395 		if (editpid == -1)
396 			pw_error(editor, 1, 1);
397 		else if (WIFSTOPPED(pstat))
398 			raise(WSTOPSIG(pstat));
399 		else if (WIFEXITED(pstat) && WEXITSTATUS(pstat) == 0)
400 			break;
401 		else
402 			pw_error(editor, 1, 1);
403 	}
404 	editpid = -1;
405 }
406 
407 void
408 pw_prompt()
409 {
410 	int first, c;
411 
412 	(void)printf("re-edit the password file? [y]: ");
413 	(void)fflush(stdout);
414 	first = c = getchar();
415 	while (c != '\n' && c != EOF)
416 		c = getchar();
417 	if (first == 'n')
418 		pw_error(NULL, 0, 0);
419 }
420 
421 void
422 pw_copy(ffd, tfd, pw)
423 	int ffd, tfd;
424 	struct passwd *pw;
425 {
426 	FILE   *from, *to;
427 	int	done;
428 	char   *p, buf[8192];
429 	char   *master = pw_file(_PATH_MASTERPASSWD);
430 
431 	if (!master)
432 		pw_error(NULL, 0, 1);
433 	if (!(from = fdopen(ffd, "r")))
434 		pw_error(master, 1, 1);
435 	if (!(to = fdopen(tfd, "w")))
436 		pw_error(pw_lck ? pw_lck : NULL, pw_lck ? 1 : 0, 1);
437 
438 	for (done = 0; fgets(buf, sizeof(buf), from);) {
439 		if (!strchr(buf, '\n')) {
440 			warnx("%s: line too long", master);
441 			pw_error(NULL, 0, 1);
442 		}
443 		if (done) {
444 			(void)fprintf(to, "%s", buf);
445 			if (ferror(to))
446 				goto err;
447 			continue;
448 		}
449 		if (!(p = strchr(buf, ':'))) {
450 			warnx("%s: corrupted entry", master);
451 			pw_error(NULL, 0, 1);
452 		}
453 		*p = '\0';
454 		if (strcmp(buf, pw->pw_name)) {
455 			*p = ':';
456 			(void)fprintf(to, "%s", buf);
457 			if (ferror(to))
458 				goto err;
459 			continue;
460 		}
461 		(void)fprintf(to, "%s:%s:%d:%d:%s:%d:%d:%s:%s:%s\n",
462 		    pw->pw_name, pw->pw_passwd, pw->pw_uid, pw->pw_gid,
463 		    pw->pw_class, pw->pw_change, pw->pw_expire, pw->pw_gecos,
464 		    pw->pw_dir, pw->pw_shell);
465 		done = 1;
466 		if (ferror(to))
467 			goto err;
468 	}
469 	if (!done)
470 		(void)fprintf(to, "%s:%s:%d:%d:%s:%d:%d:%s:%s:%s\n",
471 		    pw->pw_name, pw->pw_passwd, pw->pw_uid, pw->pw_gid,
472 		    pw->pw_class, pw->pw_change, pw->pw_expire, pw->pw_gecos,
473 		    pw->pw_dir, pw->pw_shell);
474 
475 	if (ferror(to))
476 err:
477 	pw_error(NULL, 0, 1);
478 	(void)fclose(to);
479 }
480 
481 int
482 pw_scan(bp, pw, flags)
483 	char *bp;
484 	struct passwd *pw;
485 	int *flags;
486 {
487 	u_long id;
488 	int root;
489 	char *p, *sh, *p2;
490 
491 	if (flags != (int *)NULL)
492 		*flags = 0;
493 
494 	if (!(pw->pw_name = strsep(&bp, ":")))		/* login */
495 		goto fmt;
496 	root = !strcmp(pw->pw_name, "root");
497 
498 	if (!(pw->pw_passwd = strsep(&bp, ":")))	/* passwd */
499 		goto fmt;
500 
501 	if (!(p = strsep(&bp, ":")))			/* uid */
502 		goto fmt;
503 	id = strtoul(p, &p2, 10);
504 	if (root && id) {
505 		warnx("root uid should be 0");
506 		return (0);
507 	}
508 	if (*p2 != '\0') {
509 		warnx("illegal uid field");
510 		return (0);
511 	}
512 	if (id > UID_MAX) {
513 		/* errno is set to ERANGE by strtoul(3) */
514 		warnx("uid greater than %u", UID_MAX-1);
515 		return (0);
516 	}
517 	pw->pw_uid = (uid_t)id;
518 	if ((*p == '\0') && (flags != (int *)NULL))
519 		*flags |= _PASSWORD_NOUID;
520 
521 	if (!(p = strsep(&bp, ":")))			/* gid */
522 		goto fmt;
523 	id = strtoul(p, &p2, 10);
524 	if (*p2 != '\0') {
525 		warnx("illegal gid field");
526 		return (0);
527 	}
528 	if (id > UID_MAX) {
529 		/* errno is set to ERANGE by strtoul(3) */
530 		warnx("gid greater than %u", UID_MAX-1);
531 		return (0);
532 	}
533 	pw->pw_gid = (gid_t)id;
534 	if ((*p == '\0') && (flags != (int *)NULL))
535 		*flags |= _PASSWORD_NOGID;
536 
537 	pw->pw_class = strsep(&bp, ":");		/* class */
538 	if (!(p = strsep(&bp, ":")))			/* change */
539 		goto fmt;
540 	pw->pw_change = atol(p);
541 	if ((*p == '\0') && (flags != (int *)NULL))
542 		*flags |= _PASSWORD_NOCHG;
543 	if (!(p = strsep(&bp, ":")))			/* expire */
544 		goto fmt;
545 	pw->pw_expire = atol(p);
546 	if ((*p == '\0') && (flags != (int *)NULL))
547 		*flags |= _PASSWORD_NOEXP;
548 	pw->pw_gecos = strsep(&bp, ":");		/* gecos */
549 	pw->pw_dir = strsep(&bp, ":");			/* directory */
550 	if (!(pw->pw_shell = strsep(&bp, ":")))		/* shell */
551 		goto fmt;
552 
553 	p = pw->pw_shell;
554 	if (root && *p) {				/* empty == /bin/sh */
555 		for (setusershell();;) {
556 			if (!(sh = getusershell())) {
557 				warnx("warning, unknown root shell");
558 				break;
559 			}
560 			if (!strcmp(p, sh))
561 				break;
562 		}
563 		endusershell();
564 	}
565 
566 	if ((p = strsep(&bp, ":"))) {			/* too many */
567 fmt:		warnx("corrupted entry");
568 		return (0);
569 	}
570 
571 	return (1);
572 }
573 
574 void
575 pw_error(name, err, eval)
576 	const char *name;
577 	int err, eval;
578 {
579 	char   *master = pw_file(_PATH_MASTERPASSWD);
580 
581 	if (err)
582 		warn(name);
583 	if (master)
584 		warnx("%s: unchanged", master);
585 	pw_abort();
586 	exit(eval);
587 }
588