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