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
36#ifdef HAVE_CONFIG_H
37# include "config.h"
38#endif
39
40#include <sys/param.h>
41
42#include <errno.h>
43#include <stdio.h>
44#include <stdlib.h>
45#include <string.h>
46
47#include <security/pam_appl.h>
48
49#include "openpam_impl.h"
50#include "openpam_ctype.h"
51#include "openpam_strlcat.h"
52#include "openpam_strlcpy.h"
53
54static int openpam_load_chain(pam_handle_t *, const char *, pam_facility_t);
55
56/*
57 * Validate a service name.
58 *
59 * Returns a non-zero value if the argument points to a NUL-terminated
60 * string consisting entirely of characters in the POSIX portable filename
61 * character set, excluding the path separator character.
62 */
63static int
64valid_service_name(const char *name)
65{
66	const char *p;
67
68	if (OPENPAM_FEATURE(RESTRICT_SERVICE_NAME)) {
69		/* path separator not allowed */
70		for (p = name; *p != '\0'; ++p)
71			if (!is_pfcs(*p))
72				return (0);
73	} else {
74		/* path separator allowed */
75		for (p = name; *p != '\0'; ++p)
76			if (!is_pfcs(*p) && *p != '/')
77				return (0);
78	}
79	return (1);
80}
81
82/*
83 * Parse the facility name.
84 *
85 * Returns the corresponding pam_facility_t value, or -1 if the argument
86 * is not a valid facility name.
87 */
88static pam_facility_t
89parse_facility_name(const char *name)
90{
91	int i;
92
93	for (i = 0; i < PAM_NUM_FACILITIES; ++i)
94		if (strcmp(pam_facility_name[i], name) == 0)
95			return (i);
96	return ((pam_facility_t)-1);
97}
98
99/*
100 * Parse the control flag.
101 *
102 * Returns the corresponding pam_control_t value, or -1 if the argument is
103 * not a valid control flag name.
104 */
105static pam_control_t
106parse_control_flag(const char *name)
107{
108	int i;
109
110	for (i = 0; i < PAM_NUM_CONTROL_FLAGS; ++i)
111		if (strcmp(pam_control_flag_name[i], name) == 0)
112			return (i);
113	return ((pam_control_t)-1);
114}
115
116/*
117 * Validate a file name.
118 *
119 * Returns a non-zero value if the argument points to a NUL-terminated
120 * string consisting entirely of characters in the POSIX portable filename
121 * character set, including the path separator character.
122 */
123static int
124valid_module_name(const char *name)
125{
126	const char *p;
127
128	if (OPENPAM_FEATURE(RESTRICT_MODULE_NAME)) {
129		/* path separator not allowed */
130		for (p = name; *p != '\0'; ++p)
131			if (!is_pfcs(*p))
132				return (0);
133	} else {
134		/* path separator allowed */
135		for (p = name; *p != '\0'; ++p)
136			if (!is_pfcs(*p) && *p != '/')
137				return (0);
138	}
139	return (1);
140}
141
142typedef enum { pam_conf_style, pam_d_style } openpam_style_t;
143
144/*
145 * Extracts given chains from a policy file.
146 *
147 * Returns the number of policy entries which were found for the specified
148 * service and facility, or -1 if a system error occurred or a syntax
149 * error was encountered.
150 */
151static int
152openpam_parse_chain(pam_handle_t *pamh,
153	const char *service,
154	pam_facility_t facility,
155	FILE *f,
156	const char *filename,
157	openpam_style_t style)
158{
159	pam_chain_t *this, **next;
160	pam_facility_t fclt;
161	pam_control_t ctlf;
162	char *name, *servicename, *modulename;
163	int count, lineno, ret, serrno;
164	char **wordv, *word;
165	int i, wordc;
166
167	count = 0;
168	this = NULL;
169	name = NULL;
170	lineno = 0;
171	wordc = 0;
172	wordv = NULL;
173	while ((wordv = openpam_readlinev(f, &lineno, &wordc)) != NULL) {
174		/* blank line? */
175		if (wordc == 0) {
176			FREEV(wordc, wordv);
177			continue;
178		}
179		i = 0;
180
181		/* check service name if necessary */
182		if (style == pam_conf_style &&
183		    strcmp(wordv[i++], service) != 0) {
184			FREEV(wordc, wordv);
185			continue;
186		}
187
188		/* check facility name */
189		if ((word = wordv[i++]) == NULL ||
190		    (fclt = parse_facility_name(word)) == (pam_facility_t)-1) {
191			openpam_log(PAM_LOG_ERROR,
192			    "%s(%d): missing or invalid facility",
193			    filename, lineno);
194			errno = EINVAL;
195			goto fail;
196		}
197		if (facility != fclt && facility != PAM_FACILITY_ANY) {
198			FREEV(wordc, wordv);
199			continue;
200		}
201
202		/* check for "include" */
203		if ((word = wordv[i++]) != NULL &&
204		    strcmp(word, "include") == 0) {
205			if ((servicename = wordv[i++]) == NULL ||
206			    !valid_service_name(servicename)) {
207				openpam_log(PAM_LOG_ERROR,
208				    "%s(%d): missing or invalid service name",
209				    filename, lineno);
210				errno = EINVAL;
211				goto fail;
212			}
213			if (wordv[i] != NULL) {
214				openpam_log(PAM_LOG_ERROR,
215				    "%s(%d): garbage at end of line",
216				    filename, lineno);
217				errno = EINVAL;
218				goto fail;
219			}
220			ret = openpam_load_chain(pamh, servicename, fclt);
221			FREEV(wordc, wordv);
222			if (ret < 0) {
223				/*
224				 * Bogus errno, but this ensures that the
225				 * outer loop does not just ignore the
226				 * error and keep searching.
227				 */
228				if (errno == ENOENT)
229					errno = EINVAL;
230				goto fail;
231			}
232			continue;
233		}
234
235		/* get control flag */
236		if (word == NULL || /* same word we compared to "include" */
237		    (ctlf = parse_control_flag(word)) == (pam_control_t)-1) {
238			openpam_log(PAM_LOG_ERROR,
239			    "%s(%d): missing or invalid control flag",
240			    filename, lineno);
241			errno = EINVAL;
242			goto fail;
243		}
244
245		/* get module name */
246		if ((modulename = wordv[i++]) == NULL ||
247		    !valid_module_name(modulename)) {
248			openpam_log(PAM_LOG_ERROR,
249			    "%s(%d): missing or invalid module name",
250			    filename, lineno);
251			errno = EINVAL;
252			goto fail;
253		}
254
255		/* allocate new entry */
256		if ((this = calloc(1, sizeof *this)) == NULL)
257			goto syserr;
258		this->flag = ctlf;
259
260		/* load module */
261		if ((this->module = openpam_load_module(modulename)) == NULL) {
262			if (errno == ENOENT)
263				errno = ENOEXEC;
264			goto fail;
265		}
266
267		/*
268		 * The remaining items in wordv are the module's
269		 * arguments.  We could set this->optv = wordv + i, but
270		 * then free(this->optv) wouldn't work.  Instead, we free
271		 * the words we've already consumed, shift the rest up,
272		 * and clear the tail end of the array.
273		 */
274		this->optc = wordc - i;
275		for (i = 0; i < wordc - this->optc; ++i) {
276			FREE(wordv[i]);
277		}
278		for (i = 0; i < this->optc; ++i) {
279			wordv[i] = wordv[wordc - this->optc + i];
280			wordv[wordc - this->optc + i] = NULL;
281		}
282		this->optv = wordv;
283		wordv = NULL;
284		wordc = 0;
285
286		/* hook it up */
287		for (next = &pamh->chains[fclt]; *next != NULL;
288		     next = &(*next)->next)
289			/* nothing */ ;
290		*next = this;
291		this = NULL;
292		++count;
293	}
294	/*
295	 * The loop ended because openpam_readword() returned NULL, which
296	 * can happen for four different reasons: an I/O error (ferror(f)
297	 * is true), a memory allocation failure (ferror(f) is false,
298	 * feof(f) is false, errno is non-zero), the file ended with an
299	 * unterminated quote or backslash escape (ferror(f) is false,
300	 * feof(f) is true, errno is non-zero), or the end of the file was
301	 * reached without error (ferror(f) is false, feof(f) is true,
302	 * errno is zero).
303	 */
304	if (ferror(f) || errno != 0)
305		goto syserr;
306	if (!feof(f))
307		goto fail;
308	fclose(f);
309	return (count);
310syserr:
311	serrno = errno;
312	openpam_log(PAM_LOG_ERROR, "%s: %m", filename);
313	errno = serrno;
314	/* fall through */
315fail:
316	serrno = errno;
317	if (this && this->optc && this->optv)
318		FREEV(this->optc, this->optv);
319	FREE(this);
320	FREEV(wordc, wordv);
321	FREE(wordv);
322	FREE(name);
323	fclose(f);
324	errno = serrno;
325	return (-1);
326}
327
328/*
329 * Read the specified chains from the specified file.
330 *
331 * Returns 0 if the file exists but does not contain any matching lines.
332 *
333 * Returns -1 and sets errno to ENOENT if the file does not exist.
334 *
335 * Returns -1 and sets errno to some other non-zero value if the file
336 * exists but is unsafe or unreadable, or an I/O error occurs.
337 */
338static int
339openpam_load_file(pam_handle_t *pamh,
340	const char *service,
341	pam_facility_t facility,
342	const char *filename,
343	openpam_style_t style)
344{
345	FILE *f;
346	int ret, serrno;
347
348	/* attempt to open the file */
349	if ((f = fopen(filename, "r")) == NULL) {
350		serrno = errno;
351		openpam_log(errno == ENOENT ? PAM_LOG_DEBUG : PAM_LOG_ERROR,
352		    "%s: %m", filename);
353		errno = serrno;
354		RETURNN(-1);
355	} else {
356		openpam_log(PAM_LOG_DEBUG, "found %s", filename);
357	}
358
359	/* verify type, ownership and permissions */
360	if (OPENPAM_FEATURE(VERIFY_POLICY_FILE) &&
361	    openpam_check_desc_owner_perms(filename, fileno(f)) != 0) {
362		/* already logged the cause */
363		serrno = errno;
364		fclose(f);
365		errno = serrno;
366		RETURNN(-1);
367	}
368
369	/* parse the file */
370	ret = openpam_parse_chain(pamh, service, facility,
371	    f, filename, style);
372	RETURNN(ret);
373}
374
375/*
376 * Locates the policy file for a given service and reads the given chains
377 * from it.
378 *
379 * Returns the number of policy entries which were found for the specified
380 * service and facility, or -1 if a system error occurred or a syntax
381 * error was encountered.
382 */
383static int
384openpam_load_chain(pam_handle_t *pamh,
385	const char *service,
386	pam_facility_t facility)
387{
388	const char *p, **path;
389	char filename[PATH_MAX];
390	size_t len;
391	openpam_style_t style;
392	int ret;
393
394	ENTERS(facility < 0 ? "any" : pam_facility_name[facility]);
395
396	/* either absolute or relative to cwd */
397	if (strchr(service, '/') != NULL) {
398		if ((p = strrchr(service, '.')) != NULL && strcmp(p, ".conf") == 0)
399			style = pam_conf_style;
400		else
401			style = pam_d_style;
402		ret = openpam_load_file(pamh, service, facility,
403		    service, style);
404		RETURNN(ret);
405	}
406
407	/* search standard locations */
408	for (path = openpam_policy_path; *path != NULL; ++path) {
409		/* construct filename */
410		len = strlcpy(filename, *path, sizeof filename);
411		if (len >= sizeof filename) {
412			errno = ENAMETOOLONG;
413			RETURNN(-1);
414		}
415		if (filename[len - 1] == '/') {
416			len = strlcat(filename, service, sizeof filename);
417			if (len >= sizeof filename) {
418				errno = ENAMETOOLONG;
419				RETURNN(-1);
420			}
421			style = pam_d_style;
422		} else {
423			style = pam_conf_style;
424		}
425		ret = openpam_load_file(pamh, service, facility,
426		    filename, style);
427		/* success */
428		if (ret > 0)
429			RETURNN(ret);
430		/* the file exists, but an error occurred */
431		if (ret == -1 && errno != ENOENT)
432			RETURNN(ret);
433		/* in pam.d style, an empty file counts as a hit */
434		if (ret == 0 && style == pam_d_style)
435			RETURNN(ret);
436	}
437
438	/* no hit */
439	errno = ENOENT;
440	RETURNN(-1);
441}
442
443/*
444 * OpenPAM internal
445 *
446 * Configure a service
447 */
448
449int
450openpam_configure(pam_handle_t *pamh,
451	const char *service)
452{
453	pam_facility_t fclt;
454	int serrno;
455
456	ENTERS(service);
457	if (!valid_service_name(service)) {
458		openpam_log(PAM_LOG_ERROR, "invalid service name");
459		RETURNC(PAM_SYSTEM_ERR);
460	}
461	if (openpam_load_chain(pamh, service, PAM_FACILITY_ANY) < 0) {
462		if (errno != ENOENT)
463			goto load_err;
464	}
465	for (fclt = 0; fclt < PAM_NUM_FACILITIES; ++fclt) {
466		if (pamh->chains[fclt] != NULL)
467			continue;
468		if (OPENPAM_FEATURE(FALLBACK_TO_OTHER)) {
469			if (openpam_load_chain(pamh, PAM_OTHER, fclt) < 0)
470				goto load_err;
471		}
472	}
473	RETURNC(PAM_SUCCESS);
474load_err:
475	serrno = errno;
476	openpam_clear_chains(pamh->chains);
477	errno = serrno;
478	RETURNC(PAM_SYSTEM_ERR);
479}
480
481/*
482 * NODOC
483 *
484 * Error codes:
485 *	PAM_SYSTEM_ERR
486 */
487