openpam_configure.c revision 228690
1/*-
2 * Copyright (c) 2001-2003 Networks Associates Technology, Inc.
3 * Copyright (c) 2004-2011 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 500 2011-11-22 12:07:03Z des $
36 */
37
38#ifdef HAVE_CONFIG_H
39# include "config.h"
40#endif
41
42#include <ctype.h>
43#include <errno.h>
44#include <stdio.h>
45#include <stdlib.h>
46#include <string.h>
47
48#include <security/pam_appl.h>
49
50#include "openpam_impl.h"
51#include "openpam_strlcmp.h"
52
53static int openpam_load_chain(pam_handle_t *, const char *, pam_facility_t);
54
55/*
56 * Evaluates to non-zero if the argument is a linear whitespace character.
57 */
58#define is_lws(ch)				\
59	(ch == ' ' || ch == '\t')
60
61/*
62 * Evaluates to non-zero if the argument is a printable ASCII character.
63 * Assumes that the execution character set is a superset of ASCII.
64 */
65#define is_p(ch) \
66	(ch >= '!' && ch <= '~')
67
68/*
69 * Returns non-zero if the argument belongs to the POSIX Portable Filename
70 * Character Set.  Assumes that the execution character set is a superset
71 * of ASCII.
72 */
73#define is_pfcs(ch)				\
74	((ch >= '0' && ch <= '9') ||		\
75	 (ch >= 'A' && ch <= 'Z') ||		\
76	 (ch >= 'a' && ch <= 'z') ||		\
77	 ch == '.' || ch == '_' || ch == '-')
78
79/*
80 * Parse the service name.
81 *
82 * Returns the length of the service name, or 0 if the end of the string
83 * was reached or a disallowed non-whitespace character was encountered.
84 *
85 * If parse_service_name() is successful, it updates *service to point to
86 * the first character of the service name and *line to point one
87 * character past the end.  If it reaches the end of the string, it
88 * updates *line to point to the terminating NUL character and leaves
89 * *service unmodified.  In all other cases, it leaves both *line and
90 * *service unmodified.
91 *
92 * Allowed characters are all characters in the POSIX portable filename
93 * character set.
94 */
95static int
96parse_service_name(char **line, char **service)
97{
98	char *b, *e;
99
100	for (b = *line; *b && is_lws(*b); ++b)
101		/* nothing */ ;
102	if (!*b) {
103		*line = b;
104		return (0);
105	}
106	for (e = b; *e && !is_lws(*e); ++e)
107		if (!is_pfcs(*e))
108			return (0);
109	if (e == b)
110		return (0);
111	*line = e;
112	*service = b;
113	return (e - b);
114}
115
116/*
117 * Parse the facility name.
118 *
119 * Returns the corresponding pam_facility_t value, or -1 if the end of the
120 * string was reached, a disallowed non-whitespace character was
121 * encountered, or the first word was not a recognized facility name.
122 *
123 * If parse_facility_name() is successful, it updates *line to point one
124 * character past the end of the facility name.  If it reaches the end of
125 * the string, it updates *line to point to the terminating NUL character.
126 * In all other cases, it leaves *line unmodified.
127 */
128static pam_facility_t
129parse_facility_name(char **line)
130{
131	char *b, *e;
132	int i;
133
134	for (b = *line; *b && is_lws(*b); ++b)
135		/* nothing */ ;
136	if (!*b) {
137		*line = b;
138		return ((pam_facility_t)-1);
139	}
140	for (e = b; *e && !is_lws(*e); ++e)
141		/* nothing */ ;
142	if (e == b)
143		return ((pam_facility_t)-1);
144	for (i = 0; i < PAM_NUM_FACILITIES; ++i)
145		if (strlcmp(pam_facility_name[i], b, e - b) == 0)
146			break;
147	if (i == PAM_NUM_FACILITIES)
148		return ((pam_facility_t)-1);
149	*line = e;
150	return (i);
151}
152
153/*
154 * Parse the word "include".
155 *
156 * If the next word on the line is "include", parse_include() updates
157 * *line to point one character past "include" and returns 1.  Otherwise,
158 * it leaves *line unmodified and returns 0.
159 */
160static int
161parse_include(char **line)
162{
163	char *b, *e;
164
165	for (b = *line; *b && is_lws(*b); ++b)
166		/* nothing */ ;
167	if (!*b) {
168		*line = b;
169		return (-1);
170	}
171	for (e = b; *e && !is_lws(*e); ++e)
172		/* nothing */ ;
173	if (e == b)
174		return (0);
175	if (strlcmp("include", b, e - b) != 0)
176		return (0);
177	*line = e;
178	return (1);
179}
180
181/*
182 * Parse the control flag.
183 *
184 * Returns the corresponding pam_control_t value, or -1 if the end of the
185 * string was reached, a disallowed non-whitespace character was
186 * encountered, or the first word was not a recognized control flag.
187 *
188 * If parse_control_flag() is successful, it updates *line to point one
189 * character past the end of the control flag.  If it reaches the end of
190 * the string, it updates *line to point to the terminating NUL character.
191 * In all other cases, it leaves *line unmodified.
192 */
193static pam_control_t
194parse_control_flag(char **line)
195{
196	char *b, *e;
197	int i;
198
199	for (b = *line; *b && is_lws(*b); ++b)
200		/* nothing */ ;
201	if (!*b) {
202		*line = b;
203		return ((pam_control_t)-1);
204	}
205	for (e = b; *e && !is_lws(*e); ++e)
206		/* nothing */ ;
207	if (e == b)
208		return ((pam_control_t)-1);
209	for (i = 0; i < PAM_NUM_CONTROL_FLAGS; ++i)
210		if (strlcmp(pam_control_flag_name[i], b, e - b) == 0)
211			break;
212	if (i == PAM_NUM_CONTROL_FLAGS)
213		return ((pam_control_t)-1);
214	*line = e;
215	return (i);
216}
217
218/*
219 * Parse a file name.
220 *
221 * Returns the length of the file name, or 0 if the end of the string was
222 * reached or a disallowed non-whitespace character was encountered.
223 *
224 * If parse_filename() is successful, it updates *filename to point to the
225 * first character of the filename and *line to point one character past
226 * the end.  If it reaches the end of the string, it updates *line to
227 * point to the terminating NUL character and leaves *filename unmodified.
228 * In all other cases, it leaves both *line and *filename unmodified.
229 *
230 * Allowed characters are all characters in the POSIX portable filename
231 * character set, plus the path separator (forward slash).
232 */
233static int
234parse_filename(char **line, char **filename)
235{
236	char *b, *e;
237
238	for (b = *line; *b && is_lws(*b); ++b)
239		/* nothing */ ;
240	if (!*b) {
241		*line = b;
242		return (0);
243	}
244	for (e = b; *e && !is_lws(*e); ++e)
245		if (!is_pfcs(*e) && *e != '/')
246			return (0);
247	if (e == b)
248		return (0);
249	*line = e;
250	*filename = b;
251	return (e - b);
252}
253
254/*
255 * Parse an option.
256 *
257 * Returns a dynamically allocated string containing the next module
258 * option, or NULL if the end of the string was reached or a disallowed
259 * non-whitespace character was encountered.
260 *
261 * If parse_option() is successful, it updates *line to point one
262 * character past the end of the option.  If it reaches the end of the
263 * string, it updates *line to point to the terminating NUL character.  In
264 * all other cases, it leaves *line unmodified.
265 *
266 * If parse_option() fails to allocate memory, it will return NULL and set
267 * errno to a non-zero value.
268 *
269 * Allowed characters for option names are all characters in the POSIX
270 * portable filename character set.  Allowed characters for option values
271 * are any printable non-whitespace characters.  The option value may be
272 * quoted in either single or double quotes, in which case space
273 * characters and whichever quote character was not used are allowed.
274 * Note that the entire value must be quoted, not just part of it.
275 */
276static char *
277parse_option(char **line)
278{
279	char *nb, *ne, *vb, *ve;
280	unsigned char q = 0;
281	char *option;
282	size_t size;
283
284	errno = 0;
285	for (nb = *line; *nb && is_lws(*nb); ++nb)
286		/* nothing */ ;
287	if (!*nb) {
288		*line = nb;
289		return (NULL);
290	}
291	for (ne = nb; *ne && !is_lws(*ne) && *ne != '='; ++ne)
292		if (!is_pfcs(*ne))
293			return (NULL);
294	if (ne == nb)
295		return (NULL);
296	if (*ne == '=') {
297		vb = ne + 1;
298		if (*vb == '"' || *vb == '\'')
299			q = *vb++;
300		for (ve = vb;
301		     *ve && *ve != q && (is_p(*ve) || (q && is_lws(*ve)));
302		     ++ve)
303			/* nothing */ ;
304		if (q && *ve != q)
305			/* non-printable character or missing endquote */
306			return (NULL);
307		if (q && *(ve + 1) && !is_lws(*(ve + 1)))
308			/* garbage after value */
309			return (NULL);
310	} else {
311		vb = ve = ne;
312	}
313	size = (ne - nb) + 1;
314	if (ve > vb)
315		size += (ve - vb) + 1;
316	if ((option = malloc(size)) == NULL)
317		return (NULL);
318	strncpy(option, nb, ne - nb);
319	if (ve > vb) {
320		option[ne - nb] = '=';
321		strncpy(option + (ne - nb) + 1, vb, ve - vb);
322	}
323	option[size - 1] = '\0';
324	*line = q ? ve + 1 : ve;
325	return (option);
326}
327
328/*
329 * Consume trailing whitespace.
330 *
331 * If there are no non-whitespace characters left on the line, parse_eol()
332 * updates *line to point at the terminating NUL character and returns 0.
333 * Otherwise, it leaves *line unmodified and returns a non-zero value.
334 */
335static int
336parse_eol(char **line)
337{
338	char *p;
339
340	for (p = *line; *p && is_lws(*p); ++p)
341		/* nothing */ ;
342	if (*p)
343		return ((unsigned char)*p);
344	*line = p;
345	return (0);
346}
347
348typedef enum { pam_conf_style, pam_d_style } openpam_style_t;
349
350/*
351 * Extracts given chains from a policy file.
352 */
353static int
354openpam_parse_chain(pam_handle_t *pamh,
355	const char *service,
356	pam_facility_t facility,
357	const char *filename,
358	openpam_style_t style)
359{
360	pam_chain_t *this, **next;
361	pam_facility_t fclt;
362	pam_control_t ctlf;
363	char *line, *str, *name;
364	char *option, **optv;
365	int len, lineno, ret;
366	FILE *f;
367
368	if ((f = fopen(filename, "r")) == NULL) {
369		openpam_log(errno == ENOENT ? PAM_LOG_DEBUG : PAM_LOG_NOTICE,
370		    "%s: %m", filename);
371		return (PAM_SUCCESS);
372	}
373	if (openpam_check_desc_owner_perms(filename, fileno(f)) != 0) {
374		fclose(f);
375		return (PAM_SYSTEM_ERR);
376	}
377	this = NULL;
378	name = NULL;
379	lineno = 0;
380	while ((line = openpam_readline(f, &lineno, NULL)) != NULL) {
381		/* get service name if necessary */
382		if (style == pam_conf_style) {
383			if ((len = parse_service_name(&line, &str)) == 0) {
384				openpam_log(PAM_LOG_NOTICE,
385				    "%s(%d): invalid service name (ignored)",
386				    filename, lineno);
387				FREE(line);
388				continue;
389			}
390			if (strlcmp(service, str, len) != 0) {
391				FREE(line);
392				continue;
393			}
394		}
395
396		/* get facility name */
397		if ((fclt = parse_facility_name(&line)) == (pam_facility_t)-1) {
398			openpam_log(PAM_LOG_ERROR,
399			    "%s(%d): missing or invalid facility",
400			    filename, lineno);
401			goto fail;
402		}
403		if (facility != fclt && facility != PAM_FACILITY_ANY) {
404			FREE(line);
405			continue;
406		}
407
408		/* check for "include" */
409		if (parse_include(&line)) {
410			if ((len = parse_service_name(&line, &str)) == 0) {
411				openpam_log(PAM_LOG_ERROR,
412				    "%s(%d): missing or invalid filename",
413				    filename, lineno);
414				goto fail;
415			}
416			if ((name = strndup(str, len)) == NULL)
417				goto syserr;
418			if (parse_eol(&line) != 0) {
419				openpam_log(PAM_LOG_ERROR,
420				    "%s(%d): garbage at end of line",
421				    filename, lineno);
422				goto fail;
423			}
424			ret = openpam_load_chain(pamh, name, fclt);
425			FREE(name);
426			if (ret != PAM_SUCCESS)
427				goto fail;
428			FREE(line);
429			continue;
430		}
431
432		/* get control flag */
433		if ((ctlf = parse_control_flag(&line)) == (pam_control_t)-1) {
434			openpam_log(PAM_LOG_ERROR,
435			    "%s(%d): missing or invalid control flag",
436			    filename, lineno);
437			goto fail;
438		}
439
440		/* get module name */
441		if ((len = parse_filename(&line, &str)) == 0) {
442			openpam_log(PAM_LOG_ERROR,
443			    "%s(%d): missing or invalid module name",
444			    filename, lineno);
445			goto fail;
446		}
447		if ((name = strndup(str, len)) == NULL)
448			goto syserr;
449
450		/* allocate new entry */
451		if ((this = calloc(1, sizeof *this)) == NULL)
452			goto syserr;
453		this->flag = ctlf;
454
455		/* get module options */
456		if ((this->optv = malloc(sizeof *optv)) == NULL)
457			goto syserr;
458		this->optc = 0;
459		while ((option = parse_option(&line)) != NULL) {
460			optv = realloc(this->optv,
461			    (this->optc + 2) * sizeof *optv);
462			if (optv == NULL)
463				goto syserr;
464			this->optv = optv;
465			this->optv[this->optc++] = option;
466		}
467		this->optv[this->optc] = NULL;
468		if (*line != '\0') {
469			openpam_log(PAM_LOG_ERROR,
470			    "%s(%d): syntax error in module options",
471			    filename, lineno);
472			goto fail;
473		}
474
475		/* load module */
476		this->module = openpam_load_module(name);
477		FREE(name);
478		if (this->module == NULL)
479			goto fail;
480
481		/* hook it up */
482		for (next = &pamh->chains[fclt]; *next != NULL;
483		     next = &(*next)->next)
484			/* nothing */ ;
485		*next = this;
486		this = NULL;
487
488		/* next please... */
489		FREE(line);
490	}
491	if (!feof(f))
492		goto syserr;
493	fclose(f);
494	return (PAM_SUCCESS);
495syserr:
496	openpam_log(PAM_LOG_ERROR, "%s: %m", filename);
497fail:
498	if (this && this->optc) {
499		while (this->optc--)
500			FREE(this->optv[this->optc]);
501		FREE(this->optv);
502	}
503	FREE(this);
504	FREE(line);
505	FREE(name);
506	fclose(f);
507	return (PAM_SYSTEM_ERR);
508}
509
510static const char *openpam_policy_path[] = {
511	"/etc/pam.d/",
512	"/etc/pam.conf",
513	"/usr/local/etc/pam.d/",
514	"/usr/local/etc/pam.conf",
515	NULL
516};
517
518/*
519 * Locates the policy file for a given service and reads the given chains
520 * from it.
521 */
522static int
523openpam_load_chain(pam_handle_t *pamh,
524	const char *service,
525	pam_facility_t facility)
526{
527	const char **path;
528	char *filename;
529	size_t len;
530	int ret;
531
532	for (path = openpam_policy_path; *path != NULL; ++path) {
533		len = strlen(*path);
534		if ((*path)[len - 1] == '/') {
535			if (asprintf(&filename, "%s%s", *path, service) < 0) {
536				openpam_log(PAM_LOG_ERROR, "asprintf(): %m");
537				return (PAM_BUF_ERR);
538			}
539			ret = openpam_parse_chain(pamh, service, facility,
540			    filename, pam_d_style);
541			FREE(filename);
542		} else {
543			ret = openpam_parse_chain(pamh, service, facility,
544			    *path, pam_conf_style);
545		}
546		if (ret != PAM_SUCCESS)
547			return (ret);
548	}
549	return (PAM_SUCCESS);
550}
551
552/*
553 * OpenPAM internal
554 *
555 * Configure a service
556 */
557
558int
559openpam_configure(pam_handle_t *pamh,
560	const char *service)
561{
562	pam_facility_t fclt;
563	const char *p;
564
565	for (p = service; *p; ++p)
566		if (!is_pfcs(*p))
567			return (PAM_SYSTEM_ERR);
568
569	if (openpam_load_chain(pamh, service, PAM_FACILITY_ANY) != PAM_SUCCESS)
570		goto load_err;
571
572	for (fclt = 0; fclt < PAM_NUM_FACILITIES; ++fclt) {
573		if (pamh->chains[fclt] != NULL)
574			continue;
575		if (openpam_load_chain(pamh, PAM_OTHER, fclt) != PAM_SUCCESS)
576			goto load_err;
577	}
578	return (PAM_SUCCESS);
579load_err:
580	openpam_clear_chains(pamh->chains);
581	return (PAM_SYSTEM_ERR);
582}
583
584/*
585 * NODOC
586 *
587 * Error codes:
588 *	PAM_SYSTEM_ERR
589 */
590