1/*	$NetBSD: openpam_configure.c,v 1.4.16.1 2014/06/18 02:15:27 msaitoh Exp $	*/
2
3/*-
4 * Copyright (c) 2001-2003 Networks Associates Technology, Inc.
5 * Copyright (c) 2004-2014 Dag-Erling Smørgrav
6 * All rights reserved.
7 *
8 * This software was developed for the FreeBSD Project by ThinkSec AS and
9 * Network Associates Laboratories, the Security Research Division of
10 * Network Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
11 * ("CBOSS"), as part of the DARPA CHATS research program.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
15 * are met:
16 * 1. Redistributions of source code must retain the above copyright
17 *    notice, this list of conditions and the following disclaimer.
18 * 2. Redistributions in binary form must reproduce the above copyright
19 *    notice, this list of conditions and the following disclaimer in the
20 *    documentation and/or other materials provided with the distribution.
21 * 3. The name of the author may not be used to endorse or promote
22 *    products derived from this software without specific prior written
23 *    permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 * SUCH DAMAGE.
36 *
37 * Id: openpam_configure.c 500 2011-11-22 12:07:03Z des
38 */
39
40#ifdef HAVE_CONFIG_H
41# include "config.h"
42#endif
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_strlcmp.h"
54
55static int openpam_load_chain(pam_handle_t *, const char *, pam_facility_t);
56
57/*
58 * Evaluates to non-zero if the argument is a linear whitespace character.
59 */
60#define is_lws(ch)				\
61	(ch == ' ' || ch == '\t')
62
63/*
64 * Evaluates to non-zero if the argument is a printable ASCII character.
65 * Assumes that the execution character set is a superset of ASCII.
66 */
67#define is_p(ch) \
68	(ch >= '!' && ch <= '~')
69
70/*
71 * Returns non-zero if the argument belongs to the POSIX Portable Filename
72 * Character Set.  Assumes that the execution character set is a superset
73 * of ASCII.
74 */
75#define is_pfcs(ch)				\
76	((ch >= '0' && ch <= '9') ||		\
77	 (ch >= 'A' && ch <= 'Z') ||		\
78	 (ch >= 'a' && ch <= 'z') ||		\
79	 ch == '.' || ch == '_' || ch == '-')
80
81/*
82 * Parse the service name.
83 *
84 * Returns the length of the service name, or 0 if the end of the string
85 * was reached or a disallowed non-whitespace character was encountered.
86 *
87 * If parse_service_name() is successful, it updates *service to point to
88 * the first character of the service name and *line to point one
89 * character past the end.  If it reaches the end of the string, it
90 * updates *line to point to the terminating NUL character and leaves
91 * *service unmodified.  In all other cases, it leaves both *line and
92 * *service unmodified.
93 *
94 * Allowed characters are all characters in the POSIX portable filename
95 * character set.
96 */
97static size_t
98parse_service_name(char **line, char **service)
99{
100	char *b, *e;
101
102	for (b = *line; *b && is_lws(*b); ++b)
103		/* nothing */ ;
104	if (!*b) {
105		*line = b;
106		return (0);
107	}
108	for (e = b; *e && !is_lws(*e); ++e)
109		if (!is_pfcs(*e))
110			return (0);
111	if (e == b)
112		return (0);
113	*line = e;
114	*service = b;
115	return (e - b);
116}
117
118/*
119 * Parse the facility name.
120 *
121 * Returns the corresponding pam_facility_t value, or -1 if the end of the
122 * string was reached, a disallowed non-whitespace character was
123 * encountered, or the first word was not a recognized facility name.
124 *
125 * If parse_facility_name() is successful, it updates *line to point one
126 * character past the end of the facility name.  If it reaches the end of
127 * the string, it updates *line to point to the terminating NUL character.
128 * In all other cases, it leaves *line unmodified.
129 */
130static pam_facility_t
131parse_facility_name(char **line)
132{
133	char *b, *e;
134	int i;
135
136	for (b = *line; *b && is_lws(*b); ++b)
137		/* nothing */ ;
138	if (!*b) {
139		*line = b;
140		return ((pam_facility_t)-1);
141	}
142	for (e = b; *e && !is_lws(*e); ++e)
143		/* nothing */ ;
144	if (e == b)
145		return ((pam_facility_t)-1);
146	for (i = 0; i < PAM_NUM_FACILITIES; ++i)
147		if (strlcmp(pam_facility_name[i], b, (size_t)(e - b)) == 0)
148			break;
149	if (i == PAM_NUM_FACILITIES)
150		return ((pam_facility_t)-1);
151	*line = e;
152	return (i);
153}
154
155/*
156 * Parse the word "include".
157 *
158 * If the next word on the line is "include", parse_include() updates
159 * *line to point one character past "include" and returns 1.  Otherwise,
160 * it leaves *line unmodified and returns 0.
161 */
162static int
163parse_include(char **line)
164{
165	char *b, *e;
166
167	for (b = *line; *b && is_lws(*b); ++b)
168		/* nothing */ ;
169	if (!*b) {
170		*line = b;
171		return (-1);
172	}
173	for (e = b; *e && !is_lws(*e); ++e)
174		/* nothing */ ;
175	if (e == b)
176		return (0);
177	if (strlcmp("include", b, (size_t)(e - b)) != 0)
178		return (0);
179	*line = e;
180	return (1);
181}
182
183/*
184 * Parse the control flag.
185 *
186 * Returns the corresponding pam_control_t value, or -1 if the end of the
187 * string was reached, a disallowed non-whitespace character was
188 * encountered, or the first word was not a recognized control flag.
189 *
190 * If parse_control_flag() is successful, it updates *line to point one
191 * character past the end of the control flag.  If it reaches the end of
192 * the string, it updates *line to point to the terminating NUL character.
193 * In all other cases, it leaves *line unmodified.
194 */
195static pam_control_t
196parse_control_flag(char **line)
197{
198	char *b, *e;
199	int i;
200
201	for (b = *line; *b && is_lws(*b); ++b)
202		/* nothing */ ;
203	if (!*b) {
204		*line = b;
205		return ((pam_control_t)-1);
206	}
207	for (e = b; *e && !is_lws(*e); ++e)
208		/* nothing */ ;
209	if (e == b)
210		return ((pam_control_t)-1);
211	for (i = 0; i < PAM_NUM_CONTROL_FLAGS; ++i)
212		if (strlcmp(pam_control_flag_name[i], b, (size_t)(e - b)) == 0)
213			break;
214	if (i == PAM_NUM_CONTROL_FLAGS)
215		return ((pam_control_t)-1);
216	*line = e;
217	return (i);
218}
219
220/*
221 * Parse a file name.
222 *
223 * Returns the length of the file name, or 0 if the end of the string was
224 * reached or a disallowed non-whitespace character was encountered.
225 *
226 * If parse_filename() is successful, it updates *filename to point to the
227 * first character of the filename and *line to point one character past
228 * the end.  If it reaches the end of the string, it updates *line to
229 * point to the terminating NUL character and leaves *filename unmodified.
230 * In all other cases, it leaves both *line and *filename unmodified.
231 *
232 * Allowed characters are all characters in the POSIX portable filename
233 * character set, plus the path separator (forward slash).
234 */
235static size_t
236parse_filename(char **line, char **filename)
237{
238	char *b, *e;
239
240	for (b = *line; *b && is_lws(*b); ++b)
241		/* nothing */ ;
242	if (!*b) {
243		*line = b;
244		return (0);
245	}
246	for (e = b; *e && !is_lws(*e); ++e)
247		if (!is_pfcs(*e) && *e != '/')
248			return (0);
249	if (e == b)
250		return (0);
251	*line = e;
252	*filename = b;
253	return (e - b);
254}
255
256/*
257 * Parse an option.
258 *
259 * Returns a dynamically allocated string containing the next module
260 * option, or NULL if the end of the string was reached or a disallowed
261 * non-whitespace character was encountered.
262 *
263 * If parse_option() is successful, it updates *line to point one
264 * character past the end of the option.  If it reaches the end of the
265 * string, it updates *line to point to the terminating NUL character.  In
266 * all other cases, it leaves *line unmodified.
267 *
268 * If parse_option() fails to allocate memory, it will return NULL and set
269 * errno to a non-zero value.
270 *
271 * Allowed characters for option names are all characters in the POSIX
272 * portable filename character set.  Allowed characters for option values
273 * are any printable non-whitespace characters.  The option value may be
274 * quoted in either single or double quotes, in which case space
275 * characters and whichever quote character was not used are allowed.
276 * Note that the entire value must be quoted, not just part of it.
277 */
278static char *
279parse_option(char **line)
280{
281	char *nb, *ne, *vb, *ve;
282	unsigned char q = 0;
283	char *option;
284	size_t size;
285
286	errno = 0;
287	for (nb = *line; *nb && is_lws(*nb); ++nb)
288		/* nothing */ ;
289	if (!*nb) {
290		*line = nb;
291		return (NULL);
292	}
293	for (ne = nb; *ne && !is_lws(*ne) && *ne != '='; ++ne)
294		if (!is_pfcs(*ne))
295			return (NULL);
296	if (ne == nb)
297		return (NULL);
298	if (*ne == '=') {
299		vb = ne + 1;
300		if (*vb == '"' || *vb == '\'')
301			q = *vb++;
302		for (ve = vb;
303		     *ve && *ve != q && (is_p(*ve) || (q && is_lws(*ve)));
304		     ++ve)
305			/* nothing */ ;
306		if (q && *ve != q)
307			/* non-printable character or missing endquote */
308			return (NULL);
309		if (q && *(ve + 1) && !is_lws(*(ve + 1)))
310			/* garbage after value */
311			return (NULL);
312	} else {
313		vb = ve = ne;
314	}
315	size = (ne - nb) + 1;
316	if (ve > vb)
317		size += (ve - vb) + 1;
318	if ((option = malloc(size)) == NULL)
319		return (NULL);
320	strncpy(option, nb, (size_t)(ne - nb));
321	if (ve > vb) {
322		option[ne - nb] = '=';
323		strncpy(option + (ne - nb) + 1, vb, (size_t)(ve - vb));
324	}
325	option[size - 1] = '\0';
326	*line = q ? ve + 1 : ve;
327	return (option);
328}
329
330/*
331 * Consume trailing whitespace.
332 *
333 * If there are no non-whitespace characters left on the line, parse_eol()
334 * updates *line to point at the terminating NUL character and returns 0.
335 * Otherwise, it leaves *line unmodified and returns a non-zero value.
336 */
337static int
338parse_eol(char **line)
339{
340	char *p;
341
342	for (p = *line; *p && is_lws(*p); ++p)
343		/* nothing */ ;
344	if (*p)
345		return ((unsigned char)*p);
346	*line = p;
347	return (0);
348}
349
350typedef enum { pam_conf_style, pam_d_style } openpam_style_t;
351
352/*
353 * Extracts given chains from a policy file.
354 */
355static int
356openpam_parse_chain(pam_handle_t *pamh,
357	const char *service,
358	pam_facility_t facility,
359	const char *filename,
360	openpam_style_t style)
361{
362	pam_chain_t *this, **next;
363	pam_facility_t fclt;
364	pam_control_t ctlf;
365	char *line, *str, *name;
366	char *option, **optv;
367	size_t len;
368	int lineno, ret, serrno;
369	FILE *f;
370
371	if (errno == ENOENT)
372		errno = 0;
373
374	if ((f = fopen(filename, "r")) == NULL) {
375		serrno = errno;
376		openpam_log(errno == ENOENT ? PAM_LOG_DEBUG : PAM_LOG_NOTICE,
377		    "%s: %s", filename, strerror(errno));
378		errno = serrno;
379		return (PAM_SUCCESS);
380	}
381	if (openpam_check_desc_owner_perms(filename, fileno(f)) != 0) {
382		fclose(f);
383		return (PAM_SYSTEM_ERR);
384	}
385	this = NULL;
386	name = NULL;
387	lineno = 0;
388	serrno = 0;
389	while ((line = openpam_readline(f, &lineno, NULL)) != NULL) {
390		/* get service name if necessary */
391		if (style == pam_conf_style) {
392			if ((len = parse_service_name(&line, &str)) == 0) {
393				openpam_log(PAM_LOG_NOTICE,
394				    "%s(%d): invalid service name (ignored)",
395				    filename, lineno);
396				FREE(line);
397				continue;
398			}
399			if (strlcmp(service, str, len) != 0) {
400				FREE(line);
401				continue;
402			}
403		}
404
405		/* get facility name */
406		if ((fclt = parse_facility_name(&line)) == (pam_facility_t)-1) {
407			openpam_log(PAM_LOG_ERROR,
408			    "%s(%d): missing or invalid facility",
409			    filename, lineno);
410			goto fail;
411		}
412		if (facility != fclt && facility != PAM_FACILITY_ANY) {
413			FREE(line);
414			continue;
415		}
416
417		/* check for "include" */
418		if (parse_include(&line)) {
419			if ((len = parse_service_name(&line, &str)) == 0) {
420				openpam_log(PAM_LOG_ERROR,
421				    "%s(%d): missing or invalid filename",
422				    filename, lineno);
423				goto fail;
424			}
425			if ((name = strndup(str, len)) == NULL)
426				goto syserr;
427			if (parse_eol(&line) != 0) {
428				openpam_log(PAM_LOG_ERROR,
429				    "%s(%d): garbage at end of line",
430				    filename, lineno);
431				goto fail;
432			}
433			ret = openpam_load_chain(pamh, name, fclt);
434			if (ret != PAM_SUCCESS || errno == ENOENT) {
435				serrno = errno;
436				openpam_log(PAM_LOG_ERROR, "failed loading "
437					"include for service %s in %s: %s",
438					name, filename, strerror(errno));
439				goto fail;
440			}
441			FREE(name);
442			FREE(line);
443			continue;
444		}
445
446		/* get control flag */
447		if ((ctlf = parse_control_flag(&line)) == (pam_control_t)-1) {
448			openpam_log(PAM_LOG_ERROR,
449			    "%s(%d): missing or invalid control flag",
450			    filename, lineno);
451			goto fail;
452		}
453
454		/* get module name */
455		if ((len = parse_filename(&line, &str)) == 0) {
456			openpam_log(PAM_LOG_ERROR,
457			    "%s(%d): missing or invalid module name",
458			    filename, lineno);
459			goto fail;
460		}
461		if ((name = strndup(str, len)) == NULL)
462			goto syserr;
463
464		/* allocate new entry */
465		if ((this = calloc((size_t)1, sizeof *this)) == NULL)
466			goto syserr;
467		this->flag = ctlf;
468
469		/* get module options */
470		if ((this->optv = malloc(sizeof *optv)) == NULL)
471			goto syserr;
472		this->optc = 0;
473		while ((option = parse_option(&line)) != NULL) {
474			optv = realloc(this->optv,
475			    (this->optc + 2) * sizeof *optv);
476			if (optv == NULL)
477				goto syserr;
478			this->optv = optv;
479			this->optv[this->optc++] = option;
480		}
481		this->optv[this->optc] = NULL;
482		if (*line != '\0') {
483			openpam_log(PAM_LOG_ERROR,
484			    "%s(%d): syntax error in module options",
485			    filename, lineno);
486			goto fail;
487		}
488
489		/* load module */
490		this->module = openpam_load_module(name);
491		if (this->module == NULL) {
492			serrno = (errno == ENOENT ? ENOEXEC : errno);
493			goto fail;
494		}
495		FREE(name);
496
497		/* hook it up */
498		for (next = &pamh->chains[fclt]; *next != NULL;
499		     next = &(*next)->next)
500			/* nothing */ ;
501		*next = this;
502		this = NULL;
503
504		/* next please... */
505		FREE(line);
506	}
507	if (!feof(f))
508		goto syserr;
509	fclose(f);
510	return (PAM_SUCCESS);
511syserr:
512	serrno = errno;
513	openpam_log(PAM_LOG_ERROR, "%s: %s", filename, strerror(errno));
514fail:
515	if (this && this->optc) {
516		while (this->optc--)
517			FREE(this->optv[this->optc]);
518		FREE(this->optv);
519	}
520	FREE(this);
521	FREE(line);
522	FREE(name);
523	fclose(f);
524	if (serrno == 0)
525		errno = EINVAL;
526	else
527		errno = serrno;
528	return (PAM_SYSTEM_ERR);
529}
530
531static const char *openpam_policy_path[] = {
532	"/etc/pam.d/",
533	"/etc/pam.conf",
534#ifndef __NetBSD__
535	"/usr/local/etc/pam.d/",
536	"/usr/local/etc/pam.conf",
537#else
538	/* Possibly /usr/pkg? */
539#endif
540	NULL
541};
542
543/*
544 * Locates the policy file for a given service and reads the given chains
545 * from it.
546 */
547static int
548openpam_load_chain(pam_handle_t *pamh,
549	const char *service,
550	pam_facility_t facility)
551{
552	const char **path;
553	char *filename;
554	size_t len;
555	int ret, serrno;
556
557	/* don't allow to escape from policy_path */
558	if (strchr(service, '/')) {
559		openpam_log(PAM_LOG_ERROR, "illegal service \"%s\"", service);
560		return (-PAM_SYSTEM_ERR);
561	}
562
563	ret = PAM_SYSTEM_ERR; /* shut up compiler stupidity */
564	for (path = openpam_policy_path; *path != NULL; ++path) {
565		len = strlen(*path);
566		if ((*path)[len - 1] == '/') {
567			if (asprintf(&filename, "%s%s", *path, service) < 0) {
568				openpam_log(PAM_LOG_ERROR, "asprintf(): %s",
569				    strerror(errno));
570				return (PAM_BUF_ERR);
571			}
572			ret = openpam_parse_chain(pamh, service, facility,
573			    filename, pam_d_style);
574			serrno = errno;
575			FREE(filename);
576			errno = serrno;
577		} else {
578			ret = openpam_parse_chain(pamh, service, facility,
579			    *path, pam_conf_style);
580		}
581
582		/* If /etc/pam.d/ exists, /etc/pam.conf will be ignored */
583		if (ret == PAM_SUCCESS && errno != ENOENT)
584			return (PAM_SUCCESS);
585
586		/* If we had a definitive error, bail out immediately */
587		if (ret != PAM_SUCCESS)
588			return (ret);
589	}
590	return (PAM_SUCCESS);
591}
592
593/*
594 * OpenPAM internal
595 *
596 * Configure a service
597 */
598
599int
600openpam_configure(pam_handle_t *pamh,
601	const char *service)
602{
603	pam_facility_t fclt;
604	const char *p;
605
606	for (p = service; *p; ++p)
607		if (!is_pfcs(*p))
608			return (PAM_SYSTEM_ERR);
609
610	if (openpam_load_chain(pamh, service, PAM_FACILITY_ANY) != PAM_SUCCESS)
611		goto load_err;
612
613	for (fclt = 0; fclt < PAM_NUM_FACILITIES; ++fclt) {
614		if (pamh->chains[fclt] != NULL)
615			continue;
616		if (openpam_load_chain(pamh, PAM_OTHER, fclt) != PAM_SUCCESS)
617			goto load_err;
618	}
619#ifdef __NetBSD__
620	/*
621	 * On NetBSD we require the AUTH chain to have a binding
622	 * or a required module.
623	 */
624	{
625		pam_chain_t *this = pamh->chains[PAM_AUTH];
626		for (; this != NULL; this = this->next)
627			if (this->flag == PAM_BINDING ||
628			    this->flag == PAM_REQUIRED)
629				break;
630		if (this == NULL) {
631			openpam_log(PAM_LOG_ERROR,
632			    "No required or binding component "
633			    "in service %s, facility %s",
634			    service, pam_facility_name[PAM_AUTH]);
635			goto load_err;
636		}
637	}
638#endif
639	return (PAM_SUCCESS);
640load_err:
641	openpam_clear_chains(pamh->chains);
642	return (PAM_SYSTEM_ERR);
643}
644
645/*
646 * NODOC
647 *
648 * Error codes:
649 *	PAM_SYSTEM_ERR
650 */
651