diff options
Diffstat (limited to 'lib/libpam/openpam_configure.c')
-rw-r--r-- | lib/libpam/openpam_configure.c | 465 |
1 files changed, 465 insertions, 0 deletions
diff --git a/lib/libpam/openpam_configure.c b/lib/libpam/openpam_configure.c new file mode 100644 index 0000000..8172a6f --- /dev/null +++ b/lib/libpam/openpam_configure.c @@ -0,0 +1,465 @@ +/*- + * Copyright (c) 2001-2003 Networks Associates Technology, Inc. + * Copyright (c) 2004-2012 Dag-Erling Smørgrav + * All rights reserved. + * + * This software was developed for the FreeBSD Project by ThinkSec AS and + * Network Associates Laboratories, the Security Research Division of + * Network Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 + * ("CBOSS"), as part of the DARPA CHATS research program. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: openpam_configure.c 612 2012-05-26 23:02:55Z des $ + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <sys/param.h> + +#include <ctype.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <security/pam_appl.h> + +#include "openpam_impl.h" +#include "openpam_ctype.h" +#include "openpam_strlcat.h" +#include "openpam_strlcpy.h" + +static int openpam_load_chain(pam_handle_t *, const char *, pam_facility_t); + +/* + * Validate a service name. + * + * Returns a non-zero value if the argument points to a NUL-terminated + * string consisting entirely of characters in the POSIX portable filename + * character set, excluding the path separator character. + */ +static int +valid_service_name(const char *name) +{ + const char *p; + + if (OPENPAM_FEATURE(RESTRICT_SERVICE_NAME)) { + /* path separator not allowed */ + for (p = name; *p != '\0'; ++p) + if (!is_pfcs(*p)) + return (0); + } else { + /* path separator allowed */ + for (p = name; *p != '\0'; ++p) + if (!is_pfcs(*p) && *p != '/') + return (0); + } + return (1); +} + +/* + * Parse the facility name. + * + * Returns the corresponding pam_facility_t value, or -1 if the argument + * is not a valid facility name. + */ +static pam_facility_t +parse_facility_name(const char *name) +{ + int i; + + for (i = 0; i < PAM_NUM_FACILITIES; ++i) + if (strcmp(pam_facility_name[i], name) == 0) + return (i); + return ((pam_facility_t)-1); +} + +/* + * Parse the control flag. + * + * Returns the corresponding pam_control_t value, or -1 if the argument is + * not a valid control flag name. + */ +static pam_control_t +parse_control_flag(const char *name) +{ + int i; + + for (i = 0; i < PAM_NUM_CONTROL_FLAGS; ++i) + if (strcmp(pam_control_flag_name[i], name) == 0) + return (i); + return ((pam_control_t)-1); +} + +/* + * Validate a file name. + * + * Returns a non-zero value if the argument points to a NUL-terminated + * string consisting entirely of characters in the POSIX portable filename + * character set, including the path separator character. + */ +static int +valid_module_name(const char *name) +{ + const char *p; + + if (OPENPAM_FEATURE(RESTRICT_MODULE_NAME)) { + /* path separator not allowed */ + for (p = name; *p != '\0'; ++p) + if (!is_pfcs(*p)) + return (0); + } else { + /* path separator allowed */ + for (p = name; *p != '\0'; ++p) + if (!is_pfcs(*p) && *p != '/') + return (0); + } + return (1); +} + +typedef enum { pam_conf_style, pam_d_style } openpam_style_t; + +/* + * Extracts given chains from a policy file. + * + * Returns the number of policy entries which were found for the specified + * service and facility, or -1 if a system error occurred or a syntax + * error was encountered. + */ +static int +openpam_parse_chain(pam_handle_t *pamh, + const char *service, + pam_facility_t facility, + FILE *f, + const char *filename, + openpam_style_t style) +{ + pam_chain_t *this, **next; + pam_facility_t fclt; + pam_control_t ctlf; + char *name, *servicename, *modulename; + int count, lineno, ret, serrno; + char **wordv, *word; + int i, wordc; + + count = 0; + this = NULL; + name = NULL; + lineno = 0; + wordc = 0; + wordv = NULL; + while ((wordv = openpam_readlinev(f, &lineno, &wordc)) != NULL) { + /* blank line? */ + if (wordc == 0) { + FREEV(wordc, wordv); + continue; + } + i = 0; + + /* check service name if necessary */ + if (style == pam_conf_style && + strcmp(wordv[i++], service) != 0) { + FREEV(wordc, wordv); + continue; + } + + /* check facility name */ + if ((word = wordv[i++]) == NULL || + (fclt = parse_facility_name(word)) == (pam_facility_t)-1) { + openpam_log(PAM_LOG_ERROR, + "%s(%d): missing or invalid facility", + filename, lineno); + goto fail; + } + if (facility != fclt && facility != PAM_FACILITY_ANY) { + FREEV(wordc, wordv); + continue; + } + + /* check for "include" */ + if ((word = wordv[i++]) != NULL && + strcmp(word, "include") == 0) { + if ((servicename = wordv[i++]) == NULL || + !valid_service_name(servicename)) { + openpam_log(PAM_LOG_ERROR, + "%s(%d): missing or invalid service name", + filename, lineno); + goto fail; + } + if (wordv[i] != NULL) { + openpam_log(PAM_LOG_ERROR, + "%s(%d): garbage at end of line", + filename, lineno); + goto fail; + } + ret = openpam_load_chain(pamh, servicename, fclt); + FREEV(wordc, wordv); + if (ret < 0) + goto fail; + continue; + } + + /* get control flag */ + if (word == NULL || /* same word we compared to "include" */ + (ctlf = parse_control_flag(word)) == (pam_control_t)-1) { + openpam_log(PAM_LOG_ERROR, + "%s(%d): missing or invalid control flag", + filename, lineno); + goto fail; + } + + /* get module name */ + if ((modulename = wordv[i++]) == NULL || + !valid_module_name(modulename)) { + openpam_log(PAM_LOG_ERROR, + "%s(%d): missing or invalid module name", + filename, lineno); + goto fail; + } + + /* allocate new entry */ + if ((this = calloc(1, sizeof *this)) == NULL) + goto syserr; + this->flag = ctlf; + + /* load module */ + if ((this->module = openpam_load_module(modulename)) == NULL) + goto fail; + + /* + * The remaining items in wordv are the module's + * arguments. We could set this->optv = wordv + i, but + * then free(this->optv) wouldn't work. Instead, we free + * the words we've already consumed, shift the rest up, + * and clear the tail end of the array. + */ + this->optc = wordc - i; + for (i = 0; i < wordc - this->optc; ++i) { + FREE(wordv[i]); + } + for (i = 0; i < this->optc; ++i) { + wordv[i] = wordv[wordc - this->optc + i]; + wordv[wordc - this->optc + i] = NULL; + } + this->optv = wordv; + wordv = NULL; + wordc = 0; + + /* hook it up */ + for (next = &pamh->chains[fclt]; *next != NULL; + next = &(*next)->next) + /* nothing */ ; + *next = this; + this = NULL; + ++count; + } + /* + * The loop ended because openpam_readword() returned NULL, which + * can happen for four different reasons: an I/O error (ferror(f) + * is true), a memory allocation failure (ferror(f) is false, + * errno is non-zero) + */ + if (ferror(f) || errno != 0) + goto syserr; + if (!feof(f)) + goto fail; + fclose(f); + return (count); +syserr: + serrno = errno; + openpam_log(PAM_LOG_ERROR, "%s: %m", filename); + errno = serrno; + /* fall through */ +fail: + serrno = errno; + if (this && this->optc && this->optv) + FREEV(this->optc, this->optv); + FREE(this); + FREEV(wordc, wordv); + FREE(wordv); + FREE(name); + fclose(f); + errno = serrno; + return (-1); +} + +static const char *openpam_policy_path[] = { + "/etc/pam.d/", + "/etc/pam.conf", + "/usr/local/etc/pam.d/", + "/usr/local/etc/pam.conf", + NULL +}; + +/* + * Read the specified chains from the specified file. + * + * Returns 0 if the file exists but does not contain any matching lines. + * + * Returns -1 and sets errno to ENOENT if the file does not exist. + * + * Returns -1 and sets errno to some other non-zero value if the file + * exists but is unsafe or unreadable, or an I/O error occurs. + */ +static int +openpam_load_file(pam_handle_t *pamh, + const char *service, + pam_facility_t facility, + const char *filename, + openpam_style_t style) +{ + FILE *f; + int ret, serrno; + + /* attempt to open the file */ + if ((f = fopen(filename, "r")) == NULL) { + serrno = errno; + openpam_log(errno == ENOENT ? PAM_LOG_DEBUG : PAM_LOG_ERROR, + "%s: %m", filename); + errno = serrno; + RETURNN(-1); + } else { + openpam_log(PAM_LOG_DEBUG, "found %s", filename); + } + + /* verify type, ownership and permissions */ + if (OPENPAM_FEATURE(VERIFY_POLICY_FILE) && + openpam_check_desc_owner_perms(filename, fileno(f)) != 0) { + /* already logged the cause */ + serrno = errno; + fclose(f); + errno = serrno; + RETURNN(-1); + } + + /* parse the file */ + ret = openpam_parse_chain(pamh, service, facility, + f, filename, style); + RETURNN(ret); +} + +/* + * Locates the policy file for a given service and reads the given chains + * from it. + * + * Returns the number of policy entries which were found for the specified + * service and facility, or -1 if a system error occurred or a syntax + * error was encountered. + */ +static int +openpam_load_chain(pam_handle_t *pamh, + const char *service, + pam_facility_t facility) +{ + const char *p, **path; + char filename[PATH_MAX]; + size_t len; + openpam_style_t style; + int ret; + + ENTERS(facility < 0 ? "any" : pam_facility_name[facility]); + + /* either absolute or relative to cwd */ + if (strchr(service, '/') != NULL) { + if ((p = strrchr(service, '.')) != NULL && strcmp(p, ".conf") == 0) + style = pam_conf_style; + else + style = pam_d_style; + ret = openpam_load_file(pamh, service, facility, + service, style); + RETURNN(ret); + } + + /* search standard locations */ + for (path = openpam_policy_path; *path != NULL; ++path) { + /* construct filename */ + len = strlcpy(filename, *path, sizeof filename); + if (filename[len - 1] == '/') { + len = strlcat(filename, service, sizeof filename); + if (len >= sizeof filename) { + errno = ENAMETOOLONG; + RETURNN(-1); + } + style = pam_d_style; + } else { + style = pam_conf_style; + } + ret = openpam_load_file(pamh, service, facility, + filename, style); + /* the file exists, but an error occurred */ + if (ret == -1 && errno != ENOENT) + RETURNN(ret); + /* in pam.d style, an empty file counts as a hit */ + if (ret == 0 && style == pam_d_style) + RETURNN(ret); + } + + /* no hit */ + RETURNN(0); +} + +/* + * OpenPAM internal + * + * Configure a service + */ + +int +openpam_configure(pam_handle_t *pamh, + const char *service) +{ + pam_facility_t fclt; + int serrno; + + ENTERS(service); + if (!valid_service_name(service)) { + openpam_log(PAM_LOG_ERROR, "invalid service name"); + RETURNC(PAM_SYSTEM_ERR); + } + if (openpam_load_chain(pamh, service, PAM_FACILITY_ANY) < 0) + goto load_err; + for (fclt = 0; fclt < PAM_NUM_FACILITIES; ++fclt) { + if (pamh->chains[fclt] != NULL) + continue; + if (openpam_load_chain(pamh, PAM_OTHER, fclt) < 0) + goto load_err; + } + RETURNC(PAM_SUCCESS); +load_err: + serrno = errno; + openpam_clear_chains(pamh->chains); + errno = serrno; + RETURNC(PAM_SYSTEM_ERR); +} + +/* + * NODOC + * + * Error codes: + * PAM_SYSTEM_ERR + */ |