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