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