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