1/* $NetBSD: openpam_configure.c,v 1.4.16.1 2014/06/18 02:15:27 msaitoh Exp $ */ 2 3/*- 4 * Copyright (c) 2001-2003 Networks Associates Technology, Inc. 5 * Copyright (c) 2004-2014 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 * Id: openpam_configure.c 500 2011-11-22 12:07:03Z des 38 */ 39 40#ifdef HAVE_CONFIG_H 41# include "config.h" 42#endif 43 44#include <ctype.h> 45#include <errno.h> 46#include <stdio.h> 47#include <stdlib.h> 48#include <string.h> 49 50#include <security/pam_appl.h> 51 52#include "openpam_impl.h" 53#include "openpam_strlcmp.h" 54 55static int openpam_load_chain(pam_handle_t *, const char *, pam_facility_t); 56 57/* 58 * Evaluates to non-zero if the argument is a linear whitespace character. 59 */ 60#define is_lws(ch) \ 61 (ch == ' ' || ch == '\t') 62 63/* 64 * Evaluates to non-zero if the argument is a printable ASCII character. 65 * Assumes that the execution character set is a superset of ASCII. 66 */ 67#define is_p(ch) \ 68 (ch >= '!' && ch <= '~') 69 70/* 71 * Returns non-zero if the argument belongs to the POSIX Portable Filename 72 * Character Set. Assumes that the execution character set is a superset 73 * of ASCII. 74 */ 75#define is_pfcs(ch) \ 76 ((ch >= '0' && ch <= '9') || \ 77 (ch >= 'A' && ch <= 'Z') || \ 78 (ch >= 'a' && ch <= 'z') || \ 79 ch == '.' || ch == '_' || ch == '-') 80 81/* 82 * Parse the service name. 83 * 84 * Returns the length of the service name, or 0 if the end of the string 85 * was reached or a disallowed non-whitespace character was encountered. 86 * 87 * If parse_service_name() is successful, it updates *service to point to 88 * the first character of the service name and *line to point one 89 * character past the end. If it reaches the end of the string, it 90 * updates *line to point to the terminating NUL character and leaves 91 * *service unmodified. In all other cases, it leaves both *line and 92 * *service unmodified. 93 * 94 * Allowed characters are all characters in the POSIX portable filename 95 * character set. 96 */ 97static size_t 98parse_service_name(char **line, char **service) 99{ 100 char *b, *e; 101 102 for (b = *line; *b && is_lws(*b); ++b) 103 /* nothing */ ; 104 if (!*b) { 105 *line = b; 106 return (0); 107 } 108 for (e = b; *e && !is_lws(*e); ++e) 109 if (!is_pfcs(*e)) 110 return (0); 111 if (e == b) 112 return (0); 113 *line = e; 114 *service = b; 115 return (e - b); 116} 117 118/* 119 * Parse the facility name. 120 * 121 * Returns the corresponding pam_facility_t value, or -1 if the end of the 122 * string was reached, a disallowed non-whitespace character was 123 * encountered, or the first word was not a recognized facility name. 124 * 125 * If parse_facility_name() is successful, it updates *line to point one 126 * character past the end of the facility name. If it reaches the end of 127 * the string, it updates *line to point to the terminating NUL character. 128 * In all other cases, it leaves *line unmodified. 129 */ 130static pam_facility_t 131parse_facility_name(char **line) 132{ 133 char *b, *e; 134 int i; 135 136 for (b = *line; *b && is_lws(*b); ++b) 137 /* nothing */ ; 138 if (!*b) { 139 *line = b; 140 return ((pam_facility_t)-1); 141 } 142 for (e = b; *e && !is_lws(*e); ++e) 143 /* nothing */ ; 144 if (e == b) 145 return ((pam_facility_t)-1); 146 for (i = 0; i < PAM_NUM_FACILITIES; ++i) 147 if (strlcmp(pam_facility_name[i], b, (size_t)(e - b)) == 0) 148 break; 149 if (i == PAM_NUM_FACILITIES) 150 return ((pam_facility_t)-1); 151 *line = e; 152 return (i); 153} 154 155/* 156 * Parse the word "include". 157 * 158 * If the next word on the line is "include", parse_include() updates 159 * *line to point one character past "include" and returns 1. Otherwise, 160 * it leaves *line unmodified and returns 0. 161 */ 162static int 163parse_include(char **line) 164{ 165 char *b, *e; 166 167 for (b = *line; *b && is_lws(*b); ++b) 168 /* nothing */ ; 169 if (!*b) { 170 *line = b; 171 return (-1); 172 } 173 for (e = b; *e && !is_lws(*e); ++e) 174 /* nothing */ ; 175 if (e == b) 176 return (0); 177 if (strlcmp("include", b, (size_t)(e - b)) != 0) 178 return (0); 179 *line = e; 180 return (1); 181} 182 183/* 184 * Parse the control flag. 185 * 186 * Returns the corresponding pam_control_t value, or -1 if the end of the 187 * string was reached, a disallowed non-whitespace character was 188 * encountered, or the first word was not a recognized control flag. 189 * 190 * If parse_control_flag() is successful, it updates *line to point one 191 * character past the end of the control flag. If it reaches the end of 192 * the string, it updates *line to point to the terminating NUL character. 193 * In all other cases, it leaves *line unmodified. 194 */ 195static pam_control_t 196parse_control_flag(char **line) 197{ 198 char *b, *e; 199 int i; 200 201 for (b = *line; *b && is_lws(*b); ++b) 202 /* nothing */ ; 203 if (!*b) { 204 *line = b; 205 return ((pam_control_t)-1); 206 } 207 for (e = b; *e && !is_lws(*e); ++e) 208 /* nothing */ ; 209 if (e == b) 210 return ((pam_control_t)-1); 211 for (i = 0; i < PAM_NUM_CONTROL_FLAGS; ++i) 212 if (strlcmp(pam_control_flag_name[i], b, (size_t)(e - b)) == 0) 213 break; 214 if (i == PAM_NUM_CONTROL_FLAGS) 215 return ((pam_control_t)-1); 216 *line = e; 217 return (i); 218} 219 220/* 221 * Parse a file name. 222 * 223 * Returns the length of the file name, or 0 if the end of the string was 224 * reached or a disallowed non-whitespace character was encountered. 225 * 226 * If parse_filename() is successful, it updates *filename to point to the 227 * first character of the filename and *line to point one character past 228 * the end. If it reaches the end of the string, it updates *line to 229 * point to the terminating NUL character and leaves *filename unmodified. 230 * In all other cases, it leaves both *line and *filename unmodified. 231 * 232 * Allowed characters are all characters in the POSIX portable filename 233 * character set, plus the path separator (forward slash). 234 */ 235static size_t 236parse_filename(char **line, char **filename) 237{ 238 char *b, *e; 239 240 for (b = *line; *b && is_lws(*b); ++b) 241 /* nothing */ ; 242 if (!*b) { 243 *line = b; 244 return (0); 245 } 246 for (e = b; *e && !is_lws(*e); ++e) 247 if (!is_pfcs(*e) && *e != '/') 248 return (0); 249 if (e == b) 250 return (0); 251 *line = e; 252 *filename = b; 253 return (e - b); 254} 255 256/* 257 * Parse an option. 258 * 259 * Returns a dynamically allocated string containing the next module 260 * option, or NULL if the end of the string was reached or a disallowed 261 * non-whitespace character was encountered. 262 * 263 * If parse_option() is successful, it updates *line to point one 264 * character past the end of the option. If it reaches the end of the 265 * string, it updates *line to point to the terminating NUL character. In 266 * all other cases, it leaves *line unmodified. 267 * 268 * If parse_option() fails to allocate memory, it will return NULL and set 269 * errno to a non-zero value. 270 * 271 * Allowed characters for option names are all characters in the POSIX 272 * portable filename character set. Allowed characters for option values 273 * are any printable non-whitespace characters. The option value may be 274 * quoted in either single or double quotes, in which case space 275 * characters and whichever quote character was not used are allowed. 276 * Note that the entire value must be quoted, not just part of it. 277 */ 278static char * 279parse_option(char **line) 280{ 281 char *nb, *ne, *vb, *ve; 282 unsigned char q = 0; 283 char *option; 284 size_t size; 285 286 errno = 0; 287 for (nb = *line; *nb && is_lws(*nb); ++nb) 288 /* nothing */ ; 289 if (!*nb) { 290 *line = nb; 291 return (NULL); 292 } 293 for (ne = nb; *ne && !is_lws(*ne) && *ne != '='; ++ne) 294 if (!is_pfcs(*ne)) 295 return (NULL); 296 if (ne == nb) 297 return (NULL); 298 if (*ne == '=') { 299 vb = ne + 1; 300 if (*vb == '"' || *vb == '\'') 301 q = *vb++; 302 for (ve = vb; 303 *ve && *ve != q && (is_p(*ve) || (q && is_lws(*ve))); 304 ++ve) 305 /* nothing */ ; 306 if (q && *ve != q) 307 /* non-printable character or missing endquote */ 308 return (NULL); 309 if (q && *(ve + 1) && !is_lws(*(ve + 1))) 310 /* garbage after value */ 311 return (NULL); 312 } else { 313 vb = ve = ne; 314 } 315 size = (ne - nb) + 1; 316 if (ve > vb) 317 size += (ve - vb) + 1; 318 if ((option = malloc(size)) == NULL) 319 return (NULL); 320 strncpy(option, nb, (size_t)(ne - nb)); 321 if (ve > vb) { 322 option[ne - nb] = '='; 323 strncpy(option + (ne - nb) + 1, vb, (size_t)(ve - vb)); 324 } 325 option[size - 1] = '\0'; 326 *line = q ? ve + 1 : ve; 327 return (option); 328} 329 330/* 331 * Consume trailing whitespace. 332 * 333 * If there are no non-whitespace characters left on the line, parse_eol() 334 * updates *line to point at the terminating NUL character and returns 0. 335 * Otherwise, it leaves *line unmodified and returns a non-zero value. 336 */ 337static int 338parse_eol(char **line) 339{ 340 char *p; 341 342 for (p = *line; *p && is_lws(*p); ++p) 343 /* nothing */ ; 344 if (*p) 345 return ((unsigned char)*p); 346 *line = p; 347 return (0); 348} 349 350typedef enum { pam_conf_style, pam_d_style } openpam_style_t; 351 352/* 353 * Extracts given chains from a policy file. 354 */ 355static int 356openpam_parse_chain(pam_handle_t *pamh, 357 const char *service, 358 pam_facility_t facility, 359 const char *filename, 360 openpam_style_t style) 361{ 362 pam_chain_t *this, **next; 363 pam_facility_t fclt; 364 pam_control_t ctlf; 365 char *line, *str, *name; 366 char *option, **optv; 367 size_t len; 368 int lineno, ret, serrno; 369 FILE *f; 370 371 if (errno == ENOENT) 372 errno = 0; 373 374 if ((f = fopen(filename, "r")) == NULL) { 375 serrno = errno; 376 openpam_log(errno == ENOENT ? PAM_LOG_DEBUG : PAM_LOG_NOTICE, 377 "%s: %s", filename, strerror(errno)); 378 errno = serrno; 379 return (PAM_SUCCESS); 380 } 381 if (openpam_check_desc_owner_perms(filename, fileno(f)) != 0) { 382 fclose(f); 383 return (PAM_SYSTEM_ERR); 384 } 385 this = NULL; 386 name = NULL; 387 lineno = 0; 388 serrno = 0; 389 while ((line = openpam_readline(f, &lineno, NULL)) != NULL) { 390 /* get service name if necessary */ 391 if (style == pam_conf_style) { 392 if ((len = parse_service_name(&line, &str)) == 0) { 393 openpam_log(PAM_LOG_NOTICE, 394 "%s(%d): invalid service name (ignored)", 395 filename, lineno); 396 FREE(line); 397 continue; 398 } 399 if (strlcmp(service, str, len) != 0) { 400 FREE(line); 401 continue; 402 } 403 } 404 405 /* get facility name */ 406 if ((fclt = parse_facility_name(&line)) == (pam_facility_t)-1) { 407 openpam_log(PAM_LOG_ERROR, 408 "%s(%d): missing or invalid facility", 409 filename, lineno); 410 goto fail; 411 } 412 if (facility != fclt && facility != PAM_FACILITY_ANY) { 413 FREE(line); 414 continue; 415 } 416 417 /* check for "include" */ 418 if (parse_include(&line)) { 419 if ((len = parse_service_name(&line, &str)) == 0) { 420 openpam_log(PAM_LOG_ERROR, 421 "%s(%d): missing or invalid filename", 422 filename, lineno); 423 goto fail; 424 } 425 if ((name = strndup(str, len)) == NULL) 426 goto syserr; 427 if (parse_eol(&line) != 0) { 428 openpam_log(PAM_LOG_ERROR, 429 "%s(%d): garbage at end of line", 430 filename, lineno); 431 goto fail; 432 } 433 ret = openpam_load_chain(pamh, name, fclt); 434 if (ret != PAM_SUCCESS || errno == ENOENT) { 435 serrno = errno; 436 openpam_log(PAM_LOG_ERROR, "failed loading " 437 "include for service %s in %s: %s", 438 name, filename, strerror(errno)); 439 goto fail; 440 } 441 FREE(name); 442 FREE(line); 443 continue; 444 } 445 446 /* get control flag */ 447 if ((ctlf = parse_control_flag(&line)) == (pam_control_t)-1) { 448 openpam_log(PAM_LOG_ERROR, 449 "%s(%d): missing or invalid control flag", 450 filename, lineno); 451 goto fail; 452 } 453 454 /* get module name */ 455 if ((len = parse_filename(&line, &str)) == 0) { 456 openpam_log(PAM_LOG_ERROR, 457 "%s(%d): missing or invalid module name", 458 filename, lineno); 459 goto fail; 460 } 461 if ((name = strndup(str, len)) == NULL) 462 goto syserr; 463 464 /* allocate new entry */ 465 if ((this = calloc((size_t)1, sizeof *this)) == NULL) 466 goto syserr; 467 this->flag = ctlf; 468 469 /* get module options */ 470 if ((this->optv = malloc(sizeof *optv)) == NULL) 471 goto syserr; 472 this->optc = 0; 473 while ((option = parse_option(&line)) != NULL) { 474 optv = realloc(this->optv, 475 (this->optc + 2) * sizeof *optv); 476 if (optv == NULL) 477 goto syserr; 478 this->optv = optv; 479 this->optv[this->optc++] = option; 480 } 481 this->optv[this->optc] = NULL; 482 if (*line != '\0') { 483 openpam_log(PAM_LOG_ERROR, 484 "%s(%d): syntax error in module options", 485 filename, lineno); 486 goto fail; 487 } 488 489 /* load module */ 490 this->module = openpam_load_module(name); 491 if (this->module == NULL) { 492 serrno = (errno == ENOENT ? ENOEXEC : errno); 493 goto fail; 494 } 495 FREE(name); 496 497 /* hook it up */ 498 for (next = &pamh->chains[fclt]; *next != NULL; 499 next = &(*next)->next) 500 /* nothing */ ; 501 *next = this; 502 this = NULL; 503 504 /* next please... */ 505 FREE(line); 506 } 507 if (!feof(f)) 508 goto syserr; 509 fclose(f); 510 return (PAM_SUCCESS); 511syserr: 512 serrno = errno; 513 openpam_log(PAM_LOG_ERROR, "%s: %s", filename, strerror(errno)); 514fail: 515 if (this && this->optc) { 516 while (this->optc--) 517 FREE(this->optv[this->optc]); 518 FREE(this->optv); 519 } 520 FREE(this); 521 FREE(line); 522 FREE(name); 523 fclose(f); 524 if (serrno == 0) 525 errno = EINVAL; 526 else 527 errno = serrno; 528 return (PAM_SYSTEM_ERR); 529} 530 531static const char *openpam_policy_path[] = { 532 "/etc/pam.d/", 533 "/etc/pam.conf", 534#ifndef __NetBSD__ 535 "/usr/local/etc/pam.d/", 536 "/usr/local/etc/pam.conf", 537#else 538 /* Possibly /usr/pkg? */ 539#endif 540 NULL 541}; 542 543/* 544 * Locates the policy file for a given service and reads the given chains 545 * from it. 546 */ 547static int 548openpam_load_chain(pam_handle_t *pamh, 549 const char *service, 550 pam_facility_t facility) 551{ 552 const char **path; 553 char *filename; 554 size_t len; 555 int ret, serrno; 556 557 /* don't allow to escape from policy_path */ 558 if (strchr(service, '/')) { 559 openpam_log(PAM_LOG_ERROR, "illegal service \"%s\"", service); 560 return (-PAM_SYSTEM_ERR); 561 } 562 563 ret = PAM_SYSTEM_ERR; /* shut up compiler stupidity */ 564 for (path = openpam_policy_path; *path != NULL; ++path) { 565 len = strlen(*path); 566 if ((*path)[len - 1] == '/') { 567 if (asprintf(&filename, "%s%s", *path, service) < 0) { 568 openpam_log(PAM_LOG_ERROR, "asprintf(): %s", 569 strerror(errno)); 570 return (PAM_BUF_ERR); 571 } 572 ret = openpam_parse_chain(pamh, service, facility, 573 filename, pam_d_style); 574 serrno = errno; 575 FREE(filename); 576 errno = serrno; 577 } else { 578 ret = openpam_parse_chain(pamh, service, facility, 579 *path, pam_conf_style); 580 } 581 582 /* If /etc/pam.d/ exists, /etc/pam.conf will be ignored */ 583 if (ret == PAM_SUCCESS && errno != ENOENT) 584 return (PAM_SUCCESS); 585 586 /* If we had a definitive error, bail out immediately */ 587 if (ret != PAM_SUCCESS) 588 return (ret); 589 } 590 return (PAM_SUCCESS); 591} 592 593/* 594 * OpenPAM internal 595 * 596 * Configure a service 597 */ 598 599int 600openpam_configure(pam_handle_t *pamh, 601 const char *service) 602{ 603 pam_facility_t fclt; 604 const char *p; 605 606 for (p = service; *p; ++p) 607 if (!is_pfcs(*p)) 608 return (PAM_SYSTEM_ERR); 609 610 if (openpam_load_chain(pamh, service, PAM_FACILITY_ANY) != PAM_SUCCESS) 611 goto load_err; 612 613 for (fclt = 0; fclt < PAM_NUM_FACILITIES; ++fclt) { 614 if (pamh->chains[fclt] != NULL) 615 continue; 616 if (openpam_load_chain(pamh, PAM_OTHER, fclt) != PAM_SUCCESS) 617 goto load_err; 618 } 619#ifdef __NetBSD__ 620 /* 621 * On NetBSD we require the AUTH chain to have a binding 622 * or a required module. 623 */ 624 { 625 pam_chain_t *this = pamh->chains[PAM_AUTH]; 626 for (; this != NULL; this = this->next) 627 if (this->flag == PAM_BINDING || 628 this->flag == PAM_REQUIRED) 629 break; 630 if (this == NULL) { 631 openpam_log(PAM_LOG_ERROR, 632 "No required or binding component " 633 "in service %s, facility %s", 634 service, pam_facility_name[PAM_AUTH]); 635 goto load_err; 636 } 637 } 638#endif 639 return (PAM_SUCCESS); 640load_err: 641 openpam_clear_chains(pamh->chains); 642 return (PAM_SYSTEM_ERR); 643} 644 645/* 646 * NODOC 647 * 648 * Error codes: 649 * PAM_SYSTEM_ERR 650 */ 651