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 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 * $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 61static 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 */ 70static int 71valid_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 */ 95static pam_facility_t 96parse_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 */ 112static pam_control_t 113parse_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 */ 130static int 131valid_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 149typedef 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 */ 158static int 159openpam_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); 317syserr: 318 serrno = errno; 319 openpam_log(PAM_LOG_ERROR, "%s: %m", filename); 320 errno = serrno; 321 /* fall through */ 322fail: 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 */ 345static int 346openpam_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 */ 390static int 391openpam_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 456int 457openpam_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); 502load_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