194670Sdes/*-
2115619Sdes * Copyright (c) 2001-2003 Networks Associates Technology, Inc.
3267014Sdelphij * Copyright (c) 2004-2014 Dag-Erling Sm��rgrav
494670Sdes * All rights reserved.
594670Sdes *
694670Sdes * This software was developed for the FreeBSD Project by ThinkSec AS and
799158Sdes * Network Associates Laboratories, the Security Research Division of
899158Sdes * Network Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
999158Sdes * ("CBOSS"), as part of the DARPA CHATS research program.
1094670Sdes *
1194670Sdes * Redistribution and use in source and binary forms, with or without
1294670Sdes * modification, are permitted provided that the following conditions
1394670Sdes * are met:
1494670Sdes * 1. Redistributions of source code must retain the above copyright
1594670Sdes *    notice, this list of conditions and the following disclaimer.
1694670Sdes * 2. Redistributions in binary form must reproduce the above copyright
1794670Sdes *    notice, this list of conditions and the following disclaimer in the
1894670Sdes *    documentation and/or other materials provided with the distribution.
1994670Sdes * 3. The name of the author may not be used to endorse or promote
2094670Sdes *    products derived from this software without specific prior written
2194670Sdes *    permission.
2294670Sdes *
2394670Sdes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
2494670Sdes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2594670Sdes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2694670Sdes * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
2794670Sdes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2894670Sdes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2994670Sdes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
3094670Sdes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
3194670Sdes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3294670Sdes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3394670Sdes * SUCH DAMAGE.
3494670Sdes *
35271947Sdes * $Id: openpam_configure.c 796 2014-06-03 21:30:08Z des $
3694670Sdes */
3794670Sdes
38228690Sdes#ifdef HAVE_CONFIG_H
39228690Sdes# include "config.h"
40228690Sdes#endif
41228690Sdes
42236099Sdes#include <sys/param.h>
43236099Sdes
4494670Sdes#include <errno.h>
4594670Sdes#include <stdio.h>
4694670Sdes#include <stdlib.h>
4794670Sdes#include <string.h>
4894670Sdes
4994670Sdes#include <security/pam_appl.h>
5094670Sdes
5194670Sdes#include "openpam_impl.h"
52236099Sdes#include "openpam_ctype.h"
53236099Sdes#include "openpam_strlcat.h"
54236099Sdes#include "openpam_strlcpy.h"
5594670Sdes
56228690Sdesstatic int openpam_load_chain(pam_handle_t *, const char *, pam_facility_t);
5794670Sdes
58228690Sdes/*
59236099Sdes * Validate a service name.
60228690Sdes *
61236099Sdes * Returns a non-zero value if the argument points to a NUL-terminated
62236099Sdes * string consisting entirely of characters in the POSIX portable filename
63236099Sdes * character set, excluding the path separator character.
64228690Sdes */
6594670Sdesstatic int
66236099Sdesvalid_service_name(const char *name)
67115619Sdes{
68236099Sdes	const char *p;
69115619Sdes
70236099Sdes	if (OPENPAM_FEATURE(RESTRICT_SERVICE_NAME)) {
71236099Sdes		/* path separator not allowed */
72236099Sdes		for (p = name; *p != '\0'; ++p)
73236099Sdes			if (!is_pfcs(*p))
74236099Sdes				return (0);
75236099Sdes	} else {
76236099Sdes		/* path separator allowed */
77236099Sdes		for (p = name; *p != '\0'; ++p)
78236099Sdes			if (!is_pfcs(*p) && *p != '/')
79236099Sdes				return (0);
80228690Sdes	}
81236099Sdes	return (1);
82115619Sdes}
83115619Sdes
84115619Sdes/*
85228690Sdes * Parse the facility name.
86228690Sdes *
87236099Sdes * Returns the corresponding pam_facility_t value, or -1 if the argument
88236099Sdes * is not a valid facility name.
89115619Sdes */
90228690Sdesstatic pam_facility_t
91236099Sdesparse_facility_name(const char *name)
92115619Sdes{
93228690Sdes	int i;
94115619Sdes
95228690Sdes	for (i = 0; i < PAM_NUM_FACILITIES; ++i)
96236099Sdes		if (strcmp(pam_facility_name[i], name) == 0)
97236099Sdes			return (i);
98236099Sdes	return ((pam_facility_t)-1);
99115619Sdes}
100115619Sdes
101115619Sdes/*
102228690Sdes * Parse the control flag.
103228690Sdes *
104236099Sdes * Returns the corresponding pam_control_t value, or -1 if the argument is
105236099Sdes * not a valid control flag name.
106228690Sdes */
107228690Sdesstatic pam_control_t
108236099Sdesparse_control_flag(const char *name)
109228690Sdes{
110228690Sdes	int i;
111228690Sdes
112228690Sdes	for (i = 0; i < PAM_NUM_CONTROL_FLAGS; ++i)
113236099Sdes		if (strcmp(pam_control_flag_name[i], name) == 0)
114236099Sdes			return (i);
115236099Sdes	return ((pam_control_t)-1);
116228690Sdes}
117228690Sdes
118228690Sdes/*
119236099Sdes * Validate a file name.
120228690Sdes *
121236099Sdes * Returns a non-zero value if the argument points to a NUL-terminated
122236099Sdes * string consisting entirely of characters in the POSIX portable filename
123236099Sdes * character set, including the path separator character.
124228690Sdes */
125228690Sdesstatic int
126236099Sdesvalid_module_name(const char *name)
127228690Sdes{
128236099Sdes	const char *p;
129228690Sdes
130236099Sdes	if (OPENPAM_FEATURE(RESTRICT_MODULE_NAME)) {
131236099Sdes		/* path separator not allowed */
132236099Sdes		for (p = name; *p != '\0'; ++p)
133236099Sdes			if (!is_pfcs(*p))
134236099Sdes				return (0);
135228690Sdes	} else {
136236099Sdes		/* path separator allowed */
137236099Sdes		for (p = name; *p != '\0'; ++p)
138236099Sdes			if (!is_pfcs(*p) && *p != '/')
139236099Sdes				return (0);
140228690Sdes	}
141236099Sdes	return (1);
142115619Sdes}
143115619Sdes
144115619Sdestypedef enum { pam_conf_style, pam_d_style } openpam_style_t;
145115619Sdes
146115619Sdes/*
147115619Sdes * Extracts given chains from a policy file.
148236099Sdes *
149236099Sdes * Returns the number of policy entries which were found for the specified
150236099Sdes * service and facility, or -1 if a system error occurred or a syntax
151236099Sdes * error was encountered.
152115619Sdes */
153115619Sdesstatic int
154228690Sdesopenpam_parse_chain(pam_handle_t *pamh,
15594670Sdes	const char *service,
156115619Sdes	pam_facility_t facility,
157236099Sdes	FILE *f,
15894670Sdes	const char *filename,
159115619Sdes	openpam_style_t style)
16094670Sdes{
161115619Sdes	pam_chain_t *this, **next;
162115619Sdes	pam_facility_t fclt;
163115619Sdes	pam_control_t ctlf;
164236099Sdes	char *name, *servicename, *modulename;
165236099Sdes	int count, lineno, ret, serrno;
166236099Sdes	char **wordv, *word;
167236099Sdes	int i, wordc;
16894670Sdes
169236099Sdes	count = 0;
170115619Sdes	this = NULL;
171228690Sdes	name = NULL;
172228690Sdes	lineno = 0;
173236099Sdes	wordc = 0;
174236099Sdes	wordv = NULL;
175236099Sdes	while ((wordv = openpam_readlinev(f, &lineno, &wordc)) != NULL) {
176236099Sdes		/* blank line? */
177236099Sdes		if (wordc == 0) {
178236099Sdes			FREEV(wordc, wordv);
179236099Sdes			continue;
180115619Sdes		}
181236099Sdes		i = 0;
18294670Sdes
183236099Sdes		/* check service name if necessary */
184236099Sdes		if (style == pam_conf_style &&
185236099Sdes		    strcmp(wordv[i++], service) != 0) {
186236099Sdes			FREEV(wordc, wordv);
187236099Sdes			continue;
188236099Sdes		}
189236099Sdes
190236099Sdes		/* check facility name */
191236099Sdes		if ((word = wordv[i++]) == NULL ||
192236099Sdes		    (fclt = parse_facility_name(word)) == (pam_facility_t)-1) {
193228690Sdes			openpam_log(PAM_LOG_ERROR,
194228690Sdes			    "%s(%d): missing or invalid facility",
195228690Sdes			    filename, lineno);
196267014Sdelphij			errno = EINVAL;
197115619Sdes			goto fail;
198115619Sdes		}
199115619Sdes		if (facility != fclt && facility != PAM_FACILITY_ANY) {
200236099Sdes			FREEV(wordc, wordv);
20194670Sdes			continue;
20294670Sdes		}
20394670Sdes
204228690Sdes		/* check for "include" */
205236099Sdes		if ((word = wordv[i++]) != NULL &&
206236099Sdes		    strcmp(word, "include") == 0) {
207236099Sdes			if ((servicename = wordv[i++]) == NULL ||
208236099Sdes			    !valid_service_name(servicename)) {
209228690Sdes				openpam_log(PAM_LOG_ERROR,
210236099Sdes				    "%s(%d): missing or invalid service name",
211115619Sdes				    filename, lineno);
212267014Sdelphij				errno = EINVAL;
213228690Sdes				goto fail;
214228690Sdes			}
215236099Sdes			if (wordv[i] != NULL) {
216228690Sdes				openpam_log(PAM_LOG_ERROR,
217228690Sdes				    "%s(%d): garbage at end of line",
218228690Sdes				    filename, lineno);
219267014Sdelphij				errno = EINVAL;
220228690Sdes				goto fail;
221228690Sdes			}
222236099Sdes			ret = openpam_load_chain(pamh, servicename, fclt);
223236099Sdes			FREEV(wordc, wordv);
224267014Sdelphij			if (ret < 0) {
225267014Sdelphij				/*
226267014Sdelphij				 * Bogus errno, but this ensures that the
227267014Sdelphij				 * outer loop does not just ignore the
228267014Sdelphij				 * error and keep searching.
229267014Sdelphij				 */
230267014Sdelphij				if (errno == ENOENT)
231267014Sdelphij					errno = EINVAL;
232115619Sdes				goto fail;
233267014Sdelphij			}
23494670Sdes			continue;
23594670Sdes		}
23694670Sdes
237228690Sdes		/* get control flag */
238236099Sdes		if (word == NULL || /* same word we compared to "include" */
239236099Sdes		    (ctlf = parse_control_flag(word)) == (pam_control_t)-1) {
240228690Sdes			openpam_log(PAM_LOG_ERROR,
241228690Sdes			    "%s(%d): missing or invalid control flag",
242228690Sdes			    filename, lineno);
243267014Sdelphij			errno = EINVAL;
244228690Sdes			goto fail;
245228690Sdes		}
24694670Sdes
247228690Sdes		/* get module name */
248236099Sdes		if ((modulename = wordv[i++]) == NULL ||
249236099Sdes		    !valid_module_name(modulename)) {
25094670Sdes			openpam_log(PAM_LOG_ERROR,
251228690Sdes			    "%s(%d): missing or invalid module name",
252228690Sdes			    filename, lineno);
253267014Sdelphij			errno = EINVAL;
254115619Sdes			goto fail;
25594670Sdes		}
256228690Sdes
257228690Sdes		/* allocate new entry */
258228690Sdes		if ((this = calloc(1, sizeof *this)) == NULL)
259228690Sdes			goto syserr;
260115619Sdes		this->flag = ctlf;
26194670Sdes
262236099Sdes		/* load module */
263267014Sdelphij		if ((this->module = openpam_load_module(modulename)) == NULL) {
264267014Sdelphij			if (errno == ENOENT)
265267014Sdelphij				errno = ENOEXEC;
266115619Sdes			goto fail;
267267014Sdelphij		}
268236099Sdes
269236099Sdes		/*
270236099Sdes		 * The remaining items in wordv are the module's
271236099Sdes		 * arguments.  We could set this->optv = wordv + i, but
272236099Sdes		 * then free(this->optv) wouldn't work.  Instead, we free
273236099Sdes		 * the words we've already consumed, shift the rest up,
274236099Sdes		 * and clear the tail end of the array.
275236099Sdes		 */
276236099Sdes		this->optc = wordc - i;
277236099Sdes		for (i = 0; i < wordc - this->optc; ++i) {
278236099Sdes			FREE(wordv[i]);
279236124Sdes		}
280236124Sdes		for (i = 0; i < this->optc; ++i) {
281236099Sdes			wordv[i] = wordv[wordc - this->optc + i];
282236099Sdes			wordv[wordc - this->optc + i] = NULL;
28394670Sdes		}
284236099Sdes		this->optv = wordv;
285236099Sdes		wordv = NULL;
286236099Sdes		wordc = 0;
287228690Sdes
288115619Sdes		/* hook it up */
289115619Sdes		for (next = &pamh->chains[fclt]; *next != NULL;
290115619Sdes		     next = &(*next)->next)
291115619Sdes			/* nothing */ ;
292115619Sdes		*next = this;
293115619Sdes		this = NULL;
294236099Sdes		++count;
29594670Sdes	}
296236099Sdes	/*
297236099Sdes	 * The loop ended because openpam_readword() returned NULL, which
298236099Sdes	 * can happen for four different reasons: an I/O error (ferror(f)
299236099Sdes	 * is true), a memory allocation failure (ferror(f) is false,
300267014Sdelphij	 * feof(f) is false, errno is non-zero), the file ended with an
301267014Sdelphij	 * unterminated quote or backslash escape (ferror(f) is false,
302267014Sdelphij	 * feof(f) is true, errno is non-zero), or the end of the file was
303267014Sdelphij	 * reached without error (ferror(f) is false, feof(f) is true,
304267014Sdelphij	 * errno is zero).
305236099Sdes	 */
306236099Sdes	if (ferror(f) || errno != 0)
307236099Sdes		goto syserr;
308115619Sdes	if (!feof(f))
309236099Sdes		goto fail;
31094670Sdes	fclose(f);
311236099Sdes	return (count);
312228690Sdessyserr:
313236099Sdes	serrno = errno;
314115619Sdes	openpam_log(PAM_LOG_ERROR, "%s: %m", filename);
315236099Sdes	errno = serrno;
316236099Sdes	/* fall through */
317228690Sdesfail:
318236099Sdes	serrno = errno;
319236099Sdes	if (this && this->optc && this->optv)
320236099Sdes		FREEV(this->optc, this->optv);
321115619Sdes	FREE(this);
322236099Sdes	FREEV(wordc, wordv);
323236099Sdes	FREE(wordv);
324228690Sdes	FREE(name);
325115619Sdes	fclose(f);
326236099Sdes	errno = serrno;
327236099Sdes	return (-1);
32894670Sdes}
32994670Sdes
330115619Sdes/*
331236099Sdes * Read the specified chains from the specified file.
332236099Sdes *
333236099Sdes * Returns 0 if the file exists but does not contain any matching lines.
334236099Sdes *
335236099Sdes * Returns -1 and sets errno to ENOENT if the file does not exist.
336236099Sdes *
337236099Sdes * Returns -1 and sets errno to some other non-zero value if the file
338236099Sdes * exists but is unsafe or unreadable, or an I/O error occurs.
339236099Sdes */
340236099Sdesstatic int
341236099Sdesopenpam_load_file(pam_handle_t *pamh,
342236099Sdes	const char *service,
343236099Sdes	pam_facility_t facility,
344236099Sdes	const char *filename,
345236099Sdes	openpam_style_t style)
346236099Sdes{
347236099Sdes	FILE *f;
348236099Sdes	int ret, serrno;
349236099Sdes
350236099Sdes	/* attempt to open the file */
351236099Sdes	if ((f = fopen(filename, "r")) == NULL) {
352236099Sdes		serrno = errno;
353236099Sdes		openpam_log(errno == ENOENT ? PAM_LOG_DEBUG : PAM_LOG_ERROR,
354236099Sdes		    "%s: %m", filename);
355236099Sdes		errno = serrno;
356236099Sdes		RETURNN(-1);
357236099Sdes	} else {
358236099Sdes		openpam_log(PAM_LOG_DEBUG, "found %s", filename);
359236099Sdes	}
360236099Sdes
361236099Sdes	/* verify type, ownership and permissions */
362236099Sdes	if (OPENPAM_FEATURE(VERIFY_POLICY_FILE) &&
363236099Sdes	    openpam_check_desc_owner_perms(filename, fileno(f)) != 0) {
364236099Sdes		/* already logged the cause */
365236099Sdes		serrno = errno;
366236099Sdes		fclose(f);
367236099Sdes		errno = serrno;
368236099Sdes		RETURNN(-1);
369236099Sdes	}
370236099Sdes
371236099Sdes	/* parse the file */
372236099Sdes	ret = openpam_parse_chain(pamh, service, facility,
373236099Sdes	    f, filename, style);
374236099Sdes	RETURNN(ret);
375236099Sdes}
376236099Sdes
377236099Sdes/*
378115619Sdes * Locates the policy file for a given service and reads the given chains
379115619Sdes * from it.
380236099Sdes *
381236099Sdes * Returns the number of policy entries which were found for the specified
382236099Sdes * service and facility, or -1 if a system error occurred or a syntax
383236099Sdes * error was encountered.
384115619Sdes */
38595908Sdesstatic int
386115619Sdesopenpam_load_chain(pam_handle_t *pamh,
387115619Sdes	const char *service,
388115619Sdes	pam_facility_t facility)
38994670Sdes{
390236099Sdes	const char *p, **path;
391236099Sdes	char filename[PATH_MAX];
39294670Sdes	size_t len;
393236099Sdes	openpam_style_t style;
394228690Sdes	int ret;
39594670Sdes
396236099Sdes	ENTERS(facility < 0 ? "any" : pam_facility_name[facility]);
397236099Sdes
398236099Sdes	/* either absolute or relative to cwd */
399236099Sdes	if (strchr(service, '/') != NULL) {
400236099Sdes		if ((p = strrchr(service, '.')) != NULL && strcmp(p, ".conf") == 0)
401236099Sdes			style = pam_conf_style;
402236099Sdes		else
403236099Sdes			style = pam_d_style;
404236099Sdes		ret = openpam_load_file(pamh, service, facility,
405236099Sdes		    service, style);
406236099Sdes		RETURNN(ret);
407236099Sdes	}
408236099Sdes
409236099Sdes	/* search standard locations */
41094670Sdes	for (path = openpam_policy_path; *path != NULL; ++path) {
411236099Sdes		/* construct filename */
412236099Sdes		len = strlcpy(filename, *path, sizeof filename);
413236099Sdes		if (filename[len - 1] == '/') {
414236099Sdes			len = strlcat(filename, service, sizeof filename);
415236099Sdes			if (len >= sizeof filename) {
416236099Sdes				errno = ENAMETOOLONG;
417236099Sdes				RETURNN(-1);
41894670Sdes			}
419236099Sdes			style = pam_d_style;
42094670Sdes		} else {
421236099Sdes			style = pam_conf_style;
42294670Sdes		}
423236099Sdes		ret = openpam_load_file(pamh, service, facility,
424236099Sdes		    filename, style);
425267014Sdelphij		/* success */
426267014Sdelphij		if (ret > 0)
427267014Sdelphij			RETURNN(ret);
428236099Sdes		/* the file exists, but an error occurred */
429236099Sdes		if (ret == -1 && errno != ENOENT)
430236099Sdes			RETURNN(ret);
431236099Sdes		/* in pam.d style, an empty file counts as a hit */
432236099Sdes		if (ret == 0 && style == pam_d_style)
433236099Sdes			RETURNN(ret);
43494670Sdes	}
435236099Sdes
436236099Sdes	/* no hit */
437267014Sdelphij	errno = ENOENT;
438267014Sdelphij	RETURNN(-1);
43994670Sdes}
44094670Sdes
44194670Sdes/*
44295908Sdes * OpenPAM internal
44395908Sdes *
44495908Sdes * Configure a service
44595908Sdes */
44695908Sdes
44795908Sdesint
44895908Sdesopenpam_configure(pam_handle_t *pamh,
44995908Sdes	const char *service)
45095908Sdes{
451115619Sdes	pam_facility_t fclt;
452236099Sdes	int serrno;
45395908Sdes
454236099Sdes	ENTERS(service);
455236099Sdes	if (!valid_service_name(service)) {
456236099Sdes		openpam_log(PAM_LOG_ERROR, "invalid service name");
457236099Sdes		RETURNC(PAM_SYSTEM_ERR);
458236099Sdes	}
459267014Sdelphij	if (openpam_load_chain(pamh, service, PAM_FACILITY_ANY) < 0) {
460267014Sdelphij		if (errno != ENOENT)
461267014Sdelphij			goto load_err;
462267014Sdelphij	}
463115619Sdes	for (fclt = 0; fclt < PAM_NUM_FACILITIES; ++fclt) {
464115619Sdes		if (pamh->chains[fclt] != NULL)
465115619Sdes			continue;
466236099Sdes		if (openpam_load_chain(pamh, PAM_OTHER, fclt) < 0)
467115619Sdes			goto load_err;
46895908Sdes	}
469236099Sdes	RETURNC(PAM_SUCCESS);
470228690Sdesload_err:
471236099Sdes	serrno = errno;
472115619Sdes	openpam_clear_chains(pamh->chains);
473236099Sdes	errno = serrno;
474236099Sdes	RETURNC(PAM_SYSTEM_ERR);
47595908Sdes}
47695908Sdes
47795908Sdes/*
47894670Sdes * NODOC
47994670Sdes *
48094670Sdes * Error codes:
48194670Sdes *	PAM_SYSTEM_ERR
48294670Sdes */
483