openpam_configure.c revision 115698
1/*-
2 * Copyright (c) 2001-2003 Networks Associates Technology, Inc.
3 * All rights reserved.
4 *
5 * This software was developed for the FreeBSD Project by ThinkSec AS and
6 * Network Associates Laboratories, the Security Research Division of
7 * Network Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
8 * ("CBOSS"), as part of the DARPA CHATS research program.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 * 3. The name of the author may not be used to endorse or promote
19 *    products derived from this software without specific prior written
20 *    permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
33 *
34 * $P4: //depot/projects/openpam/lib/openpam_configure.c#11 $
35 */
36
37#include <ctype.h>
38#include <errno.h>
39#include <stdio.h>
40#include <stdlib.h>
41#include <string.h>
42
43#include <security/pam_appl.h>
44
45#include "openpam_impl.h"
46
47const char *_pam_facility_name[PAM_NUM_FACILITIES] = {
48	[PAM_ACCOUNT]		= "account",
49	[PAM_AUTH]		= "auth",
50	[PAM_PASSWORD]		= "password",
51	[PAM_SESSION]		= "session",
52};
53
54const char *_pam_control_flag_name[PAM_NUM_CONTROL_FLAGS] = {
55	[PAM_BINDING]		= "binding",
56	[PAM_OPTIONAL]		= "optional",
57	[PAM_REQUIRED]		= "required",
58	[PAM_REQUISITE]		= "requisite",
59	[PAM_SUFFICIENT]	= "sufficient",
60};
61
62static int openpam_load_chain(pam_handle_t *, const char *, pam_facility_t);
63
64/*
65 * Matches a word against the first one in a string.
66 * Returns non-zero if they match.
67 */
68static int
69match_word(const char *str, const char *word)
70{
71
72	while (*str && tolower(*str) == tolower(*word))
73		++str, ++word;
74	return (*str == ' ' && *word == '\0');
75}
76
77/*
78 * Return a pointer to the next word (or the final NUL) in a string.
79 */
80static const char *
81next_word(const char *str)
82{
83
84	/* skip current word */
85	while (*str && *str != ' ')
86		++str;
87	/* skip whitespace */
88	while (*str == ' ')
89		++str;
90	return (str);
91}
92
93/*
94 * Return a malloc()ed copy of the first word in a string.
95 */
96static char *
97dup_word(const char *str)
98{
99	const char *end;
100	char *word;
101
102	for (end = str; *end && *end != ' '; ++end)
103		/* nothing */ ;
104	if (asprintf(&word, "%.*s", (int)(end - str), str) < 0)
105		return (NULL);
106	return (word);
107}
108
109/*
110 * Return the length of the first word in a string.
111 */
112static int
113wordlen(const char *str)
114{
115	int i;
116
117	for (i = 0; str[i] && str[i] != ' '; ++i)
118		/* nothing */ ;
119	return (i);
120}
121
122typedef enum { pam_conf_style, pam_d_style } openpam_style_t;
123
124/*
125 * Extracts given chains from a policy file.
126 */
127static int
128openpam_read_chain(pam_handle_t *pamh,
129	const char *service,
130	pam_facility_t facility,
131	const char *filename,
132	openpam_style_t style)
133{
134	pam_chain_t *this, **next;
135	const char *p, *q;
136	int count, i, lineno, ret;
137	pam_facility_t fclt;
138	pam_control_t ctlf;
139	char *line, *name;
140	FILE *f;
141
142	if ((f = fopen(filename, "r")) == NULL) {
143		openpam_log(errno == ENOENT ? PAM_LOG_DEBUG : PAM_LOG_NOTICE,
144		    "%s: %m", filename);
145		return (0);
146	}
147	this = NULL;
148	count = lineno = 0;
149	while ((line = openpam_readline(f, &lineno, NULL)) != NULL) {
150		p = line;
151
152		/* match service name */
153		if (style == pam_conf_style) {
154			if (!match_word(p, service)) {
155				FREE(line);
156				continue;
157			}
158			p = next_word(p);
159		}
160
161		/* match facility name */
162		for (fclt = 0; fclt < PAM_NUM_FACILITIES; ++fclt)
163			if (match_word(p, _pam_facility_name[fclt]))
164				break;
165		if (fclt == PAM_NUM_FACILITIES) {
166			openpam_log(PAM_LOG_NOTICE,
167			    "%s(%d): invalid facility '%.*s' (ignored)",
168			    filename, lineno, wordlen(p), p);
169			goto fail;
170		}
171		if (facility != fclt && facility != PAM_FACILITY_ANY) {
172			FREE(line);
173			continue;
174		}
175		p = next_word(p);
176
177		/* include other chain */
178		if (match_word(p, "include")) {
179			p = next_word(p);
180			if (*next_word(p) != '\0')
181				openpam_log(PAM_LOG_NOTICE,
182				    "%s(%d): garbage at end of 'include' line",
183				    filename, lineno);
184			if ((name = dup_word(p)) == NULL)
185				goto syserr;
186			ret = openpam_load_chain(pamh, name, fclt);
187			FREE(name);
188			if (ret < 0)
189				goto fail;
190			count += ret;
191			FREE(line);
192			continue;
193		}
194
195		/* allocate new entry */
196		if ((this = calloc(1, sizeof *this)) == NULL)
197			goto syserr;
198
199		/* control flag */
200		for (ctlf = 0; ctlf < PAM_NUM_CONTROL_FLAGS; ++ctlf)
201			if (match_word(p, _pam_control_flag_name[ctlf]))
202				break;
203		if (ctlf == PAM_NUM_CONTROL_FLAGS) {
204			openpam_log(PAM_LOG_ERROR,
205			    "%s(%d): invalid control flag '%.*s'",
206			    filename, lineno, wordlen(p), p);
207			goto fail;
208		}
209		this->flag = ctlf;
210
211		/* module name */
212		p = next_word(p);
213		if (*p == '\0') {
214			openpam_log(PAM_LOG_ERROR,
215			    "%s(%d): missing module name",
216			    filename, lineno);
217			goto fail;
218		}
219		if ((name = dup_word(p)) == NULL)
220			goto syserr;
221		this->module = openpam_load_module(name);
222		FREE(name);
223		if (this->module == NULL)
224			goto fail;
225
226		/* module options */
227		p = q = next_word(p);
228		while (*q != '\0') {
229			++this->optc;
230			q = next_word(q);
231		}
232		this->optv = calloc(this->optc + 1, sizeof(char *));
233		if (this->optv == NULL)
234			goto syserr;
235		for (i = 0; i < this->optc; ++i) {
236			if ((this->optv[i] = dup_word(p)) == NULL)
237				goto syserr;
238			p = next_word(p);
239		}
240
241		/* hook it up */
242		for (next = &pamh->chains[fclt]; *next != NULL;
243		     next = &(*next)->next)
244			/* nothing */ ;
245		*next = this;
246		this = NULL;
247		++count;
248
249		/* next please... */
250		FREE(line);
251	}
252	if (!feof(f))
253		goto syserr;
254	fclose(f);
255	return (count);
256 syserr:
257	openpam_log(PAM_LOG_ERROR, "%s: %m", filename);
258 fail:
259	FREE(this);
260	FREE(line);
261	fclose(f);
262	return (-1);
263}
264
265static const char *openpam_policy_path[] = {
266	"/etc/pam.d/",
267	"/etc/pam.conf",
268	"/usr/local/etc/pam.d/",
269	"/usr/local/etc/pam.conf",
270	NULL
271};
272
273/*
274 * Locates the policy file for a given service and reads the given chains
275 * from it.
276 */
277static int
278openpam_load_chain(pam_handle_t *pamh,
279	const char *service,
280	pam_facility_t facility)
281{
282	const char **path;
283	char *filename;
284	size_t len;
285	int r;
286
287	for (path = openpam_policy_path; *path != NULL; ++path) {
288		len = strlen(*path);
289		if ((*path)[len - 1] == '/') {
290			if (asprintf(&filename, "%s%s", *path, service) < 0) {
291				openpam_log(PAM_LOG_ERROR, "asprintf(): %m");
292				return (-PAM_BUF_ERR);
293			}
294			r = openpam_read_chain(pamh, service, facility,
295			    filename, pam_d_style);
296			FREE(filename);
297		} else {
298			r = openpam_read_chain(pamh, service, facility,
299			    *path, pam_conf_style);
300		}
301		if (r != 0)
302			return (r);
303	}
304	return (0);
305}
306
307/*
308 * OpenPAM internal
309 *
310 * Configure a service
311 */
312
313int
314openpam_configure(pam_handle_t *pamh,
315	const char *service)
316{
317	pam_facility_t fclt;
318
319	if (openpam_load_chain(pamh, service, PAM_FACILITY_ANY) < 0)
320		goto load_err;
321
322	for (fclt = 0; fclt < PAM_NUM_FACILITIES; ++fclt) {
323		if (pamh->chains[fclt] != NULL)
324			continue;
325		if (openpam_load_chain(pamh, PAM_OTHER, fclt) < 0)
326			goto load_err;
327	}
328	return (PAM_SUCCESS);
329 load_err:
330	openpam_clear_chains(pamh->chains);
331	return (PAM_SYSTEM_ERR);
332}
333
334/*
335 * NODOC
336 *
337 * Error codes:
338 *	PAM_SYSTEM_ERR
339 */
340