1 /*
2  * Copyright (c) 2000-2003,2005,2016,2020,2021 by Solar Designer
3  * Copyright (c) 2008,2009 by Dmitry V. Levin
4  * See LICENSE
5  */
6 
7 #ifdef _MSC_VER
8 #define _CRT_NONSTDC_NO_WARNINGS /* we use POSIX function names */
9 #endif
10 
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <limits.h>
15 
16 #include "passwdqc.h"
17 #include "concat.h"
18 
19 static const char *skip_prefix(const char *sample, const char *prefix)
20 {
21 	size_t len = strlen(prefix);
22 
23 	if (strncmp(sample, prefix, len))
24 		return NULL;
25 	return sample + len;
26 }
27 
28 static int
29 parse_option(passwdqc_params_t *params, char **reason, const char *option)
30 {
31 	const char *err = "Invalid parameter value";
32 	const char * const err_oom = "Out of memory";
33 	const char *p;
34 	char *e;
35 	int i, rc = 0;
36 	unsigned long v;
37 
38 	*reason = NULL;
39 	if ((p = skip_prefix(option, "min="))) {
40 		for (i = 0; i < 5; i++) {
41 			if (!strncmp(p, "disabled", 8)) {
42 				v = INT_MAX;
43 				p += 8;
44 			} else {
45 				v = strtoul(p, &e, 10);
46 				p = e;
47 			}
48 			if (i < 4 && *p++ != ',')
49 				goto parse_error;
50 			if (v > INT_MAX)
51 				goto parse_error;
52 			if (i && (int)v > params->qc.min[i - 1])
53 				goto parse_error;
54 			params->qc.min[i] = v;
55 		}
56 		if (*p)
57 			goto parse_error;
58 	} else if ((p = skip_prefix(option, "max="))) {
59 		v = strtoul(p, &e, 10);
60 		if (*e || v < 8 || v > INT_MAX)
61 			goto parse_error;
62 		if (v > 10000)
63 			v = 10000;
64 		params->qc.max = v;
65 	} else if ((p = skip_prefix(option, "passphrase="))) {
66 		v = strtoul(p, &e, 10);
67 		if (*e || v > INT_MAX)
68 			goto parse_error;
69 		params->qc.passphrase_words = v;
70 	} else if ((p = skip_prefix(option, "match="))) {
71 		v = strtoul(p, &e, 10);
72 		if (*e || v > INT_MAX)
73 			goto parse_error;
74 		params->qc.match_length = v;
75 	} else if ((p = skip_prefix(option, "similar="))) {
76 		if (!strcmp(p, "permit"))
77 			params->qc.similar_deny = 0;
78 		else if (!strcmp(p, "deny"))
79 			params->qc.similar_deny = 1;
80 		else
81 			goto parse_error;
82 	} else if ((p = skip_prefix(option, "random="))) {
83 		v = strtoul(p, &e, 10);
84 		if (!strcmp(e, ",only")) {
85 			e += 5;
86 			params->qc.min[4] = INT_MAX;
87 		}
88 		if (*e || (v && v < 24) || v > 136)
89 			goto parse_error;
90 		params->qc.random_bits = v;
91 	} else if ((p = skip_prefix(option, "wordlist="))) {
92 		free(params->qc.wordlist);
93 		params->qc.wordlist = NULL;
94 		if (*p && !(params->qc.wordlist = strdup(p))) {
95 			err = err_oom;
96 			goto parse_error;
97 		}
98 	} else if ((p = skip_prefix(option, "denylist="))) {
99 		free(params->qc.denylist);
100 		params->qc.denylist = NULL;
101 		if (*p && !(params->qc.denylist = strdup(p))) {
102 			err = err_oom;
103 			goto parse_error;
104 		}
105 	} else if ((p = skip_prefix(option, "filter="))) {
106 		free(params->qc.filter);
107 		params->qc.filter = NULL;
108 		if (*p && !(params->qc.filter = strdup(p))) {
109 			err = err_oom;
110 			goto parse_error;
111 		}
112 	} else if ((p = skip_prefix(option, "enforce="))) {
113 		params->pam.flags &= ~F_ENFORCE_MASK;
114 		if (!strcmp(p, "users"))
115 			params->pam.flags |= F_ENFORCE_USERS;
116 		else if (!strcmp(p, "everyone"))
117 			params->pam.flags |= F_ENFORCE_EVERYONE;
118 		else if (strcmp(p, "none"))
119 			goto parse_error;
120 	} else if (!strcmp(option, "non-unix")) {
121 		if (params->pam.flags & F_CHECK_OLDAUTHTOK)
122 			goto parse_error;
123 		params->pam.flags |= F_NON_UNIX;
124 	} else if ((p = skip_prefix(option, "retry="))) {
125 		v = strtoul(p, &e, 10);
126 		if (*e || v > INT_MAX)
127 			goto parse_error;
128 		params->pam.retry = v;
129 	} else if ((p = skip_prefix(option, "ask_oldauthtok"))) {
130 		params->pam.flags &= ~F_ASK_OLDAUTHTOK_MASK;
131 		if (params->pam.flags & F_USE_FIRST_PASS)
132 			goto parse_error;
133 		if (!p[0])
134 			params->pam.flags |= F_ASK_OLDAUTHTOK_PRELIM;
135 		else if (!strcmp(p, "=update"))
136 			params->pam.flags |= F_ASK_OLDAUTHTOK_UPDATE;
137 		else
138 			goto parse_error;
139 	} else if (!strcmp(option, "check_oldauthtok")) {
140 		if (params->pam.flags & F_NON_UNIX)
141 			goto parse_error;
142 		params->pam.flags |= F_CHECK_OLDAUTHTOK;
143 	} else if (!strcmp(option, "use_first_pass")) {
144 		if (params->pam.flags & F_ASK_OLDAUTHTOK_MASK)
145 			goto parse_error;
146 		params->pam.flags |= F_USE_FIRST_PASS | F_USE_AUTHTOK;
147 	} else if (!strcmp(option, "use_authtok")) {
148 		params->pam.flags |= F_USE_AUTHTOK;
149 	} else if (!strcmp(option, "noaudit")) {
150 		params->pam.flags |= F_NO_AUDIT;
151 	} else if ((p = skip_prefix(option, "config="))) {
152 		if ((rc = passwdqc_params_load(params, reason, p)))
153 			goto parse_error;
154 	} else {
155 		err = "Invalid parameter";
156 		goto parse_error;
157 	}
158 
159 	return 0;
160 
161 parse_error:
162 	passwdqc_params_free(params);
163 	e = concat("Error parsing parameter \"", option, "\": ",
164 	    (rc ? (*reason ? *reason : err_oom) : err), NULL);
165 	free(*reason);
166 	*reason = e;
167 	return rc ? rc : -1;
168 }
169 
170 int
171 passwdqc_params_parse(passwdqc_params_t *params, char **reason,
172     int argc, const char *const *argv)
173 {
174 	int i;
175 
176 	*reason = NULL;
177 	for (i = 0; i < argc; ++i) {
178 		int rc;
179 
180 		if ((rc = parse_option(params, reason, argv[i])))
181 			return rc;
182 	}
183 	return 0;
184 }
185 
186 static const passwdqc_params_t defaults = {
187 	{
188 		{INT_MAX, 24, 11, 8, 7},	/* min */
189 		72,				/* max */
190 		3,				/* passphrase_words */
191 		4,				/* match_length */
192 		1,				/* similar_deny */
193 		47,				/* random_bits */
194 		NULL,				/* wordlist */
195 		NULL,				/* denylist */
196 		NULL				/* filter */
197 	},
198 	{
199 		F_ENFORCE_EVERYONE,		/* flags */
200 		3				/* retry */
201 	}
202 };
203 
204 void passwdqc_params_reset(passwdqc_params_t *params)
205 {
206 	*params = defaults;
207 }
208 
209 void passwdqc_params_free(passwdqc_params_t *params)
210 {
211 	free(params->qc.wordlist);
212 	free(params->qc.denylist);
213 	free(params->qc.filter);
214 	passwdqc_params_reset(params);
215 }
216