openpam_configure.c revision 174832
1/*-
2 * Copyright (c) 2001-2003 Networks Associates Technology, Inc.
3 * Copyright (c) 2004-2007 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 408 2007-12-21 11:36:24Z des $
36 */
37
38#include <ctype.h>
39#include <errno.h>
40#include <stdio.h>
41#include <stdlib.h>
42#include <string.h>
43
44#include <security/pam_appl.h>
45
46#include "openpam_impl.h"
47
48const char *_pam_facility_name[PAM_NUM_FACILITIES] = {
49	[PAM_ACCOUNT]		= "account",
50	[PAM_AUTH]		= "auth",
51	[PAM_PASSWORD]		= "password",
52	[PAM_SESSION]		= "session",
53};
54
55const char *_pam_control_flag_name[PAM_NUM_CONTROL_FLAGS] = {
56	[PAM_BINDING]		= "binding",
57	[PAM_OPTIONAL]		= "optional",
58	[PAM_REQUIRED]		= "required",
59	[PAM_REQUISITE]		= "requisite",
60	[PAM_SUFFICIENT]	= "sufficient",
61};
62
63static int openpam_load_chain(pam_handle_t *, const char *, pam_facility_t);
64
65/*
66 * Matches a word against the first one in a string.
67 * Returns non-zero if they match.
68 */
69static int
70match_word(const char *str, const char *word)
71{
72
73	while (*str && tolower(*str) == tolower(*word))
74		++str, ++word;
75	return (*str == ' ' && *word == '\0');
76}
77
78/*
79 * Return a pointer to the next word (or the final NUL) in a string.
80 */
81static const char *
82next_word(const char *str)
83{
84
85	/* skip current word */
86	while (*str && *str != ' ')
87		++str;
88	/* skip whitespace */
89	while (*str == ' ')
90		++str;
91	return (str);
92}
93
94/*
95 * Return a malloc()ed copy of the first word in a string.
96 */
97static char *
98dup_word(const char *str)
99{
100	const char *end;
101	char *word;
102
103	for (end = str; *end && *end != ' '; ++end)
104		/* nothing */ ;
105	if (asprintf(&word, "%.*s", (int)(end - str), str) < 0)
106		return (NULL);
107	return (word);
108}
109
110/*
111 * Return the length of the first word in a string.
112 */
113static int
114wordlen(const char *str)
115{
116	int i;
117
118	for (i = 0; str[i] && str[i] != ' '; ++i)
119		/* nothing */ ;
120	return (i);
121}
122
123typedef enum { pam_conf_style, pam_d_style } openpam_style_t;
124
125/*
126 * Extracts given chains from a policy file.
127 */
128static int
129openpam_read_chain(pam_handle_t *pamh,
130	const char *service,
131	pam_facility_t facility,
132	const char *filename,
133	openpam_style_t style)
134{
135	pam_chain_t *this, **next;
136	const char *p, *q;
137	int count, i, lineno, ret;
138	pam_facility_t fclt;
139	pam_control_t ctlf;
140	char *line, *name;
141	FILE *f;
142
143	if ((f = fopen(filename, "r")) == NULL) {
144		openpam_log(errno == ENOENT ? PAM_LOG_DEBUG : PAM_LOG_NOTICE,
145		    "%s: %m", filename);
146		return (0);
147	}
148	this = NULL;
149	count = lineno = 0;
150	while ((line = openpam_readline(f, &lineno, NULL)) != NULL) {
151		p = line;
152
153		/* match service name */
154		if (style == pam_conf_style) {
155			if (!match_word(p, service)) {
156				FREE(line);
157				continue;
158			}
159			p = next_word(p);
160		}
161
162		/* match facility name */
163		for (fclt = 0; fclt < PAM_NUM_FACILITIES; ++fclt)
164			if (match_word(p, _pam_facility_name[fclt]))
165				break;
166		if (fclt == PAM_NUM_FACILITIES) {
167			openpam_log(PAM_LOG_NOTICE,
168			    "%s(%d): invalid facility '%.*s' (ignored)",
169			    filename, lineno, wordlen(p), p);
170			goto fail;
171		}
172		if (facility != fclt && facility != PAM_FACILITY_ANY) {
173			FREE(line);
174			continue;
175		}
176		p = next_word(p);
177
178		/* include other chain */
179		if (match_word(p, "include")) {
180			p = next_word(p);
181			if (*next_word(p) != '\0')
182				openpam_log(PAM_LOG_NOTICE,
183				    "%s(%d): garbage at end of 'include' line",
184				    filename, lineno);
185			if ((name = dup_word(p)) == NULL)
186				goto syserr;
187			ret = openpam_load_chain(pamh, name, fclt);
188			FREE(name);
189			if (ret < 0)
190				goto fail;
191			count += ret;
192			FREE(line);
193			continue;
194		}
195
196		/* allocate new entry */
197		if ((this = calloc(1, sizeof *this)) == NULL)
198			goto syserr;
199
200		/* control flag */
201		for (ctlf = 0; ctlf < PAM_NUM_CONTROL_FLAGS; ++ctlf)
202			if (match_word(p, _pam_control_flag_name[ctlf]))
203				break;
204		if (ctlf == PAM_NUM_CONTROL_FLAGS) {
205			openpam_log(PAM_LOG_ERROR,
206			    "%s(%d): invalid control flag '%.*s'",
207			    filename, lineno, wordlen(p), p);
208			goto fail;
209		}
210		this->flag = ctlf;
211
212		/* module name */
213		p = next_word(p);
214		if (*p == '\0') {
215			openpam_log(PAM_LOG_ERROR,
216			    "%s(%d): missing module name",
217			    filename, lineno);
218			goto fail;
219		}
220		if ((name = dup_word(p)) == NULL)
221			goto syserr;
222		this->module = openpam_load_module(name);
223		FREE(name);
224		if (this->module == NULL)
225			goto fail;
226
227		/* module options */
228		p = q = next_word(p);
229		while (*q != '\0') {
230			++this->optc;
231			q = next_word(q);
232		}
233		this->optv = calloc(this->optc + 1, sizeof(char *));
234		if (this->optv == NULL)
235			goto syserr;
236		for (i = 0; i < this->optc; ++i) {
237			if ((this->optv[i] = dup_word(p)) == NULL)
238				goto syserr;
239			p = next_word(p);
240		}
241
242		/* hook it up */
243		for (next = &pamh->chains[fclt]; *next != NULL;
244		     next = &(*next)->next)
245			/* nothing */ ;
246		*next = this;
247		this = NULL;
248		++count;
249
250		/* next please... */
251		FREE(line);
252	}
253	if (!feof(f))
254		goto syserr;
255	fclose(f);
256	return (count);
257 syserr:
258	openpam_log(PAM_LOG_ERROR, "%s: %m", filename);
259 fail:
260	FREE(this);
261	FREE(line);
262	fclose(f);
263	return (-1);
264}
265
266static const char *openpam_policy_path[] = {
267	"/etc/pam.d/",
268	"/etc/pam.conf",
269	"/usr/local/etc/pam.d/",
270	"/usr/local/etc/pam.conf",
271	NULL
272};
273
274/*
275 * Locates the policy file for a given service and reads the given chains
276 * from it.
277 */
278static int
279openpam_load_chain(pam_handle_t *pamh,
280	const char *service,
281	pam_facility_t facility)
282{
283	const char **path;
284	char *filename;
285	size_t len;
286	int r;
287
288	for (path = openpam_policy_path; *path != NULL; ++path) {
289		len = strlen(*path);
290		if ((*path)[len - 1] == '/') {
291			if (asprintf(&filename, "%s%s", *path, service) < 0) {
292				openpam_log(PAM_LOG_ERROR, "asprintf(): %m");
293				return (-PAM_BUF_ERR);
294			}
295			r = openpam_read_chain(pamh, service, facility,
296			    filename, pam_d_style);
297			FREE(filename);
298		} else {
299			r = openpam_read_chain(pamh, service, facility,
300			    *path, pam_conf_style);
301		}
302		if (r != 0)
303			return (r);
304	}
305	return (0);
306}
307
308/*
309 * OpenPAM internal
310 *
311 * Configure a service
312 */
313
314int
315openpam_configure(pam_handle_t *pamh,
316	const char *service)
317{
318	pam_facility_t fclt;
319
320	if (openpam_load_chain(pamh, service, PAM_FACILITY_ANY) < 0)
321		goto load_err;
322
323	for (fclt = 0; fclt < PAM_NUM_FACILITIES; ++fclt) {
324		if (pamh->chains[fclt] != NULL)
325			continue;
326		if (openpam_load_chain(pamh, PAM_OTHER, fclt) < 0)
327			goto load_err;
328	}
329	return (PAM_SUCCESS);
330 load_err:
331	openpam_clear_chains(pamh->chains);
332	return (PAM_SYSTEM_ERR);
333}
334
335/*
336 * NODOC
337 *
338 * Error codes:
339 *	PAM_SYSTEM_ERR
340 */
341