xref: /openbsd/usr.bin/passwd/pwd_check.c (revision e8059197)
1*e8059197Sop /*	$OpenBSD: pwd_check.c,v 1.19 2024/05/24 13:32:03 op Exp $	*/
25bb578b1Smillert 
398cf3d57Sprovos /*
498cf3d57Sprovos  * Copyright 2000 Niels Provos <provos@citi.umich.edu>
598cf3d57Sprovos  * All rights reserved.
698cf3d57Sprovos  *
798cf3d57Sprovos  * Redistribution and use in source and binary forms, with or without
898cf3d57Sprovos  * modification, are permitted provided that the following conditions
998cf3d57Sprovos  * are met:
1098cf3d57Sprovos  * 1. Redistributions of source code must retain the above copyright
1198cf3d57Sprovos  *    notice, this list of conditions and the following disclaimer.
1298cf3d57Sprovos  * 2. Redistributions in binary form must reproduce the above copyright
1398cf3d57Sprovos  *    notice, this list of conditions and the following disclaimer in the
1498cf3d57Sprovos  *    documentation and/or other materials provided with the distribution.
1598cf3d57Sprovos  * 3. All advertising materials mentioning features or use of this software
1698cf3d57Sprovos  *    must display the following acknowledgement:
1798cf3d57Sprovos  *      This product includes software developed by Niels Provos.
1898cf3d57Sprovos  * 4. The name of the author may not be used to endorse or promote products
1998cf3d57Sprovos  *    derived from this software without specific prior written permission.
2098cf3d57Sprovos  *
2198cf3d57Sprovos  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
2298cf3d57Sprovos  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
2398cf3d57Sprovos  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
2498cf3d57Sprovos  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
2598cf3d57Sprovos  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
2698cf3d57Sprovos  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2798cf3d57Sprovos  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2898cf3d57Sprovos  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2998cf3d57Sprovos  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
3098cf3d57Sprovos  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3198cf3d57Sprovos  */
3298cf3d57Sprovos 
3398cf3d57Sprovos #include <sys/types.h>
3498cf3d57Sprovos #include <sys/wait.h>
35f063c472Smillert 
36f063c472Smillert #include <stdio.h>
3798cf3d57Sprovos #include <stdlib.h>
38f063c472Smillert #include <string.h>
3998cf3d57Sprovos #include <unistd.h>
4098cf3d57Sprovos #include <limits.h>
41f063c472Smillert #include <errno.h>
42b85939c3Smillert #include <err.h>
4398cf3d57Sprovos #include <regex.h>
4498cf3d57Sprovos #include <grp.h>
4598cf3d57Sprovos #include <paths.h>
46fc30494eSmillert #include <login_cap.h>
477813863fSdjm #include <signal.h>
4898cf3d57Sprovos 
49a661090bSdjm int pwd_check(login_cap_t *, char *);
50a661090bSdjm int pwd_gettries(login_cap_t *);
51a661090bSdjm 
5298cf3d57Sprovos struct pattern {
5398cf3d57Sprovos 	char *match;
5498cf3d57Sprovos 	int flags;
5598cf3d57Sprovos 	char *response;
5698cf3d57Sprovos };
5798cf3d57Sprovos 
5898cf3d57Sprovos struct pattern patterns[] = {
5998cf3d57Sprovos 	{
6098cf3d57Sprovos 		"^[0-9]*$",
6198cf3d57Sprovos 		REG_EXTENDED|REG_NOSUB,
6298cf3d57Sprovos 		"Please don't use all-digit passwords."
6398cf3d57Sprovos 	},
6498cf3d57Sprovos 	{
6598cf3d57Sprovos 		"^[a-z]{1,9}$",
6698cf3d57Sprovos 		REG_EXTENDED|REG_NOSUB,
6798cf3d57Sprovos 		"Please don't use an all-lower case password."
6898cf3d57Sprovos 	},
6998cf3d57Sprovos 	{
7098cf3d57Sprovos 		"^[a-z]{1,6}[0-9]+$",
7198cf3d57Sprovos 		REG_EXTENDED|REG_NOSUB|REG_ICASE,
7298cf3d57Sprovos 		"Please use a more complicated password."
7398cf3d57Sprovos 	},
7498cf3d57Sprovos 	{
7598cf3d57Sprovos 		"^([a-z][0-9]){1,4}$",
7698cf3d57Sprovos 		REG_EXTENDED|REG_NOSUB|REG_ICASE,
7798cf3d57Sprovos 		"Please use a more complicated password."
7898cf3d57Sprovos 	},
7998cf3d57Sprovos 	{
8098cf3d57Sprovos 		"^([0-9][a-z]){1,4}$",
8198cf3d57Sprovos 		REG_EXTENDED|REG_NOSUB|REG_ICASE,
8298cf3d57Sprovos 		"Please use a more complicated password."
8398cf3d57Sprovos 	}
8498cf3d57Sprovos };
8598cf3d57Sprovos 
8698cf3d57Sprovos int
pwd_check(login_cap_t * lc,char * password)870608549dSmillert pwd_check(login_cap_t *lc, char *password)
8898cf3d57Sprovos {
8998cf3d57Sprovos 	regex_t rgx;
90fc30494eSmillert 	int i, res, min_len;
915bb578b1Smillert 	char *checker;
927813863fSdjm 	char *argp[] = { "sh", "-c", NULL, NULL};
9398cf3d57Sprovos 	int pipefds[2];
946528d8bdStedu 	pid_t child;
957813863fSdjm 	uid_t uid;
967813863fSdjm 	gid_t gid;
9798cf3d57Sprovos 
98fc30494eSmillert 	min_len = (int)login_getcapnum(lc, "minpasswordlen", 6, 6);
99fc30494eSmillert 	if (min_len > 0 && strlen(password) < min_len) {
1002dcc237cSrobert 		fprintf(stderr, "Please enter a longer password.\n");
10198cf3d57Sprovos 		return (0);
10298cf3d57Sprovos 	}
10398cf3d57Sprovos 
1047813863fSdjm 	/* External password check program */
1055bb578b1Smillert 	checker = login_getcapstr(lc, "passwordcheck", NULL, NULL);
10698cf3d57Sprovos 
1077813863fSdjm 	/* Pipes are only used for external checker */
1087813863fSdjm 	if (checker != NULL && pipe(pipefds) == -1) {
10998cf3d57Sprovos 		warn("pipe");
11098cf3d57Sprovos 		goto out;
11198cf3d57Sprovos 	}
11298cf3d57Sprovos 
1137813863fSdjm 	/* Check password in low-privileged child */
1147813863fSdjm 	switch (child = fork()) {
1157813863fSdjm 	case -1:
11698cf3d57Sprovos 		warn("fork");
117409c6020Stobias 		close(pipefds[0]);
118409c6020Stobias 		close(pipefds[1]);
11998cf3d57Sprovos 		goto out;
1207813863fSdjm 	case 0:
1217813863fSdjm 		(void)signal(SIGINT, SIG_DFL);
1227813863fSdjm 		(void)signal(SIGQUIT, SIG_DFL);
1237813863fSdjm 		uid = getuid();
1247813863fSdjm 		gid = getgid();
1257813863fSdjm 		if (setresgid(gid, gid, gid) == -1) {
1267813863fSdjm 			warn("setresgid");
1277813863fSdjm 			exit(1);
1287813863fSdjm 		}
1297813863fSdjm 		if (setgroups(1, &gid) == -1) {
1307813863fSdjm 			warn("setgroups");
1317813863fSdjm 			exit(1);
1327813863fSdjm 		}
1337813863fSdjm 		if (setresuid(uid, uid, uid) == -1) {
1347813863fSdjm 			warn("setresuid");
1357813863fSdjm 			exit(1);
1367813863fSdjm 		}
1377813863fSdjm 
1387d4d297fSajacoutot 		if (checker == NULL) {
139a21fe559Sderaadt 			if (pledge("stdio", NULL) == -1)
140a21fe559Sderaadt 				err(1, "pledge");
141a21fe559Sderaadt 
1427813863fSdjm 			for (i = 0; i < sizeof(patterns) / sizeof(*patterns); i++) {
1432aeb6b04Sderaadt 				int ret;
1442aeb6b04Sderaadt 
1457813863fSdjm 				if (regcomp(&rgx, patterns[i].match,
1467813863fSdjm 				    patterns[i].flags) != 0)
1477813863fSdjm 					continue;
1482aeb6b04Sderaadt 				ret = regexec(&rgx, password, 0, NULL, 0);
1497813863fSdjm 				regfree(&rgx);
1502aeb6b04Sderaadt 				if (ret == 0) {
1512dcc237cSrobert 					fprintf(stderr, "%s\n", patterns[i].response);
1527813863fSdjm 					exit(1);
1537813863fSdjm 				}
1547813863fSdjm 			}
1557d4d297fSajacoutot 			/* no external checker in use, accept the password */
1567813863fSdjm 			exit(0);
1577d4d297fSajacoutot 		}
1587813863fSdjm 
159a21fe559Sderaadt 		if (pledge("stdio exec", NULL) == -1)
160a21fe559Sderaadt 			err(1, "pledge");
161a21fe559Sderaadt 
1627813863fSdjm 		/* Otherwise, pass control to checker program */
1637813863fSdjm 		argp[2] = checker;
1647813863fSdjm 		if (dup2(pipefds[0], STDIN_FILENO) == -1) {
1657813863fSdjm 			warn("dup2");
1667813863fSdjm 			exit(1);
16798cf3d57Sprovos 		}
16898cf3d57Sprovos 		close(pipefds[0]);
1697813863fSdjm 		close(pipefds[1]);
17098cf3d57Sprovos 
1717813863fSdjm 		if (execv(_PATH_BSHELL, argp) == -1) {
1727813863fSdjm 			warn("exec");
1737813863fSdjm 			exit(1);
1747813863fSdjm 		}
1757813863fSdjm 		/* NOTREACHED */
1767813863fSdjm 	default:
1777813863fSdjm 		break; /* parent continues below */
1787813863fSdjm 	}
1797813863fSdjm 
1807813863fSdjm 	if (checker != NULL) {
18198cf3d57Sprovos 		/* Send the password to STDIN of child */
1827813863fSdjm 		close(pipefds[0]);
18398cf3d57Sprovos 		write(pipefds[1], password, strlen(password) + 1);
18498cf3d57Sprovos 		close(pipefds[1]);
1857813863fSdjm 	}
18698cf3d57Sprovos 
18798cf3d57Sprovos 	/* get the return value from the child */
1882aeb6b04Sderaadt 	while (waitpid(child, &res, 0) == -1) {
189409c6020Stobias 		if (errno != EINTR) {
190409c6020Stobias 			warn("waitpid");
191409c6020Stobias 			goto out;
192409c6020Stobias 		}
1932aeb6b04Sderaadt 	}
1942aeb6b04Sderaadt 	if (WIFEXITED(res) && WEXITSTATUS(res) == 0) {
1955bb578b1Smillert 		free(checker);
19698cf3d57Sprovos 		return (1);
1975bb578b1Smillert 	}
19898cf3d57Sprovos 
19998cf3d57Sprovos  out:
2005bb578b1Smillert 	free(checker);
2012dcc237cSrobert 	fprintf(stderr, "Please use a different password. Unusual capitalization,\n");
2022dcc237cSrobert 	fprintf(stderr, "control characters, or digits are suggested.\n");
2037813863fSdjm 
20498cf3d57Sprovos 	return (0);
20598cf3d57Sprovos }
20698cf3d57Sprovos 
207ac597990Sderaadt int
pwd_gettries(login_cap_t * lc)2080608549dSmillert pwd_gettries(login_cap_t *lc)
209fc30494eSmillert {
210fc30494eSmillert 	quad_t ntries;
211fc30494eSmillert 
212fc30494eSmillert 	if ((ntries = login_getcapnum(lc, "passwordtries", -1, -1)) != -1) {
213184d11c7Smoritz 		if (ntries >= 0 && ntries <= INT_MAX)
2145bb578b1Smillert 			return((int)ntries);
215fc30494eSmillert 		fprintf(stderr,
216*e8059197Sop 		    "Warning: passwordtries out of range in /etc/login.conf");
217fc30494eSmillert 	}
21898cf3d57Sprovos 
2190608549dSmillert 	/*
2205bb578b1Smillert 	 * If no amount of tries is specified, return a default of 3,
2215bb578b1Smillert 	 * meaning that after 3 attempts where the user is foiled by the
2225bb578b1Smillert 	 * password checks, it will no longer be checked and they can set
2235bb578b1Smillert 	 * it to whatever they like.  This is the historic BSD behavior.
22498cf3d57Sprovos 	 */
22598cf3d57Sprovos 	return (3);
22698cf3d57Sprovos }
227