Home | History | Annotate | Line # | Download | only in libpam
openpam_configure.c revision 1.4
      1 /*	$NetBSD: openpam_configure.c,v 1.4 2018/05/16 13:54:03 joerg Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 2001-2003 Networks Associates Technology, Inc.
      5  * Copyright (c) 2004-2015 Dag-Erling Smrgrav
      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  * $OpenPAM: openpam_configure.c 938 2017-04-30 21:34:42Z des $
     38  */
     39 
     40 #ifdef HAVE_CONFIG_H
     41 # include "config.h"
     42 #endif
     43 
     44 #include <sys/cdefs.h>
     45 __RCSID("$NetBSD: openpam_configure.c,v 1.4 2018/05/16 13:54:03 joerg Exp $");
     46 
     47 #include <sys/param.h>
     48 
     49 #include <errno.h>
     50 #include <stdio.h>
     51 #include <stdlib.h>
     52 #include <string.h>
     53 
     54 #include <security/pam_appl.h>
     55 
     56 #include "openpam_impl.h"
     57 #include "openpam_ctype.h"
     58 #include "openpam_strlcat.h"
     59 #include "openpam_strlcpy.h"
     60 
     61 static int openpam_load_chain(pam_handle_t *, const char *, pam_facility_t);
     62 
     63 /*
     64  * Validate a service name.
     65  *
     66  * Returns a non-zero value if the argument points to a NUL-terminated
     67  * string consisting entirely of characters in the POSIX portable filename
     68  * character set, excluding the path separator character.
     69  */
     70 static int
     71 valid_service_name(const char *name)
     72 {
     73 	const char *p;
     74 
     75 	if (OPENPAM_FEATURE(RESTRICT_SERVICE_NAME)) {
     76 		/* path separator not allowed */
     77 		for (p = name; *p != '\0'; ++p)
     78 			if (!is_pfcs(*p))
     79 				return (0);
     80 	} else {
     81 		/* path separator allowed */
     82 		for (p = name; *p != '\0'; ++p)
     83 			if (!is_pfcs(*p) && *p != '/')
     84 				return (0);
     85 	}
     86 	return (1);
     87 }
     88 
     89 /*
     90  * Parse the facility name.
     91  *
     92  * Returns the corresponding pam_facility_t value, or -1 if the argument
     93  * is not a valid facility name.
     94  */
     95 static pam_facility_t
     96 parse_facility_name(const char *name)
     97 {
     98 	int i;
     99 
    100 	for (i = 0; i < PAM_NUM_FACILITIES; ++i)
    101 		if (strcmp(pam_facility_name[i], name) == 0)
    102 			return (i);
    103 	return ((pam_facility_t)-1);
    104 }
    105 
    106 /*
    107  * Parse the control flag.
    108  *
    109  * Returns the corresponding pam_control_t value, or -1 if the argument is
    110  * not a valid control flag name.
    111  */
    112 static pam_control_t
    113 parse_control_flag(const char *name)
    114 {
    115 	pam_control_t i;
    116 
    117 	for (i = PAM_BINDING; i < PAM_NUM_CONTROL_FLAGS; ++i)
    118 		if (strcmp(pam_control_flag_name[i], name) == 0)
    119 			return (i);
    120 	return ((pam_control_t)-1);
    121 }
    122 
    123 /*
    124  * Validate a file name.
    125  *
    126  * Returns a non-zero value if the argument points to a NUL-terminated
    127  * string consisting entirely of characters in the POSIX portable filename
    128  * character set, including the path separator character.
    129  */
    130 static int
    131 valid_module_name(const char *name)
    132 {
    133 	const char *p;
    134 
    135 	if (OPENPAM_FEATURE(RESTRICT_MODULE_NAME)) {
    136 		/* path separator not allowed */
    137 		for (p = name; *p != '\0'; ++p)
    138 			if (!is_pfcs(*p))
    139 				return (0);
    140 	} else {
    141 		/* path separator allowed */
    142 		for (p = name; *p != '\0'; ++p)
    143 			if (!is_pfcs(*p) && *p != '/')
    144 				return (0);
    145 	}
    146 	return (1);
    147 }
    148 
    149 typedef enum { pam_conf_style, pam_d_style } openpam_style_t;
    150 
    151 /*
    152  * Extracts given chains from a policy file.
    153  *
    154  * Returns the number of policy entries which were found for the specified
    155  * service and facility, or -1 if a system error occurred or a syntax
    156  * error was encountered.
    157  */
    158 static int
    159 openpam_parse_chain(pam_handle_t *pamh,
    160 	const char *service,
    161 	pam_facility_t facility,
    162 	FILE *f,
    163 	const char *filename,
    164 	openpam_style_t style)
    165 {
    166 	pam_chain_t *this, **next;
    167 	pam_facility_t fclt;
    168 	pam_control_t ctlf;
    169 	char *name, *servicename, *modulename;
    170 	int count, lineno, ret, serrno;
    171 	char **wordv, *word;
    172 	int i, wordc;
    173 
    174 	count = 0;
    175 	this = NULL;
    176 	name = NULL;
    177 	lineno = 0;
    178 	wordc = 0;
    179 	wordv = NULL;
    180 	while ((wordv = openpam_readlinev(f, &lineno, &wordc)) != NULL) {
    181 		/* blank line? */
    182 		if (wordc == 0) {
    183 			FREEV(wordc, wordv);
    184 			continue;
    185 		}
    186 		i = 0;
    187 
    188 		/* check service name if necessary */
    189 		if (style == pam_conf_style &&
    190 		    strcmp(wordv[i++], service) != 0) {
    191 			FREEV(wordc, wordv);
    192 			continue;
    193 		}
    194 
    195 		/* check facility name */
    196 		if ((word = wordv[i++]) == NULL ||
    197 		    (fclt = parse_facility_name(word)) == (pam_facility_t)-1) {
    198 			openpam_log(PAM_LOG_ERROR,
    199 			    "%s(%d): missing or invalid facility",
    200 			    filename, lineno);
    201 			errno = EINVAL;
    202 			goto fail;
    203 		}
    204 		if (facility != fclt && facility != PAM_FACILITY_ANY) {
    205 			FREEV(wordc, wordv);
    206 			continue;
    207 		}
    208 
    209 		/* check for "include" */
    210 		if ((word = wordv[i++]) != NULL &&
    211 		    strcmp(word, "include") == 0) {
    212 			if ((servicename = wordv[i++]) == NULL ||
    213 			    !valid_service_name(servicename)) {
    214 				openpam_log(PAM_LOG_ERROR,
    215 				    "%s(%d): missing or invalid service name",
    216 				    filename, lineno);
    217 				errno = EINVAL;
    218 				goto fail;
    219 			}
    220 			if (wordv[i] != NULL) {
    221 				openpam_log(PAM_LOG_ERROR,
    222 				    "%s(%d): garbage at end of line",
    223 				    filename, lineno);
    224 				errno = EINVAL;
    225 				goto fail;
    226 			}
    227 			ret = openpam_load_chain(pamh, servicename, fclt);
    228 			FREEV(wordc, wordv);
    229 			if (ret < 0) {
    230 				/*
    231 				 * Bogus errno, but this ensures that the
    232 				 * outer loop does not just ignore the
    233 				 * error and keep searching.
    234 				 */
    235 				if (errno == ENOENT)
    236 					errno = EINVAL;
    237 				goto fail;
    238 			}
    239 			continue;
    240 		}
    241 
    242 		/* get control flag */
    243 		if (word == NULL || /* same word we compared to "include" */
    244 		    (ctlf = parse_control_flag(word)) == (pam_control_t)-1) {
    245 			openpam_log(PAM_LOG_ERROR,
    246 			    "%s(%d): missing or invalid control flag",
    247 			    filename, lineno);
    248 			errno = EINVAL;
    249 			goto fail;
    250 		}
    251 
    252 		/* get module name */
    253 		if ((modulename = wordv[i++]) == NULL ||
    254 		    !valid_module_name(modulename)) {
    255 			openpam_log(PAM_LOG_ERROR,
    256 			    "%s(%d): missing or invalid module name",
    257 			    filename, lineno);
    258 			errno = EINVAL;
    259 			goto fail;
    260 		}
    261 
    262 		/* allocate new entry */
    263 		if ((this = calloc((size_t)1, sizeof *this)) == NULL)
    264 			goto syserr;
    265 		this->flag = (int)ctlf;
    266 
    267 		/* load module */
    268 		if ((this->module = openpam_load_module(modulename)) == NULL) {
    269 			if (errno == ENOENT)
    270 				errno = ENOEXEC;
    271 			goto fail;
    272 		}
    273 
    274 		/*
    275 		 * The remaining items in wordv are the module's
    276 		 * arguments.  We could set this->optv = wordv + i, but
    277 		 * then free(this->optv) wouldn't work.  Instead, we free
    278 		 * the words we've already consumed, shift the rest up,
    279 		 * and clear the tail end of the array.
    280 		 */
    281 		this->optc = wordc - i;
    282 		for (i = 0; i < wordc - this->optc; ++i) {
    283 			FREE(wordv[i]);
    284 		}
    285 		for (i = 0; i < this->optc; ++i) {
    286 			wordv[i] = wordv[wordc - this->optc + i];
    287 			wordv[wordc - this->optc + i] = NULL;
    288 		}
    289 		this->optv = wordv;
    290 		wordv = NULL;
    291 		wordc = 0;
    292 
    293 		/* hook it up */
    294 		for (next = &pamh->chains[fclt]; *next != NULL;
    295 		     next = &(*next)->next)
    296 			/* nothing */ ;
    297 		*next = this;
    298 		this = NULL;
    299 		++count;
    300 	}
    301 	/*
    302 	 * The loop ended because openpam_readword() returned NULL, which
    303 	 * can happen for four different reasons: an I/O error (ferror(f)
    304 	 * is true), a memory allocation failure (ferror(f) is false,
    305 	 * feof(f) is false, errno is non-zero), the file ended with an
    306 	 * unterminated quote or backslash escape (ferror(f) is false,
    307 	 * feof(f) is true, errno is non-zero), or the end of the file was
    308 	 * reached without error (ferror(f) is false, feof(f) is true,
    309 	 * errno is zero).
    310 	 */
    311 	if (ferror(f) || errno != 0)
    312 		goto syserr;
    313 	if (!feof(f))
    314 		goto fail;
    315 	fclose(f);
    316 	return (count);
    317 syserr:
    318 	serrno = errno;
    319 	openpam_log(PAM_LOG_ERROR, "%s: %m", filename);
    320 	errno = serrno;
    321 	/* fall through */
    322 fail:
    323 	serrno = errno;
    324 	if (this && this->optc && this->optv)
    325 		FREEV(this->optc, this->optv);
    326 	FREE(this);
    327 	FREEV(wordc, wordv);
    328 	FREE(wordv);
    329 	FREE(name);
    330 	fclose(f);
    331 	errno = serrno;
    332 	return (-1);
    333 }
    334 
    335 /*
    336  * Read the specified chains from the specified file.
    337  *
    338  * Returns 0 if the file exists but does not contain any matching lines.
    339  *
    340  * Returns -1 and sets errno to ENOENT if the file does not exist.
    341  *
    342  * Returns -1 and sets errno to some other non-zero value if the file
    343  * exists but is unsafe or unreadable, or an I/O error occurs.
    344  */
    345 static int
    346 openpam_load_file(pam_handle_t *pamh,
    347 	const char *service,
    348 	pam_facility_t facility,
    349 	const char *filename,
    350 	openpam_style_t style)
    351 {
    352 	FILE *f;
    353 	int ret, serrno;
    354 
    355 	/* attempt to open the file */
    356 	if ((f = fopen(filename, "r")) == NULL) {
    357 		serrno = errno;
    358 		openpam_log(errno == ENOENT ? PAM_LOG_DEBUG : PAM_LOG_ERROR,
    359 		    "%s: %m", filename);
    360 		errno = serrno;
    361 		RETURNN(-1);
    362 	} else {
    363 		openpam_log(PAM_LOG_DEBUG, "found %s", filename);
    364 	}
    365 
    366 	/* verify type, ownership and permissions */
    367 	if (OPENPAM_FEATURE(VERIFY_POLICY_FILE) &&
    368 	    openpam_check_desc_owner_perms(filename, fileno(f)) != 0) {
    369 		/* already logged the cause */
    370 		serrno = errno;
    371 		fclose(f);
    372 		errno = serrno;
    373 		RETURNN(-1);
    374 	}
    375 
    376 	/* parse the file */
    377 	ret = openpam_parse_chain(pamh, service, facility,
    378 	    f, filename, style);
    379 	RETURNN(ret);
    380 }
    381 
    382 /*
    383  * Locates the policy file for a given service and reads the given chains
    384  * from it.
    385  *
    386  * Returns the number of policy entries which were found for the specified
    387  * service and facility, or -1 if a system error occurred or a syntax
    388  * error was encountered.
    389  */
    390 static int
    391 openpam_load_chain(pam_handle_t *pamh,
    392 	const char *service,
    393 	pam_facility_t facility)
    394 {
    395 	const char *p, **path;
    396 	char filename[PATH_MAX];
    397 	size_t len;
    398 	openpam_style_t style;
    399 	int ret;
    400 
    401 	ENTERS(facility < 0 ? "any" : pam_facility_name[facility]);
    402 
    403 	/* either absolute or relative to cwd */
    404 	if (strchr(service, '/') != NULL) {
    405 		if ((p = strrchr(service, '.')) != NULL && strcmp(p, ".conf") == 0)
    406 			style = pam_conf_style;
    407 		else
    408 			style = pam_d_style;
    409 		ret = openpam_load_file(pamh, service, facility,
    410 		    service, style);
    411 		RETURNN(ret);
    412 	}
    413 
    414 	/* search standard locations */
    415 	for (path = openpam_policy_path; *path != NULL; ++path) {
    416 		/* construct filename */
    417 		len = strlcpy(filename, *path, sizeof filename);
    418 		if (len >= sizeof filename) {
    419 			errno = ENAMETOOLONG;
    420 			RETURNN(-1);
    421 		}
    422 		if (filename[len - 1] == '/') {
    423 			len = strlcat(filename, service, sizeof filename);
    424 			if (len >= sizeof filename) {
    425 				errno = ENAMETOOLONG;
    426 				RETURNN(-1);
    427 			}
    428 			style = pam_d_style;
    429 		} else {
    430 			style = pam_conf_style;
    431 		}
    432 		ret = openpam_load_file(pamh, service, facility,
    433 		    filename, style);
    434 		/* success */
    435 		if (ret > 0)
    436 			RETURNN(ret);
    437 		/* the file exists, but an error occurred */
    438 		if (ret == -1 && errno != ENOENT)
    439 			RETURNN(ret);
    440 		/* in pam.d style, an empty file counts as a hit */
    441 		if (ret == 0 && style == pam_d_style)
    442 			RETURNN(ret);
    443 	}
    444 
    445 	/* no hit */
    446 	errno = ENOENT;
    447 	RETURNN(-1);
    448 }
    449 
    450 /*
    451  * OpenPAM internal
    452  *
    453  * Configure a service
    454  */
    455 
    456 int
    457 openpam_configure(pam_handle_t *pamh,
    458 	const char *service)
    459 {
    460 	pam_facility_t fclt;
    461 	int serrno;
    462 
    463 	ENTERS(service);
    464 	if (!valid_service_name(service)) {
    465 		openpam_log(PAM_LOG_ERROR, "invalid service name");
    466 		RETURNC(PAM_SYSTEM_ERR);
    467 	}
    468 	if (openpam_load_chain(pamh, service, PAM_FACILITY_ANY) < 0) {
    469 		if (errno != ENOENT)
    470 			goto load_err;
    471 	}
    472 	for (fclt = 0; fclt < PAM_NUM_FACILITIES; ++fclt) {
    473 		if (pamh->chains[fclt] != NULL)
    474 			continue;
    475 		if (OPENPAM_FEATURE(FALLBACK_TO_OTHER)) {
    476 			if (openpam_load_chain(pamh, PAM_OTHER, fclt) < 0)
    477 				goto load_err;
    478 		}
    479 	}
    480 #ifdef __NetBSD__
    481 	/*
    482 	 * On NetBSD we require the AUTH chain to have a binding,
    483 	 * a required, or requisite module.
    484 	 */
    485 	{
    486 		pam_chain_t *this = pamh->chains[PAM_AUTH];
    487 		for (; this != NULL; this = this->next)
    488 			if (this->flag == PAM_BINDING ||
    489 			    this->flag == PAM_REQUIRED ||
    490 			    this->flag == PAM_REQUISITE)
    491 				break;
    492 		if (this == NULL) {
    493 			openpam_log(PAM_LOG_ERROR,
    494 			    "No required, requisite, or binding component "
    495 			    "in service %s, facility %s",
    496 			    service, pam_facility_name[PAM_AUTH]);
    497 			goto load_err;
    498 		}
    499 	}
    500 #endif
    501 	RETURNC(PAM_SUCCESS);
    502 load_err:
    503 	serrno = errno;
    504 	openpam_clear_chains(pamh->chains);
    505 	errno = serrno;
    506 	RETURNC(PAM_SYSTEM_ERR);
    507 }
    508 
    509 /*
    510  * NODOC
    511  *
    512  * Error codes:
    513  *	PAM_SYSTEM_ERR
    514  */
    515