1/*	$OpenBSD: pwd_check.c,v 1.19 2024/05/24 13:32:03 op Exp $	*/
2
3/*
4 * Copyright 2000 Niels Provos <provos@citi.umich.edu>
5 * 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 Niels Provos.
18 * 4. The name of the author may not be used to endorse or promote products
19 *    derived from this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33#include <sys/types.h>
34#include <sys/wait.h>
35
36#include <stdio.h>
37#include <stdlib.h>
38#include <string.h>
39#include <unistd.h>
40#include <limits.h>
41#include <errno.h>
42#include <err.h>
43#include <regex.h>
44#include <grp.h>
45#include <paths.h>
46#include <login_cap.h>
47#include <signal.h>
48
49int pwd_check(login_cap_t *, char *);
50int pwd_gettries(login_cap_t *);
51
52struct pattern {
53	char *match;
54	int flags;
55	char *response;
56};
57
58struct pattern patterns[] = {
59	{
60		"^[0-9]*$",
61		REG_EXTENDED|REG_NOSUB,
62		"Please don't use all-digit passwords."
63	},
64	{
65		"^[a-z]{1,9}$",
66		REG_EXTENDED|REG_NOSUB,
67		"Please don't use an all-lower case password."
68	},
69	{
70		"^[a-z]{1,6}[0-9]+$",
71		REG_EXTENDED|REG_NOSUB|REG_ICASE,
72		"Please use a more complicated password."
73	},
74	{
75		"^([a-z][0-9]){1,4}$",
76		REG_EXTENDED|REG_NOSUB|REG_ICASE,
77		"Please use a more complicated password."
78	},
79	{
80		"^([0-9][a-z]){1,4}$",
81		REG_EXTENDED|REG_NOSUB|REG_ICASE,
82		"Please use a more complicated password."
83	}
84};
85
86int
87pwd_check(login_cap_t *lc, char *password)
88{
89	regex_t rgx;
90	int i, res, min_len;
91	char *checker;
92	char *argp[] = { "sh", "-c", NULL, NULL};
93	int pipefds[2];
94	pid_t child;
95	uid_t uid;
96	gid_t gid;
97
98	min_len = (int)login_getcapnum(lc, "minpasswordlen", 6, 6);
99	if (min_len > 0 && strlen(password) < min_len) {
100		fprintf(stderr, "Please enter a longer password.\n");
101		return (0);
102	}
103
104	/* External password check program */
105	checker = login_getcapstr(lc, "passwordcheck", NULL, NULL);
106
107	/* Pipes are only used for external checker */
108	if (checker != NULL && pipe(pipefds) == -1) {
109		warn("pipe");
110		goto out;
111	}
112
113	/* Check password in low-privileged child */
114	switch (child = fork()) {
115	case -1:
116		warn("fork");
117		close(pipefds[0]);
118		close(pipefds[1]);
119		goto out;
120	case 0:
121		(void)signal(SIGINT, SIG_DFL);
122		(void)signal(SIGQUIT, SIG_DFL);
123		uid = getuid();
124		gid = getgid();
125		if (setresgid(gid, gid, gid) == -1) {
126			warn("setresgid");
127			exit(1);
128		}
129		if (setgroups(1, &gid) == -1) {
130			warn("setgroups");
131			exit(1);
132		}
133		if (setresuid(uid, uid, uid) == -1) {
134			warn("setresuid");
135			exit(1);
136		}
137
138		if (checker == NULL) {
139			if (pledge("stdio", NULL) == -1)
140				err(1, "pledge");
141
142			for (i = 0; i < sizeof(patterns) / sizeof(*patterns); i++) {
143				int ret;
144
145				if (regcomp(&rgx, patterns[i].match,
146				    patterns[i].flags) != 0)
147					continue;
148				ret = regexec(&rgx, password, 0, NULL, 0);
149				regfree(&rgx);
150				if (ret == 0) {
151					fprintf(stderr, "%s\n", patterns[i].response);
152					exit(1);
153				}
154			}
155			/* no external checker in use, accept the password */
156			exit(0);
157		}
158
159		if (pledge("stdio exec", NULL) == -1)
160			err(1, "pledge");
161
162		/* Otherwise, pass control to checker program */
163		argp[2] = checker;
164		if (dup2(pipefds[0], STDIN_FILENO) == -1) {
165			warn("dup2");
166			exit(1);
167		}
168		close(pipefds[0]);
169		close(pipefds[1]);
170
171		if (execv(_PATH_BSHELL, argp) == -1) {
172			warn("exec");
173			exit(1);
174		}
175		/* NOTREACHED */
176	default:
177		break; /* parent continues below */
178	}
179
180	if (checker != NULL) {
181		/* Send the password to STDIN of child */
182		close(pipefds[0]);
183		write(pipefds[1], password, strlen(password) + 1);
184		close(pipefds[1]);
185	}
186
187	/* get the return value from the child */
188	while (waitpid(child, &res, 0) == -1) {
189		if (errno != EINTR) {
190			warn("waitpid");
191			goto out;
192		}
193	}
194	if (WIFEXITED(res) && WEXITSTATUS(res) == 0) {
195		free(checker);
196		return (1);
197	}
198
199 out:
200	free(checker);
201	fprintf(stderr, "Please use a different password. Unusual capitalization,\n");
202	fprintf(stderr, "control characters, or digits are suggested.\n");
203
204	return (0);
205}
206
207int
208pwd_gettries(login_cap_t *lc)
209{
210	quad_t ntries;
211
212	if ((ntries = login_getcapnum(lc, "passwordtries", -1, -1)) != -1) {
213		if (ntries >= 0 && ntries <= INT_MAX)
214			return((int)ntries);
215		fprintf(stderr,
216		    "Warning: passwordtries out of range in /etc/login.conf");
217	}
218
219	/*
220	 * If no amount of tries is specified, return a default of 3,
221	 * meaning that after 3 attempts where the user is foiled by the
222	 * password checks, it will no longer be checked and they can set
223	 * it to whatever they like.  This is the historic BSD behavior.
224	 */
225	return (3);
226}
227