1 /*- 2 * Copyright (c) 2001-2003 Networks Associates Technology, Inc. 3 * Copyright (c) 2004-2025 Dag-Erling Smørgrav 4 * All rights reserved. 5 * 6 * This software was developed for the FreeBSD Project by ThinkSec AS and 7 * Network Associates Laboratories, the Security Research Division of 8 * Network Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 9 * ("CBOSS"), as part of the DARPA CHATS research program. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 3. The name of the author may not be used to endorse or promote 20 * products derived from this software without specific prior written 21 * permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36 #ifdef HAVE_CONFIG_H 37 # include "config.h" 38 #endif 39 40 #include <sys/param.h> 41 42 #include <errno.h> 43 #include <stdio.h> 44 #include <stdlib.h> 45 #include <string.h> 46 47 #include <security/pam_appl.h> 48 49 #include "openpam_impl.h" 50 #include "openpam_ctype.h" 51 #include "openpam_strlcat.h" 52 #include "openpam_strlcpy.h" 53 54 static int openpam_load_chain(pam_handle_t *, const char *, pam_facility_t); 55 56 /* 57 * Validate a service name. 58 * 59 * Returns a non-zero value if the argument points to a NUL-terminated 60 * string consisting entirely of characters in the POSIX portable filename 61 * character set, excluding the path separator character. 62 */ 63 static int 64 valid_service_name(const char *name) 65 { 66 const char *p; 67 68 if (OPENPAM_FEATURE(RESTRICT_SERVICE_NAME)) { 69 /* path separator not allowed */ 70 for (p = name; *p != '\0'; ++p) 71 if (!is_pfcs(*p)) 72 return (0); 73 } else { 74 /* path separator allowed */ 75 for (p = name; *p != '\0'; ++p) 76 if (!is_pfcs(*p) && *p != '/') 77 return (0); 78 } 79 return (1); 80 } 81 82 /* 83 * Parse the facility name. 84 * 85 * Returns the corresponding pam_facility_t value, or -1 if the argument 86 * is not a valid facility name. 87 */ 88 static pam_facility_t 89 parse_facility_name(const char *name) 90 { 91 int i; 92 93 for (i = 0; i < PAM_NUM_FACILITIES; ++i) 94 if (strcmp(pam_facility_name[i], name) == 0) 95 return (i); 96 return ((pam_facility_t)-1); 97 } 98 99 /* 100 * Parse the control flag. 101 * 102 * Returns the corresponding pam_control_t value, or -1 if the argument is 103 * not a valid control flag name. 104 */ 105 static pam_control_t 106 parse_control_flag(const char *name) 107 { 108 int i; 109 110 for (i = 0; i < PAM_NUM_CONTROL_FLAGS; ++i) 111 if (strcmp(pam_control_flag_name[i], name) == 0) 112 return (i); 113 return ((pam_control_t)-1); 114 } 115 116 /* 117 * Validate a file name. 118 * 119 * Returns a non-zero value if the argument points to a NUL-terminated 120 * string consisting entirely of characters in the POSIX portable filename 121 * character set, including the path separator character. 122 */ 123 static int 124 valid_module_name(const char *name) 125 { 126 const char *p; 127 128 if (OPENPAM_FEATURE(RESTRICT_MODULE_NAME)) { 129 /* path separator not allowed */ 130 for (p = name; *p != '\0'; ++p) 131 if (!is_pfcs(*p)) 132 return (0); 133 } else { 134 /* path separator allowed */ 135 for (p = name; *p != '\0'; ++p) 136 if (!is_pfcs(*p) && *p != '/') 137 return (0); 138 } 139 return (1); 140 } 141 142 typedef enum { pam_conf_style, pam_d_style } openpam_style_t; 143 144 /* 145 * Extracts given chains from a policy file. 146 * 147 * Returns the number of policy entries which were found for the specified 148 * service and facility, or -1 if a system error occurred or a syntax 149 * error was encountered. 150 */ 151 static int 152 openpam_parse_chain(pam_handle_t *pamh, 153 const char *service, 154 pam_facility_t facility, 155 FILE *f, 156 const char *filename, 157 openpam_style_t style) 158 { 159 pam_chain_t *this, **next; 160 pam_module_t *module; 161 pam_facility_t fclt; 162 pam_control_t ctlf; 163 char *name, *servicename, *modulename; 164 int count, lineno, nonfatal, ret, serrno; 165 char **wordv, *word; 166 int i, wordc; 167 168 count = 0; 169 this = NULL; 170 name = NULL; 171 lineno = 0; 172 wordc = 0; 173 wordv = NULL; 174 while ((wordv = openpam_readlinev(f, &lineno, &wordc)) != NULL) { 175 /* blank line? */ 176 if (wordc == 0) { 177 FREEV(wordc, wordv); 178 continue; 179 } 180 i = 0; 181 182 /* check service name if necessary */ 183 if (style == pam_conf_style && 184 strcmp(wordv[i++], service) != 0) { 185 FREEV(wordc, wordv); 186 continue; 187 } 188 189 /* check facility name */ 190 if ((word = wordv[i++]) == NULL) { 191 openpam_log(PAM_LOG_ERROR, 192 "%s(%d): missing facility", 193 filename, lineno); 194 errno = EINVAL; 195 goto fail; 196 } 197 if (*word == '-') { 198 nonfatal = 1; 199 word++; 200 } else { 201 nonfatal = 0; 202 } 203 if ((fclt = parse_facility_name(word)) == (pam_facility_t)-1) { 204 openpam_log(PAM_LOG_ERROR, 205 "%s(%d): invalid facility", 206 filename, lineno); 207 errno = EINVAL; 208 goto fail; 209 } 210 if (facility != fclt && facility != PAM_FACILITY_ANY) { 211 FREEV(wordc, wordv); 212 continue; 213 } 214 215 /* control flag or "include" */ 216 if ((word = wordv[i++]) == NULL) { 217 openpam_log(PAM_LOG_ERROR, 218 "%s(%d): missing control flag", 219 filename, lineno); 220 errno = EINVAL; 221 goto fail; 222 } 223 if (strcmp(word, "include") == 0) { 224 if ((servicename = wordv[i++]) == NULL) { 225 openpam_log(PAM_LOG_ERROR, 226 "%s(%d): missing service name", 227 filename, lineno); 228 errno = EINVAL; 229 goto fail; 230 } 231 if (!valid_service_name(servicename)) { 232 openpam_log(PAM_LOG_ERROR, 233 "%s(%d): invalid service name", 234 filename, lineno); 235 errno = EINVAL; 236 goto fail; 237 } 238 if (wordv[i] != NULL) { 239 openpam_log(PAM_LOG_ERROR, 240 "%s(%d): garbage at end of line", 241 filename, lineno); 242 errno = EINVAL; 243 goto fail; 244 } 245 ret = openpam_load_chain(pamh, servicename, fclt); 246 FREEV(wordc, wordv); 247 if (ret < 0) { 248 /* 249 * Bogus errno, but this ensures that the 250 * outer loop does not just ignore the 251 * error and keep searching. 252 */ 253 if (errno == ENOENT) { 254 if (nonfatal) 255 continue; 256 errno = EINVAL; 257 } 258 goto fail; 259 } 260 continue; 261 } 262 if ((ctlf = parse_control_flag(word)) == (pam_control_t)-1) { 263 openpam_log(PAM_LOG_ERROR, 264 "%s(%d): invalid control flag", 265 filename, lineno); 266 errno = EINVAL; 267 goto fail; 268 } 269 270 /* get module name */ 271 if ((modulename = wordv[i++]) == NULL) { 272 openpam_log(PAM_LOG_ERROR, 273 "%s(%d): missing module name", 274 filename, lineno); 275 errno = EINVAL; 276 goto fail; 277 } 278 if (!valid_module_name(modulename)) { 279 openpam_log(PAM_LOG_ERROR, 280 "%s(%d): invalid module name", 281 filename, lineno); 282 errno = EINVAL; 283 goto fail; 284 } 285 286 /* load module */ 287 if ((module = openpam_load_module(modulename)) == NULL) { 288 if (errno == ENOENT) { 289 if (nonfatal) { 290 FREEV(wordc, wordv); 291 continue; 292 } 293 errno = ENOEXEC; 294 } 295 goto fail; 296 } 297 298 /* allocate new entry */ 299 if ((this = calloc(1, sizeof *this)) == NULL) 300 goto syserr; 301 this->flag = ctlf; 302 this->module = module; 303 304 /* 305 * The remaining items in wordv are the module's 306 * arguments. We could set this->optv = wordv + i, but 307 * then free(this->optv) wouldn't work. Instead, we free 308 * the words we've already consumed, shift the rest up, 309 * and clear the tail end of the array. 310 */ 311 this->optc = wordc - i; 312 for (i = 0; i < wordc - this->optc; ++i) { 313 FREE(wordv[i]); 314 } 315 for (i = 0; i < this->optc; ++i) { 316 wordv[i] = wordv[wordc - this->optc + i]; 317 wordv[wordc - this->optc + i] = NULL; 318 } 319 this->optv = wordv; 320 wordv = NULL; 321 wordc = 0; 322 323 /* hook it up */ 324 for (next = &pamh->chains[fclt]; *next != NULL; 325 next = &(*next)->next) 326 /* nothing */ ; 327 *next = this; 328 this = NULL; 329 ++count; 330 } 331 /* 332 * The loop ended because openpam_readword() returned NULL, which 333 * can happen for four different reasons: an I/O error (ferror(f) 334 * is true), a memory allocation failure (ferror(f) is false, 335 * feof(f) is false, errno is non-zero), the file ended with an 336 * unterminated quote or backslash escape (ferror(f) is false, 337 * feof(f) is true, errno is non-zero), or the end of the file was 338 * reached without error (ferror(f) is false, feof(f) is true, 339 * errno is zero). 340 */ 341 if (ferror(f) || errno != 0) 342 goto syserr; 343 if (!feof(f)) 344 goto fail; 345 fclose(f); 346 return (count); 347 syserr: 348 serrno = errno; 349 openpam_log(PAM_LOG_ERROR, "%s: %m", filename); 350 errno = serrno; 351 /* fall through */ 352 fail: 353 serrno = errno; 354 if (this && this->optc && this->optv) 355 FREEV(this->optc, this->optv); 356 FREE(this); 357 FREEV(wordc, wordv); 358 FREE(wordv); 359 FREE(name); 360 fclose(f); 361 errno = serrno; 362 return (-1); 363 } 364 365 /* 366 * Read the specified chains from the specified file. 367 * 368 * Returns 0 if the file exists but does not contain any matching lines. 369 * 370 * Returns -1 and sets errno to ENOENT if the file does not exist. 371 * 372 * Returns -1 and sets errno to some other non-zero value if the file 373 * exists but is unsafe or unreadable, or an I/O error occurs. 374 */ 375 static int 376 openpam_load_file(pam_handle_t *pamh, 377 const char *service, 378 pam_facility_t facility, 379 const char *filename, 380 openpam_style_t style) 381 { 382 FILE *f; 383 int ret, serrno; 384 385 /* attempt to open the file */ 386 if ((f = fopen(filename, "r")) == NULL) { 387 serrno = errno; 388 openpam_log(errno == ENOENT ? PAM_LOG_DEBUG : PAM_LOG_ERROR, 389 "%s: %m", filename); 390 errno = serrno; 391 RETURNN(-1); 392 } else { 393 openpam_log(PAM_LOG_DEBUG, "found %s", filename); 394 } 395 396 /* verify type, ownership and permissions */ 397 if (OPENPAM_FEATURE(VERIFY_POLICY_FILE) && 398 openpam_check_desc_owner_perms(filename, fileno(f)) != 0) { 399 /* already logged the cause */ 400 serrno = errno; 401 fclose(f); 402 errno = serrno; 403 RETURNN(-1); 404 } 405 406 /* parse the file */ 407 ret = openpam_parse_chain(pamh, service, facility, 408 f, filename, style); 409 RETURNN(ret); 410 } 411 412 /* 413 * Locates the policy file for a given service and reads the given chains 414 * from it. 415 * 416 * Returns the number of policy entries which were found for the specified 417 * service and facility, or -1 if a system error occurred or a syntax 418 * error was encountered. 419 */ 420 static int 421 openpam_load_chain(pam_handle_t *pamh, 422 const char *service, 423 pam_facility_t facility) 424 { 425 const char *p, **path; 426 char filename[PATH_MAX]; 427 size_t len; 428 openpam_style_t style; 429 int ret; 430 431 ENTERS(facility < 0 ? "any" : pam_facility_name[facility]); 432 433 /* either absolute or relative to cwd */ 434 if (strchr(service, '/') != NULL) { 435 if ((p = strrchr(service, '.')) != NULL && strcmp(p, ".conf") == 0) 436 style = pam_conf_style; 437 else 438 style = pam_d_style; 439 ret = openpam_load_file(pamh, service, facility, 440 service, style); 441 RETURNN(ret); 442 } 443 444 /* search standard locations */ 445 for (path = openpam_policy_path; *path != NULL; ++path) { 446 /* construct filename */ 447 len = strlcpy(filename, *path, sizeof filename); 448 if (len >= sizeof filename) { 449 errno = ENAMETOOLONG; 450 RETURNN(-1); 451 } 452 if (filename[len - 1] == '/') { 453 len = strlcat(filename, service, sizeof filename); 454 if (len >= sizeof filename) { 455 errno = ENAMETOOLONG; 456 RETURNN(-1); 457 } 458 style = pam_d_style; 459 } else { 460 style = pam_conf_style; 461 } 462 ret = openpam_load_file(pamh, service, facility, 463 filename, style); 464 /* success */ 465 if (ret > 0) 466 RETURNN(ret); 467 /* the file exists, but an error occurred */ 468 if (ret == -1 && errno != ENOENT) 469 RETURNN(ret); 470 /* in pam.d style, an empty file counts as a hit */ 471 if (ret == 0 && style == pam_d_style) 472 RETURNN(ret); 473 } 474 475 /* no hit */ 476 errno = ENOENT; 477 RETURNN(-1); 478 } 479 480 /* 481 * OpenPAM internal 482 * 483 * Configure a service 484 */ 485 486 int 487 openpam_configure(pam_handle_t *pamh, 488 const char *service) 489 { 490 pam_facility_t fclt; 491 int serrno; 492 493 ENTERS(service); 494 if (!valid_service_name(service)) { 495 openpam_log(PAM_LOG_ERROR, "invalid service name"); 496 RETURNC(PAM_SYSTEM_ERR); 497 } 498 if (openpam_load_chain(pamh, service, PAM_FACILITY_ANY) < 0) { 499 if (errno != ENOENT) 500 goto load_err; 501 } 502 for (fclt = 0; fclt < PAM_NUM_FACILITIES; ++fclt) { 503 if (pamh->chains[fclt] != NULL) 504 continue; 505 if (OPENPAM_FEATURE(FALLBACK_TO_OTHER)) { 506 if (openpam_load_chain(pamh, PAM_OTHER, fclt) < 0) 507 goto load_err; 508 } 509 } 510 RETURNC(PAM_SUCCESS); 511 load_err: 512 serrno = errno; 513 openpam_clear_chains(pamh->chains); 514 errno = serrno; 515 RETURNC(PAM_SYSTEM_ERR); 516 } 517 518 /* 519 * NODOC 520 * 521 * Error codes: 522 * PAM_SYSTEM_ERR 523 */ 524