194670Sdes/*- 2115619Sdes * Copyright (c) 2001-2003 Networks Associates Technology, Inc. 3267014Sdelphij * Copyright (c) 2004-2014 Dag-Erling Sm��rgrav 494670Sdes * All rights reserved. 594670Sdes * 694670Sdes * This software was developed for the FreeBSD Project by ThinkSec AS and 799158Sdes * Network Associates Laboratories, the Security Research Division of 899158Sdes * Network Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 999158Sdes * ("CBOSS"), as part of the DARPA CHATS research program. 1094670Sdes * 1194670Sdes * Redistribution and use in source and binary forms, with or without 1294670Sdes * modification, are permitted provided that the following conditions 1394670Sdes * are met: 1494670Sdes * 1. Redistributions of source code must retain the above copyright 1594670Sdes * notice, this list of conditions and the following disclaimer. 1694670Sdes * 2. Redistributions in binary form must reproduce the above copyright 1794670Sdes * notice, this list of conditions and the following disclaimer in the 1894670Sdes * documentation and/or other materials provided with the distribution. 1994670Sdes * 3. The name of the author may not be used to endorse or promote 2094670Sdes * products derived from this software without specific prior written 2194670Sdes * permission. 2294670Sdes * 2394670Sdes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 2494670Sdes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 2594670Sdes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 2694670Sdes * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 2794670Sdes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 2894670Sdes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2994670Sdes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 3094670Sdes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 3194670Sdes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 3294670Sdes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 3394670Sdes * SUCH DAMAGE. 3494670Sdes * 35271947Sdes * $Id: openpam_configure.c 796 2014-06-03 21:30:08Z des $ 3694670Sdes */ 3794670Sdes 38228690Sdes#ifdef HAVE_CONFIG_H 39228690Sdes# include "config.h" 40228690Sdes#endif 41228690Sdes 42236099Sdes#include <sys/param.h> 43236099Sdes 4494670Sdes#include <errno.h> 4594670Sdes#include <stdio.h> 4694670Sdes#include <stdlib.h> 4794670Sdes#include <string.h> 4894670Sdes 4994670Sdes#include <security/pam_appl.h> 5094670Sdes 5194670Sdes#include "openpam_impl.h" 52236099Sdes#include "openpam_ctype.h" 53236099Sdes#include "openpam_strlcat.h" 54236099Sdes#include "openpam_strlcpy.h" 5594670Sdes 56228690Sdesstatic int openpam_load_chain(pam_handle_t *, const char *, pam_facility_t); 5794670Sdes 58228690Sdes/* 59236099Sdes * Validate a service name. 60228690Sdes * 61236099Sdes * Returns a non-zero value if the argument points to a NUL-terminated 62236099Sdes * string consisting entirely of characters in the POSIX portable filename 63236099Sdes * character set, excluding the path separator character. 64228690Sdes */ 6594670Sdesstatic int 66236099Sdesvalid_service_name(const char *name) 67115619Sdes{ 68236099Sdes const char *p; 69115619Sdes 70236099Sdes if (OPENPAM_FEATURE(RESTRICT_SERVICE_NAME)) { 71236099Sdes /* path separator not allowed */ 72236099Sdes for (p = name; *p != '\0'; ++p) 73236099Sdes if (!is_pfcs(*p)) 74236099Sdes return (0); 75236099Sdes } else { 76236099Sdes /* path separator allowed */ 77236099Sdes for (p = name; *p != '\0'; ++p) 78236099Sdes if (!is_pfcs(*p) && *p != '/') 79236099Sdes return (0); 80228690Sdes } 81236099Sdes return (1); 82115619Sdes} 83115619Sdes 84115619Sdes/* 85228690Sdes * Parse the facility name. 86228690Sdes * 87236099Sdes * Returns the corresponding pam_facility_t value, or -1 if the argument 88236099Sdes * is not a valid facility name. 89115619Sdes */ 90228690Sdesstatic pam_facility_t 91236099Sdesparse_facility_name(const char *name) 92115619Sdes{ 93228690Sdes int i; 94115619Sdes 95228690Sdes for (i = 0; i < PAM_NUM_FACILITIES; ++i) 96236099Sdes if (strcmp(pam_facility_name[i], name) == 0) 97236099Sdes return (i); 98236099Sdes return ((pam_facility_t)-1); 99115619Sdes} 100115619Sdes 101115619Sdes/* 102228690Sdes * Parse the control flag. 103228690Sdes * 104236099Sdes * Returns the corresponding pam_control_t value, or -1 if the argument is 105236099Sdes * not a valid control flag name. 106228690Sdes */ 107228690Sdesstatic pam_control_t 108236099Sdesparse_control_flag(const char *name) 109228690Sdes{ 110228690Sdes int i; 111228690Sdes 112228690Sdes for (i = 0; i < PAM_NUM_CONTROL_FLAGS; ++i) 113236099Sdes if (strcmp(pam_control_flag_name[i], name) == 0) 114236099Sdes return (i); 115236099Sdes return ((pam_control_t)-1); 116228690Sdes} 117228690Sdes 118228690Sdes/* 119236099Sdes * Validate a file name. 120228690Sdes * 121236099Sdes * Returns a non-zero value if the argument points to a NUL-terminated 122236099Sdes * string consisting entirely of characters in the POSIX portable filename 123236099Sdes * character set, including the path separator character. 124228690Sdes */ 125228690Sdesstatic int 126236099Sdesvalid_module_name(const char *name) 127228690Sdes{ 128236099Sdes const char *p; 129228690Sdes 130236099Sdes if (OPENPAM_FEATURE(RESTRICT_MODULE_NAME)) { 131236099Sdes /* path separator not allowed */ 132236099Sdes for (p = name; *p != '\0'; ++p) 133236099Sdes if (!is_pfcs(*p)) 134236099Sdes return (0); 135228690Sdes } else { 136236099Sdes /* path separator allowed */ 137236099Sdes for (p = name; *p != '\0'; ++p) 138236099Sdes if (!is_pfcs(*p) && *p != '/') 139236099Sdes return (0); 140228690Sdes } 141236099Sdes return (1); 142115619Sdes} 143115619Sdes 144115619Sdestypedef enum { pam_conf_style, pam_d_style } openpam_style_t; 145115619Sdes 146115619Sdes/* 147115619Sdes * Extracts given chains from a policy file. 148236099Sdes * 149236099Sdes * Returns the number of policy entries which were found for the specified 150236099Sdes * service and facility, or -1 if a system error occurred or a syntax 151236099Sdes * error was encountered. 152115619Sdes */ 153115619Sdesstatic int 154228690Sdesopenpam_parse_chain(pam_handle_t *pamh, 15594670Sdes const char *service, 156115619Sdes pam_facility_t facility, 157236099Sdes FILE *f, 15894670Sdes const char *filename, 159115619Sdes openpam_style_t style) 16094670Sdes{ 161115619Sdes pam_chain_t *this, **next; 162115619Sdes pam_facility_t fclt; 163115619Sdes pam_control_t ctlf; 164236099Sdes char *name, *servicename, *modulename; 165236099Sdes int count, lineno, ret, serrno; 166236099Sdes char **wordv, *word; 167236099Sdes int i, wordc; 16894670Sdes 169236099Sdes count = 0; 170115619Sdes this = NULL; 171228690Sdes name = NULL; 172228690Sdes lineno = 0; 173236099Sdes wordc = 0; 174236099Sdes wordv = NULL; 175236099Sdes while ((wordv = openpam_readlinev(f, &lineno, &wordc)) != NULL) { 176236099Sdes /* blank line? */ 177236099Sdes if (wordc == 0) { 178236099Sdes FREEV(wordc, wordv); 179236099Sdes continue; 180115619Sdes } 181236099Sdes i = 0; 18294670Sdes 183236099Sdes /* check service name if necessary */ 184236099Sdes if (style == pam_conf_style && 185236099Sdes strcmp(wordv[i++], service) != 0) { 186236099Sdes FREEV(wordc, wordv); 187236099Sdes continue; 188236099Sdes } 189236099Sdes 190236099Sdes /* check facility name */ 191236099Sdes if ((word = wordv[i++]) == NULL || 192236099Sdes (fclt = parse_facility_name(word)) == (pam_facility_t)-1) { 193228690Sdes openpam_log(PAM_LOG_ERROR, 194228690Sdes "%s(%d): missing or invalid facility", 195228690Sdes filename, lineno); 196267014Sdelphij errno = EINVAL; 197115619Sdes goto fail; 198115619Sdes } 199115619Sdes if (facility != fclt && facility != PAM_FACILITY_ANY) { 200236099Sdes FREEV(wordc, wordv); 20194670Sdes continue; 20294670Sdes } 20394670Sdes 204228690Sdes /* check for "include" */ 205236099Sdes if ((word = wordv[i++]) != NULL && 206236099Sdes strcmp(word, "include") == 0) { 207236099Sdes if ((servicename = wordv[i++]) == NULL || 208236099Sdes !valid_service_name(servicename)) { 209228690Sdes openpam_log(PAM_LOG_ERROR, 210236099Sdes "%s(%d): missing or invalid service name", 211115619Sdes filename, lineno); 212267014Sdelphij errno = EINVAL; 213228690Sdes goto fail; 214228690Sdes } 215236099Sdes if (wordv[i] != NULL) { 216228690Sdes openpam_log(PAM_LOG_ERROR, 217228690Sdes "%s(%d): garbage at end of line", 218228690Sdes filename, lineno); 219267014Sdelphij errno = EINVAL; 220228690Sdes goto fail; 221228690Sdes } 222236099Sdes ret = openpam_load_chain(pamh, servicename, fclt); 223236099Sdes FREEV(wordc, wordv); 224267014Sdelphij if (ret < 0) { 225267014Sdelphij /* 226267014Sdelphij * Bogus errno, but this ensures that the 227267014Sdelphij * outer loop does not just ignore the 228267014Sdelphij * error and keep searching. 229267014Sdelphij */ 230267014Sdelphij if (errno == ENOENT) 231267014Sdelphij errno = EINVAL; 232115619Sdes goto fail; 233267014Sdelphij } 23494670Sdes continue; 23594670Sdes } 23694670Sdes 237228690Sdes /* get control flag */ 238236099Sdes if (word == NULL || /* same word we compared to "include" */ 239236099Sdes (ctlf = parse_control_flag(word)) == (pam_control_t)-1) { 240228690Sdes openpam_log(PAM_LOG_ERROR, 241228690Sdes "%s(%d): missing or invalid control flag", 242228690Sdes filename, lineno); 243267014Sdelphij errno = EINVAL; 244228690Sdes goto fail; 245228690Sdes } 24694670Sdes 247228690Sdes /* get module name */ 248236099Sdes if ((modulename = wordv[i++]) == NULL || 249236099Sdes !valid_module_name(modulename)) { 25094670Sdes openpam_log(PAM_LOG_ERROR, 251228690Sdes "%s(%d): missing or invalid module name", 252228690Sdes filename, lineno); 253267014Sdelphij errno = EINVAL; 254115619Sdes goto fail; 25594670Sdes } 256228690Sdes 257228690Sdes /* allocate new entry */ 258228690Sdes if ((this = calloc(1, sizeof *this)) == NULL) 259228690Sdes goto syserr; 260115619Sdes this->flag = ctlf; 26194670Sdes 262236099Sdes /* load module */ 263267014Sdelphij if ((this->module = openpam_load_module(modulename)) == NULL) { 264267014Sdelphij if (errno == ENOENT) 265267014Sdelphij errno = ENOEXEC; 266115619Sdes goto fail; 267267014Sdelphij } 268236099Sdes 269236099Sdes /* 270236099Sdes * The remaining items in wordv are the module's 271236099Sdes * arguments. We could set this->optv = wordv + i, but 272236099Sdes * then free(this->optv) wouldn't work. Instead, we free 273236099Sdes * the words we've already consumed, shift the rest up, 274236099Sdes * and clear the tail end of the array. 275236099Sdes */ 276236099Sdes this->optc = wordc - i; 277236099Sdes for (i = 0; i < wordc - this->optc; ++i) { 278236099Sdes FREE(wordv[i]); 279236124Sdes } 280236124Sdes for (i = 0; i < this->optc; ++i) { 281236099Sdes wordv[i] = wordv[wordc - this->optc + i]; 282236099Sdes wordv[wordc - this->optc + i] = NULL; 28394670Sdes } 284236099Sdes this->optv = wordv; 285236099Sdes wordv = NULL; 286236099Sdes wordc = 0; 287228690Sdes 288115619Sdes /* hook it up */ 289115619Sdes for (next = &pamh->chains[fclt]; *next != NULL; 290115619Sdes next = &(*next)->next) 291115619Sdes /* nothing */ ; 292115619Sdes *next = this; 293115619Sdes this = NULL; 294236099Sdes ++count; 29594670Sdes } 296236099Sdes /* 297236099Sdes * The loop ended because openpam_readword() returned NULL, which 298236099Sdes * can happen for four different reasons: an I/O error (ferror(f) 299236099Sdes * is true), a memory allocation failure (ferror(f) is false, 300267014Sdelphij * feof(f) is false, errno is non-zero), the file ended with an 301267014Sdelphij * unterminated quote or backslash escape (ferror(f) is false, 302267014Sdelphij * feof(f) is true, errno is non-zero), or the end of the file was 303267014Sdelphij * reached without error (ferror(f) is false, feof(f) is true, 304267014Sdelphij * errno is zero). 305236099Sdes */ 306236099Sdes if (ferror(f) || errno != 0) 307236099Sdes goto syserr; 308115619Sdes if (!feof(f)) 309236099Sdes goto fail; 31094670Sdes fclose(f); 311236099Sdes return (count); 312228690Sdessyserr: 313236099Sdes serrno = errno; 314115619Sdes openpam_log(PAM_LOG_ERROR, "%s: %m", filename); 315236099Sdes errno = serrno; 316236099Sdes /* fall through */ 317228690Sdesfail: 318236099Sdes serrno = errno; 319236099Sdes if (this && this->optc && this->optv) 320236099Sdes FREEV(this->optc, this->optv); 321115619Sdes FREE(this); 322236099Sdes FREEV(wordc, wordv); 323236099Sdes FREE(wordv); 324228690Sdes FREE(name); 325115619Sdes fclose(f); 326236099Sdes errno = serrno; 327236099Sdes return (-1); 32894670Sdes} 32994670Sdes 330115619Sdes/* 331236099Sdes * Read the specified chains from the specified file. 332236099Sdes * 333236099Sdes * Returns 0 if the file exists but does not contain any matching lines. 334236099Sdes * 335236099Sdes * Returns -1 and sets errno to ENOENT if the file does not exist. 336236099Sdes * 337236099Sdes * Returns -1 and sets errno to some other non-zero value if the file 338236099Sdes * exists but is unsafe or unreadable, or an I/O error occurs. 339236099Sdes */ 340236099Sdesstatic int 341236099Sdesopenpam_load_file(pam_handle_t *pamh, 342236099Sdes const char *service, 343236099Sdes pam_facility_t facility, 344236099Sdes const char *filename, 345236099Sdes openpam_style_t style) 346236099Sdes{ 347236099Sdes FILE *f; 348236099Sdes int ret, serrno; 349236099Sdes 350236099Sdes /* attempt to open the file */ 351236099Sdes if ((f = fopen(filename, "r")) == NULL) { 352236099Sdes serrno = errno; 353236099Sdes openpam_log(errno == ENOENT ? PAM_LOG_DEBUG : PAM_LOG_ERROR, 354236099Sdes "%s: %m", filename); 355236099Sdes errno = serrno; 356236099Sdes RETURNN(-1); 357236099Sdes } else { 358236099Sdes openpam_log(PAM_LOG_DEBUG, "found %s", filename); 359236099Sdes } 360236099Sdes 361236099Sdes /* verify type, ownership and permissions */ 362236099Sdes if (OPENPAM_FEATURE(VERIFY_POLICY_FILE) && 363236099Sdes openpam_check_desc_owner_perms(filename, fileno(f)) != 0) { 364236099Sdes /* already logged the cause */ 365236099Sdes serrno = errno; 366236099Sdes fclose(f); 367236099Sdes errno = serrno; 368236099Sdes RETURNN(-1); 369236099Sdes } 370236099Sdes 371236099Sdes /* parse the file */ 372236099Sdes ret = openpam_parse_chain(pamh, service, facility, 373236099Sdes f, filename, style); 374236099Sdes RETURNN(ret); 375236099Sdes} 376236099Sdes 377236099Sdes/* 378115619Sdes * Locates the policy file for a given service and reads the given chains 379115619Sdes * from it. 380236099Sdes * 381236099Sdes * Returns the number of policy entries which were found for the specified 382236099Sdes * service and facility, or -1 if a system error occurred or a syntax 383236099Sdes * error was encountered. 384115619Sdes */ 38595908Sdesstatic int 386115619Sdesopenpam_load_chain(pam_handle_t *pamh, 387115619Sdes const char *service, 388115619Sdes pam_facility_t facility) 38994670Sdes{ 390236099Sdes const char *p, **path; 391236099Sdes char filename[PATH_MAX]; 39294670Sdes size_t len; 393236099Sdes openpam_style_t style; 394228690Sdes int ret; 39594670Sdes 396236099Sdes ENTERS(facility < 0 ? "any" : pam_facility_name[facility]); 397236099Sdes 398236099Sdes /* either absolute or relative to cwd */ 399236099Sdes if (strchr(service, '/') != NULL) { 400236099Sdes if ((p = strrchr(service, '.')) != NULL && strcmp(p, ".conf") == 0) 401236099Sdes style = pam_conf_style; 402236099Sdes else 403236099Sdes style = pam_d_style; 404236099Sdes ret = openpam_load_file(pamh, service, facility, 405236099Sdes service, style); 406236099Sdes RETURNN(ret); 407236099Sdes } 408236099Sdes 409236099Sdes /* search standard locations */ 41094670Sdes for (path = openpam_policy_path; *path != NULL; ++path) { 411236099Sdes /* construct filename */ 412236099Sdes len = strlcpy(filename, *path, sizeof filename); 413236099Sdes if (filename[len - 1] == '/') { 414236099Sdes len = strlcat(filename, service, sizeof filename); 415236099Sdes if (len >= sizeof filename) { 416236099Sdes errno = ENAMETOOLONG; 417236099Sdes RETURNN(-1); 41894670Sdes } 419236099Sdes style = pam_d_style; 42094670Sdes } else { 421236099Sdes style = pam_conf_style; 42294670Sdes } 423236099Sdes ret = openpam_load_file(pamh, service, facility, 424236099Sdes filename, style); 425267014Sdelphij /* success */ 426267014Sdelphij if (ret > 0) 427267014Sdelphij RETURNN(ret); 428236099Sdes /* the file exists, but an error occurred */ 429236099Sdes if (ret == -1 && errno != ENOENT) 430236099Sdes RETURNN(ret); 431236099Sdes /* in pam.d style, an empty file counts as a hit */ 432236099Sdes if (ret == 0 && style == pam_d_style) 433236099Sdes RETURNN(ret); 43494670Sdes } 435236099Sdes 436236099Sdes /* no hit */ 437267014Sdelphij errno = ENOENT; 438267014Sdelphij RETURNN(-1); 43994670Sdes} 44094670Sdes 44194670Sdes/* 44295908Sdes * OpenPAM internal 44395908Sdes * 44495908Sdes * Configure a service 44595908Sdes */ 44695908Sdes 44795908Sdesint 44895908Sdesopenpam_configure(pam_handle_t *pamh, 44995908Sdes const char *service) 45095908Sdes{ 451115619Sdes pam_facility_t fclt; 452236099Sdes int serrno; 45395908Sdes 454236099Sdes ENTERS(service); 455236099Sdes if (!valid_service_name(service)) { 456236099Sdes openpam_log(PAM_LOG_ERROR, "invalid service name"); 457236099Sdes RETURNC(PAM_SYSTEM_ERR); 458236099Sdes } 459267014Sdelphij if (openpam_load_chain(pamh, service, PAM_FACILITY_ANY) < 0) { 460267014Sdelphij if (errno != ENOENT) 461267014Sdelphij goto load_err; 462267014Sdelphij } 463115619Sdes for (fclt = 0; fclt < PAM_NUM_FACILITIES; ++fclt) { 464115619Sdes if (pamh->chains[fclt] != NULL) 465115619Sdes continue; 466236099Sdes if (openpam_load_chain(pamh, PAM_OTHER, fclt) < 0) 467115619Sdes goto load_err; 46895908Sdes } 469236099Sdes RETURNC(PAM_SUCCESS); 470228690Sdesload_err: 471236099Sdes serrno = errno; 472115619Sdes openpam_clear_chains(pamh->chains); 473236099Sdes errno = serrno; 474236099Sdes RETURNC(PAM_SYSTEM_ERR); 47595908Sdes} 47695908Sdes 47795908Sdes/* 47894670Sdes * NODOC 47994670Sdes * 48094670Sdes * Error codes: 48194670Sdes * PAM_SYSTEM_ERR 48294670Sdes */ 483