1 /*-
2  * Copyright (c) 2001-2003 Networks Associates Technology, Inc.
3  * Copyright (c) 2004-2015 Dag-Erling Smørgrav
4  * All rights reserved.
5  *
6  * This software was developed for the FreeBSD Project by ThinkSec AS and
7  * Network Associates Laboratories, the Security Research Division of
8  * Network Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
9  * ("CBOSS"), as part of the DARPA CHATS research program.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. The name of the author may not be used to endorse or promote
20  *    products derived from this software without specific prior written
21  *    permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  *
35  * $OpenPAM: openpam_configure.c 938 2017-04-30 21:34:42Z des $
36  */
37 
38 #ifdef HAVE_CONFIG_H
39 # include "config.h"
40 #endif
41 
42 #include <sys/param.h>
43 
44 #include <errno.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 
49 #include <security/pam_appl.h>
50 
51 #include "openpam_impl.h"
52 #include "openpam_ctype.h"
53 #include "openpam_strlcat.h"
54 #include "openpam_strlcpy.h"
55 
56 static int openpam_load_chain(pam_handle_t *, const char *, pam_facility_t);
57 
58 /*
59  * Validate a service name.
60  *
61  * Returns a non-zero value if the argument points to a NUL-terminated
62  * string consisting entirely of characters in the POSIX portable filename
63  * character set, excluding the path separator character.
64  */
65 static int
66 valid_service_name(const char *name)
67 {
68 	const char *p;
69 
70 	if (OPENPAM_FEATURE(RESTRICT_SERVICE_NAME)) {
71 		/* path separator not allowed */
72 		for (p = name; *p != '\0'; ++p)
73 			if (!is_pfcs(*p))
74 				return (0);
75 	} else {
76 		/* path separator allowed */
77 		for (p = name; *p != '\0'; ++p)
78 			if (!is_pfcs(*p) && *p != '/')
79 				return (0);
80 	}
81 	return (1);
82 }
83 
84 /*
85  * Parse the facility name.
86  *
87  * Returns the corresponding pam_facility_t value, or -1 if the argument
88  * is not a valid facility name.
89  */
90 static pam_facility_t
91 parse_facility_name(const char *name)
92 {
93 	int i;
94 
95 	for (i = 0; i < PAM_NUM_FACILITIES; ++i)
96 		if (strcmp(pam_facility_name[i], name) == 0)
97 			return (i);
98 	return ((pam_facility_t)-1);
99 }
100 
101 /*
102  * Parse the control flag.
103  *
104  * Returns the corresponding pam_control_t value, or -1 if the argument is
105  * not a valid control flag name.
106  */
107 static pam_control_t
108 parse_control_flag(const char *name)
109 {
110 	int i;
111 
112 	for (i = 0; i < PAM_NUM_CONTROL_FLAGS; ++i)
113 		if (strcmp(pam_control_flag_name[i], name) == 0)
114 			return (i);
115 	return ((pam_control_t)-1);
116 }
117 
118 /*
119  * Validate a file name.
120  *
121  * Returns a non-zero value if the argument points to a NUL-terminated
122  * string consisting entirely of characters in the POSIX portable filename
123  * character set, including the path separator character.
124  */
125 static int
126 valid_module_name(const char *name)
127 {
128 	const char *p;
129 
130 	if (OPENPAM_FEATURE(RESTRICT_MODULE_NAME)) {
131 		/* path separator not allowed */
132 		for (p = name; *p != '\0'; ++p)
133 			if (!is_pfcs(*p))
134 				return (0);
135 	} else {
136 		/* path separator allowed */
137 		for (p = name; *p != '\0'; ++p)
138 			if (!is_pfcs(*p) && *p != '/')
139 				return (0);
140 	}
141 	return (1);
142 }
143 
144 typedef enum { pam_conf_style, pam_d_style } openpam_style_t;
145 
146 /*
147  * Extracts given chains from a policy file.
148  *
149  * Returns the number of policy entries which were found for the specified
150  * service and facility, or -1 if a system error occurred or a syntax
151  * error was encountered.
152  */
153 static int
154 openpam_parse_chain(pam_handle_t *pamh,
155 	const char *service,
156 	pam_facility_t facility,
157 	FILE *f,
158 	const char *filename,
159 	openpam_style_t style)
160 {
161 	pam_chain_t *this, **next;
162 	pam_facility_t fclt;
163 	pam_control_t ctlf;
164 	char *name, *servicename, *modulename;
165 	int count, lineno, ret, serrno;
166 	char **wordv, *word;
167 	int i, wordc;
168 
169 	count = 0;
170 	this = NULL;
171 	name = NULL;
172 	lineno = 0;
173 	wordc = 0;
174 	wordv = NULL;
175 	while ((wordv = openpam_readlinev(f, &lineno, &wordc)) != NULL) {
176 		/* blank line? */
177 		if (wordc == 0) {
178 			FREEV(wordc, wordv);
179 			continue;
180 		}
181 		i = 0;
182 
183 		/* check service name if necessary */
184 		if (style == pam_conf_style &&
185 		    strcmp(wordv[i++], service) != 0) {
186 			FREEV(wordc, wordv);
187 			continue;
188 		}
189 
190 		/* check facility name */
191 		if ((word = wordv[i++]) == NULL ||
192 		    (fclt = parse_facility_name(word)) == (pam_facility_t)-1) {
193 			openpam_log(PAM_LOG_ERROR,
194 			    "%s(%d): missing or invalid facility",
195 			    filename, lineno);
196 			errno = EINVAL;
197 			goto fail;
198 		}
199 		if (facility != fclt && facility != PAM_FACILITY_ANY) {
200 			FREEV(wordc, wordv);
201 			continue;
202 		}
203 
204 		/* check for "include" */
205 		if ((word = wordv[i++]) != NULL &&
206 		    strcmp(word, "include") == 0) {
207 			if ((servicename = wordv[i++]) == NULL ||
208 			    !valid_service_name(servicename)) {
209 				openpam_log(PAM_LOG_ERROR,
210 				    "%s(%d): missing or invalid service name",
211 				    filename, lineno);
212 				errno = EINVAL;
213 				goto fail;
214 			}
215 			if (wordv[i] != NULL) {
216 				openpam_log(PAM_LOG_ERROR,
217 				    "%s(%d): garbage at end of line",
218 				    filename, lineno);
219 				errno = EINVAL;
220 				goto fail;
221 			}
222 			ret = openpam_load_chain(pamh, servicename, fclt);
223 			FREEV(wordc, wordv);
224 			if (ret < 0) {
225 				/*
226 				 * Bogus errno, but this ensures that the
227 				 * outer loop does not just ignore the
228 				 * error and keep searching.
229 				 */
230 				if (errno == ENOENT)
231 					errno = EINVAL;
232 				goto fail;
233 			}
234 			continue;
235 		}
236 
237 		/* get control flag */
238 		if (word == NULL || /* same word we compared to "include" */
239 		    (ctlf = parse_control_flag(word)) == (pam_control_t)-1) {
240 			openpam_log(PAM_LOG_ERROR,
241 			    "%s(%d): missing or invalid control flag",
242 			    filename, lineno);
243 			errno = EINVAL;
244 			goto fail;
245 		}
246 
247 		/* get module name */
248 		if ((modulename = wordv[i++]) == NULL ||
249 		    !valid_module_name(modulename)) {
250 			openpam_log(PAM_LOG_ERROR,
251 			    "%s(%d): missing or invalid module name",
252 			    filename, lineno);
253 			errno = EINVAL;
254 			goto fail;
255 		}
256 
257 		/*
258 		 * Check if blacklisted.  Legacy support for removed previously
259 		 * enabled modules only.
260 		 */
261 		if ((strcmp(modulename, "pam_opie.so") == 0) ||
262 		    (strcmp(modulename, "pam_opieaccess.so") == 0)) {
263 			openpam_log(PAM_LOG_ERROR,
264 			    "ignoring blacklisted %s module,"
265 			    " update your /etc/pam.d/ configs",
266 			    modulename);
267 			FREEV(wordc, wordv);
268 			continue;
269 		}
270 
271 		/* allocate new entry */
272 		if ((this = calloc(1, sizeof *this)) == NULL)
273 			goto syserr;
274 		this->flag = ctlf;
275 
276 		/* load module */
277 		if ((this->module = openpam_load_module(modulename)) == NULL) {
278 			if (errno == ENOENT)
279 				errno = ENOEXEC;
280 			goto fail;
281 		}
282 
283 		/*
284 		 * The remaining items in wordv are the module's
285 		 * arguments.  We could set this->optv = wordv + i, but
286 		 * then free(this->optv) wouldn't work.  Instead, we free
287 		 * the words we've already consumed, shift the rest up,
288 		 * and clear the tail end of the array.
289 		 */
290 		this->optc = wordc - i;
291 		for (i = 0; i < wordc - this->optc; ++i) {
292 			FREE(wordv[i]);
293 		}
294 		for (i = 0; i < this->optc; ++i) {
295 			wordv[i] = wordv[wordc - this->optc + i];
296 			wordv[wordc - this->optc + i] = NULL;
297 		}
298 		this->optv = wordv;
299 		wordv = NULL;
300 		wordc = 0;
301 
302 		/* hook it up */
303 		for (next = &pamh->chains[fclt]; *next != NULL;
304 		     next = &(*next)->next)
305 			/* nothing */ ;
306 		*next = this;
307 		this = NULL;
308 		++count;
309 	}
310 	/*
311 	 * The loop ended because openpam_readword() returned NULL, which
312 	 * can happen for four different reasons: an I/O error (ferror(f)
313 	 * is true), a memory allocation failure (ferror(f) is false,
314 	 * feof(f) is false, errno is non-zero), the file ended with an
315 	 * unterminated quote or backslash escape (ferror(f) is false,
316 	 * feof(f) is true, errno is non-zero), or the end of the file was
317 	 * reached without error (ferror(f) is false, feof(f) is true,
318 	 * errno is zero).
319 	 */
320 	if (ferror(f) || errno != 0)
321 		goto syserr;
322 	if (!feof(f))
323 		goto fail;
324 	fclose(f);
325 	return (count);
326 syserr:
327 	serrno = errno;
328 	openpam_log(PAM_LOG_ERROR, "%s: %m", filename);
329 	errno = serrno;
330 	/* fall through */
331 fail:
332 	serrno = errno;
333 	if (this && this->optc && this->optv)
334 		FREEV(this->optc, this->optv);
335 	FREE(this);
336 	FREEV(wordc, wordv);
337 	FREE(wordv);
338 	FREE(name);
339 	fclose(f);
340 	errno = serrno;
341 	return (-1);
342 }
343 
344 /*
345  * Read the specified chains from the specified file.
346  *
347  * Returns 0 if the file exists but does not contain any matching lines.
348  *
349  * Returns -1 and sets errno to ENOENT if the file does not exist.
350  *
351  * Returns -1 and sets errno to some other non-zero value if the file
352  * exists but is unsafe or unreadable, or an I/O error occurs.
353  */
354 static int
355 openpam_load_file(pam_handle_t *pamh,
356 	const char *service,
357 	pam_facility_t facility,
358 	const char *filename,
359 	openpam_style_t style)
360 {
361 	FILE *f;
362 	int ret, serrno;
363 
364 	/* attempt to open the file */
365 	if ((f = fopen(filename, "r")) == NULL) {
366 		serrno = errno;
367 		openpam_log(errno == ENOENT ? PAM_LOG_DEBUG : PAM_LOG_ERROR,
368 		    "%s: %m", filename);
369 		errno = serrno;
370 		RETURNN(-1);
371 	} else {
372 		openpam_log(PAM_LOG_DEBUG, "found %s", filename);
373 	}
374 
375 	/* verify type, ownership and permissions */
376 	if (OPENPAM_FEATURE(VERIFY_POLICY_FILE) &&
377 	    openpam_check_desc_owner_perms(filename, fileno(f)) != 0) {
378 		/* already logged the cause */
379 		serrno = errno;
380 		fclose(f);
381 		errno = serrno;
382 		RETURNN(-1);
383 	}
384 
385 	/* parse the file */
386 	ret = openpam_parse_chain(pamh, service, facility,
387 	    f, filename, style);
388 	RETURNN(ret);
389 }
390 
391 /*
392  * Locates the policy file for a given service and reads the given chains
393  * from it.
394  *
395  * Returns the number of policy entries which were found for the specified
396  * service and facility, or -1 if a system error occurred or a syntax
397  * error was encountered.
398  */
399 static int
400 openpam_load_chain(pam_handle_t *pamh,
401 	const char *service,
402 	pam_facility_t facility)
403 {
404 	const char *p, **path;
405 	char filename[PATH_MAX];
406 	size_t len;
407 	openpam_style_t style;
408 	int ret;
409 
410 	ENTERS(facility < 0 ? "any" : pam_facility_name[facility]);
411 
412 	/* either absolute or relative to cwd */
413 	if (strchr(service, '/') != NULL) {
414 		if ((p = strrchr(service, '.')) != NULL && strcmp(p, ".conf") == 0)
415 			style = pam_conf_style;
416 		else
417 			style = pam_d_style;
418 		ret = openpam_load_file(pamh, service, facility,
419 		    service, style);
420 		RETURNN(ret);
421 	}
422 
423 	/* search standard locations */
424 	for (path = openpam_policy_path; *path != NULL; ++path) {
425 		/* construct filename */
426 		len = strlcpy(filename, *path, sizeof filename);
427 		if (len >= sizeof filename) {
428 			errno = ENAMETOOLONG;
429 			RETURNN(-1);
430 		}
431 		if (filename[len - 1] == '/') {
432 			len = strlcat(filename, service, sizeof filename);
433 			if (len >= sizeof filename) {
434 				errno = ENAMETOOLONG;
435 				RETURNN(-1);
436 			}
437 			style = pam_d_style;
438 		} else {
439 			style = pam_conf_style;
440 		}
441 		ret = openpam_load_file(pamh, service, facility,
442 		    filename, style);
443 		/* success */
444 		if (ret > 0)
445 			RETURNN(ret);
446 		/* the file exists, but an error occurred */
447 		if (ret == -1 && errno != ENOENT)
448 			RETURNN(ret);
449 		/* in pam.d style, an empty file counts as a hit */
450 		if (ret == 0 && style == pam_d_style)
451 			RETURNN(ret);
452 	}
453 
454 	/* no hit */
455 	errno = ENOENT;
456 	RETURNN(-1);
457 }
458 
459 /*
460  * OpenPAM internal
461  *
462  * Configure a service
463  */
464 
465 int
466 openpam_configure(pam_handle_t *pamh,
467 	const char *service)
468 {
469 	pam_facility_t fclt;
470 	int serrno;
471 
472 	ENTERS(service);
473 	if (!valid_service_name(service)) {
474 		openpam_log(PAM_LOG_ERROR, "invalid service name");
475 		RETURNC(PAM_SYSTEM_ERR);
476 	}
477 	if (openpam_load_chain(pamh, service, PAM_FACILITY_ANY) < 0) {
478 		if (errno != ENOENT)
479 			goto load_err;
480 	}
481 	for (fclt = 0; fclt < PAM_NUM_FACILITIES; ++fclt) {
482 		if (pamh->chains[fclt] != NULL)
483 			continue;
484 		if (OPENPAM_FEATURE(FALLBACK_TO_OTHER)) {
485 			if (openpam_load_chain(pamh, PAM_OTHER, fclt) < 0)
486 				goto load_err;
487 		}
488 	}
489 	RETURNC(PAM_SUCCESS);
490 load_err:
491 	serrno = errno;
492 	openpam_clear_chains(pamh->chains);
493 	errno = serrno;
494 	RETURNC(PAM_SYSTEM_ERR);
495 }
496 
497 /*
498  * NODOC
499  *
500  * Error codes:
501  *	PAM_SYSTEM_ERR
502  */
503