xref: /openbsd/usr.bin/passwd/pwd_check.c (revision 6528d8bd)
1*6528d8bdStedu /*	$OpenBSD: pwd_check.c,v 1.7 2004/03/14 22:53:18 tedu Exp $	*/
298cf3d57Sprovos /*
398cf3d57Sprovos  * Copyright 2000 Niels Provos <provos@citi.umich.edu>
498cf3d57Sprovos  * All rights reserved.
598cf3d57Sprovos  *
698cf3d57Sprovos  * Redistribution and use in source and binary forms, with or without
798cf3d57Sprovos  * modification, are permitted provided that the following conditions
898cf3d57Sprovos  * are met:
998cf3d57Sprovos  * 1. Redistributions of source code must retain the above copyright
1098cf3d57Sprovos  *    notice, this list of conditions and the following disclaimer.
1198cf3d57Sprovos  * 2. Redistributions in binary form must reproduce the above copyright
1298cf3d57Sprovos  *    notice, this list of conditions and the following disclaimer in the
1398cf3d57Sprovos  *    documentation and/or other materials provided with the distribution.
1498cf3d57Sprovos  * 3. All advertising materials mentioning features or use of this software
1598cf3d57Sprovos  *    must display the following acknowledgement:
1698cf3d57Sprovos  *      This product includes software developed by Niels Provos.
1798cf3d57Sprovos  * 4. The name of the author may not be used to endorse or promote products
1898cf3d57Sprovos  *    derived from this software without specific prior written permission.
1998cf3d57Sprovos  *
2098cf3d57Sprovos  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
2198cf3d57Sprovos  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
2298cf3d57Sprovos  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
2398cf3d57Sprovos  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
2498cf3d57Sprovos  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
2598cf3d57Sprovos  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2698cf3d57Sprovos  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2798cf3d57Sprovos  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2898cf3d57Sprovos  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2998cf3d57Sprovos  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3098cf3d57Sprovos  */
3198cf3d57Sprovos 
3298cf3d57Sprovos #include <sys/types.h>
3398cf3d57Sprovos #include <sys/wait.h>
34f063c472Smillert 
35f063c472Smillert #include <stdio.h>
3698cf3d57Sprovos #include <stdlib.h>
37f063c472Smillert #include <string.h>
3898cf3d57Sprovos #include <unistd.h>
3998cf3d57Sprovos #include <limits.h>
40f063c472Smillert #include <errno.h>
41b85939c3Smillert #include <err.h>
4298cf3d57Sprovos #include <regex.h>
4398cf3d57Sprovos #include <grp.h>
4498cf3d57Sprovos #include <paths.h>
4598cf3d57Sprovos #include <pwd.h>
46b85939c3Smillert #include <util.h>
47fc30494eSmillert #include <login_cap.h>
4898cf3d57Sprovos 
4998cf3d57Sprovos struct pattern {
5098cf3d57Sprovos 	char *match;
5198cf3d57Sprovos 	int flags;
5298cf3d57Sprovos 	char *response;
5398cf3d57Sprovos };
5498cf3d57Sprovos 
5598cf3d57Sprovos struct pattern patterns[] = {
5698cf3d57Sprovos 	{
5798cf3d57Sprovos 		"^[0-9]*$",
5898cf3d57Sprovos 		REG_EXTENDED|REG_NOSUB,
5998cf3d57Sprovos 		"Please don't use all-digit passwords."
6098cf3d57Sprovos 	},
6198cf3d57Sprovos 	{
6298cf3d57Sprovos 		"^[a-z]{1,9}$",
6398cf3d57Sprovos 		REG_EXTENDED|REG_NOSUB,
6498cf3d57Sprovos 		"Please don't use an all-lower case password."
6598cf3d57Sprovos 	},
6698cf3d57Sprovos 	{
6798cf3d57Sprovos 		"^[a-z]{1,6}[0-9]+$",
6898cf3d57Sprovos 		REG_EXTENDED|REG_NOSUB|REG_ICASE,
6998cf3d57Sprovos 		"Please use a more complicated password."
7098cf3d57Sprovos 	},
7198cf3d57Sprovos 	{
7298cf3d57Sprovos 		"^([a-z][0-9]){1,4}$",
7398cf3d57Sprovos 		REG_EXTENDED|REG_NOSUB|REG_ICASE,
7498cf3d57Sprovos 		"Please use a more complicated password."
7598cf3d57Sprovos 	},
7698cf3d57Sprovos 	{
7798cf3d57Sprovos 		"^([0-9][a-z]){1,4}$",
7898cf3d57Sprovos 		REG_EXTENDED|REG_NOSUB|REG_ICASE,
7998cf3d57Sprovos 		"Please use a more complicated password."
8098cf3d57Sprovos 	}
8198cf3d57Sprovos };
8298cf3d57Sprovos 
8398cf3d57Sprovos int
84fc30494eSmillert pwd_check(struct passwd *pwd, login_cap_t *lc, char *password)
8598cf3d57Sprovos {
8698cf3d57Sprovos 	regex_t rgx;
87fc30494eSmillert 	int i, res, min_len;
88fc30494eSmillert 	char *cp, option[LINE_MAX];
8998cf3d57Sprovos 	int pipefds[2];
90*6528d8bdStedu 	pid_t child;
9198cf3d57Sprovos 
92fc30494eSmillert 	min_len = (int) login_getcapnum(lc, "minpasswordlen", 6, 6);
93fc30494eSmillert 	if (min_len > 0 && strlen(password) < min_len) {
9498cf3d57Sprovos 		printf("Please enter a longer password.\n");
9598cf3d57Sprovos 		return (0);
9698cf3d57Sprovos 	}
9798cf3d57Sprovos 
9898cf3d57Sprovos 	for (i = 0; i < sizeof(patterns)/sizeof(struct pattern); i++) {
9998cf3d57Sprovos 		if (regcomp(&rgx, patterns[i].match, patterns[i].flags) != 0)
10098cf3d57Sprovos 			continue;
10198cf3d57Sprovos 		res = regexec(&rgx, password, 0, NULL, 0);
10298cf3d57Sprovos 		regfree(&rgx);
10398cf3d57Sprovos 		if (!res) {
10498cf3d57Sprovos 			printf("%s\nUnusual capitalization, control characters or digits are suggested.\n", patterns[i].response);
10598cf3d57Sprovos 			return (0);
10698cf3d57Sprovos 		}
10798cf3d57Sprovos 	}
10898cf3d57Sprovos 
10998cf3d57Sprovos 	/* Okay, now pass control to an external program */
110fc30494eSmillert 
111fc30494eSmillert 	/*
112fc30494eSmillert 	 * Check login.conf, falling back onto the deprecated passwd.conf
113fc30494eSmillert 	 */
114fc30494eSmillert 	if ((cp = login_getcapstr(lc, "passwordcheck", NULL, NULL)) != NULL) {
115fc30494eSmillert 		strlcpy(option, cp, sizeof(option));
116fc30494eSmillert 		free(cp);
117fc30494eSmillert 	} else {
11898cf3d57Sprovos 		pw_getconf(option, LINE_MAX, pwd->pw_name, "pwdcheck");
11998cf3d57Sprovos 
12098cf3d57Sprovos 		/* Try to find an entry for the group */
12198cf3d57Sprovos 		if (*option == 0) {
12298cf3d57Sprovos 			struct group *grp;
12398cf3d57Sprovos 			char grpkey[LINE_MAX];
12498cf3d57Sprovos 
12598cf3d57Sprovos 			grp = getgrgid(pwd->pw_gid);
12698cf3d57Sprovos 			if (grp != NULL) {
127ac597990Sderaadt 				snprintf(grpkey, LINE_MAX, ":%s",
12869af51b1Sitojun 				    grp->gr_name);
12969af51b1Sitojun 				pw_getconf(option, LINE_MAX, grpkey,
13069af51b1Sitojun 				    "pwdcheck");
13169af51b1Sitojun 			}
13269af51b1Sitojun 			if (grp != NULL && *option == 0 &&
13369af51b1Sitojun 			    strchr(pwd->pw_name, '.') == NULL) {
134ac597990Sderaadt 				snprintf(grpkey, LINE_MAX, ".%s",
135fc30494eSmillert 				    grp->gr_name);
136fc30494eSmillert 				pw_getconf(option, LINE_MAX, grpkey,
137fc30494eSmillert 				    "pwdcheck");
13898cf3d57Sprovos 			}
13998cf3d57Sprovos 			if (*option == 0)
140fc30494eSmillert 				pw_getconf(option, LINE_MAX, "default",
141fc30494eSmillert 				    "pwdcheck");
142fc30494eSmillert 		}
14398cf3d57Sprovos 	}
14498cf3d57Sprovos 
14598cf3d57Sprovos 	/* If no checker is specified, we accept the password */
14698cf3d57Sprovos 	if (*option == 0)
14798cf3d57Sprovos 		return (1);
14898cf3d57Sprovos 
14998cf3d57Sprovos 	if (pipe(pipefds) == -1) {
15098cf3d57Sprovos 		warn("pipe");
15198cf3d57Sprovos 		goto out;
15298cf3d57Sprovos 	}
15398cf3d57Sprovos 
154*6528d8bdStedu 	child = fork();
155*6528d8bdStedu 	if (child == 0) {
15698cf3d57Sprovos 		char *argp[] = { "sh", "-c", NULL, NULL};
15798cf3d57Sprovos 
15898cf3d57Sprovos 		/* Drop privileges */
15998cf3d57Sprovos 		seteuid(getuid());
16098cf3d57Sprovos 		setuid(getuid());
16198cf3d57Sprovos 
16298cf3d57Sprovos 		if (dup2(pipefds[0], STDIN_FILENO) == -1)
16398cf3d57Sprovos 			exit(1);
16498cf3d57Sprovos 
16598cf3d57Sprovos 		close(pipefds[0]);
16698cf3d57Sprovos 		close(pipefds[1]);
16798cf3d57Sprovos 
16898cf3d57Sprovos 		argp[2] = option;
16998cf3d57Sprovos 		if (execv(_PATH_BSHELL, argp) == -1)
17098cf3d57Sprovos 			exit(1);
17198cf3d57Sprovos 		/* NOT REACHED */
172*6528d8bdStedu 	} else if (child == -1) {
17398cf3d57Sprovos 		warn("fork");
17498cf3d57Sprovos 		goto out;
17598cf3d57Sprovos 	}
17698cf3d57Sprovos 	close(pipefds[0]);
17798cf3d57Sprovos 
17898cf3d57Sprovos 	/* Send the password to STDIN of child */
17998cf3d57Sprovos 	write(pipefds[1], password, strlen(password) + 1);
18098cf3d57Sprovos 	close(pipefds[1]);
18198cf3d57Sprovos 
18298cf3d57Sprovos 	/* get the return value from the child */
183*6528d8bdStedu 	wait(&child);
184*6528d8bdStedu 	if (WIFEXITED(child) && WEXITSTATUS(child) == 0)
18598cf3d57Sprovos 		return (1);
18698cf3d57Sprovos 
18798cf3d57Sprovos  out:
188ac597990Sderaadt 	printf("Please use a different password. Unusual capitalization,\n");
189ac597990Sderaadt 	printf("control characters, or digits are suggested.\n");
19098cf3d57Sprovos 	return (0);
19198cf3d57Sprovos }
19298cf3d57Sprovos 
193ac597990Sderaadt int
194ac597990Sderaadt pwd_gettries(struct passwd *pwd, login_cap_t *lc)
195fc30494eSmillert {
19698cf3d57Sprovos 	char option[LINE_MAX];
19798cf3d57Sprovos 	char *ep = option;
198fc30494eSmillert 	quad_t ntries;
199ac597990Sderaadt 	long lval;
200fc30494eSmillert 
201fc30494eSmillert 	/*
202fc30494eSmillert 	 * Check login.conf, falling back onto the deprecated passwd.conf
203fc30494eSmillert 	 */
204fc30494eSmillert 	if ((ntries = login_getcapnum(lc, "passwordtries", -1, -1)) != -1) {
205fc30494eSmillert 		if (ntries > INT_MAX || ntries < 0) {
206fc30494eSmillert 			fprintf(stderr,
207fc30494eSmillert 			    "Warning: pwdtries out of range in /etc/login.conf");
208fc30494eSmillert 			goto out;
209fc30494eSmillert 		}
210fc30494eSmillert 		return((int)ntries);
211fc30494eSmillert 	}
21298cf3d57Sprovos 
21398cf3d57Sprovos 	pw_getconf(option, LINE_MAX, pwd->pw_name, "pwdtries");
21498cf3d57Sprovos 
21598cf3d57Sprovos 	/* Try to find an entry for the group */
21698cf3d57Sprovos 	if (*option == 0) {
21798cf3d57Sprovos 		struct group *grp;
21898cf3d57Sprovos 		char grpkey[LINE_MAX];
21998cf3d57Sprovos 
22098cf3d57Sprovos 		grp = getgrgid(pwd->pw_gid);
22198cf3d57Sprovos 		if (grp != NULL) {
222ac597990Sderaadt 			snprintf(grpkey, LINE_MAX, ":%s", grp->gr_name);
22369af51b1Sitojun 			pw_getconf(option, LINE_MAX, grpkey, "pwdtries");
22469af51b1Sitojun 		}
22569af51b1Sitojun 		if (grp != NULL && *option == 0 &&
22669af51b1Sitojun 		    strchr(pwd->pw_name, '.') == NULL) {
227ac597990Sderaadt 			snprintf(grpkey, LINE_MAX, ".%s", grp->gr_name);
22898cf3d57Sprovos 			pw_getconf(option, LINE_MAX, grpkey, "pwdtries");
22998cf3d57Sprovos 		}
23098cf3d57Sprovos 		if (*option == 0)
23198cf3d57Sprovos 			pw_getconf(option, LINE_MAX, "default", "pwdtries");
23298cf3d57Sprovos 	}
23398cf3d57Sprovos 
23498cf3d57Sprovos 	if (*option == 0)
23598cf3d57Sprovos 		goto out;
236ac597990Sderaadt 
23798cf3d57Sprovos 	errno = 0;
23898cf3d57Sprovos 	lval = strtol(option, &ep, 10);
23998cf3d57Sprovos 	if (option[0] == '\0' || *ep != '\0') {
24098cf3d57Sprovos 		fprintf(stderr,
24198cf3d57Sprovos 		    "Warning: Bad pwdtries line in /etc/passwd.conf");
24298cf3d57Sprovos 		goto out;
24398cf3d57Sprovos 	}
244ac597990Sderaadt 	if ((errno == ERANGE && (lval == LONG_MAX || lval == LONG_MIN)) ||
245fc30494eSmillert 	    (lval > INT_MAX || lval < 0)) {
24698cf3d57Sprovos 		fprintf(stderr,
24798cf3d57Sprovos 		    "Warning: pwdtries out of range in /etc/passwd.conf");
24898cf3d57Sprovos 		goto out;
24998cf3d57Sprovos 	}
25098cf3d57Sprovos 	return((int) lval);
25198cf3d57Sprovos 
25298cf3d57Sprovos 	/* If no amount of tries is specified, return a default of
25398cf3d57Sprovos 	 * 3, meaning that after 3 attempts where the user is foiled
25498cf3d57Sprovos 	 * by the password checks, it will no longer be checked and
25598cf3d57Sprovos 	 * they can set it to whatever they like.
25698cf3d57Sprovos 	 */
25798cf3d57Sprovos 	out:
25898cf3d57Sprovos 		return (3);
25998cf3d57Sprovos }
260