openpam_configure.c revision 267013
152284Sobrien/*-
290075Sobrien * Copyright (c) 2001-2003 Networks Associates Technology, Inc.
352284Sobrien * Copyright (c) 2004-2014 Dag-Erling Sm��rgrav
490075Sobrien * All rights reserved.
552284Sobrien *
690075Sobrien * This software was developed for the FreeBSD Project by ThinkSec AS and
790075Sobrien * Network Associates Laboratories, the Security Research Division of
890075Sobrien * Network Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
990075Sobrien * ("CBOSS"), as part of the DARPA CHATS research program.
1052284Sobrien *
1190075Sobrien * Redistribution and use in source and binary forms, with or without
1290075Sobrien * modification, are permitted provided that the following conditions
1390075Sobrien * are met:
1490075Sobrien * 1. Redistributions of source code must retain the above copyright
1552284Sobrien *    notice, this list of conditions and the following disclaimer.
1652284Sobrien * 2. Redistributions in binary form must reproduce the above copyright
1790075Sobrien *    notice, this list of conditions and the following disclaimer in the
1890075Sobrien *    documentation and/or other materials provided with the distribution.
1990075Sobrien * 3. The name of the author may not be used to endorse or promote
2052284Sobrien *    products derived from this software without specific prior written
2190075Sobrien *    permission.
2290075Sobrien *
2390075Sobrien * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
2452284Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2552284Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2652284Sobrien * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
2752284Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2890075Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2952284Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
3052284Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
3190075Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3290075Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3390075Sobrien * SUCH DAMAGE.
3490075Sobrien *
3590075Sobrien * $Id: openpam_configure.c 667 2013-03-17 14:24:00Z des $
3690075Sobrien */
3752284Sobrien
3852284Sobrien#ifdef HAVE_CONFIG_H
3952284Sobrien# include "config.h"
4052284Sobrien#endif
4152284Sobrien
4290075Sobrien#include <sys/param.h>
4352284Sobrien
4490075Sobrien#include <errno.h>
4590075Sobrien#include <stdio.h>
4690075Sobrien#include <stdlib.h>
4790075Sobrien#include <string.h>
4852284Sobrien
4990075Sobrien#include <security/pam_appl.h>
5090075Sobrien
5190075Sobrien#include "openpam_impl.h"
5252284Sobrien#include "openpam_ctype.h"
5390075Sobrien#include "openpam_strlcat.h"
5490075Sobrien#include "openpam_strlcpy.h"
5590075Sobrien
5690075Sobrienstatic int openpam_load_chain(pam_handle_t *, const char *, pam_facility_t);
5752284Sobrien
5852284Sobrien/*
5952284Sobrien * Validate a service name.
6052284Sobrien *
6190075Sobrien * Returns a non-zero value if the argument points to a NUL-terminated
6290075Sobrien * string consisting entirely of characters in the POSIX portable filename
6352284Sobrien * character set, excluding the path separator character.
6452284Sobrien */
6552284Sobrienstatic int
6690075Sobrienvalid_service_name(const char *name)
6790075Sobrien{
6852284Sobrien	const char *p;
6952284Sobrien
7090075Sobrien	if (OPENPAM_FEATURE(RESTRICT_SERVICE_NAME)) {
7152284Sobrien		/* path separator not allowed */
7290075Sobrien		for (p = name; *p != '\0'; ++p)
7390075Sobrien			if (!is_pfcs(*p))
7490075Sobrien				return (0);
7590075Sobrien	} else {
7690075Sobrien		/* path separator allowed */
7790075Sobrien		for (p = name; *p != '\0'; ++p)
7890075Sobrien			if (!is_pfcs(*p) && *p != '/')
7990075Sobrien				return (0);
8090075Sobrien	}
8190075Sobrien	return (1);
8290075Sobrien}
8390075Sobrien
8490075Sobrien/*
8590075Sobrien * Parse the facility name.
8652284Sobrien *
8752284Sobrien * Returns the corresponding pam_facility_t value, or -1 if the argument
8890075Sobrien * is not a valid facility name.
8990075Sobrien */
9052284Sobrienstatic pam_facility_t
9190075Sobrienparse_facility_name(const char *name)
9252284Sobrien{
9390075Sobrien	int i;
9490075Sobrien
9590075Sobrien	for (i = 0; i < PAM_NUM_FACILITIES; ++i)
9690075Sobrien		if (strcmp(pam_facility_name[i], name) == 0)
9790075Sobrien			return (i);
9890075Sobrien	return ((pam_facility_t)-1);
9990075Sobrien}
10090075Sobrien
10190075Sobrien/*
10290075Sobrien * Parse the control flag.
10390075Sobrien *
10490075Sobrien * Returns the corresponding pam_control_t value, or -1 if the argument is
10552284Sobrien * not a valid control flag name.
10690075Sobrien */
10790075Sobrienstatic pam_control_t
10890075Sobrienparse_control_flag(const char *name)
10990075Sobrien{
11090075Sobrien	int i;
11190075Sobrien
11290075Sobrien	for (i = 0; i < PAM_NUM_CONTROL_FLAGS; ++i)
11390075Sobrien		if (strcmp(pam_control_flag_name[i], name) == 0)
11490075Sobrien			return (i);
11590075Sobrien	return ((pam_control_t)-1);
11690075Sobrien}
11790075Sobrien
11852284Sobrien/*
11990075Sobrien * Validate a file name.
12090075Sobrien *
12152284Sobrien * Returns a non-zero value if the argument points to a NUL-terminated
12290075Sobrien * string consisting entirely of characters in the POSIX portable filename
12352284Sobrien * character set, including the path separator character.
12452284Sobrien */
12552284Sobrienstatic int
12652284Sobrienvalid_module_name(const char *name)
12790075Sobrien{
12890075Sobrien	const char *p;
12952284Sobrien
13052284Sobrien	if (OPENPAM_FEATURE(RESTRICT_MODULE_NAME)) {
13190075Sobrien		/* path separator not allowed */
13290075Sobrien		for (p = name; *p != '\0'; ++p)
13390075Sobrien			if (!is_pfcs(*p))
13490075Sobrien				return (0);
13590075Sobrien	} else {
13690075Sobrien		/* path separator allowed */
13790075Sobrien		for (p = name; *p != '\0'; ++p)
13890075Sobrien			if (!is_pfcs(*p) && *p != '/')
13990075Sobrien				return (0);
14090075Sobrien	}
14190075Sobrien	return (1);
142}
143
144typedef enum { pam_conf_style, pam_d_style } openpam_style_t;
145
146/*
147 * Extracts given chains from a policy file.
148 *
149 * Returns the number of policy entries which were found for the specified
150 * service and facility, or -1 if a system error occurred or a syntax
151 * error was encountered.
152 */
153static int
154openpam_parse_chain(pam_handle_t *pamh,
155	const char *service,
156	pam_facility_t facility,
157	FILE *f,
158	const char *filename,
159	openpam_style_t style)
160{
161	pam_chain_t *this, **next;
162	pam_facility_t fclt;
163	pam_control_t ctlf;
164	char *name, *servicename, *modulename;
165	int count, lineno, ret, serrno;
166	char **wordv, *word;
167	int i, wordc;
168
169	count = 0;
170	this = NULL;
171	name = NULL;
172	lineno = 0;
173	wordc = 0;
174	wordv = NULL;
175	while ((wordv = openpam_readlinev(f, &lineno, &wordc)) != NULL) {
176		/* blank line? */
177		if (wordc == 0) {
178			FREEV(wordc, wordv);
179			continue;
180		}
181		i = 0;
182
183		/* check service name if necessary */
184		if (style == pam_conf_style &&
185		    strcmp(wordv[i++], service) != 0) {
186			FREEV(wordc, wordv);
187			continue;
188		}
189
190		/* check facility name */
191		if ((word = wordv[i++]) == NULL ||
192		    (fclt = parse_facility_name(word)) == (pam_facility_t)-1) {
193			openpam_log(PAM_LOG_ERROR,
194			    "%s(%d): missing or invalid facility",
195			    filename, lineno);
196			errno = EINVAL;
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				errno = EINVAL;
213				goto fail;
214			}
215			if (wordv[i] != NULL) {
216				openpam_log(PAM_LOG_ERROR,
217				    "%s(%d): garbage at end of line",
218				    filename, lineno);
219				errno = EINVAL;
220				goto fail;
221			}
222			ret = openpam_load_chain(pamh, servicename, fclt);
223			FREEV(wordc, wordv);
224			if (ret < 0) {
225				/*
226				 * Bogus errno, but this ensures that the
227				 * outer loop does not just ignore the
228				 * error and keep searching.
229				 */
230				if (errno == ENOENT)
231					errno = EINVAL;
232				goto fail;
233			}
234			continue;
235		}
236
237		/* get control flag */
238		if (word == NULL || /* same word we compared to "include" */
239		    (ctlf = parse_control_flag(word)) == (pam_control_t)-1) {
240			openpam_log(PAM_LOG_ERROR,
241			    "%s(%d): missing or invalid control flag",
242			    filename, lineno);
243			errno = EINVAL;
244			goto fail;
245		}
246
247		/* get module name */
248		if ((modulename = wordv[i++]) == NULL ||
249		    !valid_module_name(modulename)) {
250			openpam_log(PAM_LOG_ERROR,
251			    "%s(%d): missing or invalid module name",
252			    filename, lineno);
253			errno = EINVAL;
254			goto fail;
255		}
256
257		/* allocate new entry */
258		if ((this = calloc(1, sizeof *this)) == NULL)
259			goto syserr;
260		this->flag = ctlf;
261
262		/* load module */
263		if ((this->module = openpam_load_module(modulename)) == NULL) {
264			if (errno == ENOENT)
265				errno = ENOEXEC;
266			goto fail;
267		}
268
269		/*
270		 * The remaining items in wordv are the module's
271		 * arguments.  We could set this->optv = wordv + i, but
272		 * then free(this->optv) wouldn't work.  Instead, we free
273		 * the words we've already consumed, shift the rest up,
274		 * and clear the tail end of the array.
275		 */
276		this->optc = wordc - i;
277		for (i = 0; i < wordc - this->optc; ++i) {
278			FREE(wordv[i]);
279		}
280		for (i = 0; i < this->optc; ++i) {
281			wordv[i] = wordv[wordc - this->optc + i];
282			wordv[wordc - this->optc + i] = NULL;
283		}
284		this->optv = wordv;
285		wordv = NULL;
286		wordc = 0;
287
288		/* hook it up */
289		for (next = &pamh->chains[fclt]; *next != NULL;
290		     next = &(*next)->next)
291			/* nothing */ ;
292		*next = this;
293		this = NULL;
294		++count;
295	}
296	/*
297	 * The loop ended because openpam_readword() returned NULL, which
298	 * can happen for four different reasons: an I/O error (ferror(f)
299	 * is true), a memory allocation failure (ferror(f) is false,
300	 * feof(f) is false, errno is non-zero), the file ended with an
301	 * unterminated quote or backslash escape (ferror(f) is false,
302	 * feof(f) is true, errno is non-zero), or the end of the file was
303	 * reached without error (ferror(f) is false, feof(f) is true,
304	 * errno is zero).
305	 */
306	if (ferror(f) || errno != 0)
307		goto syserr;
308	if (!feof(f))
309		goto fail;
310	fclose(f);
311	return (count);
312syserr:
313	serrno = errno;
314	openpam_log(PAM_LOG_ERROR, "%s: %m", filename);
315	errno = serrno;
316	/* fall through */
317fail:
318	serrno = errno;
319	if (this && this->optc && this->optv)
320		FREEV(this->optc, this->optv);
321	FREE(this);
322	FREEV(wordc, wordv);
323	FREE(wordv);
324	FREE(name);
325	fclose(f);
326	errno = serrno;
327	return (-1);
328}
329
330/*
331 * Read the specified chains from the specified file.
332 *
333 * Returns 0 if the file exists but does not contain any matching lines.
334 *
335 * Returns -1 and sets errno to ENOENT if the file does not exist.
336 *
337 * Returns -1 and sets errno to some other non-zero value if the file
338 * exists but is unsafe or unreadable, or an I/O error occurs.
339 */
340static int
341openpam_load_file(pam_handle_t *pamh,
342	const char *service,
343	pam_facility_t facility,
344	const char *filename,
345	openpam_style_t style)
346{
347	FILE *f;
348	int ret, serrno;
349
350	/* attempt to open the file */
351	if ((f = fopen(filename, "r")) == NULL) {
352		serrno = errno;
353		openpam_log(errno == ENOENT ? PAM_LOG_DEBUG : PAM_LOG_ERROR,
354		    "%s: %m", filename);
355		errno = serrno;
356		RETURNN(-1);
357	} else {
358		openpam_log(PAM_LOG_DEBUG, "found %s", filename);
359	}
360
361	/* verify type, ownership and permissions */
362	if (OPENPAM_FEATURE(VERIFY_POLICY_FILE) &&
363	    openpam_check_desc_owner_perms(filename, fileno(f)) != 0) {
364		/* already logged the cause */
365		serrno = errno;
366		fclose(f);
367		errno = serrno;
368		RETURNN(-1);
369	}
370
371	/* parse the file */
372	ret = openpam_parse_chain(pamh, service, facility,
373	    f, filename, style);
374	RETURNN(ret);
375}
376
377/*
378 * Locates the policy file for a given service and reads the given chains
379 * from it.
380 *
381 * Returns the number of policy entries which were found for the specified
382 * service and facility, or -1 if a system error occurred or a syntax
383 * error was encountered.
384 */
385static int
386openpam_load_chain(pam_handle_t *pamh,
387	const char *service,
388	pam_facility_t facility)
389{
390	const char *p, **path;
391	char filename[PATH_MAX];
392	size_t len;
393	openpam_style_t style;
394	int ret;
395
396	ENTERS(facility < 0 ? "any" : pam_facility_name[facility]);
397
398	/* either absolute or relative to cwd */
399	if (strchr(service, '/') != NULL) {
400		if ((p = strrchr(service, '.')) != NULL && strcmp(p, ".conf") == 0)
401			style = pam_conf_style;
402		else
403			style = pam_d_style;
404		ret = openpam_load_file(pamh, service, facility,
405		    service, style);
406		RETURNN(ret);
407	}
408
409	/* search standard locations */
410	for (path = openpam_policy_path; *path != NULL; ++path) {
411		/* construct filename */
412		len = strlcpy(filename, *path, sizeof filename);
413		if (filename[len - 1] == '/') {
414			len = strlcat(filename, service, sizeof filename);
415			if (len >= sizeof filename) {
416				errno = ENAMETOOLONG;
417				RETURNN(-1);
418			}
419			style = pam_d_style;
420		} else {
421			style = pam_conf_style;
422		}
423		ret = openpam_load_file(pamh, service, facility,
424		    filename, style);
425		/* success */
426		if (ret > 0)
427			RETURNN(ret);
428		/* the file exists, but an error occurred */
429		if (ret == -1 && errno != ENOENT)
430			RETURNN(ret);
431		/* in pam.d style, an empty file counts as a hit */
432		if (ret == 0 && style == pam_d_style)
433			RETURNN(ret);
434	}
435
436	/* no hit */
437	errno = ENOENT;
438	RETURNN(-1);
439}
440
441/*
442 * OpenPAM internal
443 *
444 * Configure a service
445 */
446
447int
448openpam_configure(pam_handle_t *pamh,
449	const char *service)
450{
451	pam_facility_t fclt;
452	int serrno;
453
454	ENTERS(service);
455	if (!valid_service_name(service)) {
456		openpam_log(PAM_LOG_ERROR, "invalid service name");
457		RETURNC(PAM_SYSTEM_ERR);
458	}
459	if (openpam_load_chain(pamh, service, PAM_FACILITY_ANY) < 0) {
460		if (errno != ENOENT)
461			goto load_err;
462	}
463	for (fclt = 0; fclt < PAM_NUM_FACILITIES; ++fclt) {
464		if (pamh->chains[fclt] != NULL)
465			continue;
466		if (openpam_load_chain(pamh, PAM_OTHER, fclt) < 0)
467			goto load_err;
468	}
469	RETURNC(PAM_SUCCESS);
470load_err:
471	serrno = errno;
472	openpam_clear_chains(pamh->chains);
473	errno = serrno;
474	RETURNC(PAM_SYSTEM_ERR);
475}
476
477/*
478 * NODOC
479 *
480 * Error codes:
481 *	PAM_SYSTEM_ERR
482 */
483