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