1 /* 2 * Parse PAM options into a struct. 3 * 4 * Given a struct in which to store options and a specification for what 5 * options go where, parse both the PAM configuration options and any options 6 * from a Kerberos krb5.conf file and fill out the struct. 7 * 8 * The canonical version of this file is maintained in the rra-c-util package, 9 * which can be found at <https://www.eyrie.org/~eagle/software/rra-c-util/>. 10 * 11 * Written by Russ Allbery <eagle@eyrie.org> 12 * Copyright 2020 Russ Allbery <eagle@eyrie.org> 13 * Copyright 2006-2008, 2010-2011, 2013-2014 14 * The Board of Trustees of the Leland Stanford Junior University 15 * 16 * Permission is hereby granted, free of charge, to any person obtaining a 17 * copy of this software and associated documentation files (the "Software"), 18 * to deal in the Software without restriction, including without limitation 19 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 20 * and/or sell copies of the Software, and to permit persons to whom the 21 * Software is furnished to do so, subject to the following conditions: 22 * 23 * The above copyright notice and this permission notice shall be included in 24 * all copies or substantial portions of the Software. 25 * 26 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 29 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 31 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 32 * DEALINGS IN THE SOFTWARE. 33 * 34 * SPDX-License-Identifier: MIT 35 */ 36 37 #include <config.h> 38 #ifdef HAVE_KRB5 39 # include <portable/krb5.h> 40 #endif 41 #include <portable/system.h> 42 43 #include <errno.h> 44 45 #include <pam-util/args.h> 46 #include <pam-util/logging.h> 47 #include <pam-util/options.h> 48 #include <pam-util/vector.h> 49 50 /* Used for unused parameters to silence gcc warnings. */ 51 #define UNUSED __attribute__((__unused__)) 52 53 /* 54 * Macros used to resolve a void * pointer to the configuration struct and an 55 * offset into a pointer to the appropriate type. Scary violations of the C 56 * type system lurk here. 57 */ 58 /* clang-format off */ 59 #define CONF_BOOL(c, o) (bool *) (void *)((char *) (c) + (o)) 60 #define CONF_NUMBER(c, o) (long *) (void *)((char *) (c) + (o)) 61 #define CONF_STRING(c, o) (char **) (void *)((char *) (c) + (o)) 62 #define CONF_LIST(c, o) (struct vector **)(void *)((char *) (c) + (o)) 63 /* clang-format on */ 64 65 /* 66 * We can only process times properly if we have Kerberos. If not, they fall 67 * back to longs and we convert them as numbers. 68 */ 69 /* clang-format off */ 70 #ifdef HAVE_KRB5 71 # define CONF_TIME(c, o) (krb5_deltat *)(void *)((char *) (c) + (o)) 72 #else 73 # define CONF_TIME(c, o) (long *) (void *)((char *) (c) + (o)) 74 #endif 75 /* clang-format on */ 76 77 78 /* 79 * Set a vector argument to its default. This needs to do a deep copy of the 80 * vector so that we can safely free it when freeing the configuration. Takes 81 * the PAM argument struct, the pointer in which to store the vector, and the 82 * default vector. Returns true if the default was set correctly and false on 83 * memory allocation failure, which is also reported with putil_crit(). 84 */ 85 static bool 86 copy_default_list(struct pam_args *args, struct vector **setting, 87 const struct vector *defval) 88 { 89 struct vector *result = NULL; 90 91 *setting = NULL; 92 if (defval != NULL && defval->strings != NULL) { 93 result = vector_copy(defval); 94 if (result == NULL) { 95 putil_crit(args, "cannot allocate memory: %s", strerror(errno)); 96 return false; 97 } 98 *setting = result; 99 } 100 return true; 101 } 102 103 104 /* 105 * Set a vector argument to a default based on a string. Takes the PAM 106 * argument struct,t he pointer into which to store the vector, and the 107 * default string. Returns true if the default was set correctly and false on 108 * memory allocation failure, which is also reported with putil_crit(). 109 */ 110 static bool 111 default_list_string(struct pam_args *args, struct vector **setting, 112 const char *defval) 113 { 114 struct vector *result = NULL; 115 116 *setting = NULL; 117 if (defval != NULL) { 118 result = vector_split_multi(defval, " \t,", NULL); 119 if (result == NULL) { 120 putil_crit(args, "cannot allocate memory: %s", strerror(errno)); 121 return false; 122 } 123 *setting = result; 124 } 125 return true; 126 } 127 128 129 /* 130 * Set the defaults for the PAM configuration. Takes the PAM arguments, an 131 * option table defined as above, and the number of entries in the table. The 132 * config member of the args struct must already be allocated. Returns true 133 * on success and false on error (generally out of memory). Errors will 134 * already be reported using putil_crit(). 135 * 136 * This function must be called before either putil_args_krb5() or 137 * putil_args_parse(), since neither of those functions set defaults. 138 */ 139 bool 140 putil_args_defaults(struct pam_args *args, const struct option options[], 141 size_t optlen) 142 { 143 size_t opt; 144 145 for (opt = 0; opt < optlen; opt++) { 146 bool *bp; 147 long *lp; 148 #ifdef HAVE_KRB5 149 krb5_deltat *tp; 150 #else 151 long *tp; 152 #endif 153 char **sp; 154 struct vector **vp; 155 156 switch (options[opt].type) { 157 case TYPE_BOOLEAN: 158 bp = CONF_BOOL(args->config, options[opt].location); 159 *bp = options[opt].defaults.boolean; 160 break; 161 case TYPE_NUMBER: 162 lp = CONF_NUMBER(args->config, options[opt].location); 163 *lp = options[opt].defaults.number; 164 break; 165 case TYPE_TIME: 166 tp = CONF_TIME(args->config, options[opt].location); 167 *tp = (krb5_deltat) options[opt].defaults.number; 168 break; 169 case TYPE_STRING: 170 sp = CONF_STRING(args->config, options[opt].location); 171 if (options[opt].defaults.string == NULL) 172 *sp = NULL; 173 else { 174 *sp = strdup(options[opt].defaults.string); 175 if (*sp == NULL) { 176 putil_crit(args, "cannot allocate memory: %s", 177 strerror(errno)); 178 return false; 179 } 180 } 181 break; 182 case TYPE_LIST: 183 vp = CONF_LIST(args->config, options[opt].location); 184 if (!copy_default_list(args, vp, options[opt].defaults.list)) 185 return false; 186 break; 187 case TYPE_STRLIST: 188 vp = CONF_LIST(args->config, options[opt].location); 189 if (!default_list_string(args, vp, options[opt].defaults.string)) 190 return false; 191 break; 192 } 193 } 194 return true; 195 } 196 197 198 #ifdef HAVE_KRB5 199 /* 200 * Load a boolean option from Kerberos appdefaults. Takes the PAM argument 201 * struct, the section name, the realm, the option, and the result location. 202 * 203 * The stupidity of rewriting the realm argument into a krb5_data is required 204 * by MIT Kerberos. 205 */ 206 static void 207 default_boolean(struct pam_args *args, const char *section, const char *realm, 208 const char *opt, bool *result) 209 { 210 int tmp; 211 # ifdef HAVE_KRB5_REALM 212 krb5_const_realm rdata = realm; 213 # else 214 krb5_data realm_struct; 215 const krb5_data *rdata; 216 217 if (realm == NULL) 218 rdata = NULL; 219 else { 220 rdata = &realm_struct; 221 realm_struct.magic = KV5M_DATA; 222 realm_struct.data = (void *) realm; 223 realm_struct.length = (unsigned int) strlen(realm); 224 } 225 # endif 226 227 /* 228 * The MIT version of krb5_appdefault_boolean takes an int * and the 229 * Heimdal version takes a krb5_boolean *, so hope that Heimdal always 230 * defines krb5_boolean to int or this will require more portability work. 231 */ 232 krb5_appdefault_boolean(args->ctx, section, rdata, opt, *result, &tmp); 233 *result = tmp; 234 } 235 236 237 /* 238 * Load a number option from Kerberos appdefaults. Takes the PAM argument 239 * struct, the section name, the realm, the option, and the result location. 240 * The native interface doesn't support numbers, so we actually read a string 241 * and then convert. 242 */ 243 static void 244 default_number(struct pam_args *args, const char *section, const char *realm, 245 const char *opt, long *result) 246 { 247 char *tmp = NULL; 248 char *end; 249 long value; 250 # ifdef HAVE_KRB5_REALM 251 krb5_const_realm rdata = realm; 252 # else 253 krb5_data realm_struct; 254 const krb5_data *rdata; 255 256 if (realm == NULL) 257 rdata = NULL; 258 else { 259 rdata = &realm_struct; 260 realm_struct.magic = KV5M_DATA; 261 realm_struct.data = (void *) realm; 262 realm_struct.length = (unsigned int) strlen(realm); 263 } 264 # endif 265 266 krb5_appdefault_string(args->ctx, section, rdata, opt, "", &tmp); 267 if (tmp != NULL && tmp[0] != '\0') { 268 errno = 0; 269 value = strtol(tmp, &end, 10); 270 if (errno != 0 || *end != '\0') 271 putil_err(args, "invalid number in krb5.conf setting for %s: %s", 272 opt, tmp); 273 else 274 *result = value; 275 } 276 free(tmp); 277 } 278 279 280 /* 281 * Load a time option from Kerberos appdefaults. Takes the PAM argument 282 * struct, the section name, the realm, the option, and the result location. 283 * The native interface doesn't support numbers, so we actually read a string 284 * and then convert using krb5_string_to_deltat. 285 */ 286 static void 287 default_time(struct pam_args *args, const char *section, const char *realm, 288 const char *opt, krb5_deltat *result) 289 { 290 char *tmp = NULL; 291 krb5_deltat value; 292 krb5_error_code retval; 293 # ifdef HAVE_KRB5_REALM 294 krb5_const_realm rdata = realm; 295 # else 296 krb5_data realm_struct; 297 const krb5_data *rdata; 298 299 if (realm == NULL) 300 rdata = NULL; 301 else { 302 rdata = &realm_struct; 303 realm_struct.magic = KV5M_DATA; 304 realm_struct.data = (void *) realm; 305 realm_struct.length = (unsigned int) strlen(realm); 306 } 307 # endif 308 309 krb5_appdefault_string(args->ctx, section, rdata, opt, "", &tmp); 310 if (tmp != NULL && tmp[0] != '\0') { 311 retval = krb5_string_to_deltat(tmp, &value); 312 if (retval != 0) 313 putil_err(args, "invalid time in krb5.conf setting for %s: %s", 314 opt, tmp); 315 else 316 *result = value; 317 } 318 free(tmp); 319 } 320 321 322 /* 323 * Load a string option from Kerberos appdefaults. Takes the PAM argument 324 * struct, the section name, the realm, the option, and the result location. 325 * 326 * This requires an annoying workaround because one cannot specify a default 327 * value of NULL with MIT Kerberos, since MIT Kerberos unconditionally calls 328 * strdup on the default value. There's also no way to determine if memory 329 * allocation failed while parsing or while setting the default value, so we 330 * don't return an error code. 331 */ 332 static void 333 default_string(struct pam_args *args, const char *section, const char *realm, 334 const char *opt, char **result) 335 { 336 char *value = NULL; 337 # ifdef HAVE_KRB5_REALM 338 krb5_const_realm rdata = realm; 339 # else 340 krb5_data realm_struct; 341 const krb5_data *rdata; 342 343 if (realm == NULL) 344 rdata = NULL; 345 else { 346 rdata = &realm_struct; 347 realm_struct.magic = KV5M_DATA; 348 realm_struct.data = (void *) realm; 349 realm_struct.length = (unsigned int) strlen(realm); 350 } 351 # endif 352 353 krb5_appdefault_string(args->ctx, section, rdata, opt, "", &value); 354 if (value != NULL) { 355 if (value[0] == '\0') 356 free(value); 357 else { 358 if (*result != NULL) 359 free(*result); 360 *result = value; 361 } 362 } 363 } 364 365 366 /* 367 * Load a list option from Kerberos appdefaults. Takes the PAM arguments, the 368 * context, the section name, the realm, the option, and the result location. 369 * 370 * We may fail here due to memory allocation problems, in which case we return 371 * false to indicate that PAM setup should abort. 372 */ 373 static bool 374 default_list(struct pam_args *args, const char *section, const char *realm, 375 const char *opt, struct vector **result) 376 { 377 char *tmp = NULL; 378 struct vector *value; 379 380 default_string(args, section, realm, opt, &tmp); 381 if (tmp != NULL) { 382 value = vector_split_multi(tmp, " \t,", NULL); 383 if (value == NULL) { 384 free(tmp); 385 putil_crit(args, "cannot allocate vector: %s", strerror(errno)); 386 return false; 387 } 388 if (*result != NULL) 389 vector_free(*result); 390 *result = value; 391 free(tmp); 392 } 393 return true; 394 } 395 396 397 /* 398 * The public interface for getting configuration information from krb5.conf. 399 * Takes the PAM arguments, the krb5.conf section, the options specification, 400 * and the number of options in the options table. The config member of the 401 * args struct must already be allocated. Iterate through the option list 402 * and, for every option where krb5_config is true, see if it's set in the 403 * Kerberos configuration. 404 * 405 * This looks obviously slow, but there haven't been any reports of problems 406 * and there's no better interface. But if you wonder where the cycles in 407 * your computer are getting wasted, well, here's one place. 408 */ 409 bool 410 putil_args_krb5(struct pam_args *args, const char *section, 411 const struct option options[], size_t optlen) 412 { 413 size_t i; 414 char *realm; 415 bool free_realm = false; 416 417 /* Having no local realm may be intentional, so don't report an error. */ 418 if (args->realm != NULL) 419 realm = args->realm; 420 else { 421 if (krb5_get_default_realm(args->ctx, &realm) < 0) 422 realm = NULL; 423 else 424 free_realm = true; 425 } 426 for (i = 0; i < optlen; i++) { 427 const struct option *opt = &options[i]; 428 429 if (!opt->krb5_config) 430 continue; 431 switch (opt->type) { 432 case TYPE_BOOLEAN: 433 default_boolean(args, section, realm, opt->name, 434 CONF_BOOL(args->config, opt->location)); 435 break; 436 case TYPE_NUMBER: 437 default_number(args, section, realm, opt->name, 438 CONF_NUMBER(args->config, opt->location)); 439 break; 440 case TYPE_TIME: 441 default_time(args, section, realm, opt->name, 442 CONF_TIME(args->config, opt->location)); 443 break; 444 case TYPE_STRING: 445 default_string(args, section, realm, opt->name, 446 CONF_STRING(args->config, opt->location)); 447 break; 448 case TYPE_LIST: 449 case TYPE_STRLIST: 450 if (!default_list(args, section, realm, opt->name, 451 CONF_LIST(args->config, opt->location))) 452 return false; 453 break; 454 } 455 } 456 if (free_realm) 457 krb5_free_default_realm(args->ctx, realm); 458 return true; 459 } 460 461 #else /* !HAVE_KRB5 */ 462 463 /* 464 * Stub function for getting configuration information from krb5.conf used 465 * when the PAM module is not built with Kerberos support so that the function 466 * can be called unconditionally. 467 */ 468 bool 469 putil_args_krb5(struct pam_args *args UNUSED, const char *section UNUSED, 470 const struct option options[] UNUSED, size_t optlen UNUSED) 471 { 472 return true; 473 } 474 475 #endif /* !HAVE_KRB5 */ 476 477 478 /* 479 * bsearch comparison function for finding PAM arguments in an array of struct 480 * options. We only compare up to the first '=' in the key so that we don't 481 * have to munge the string before searching. 482 */ 483 static int 484 option_compare(const void *key, const void *member) 485 { 486 const char *string = key; 487 const struct option *option = member; 488 const char *p; 489 size_t length; 490 int result; 491 492 p = strchr(string, '='); 493 if (p == NULL) 494 return strcmp(string, option->name); 495 else { 496 length = (size_t)(p - string); 497 if (length == 0) 498 return -1; 499 result = strncmp(string, option->name, length); 500 if (result == 0 && strlen(option->name) > length) 501 return -1; 502 return result; 503 } 504 } 505 506 507 /* 508 * Given a PAM argument, convert the value portion of the argument to a 509 * boolean and store it in the provided location. If the value is missing, 510 * that's equivalent to a true value. If the value is invalid, report an 511 * error and leave the location unchanged. 512 */ 513 static void 514 convert_boolean(struct pam_args *args, const char *arg, bool *setting) 515 { 516 const char *value; 517 518 value = strchr(arg, '='); 519 if (value == NULL) 520 *setting = true; 521 else { 522 value++; 523 /* clang-format off */ 524 if ( strcasecmp(value, "true") == 0 525 || strcasecmp(value, "yes") == 0 526 || strcasecmp(value, "on") == 0 527 || strcmp (value, "1") == 0) 528 *setting = true; 529 else if ( strcasecmp(value, "false") == 0 530 || strcasecmp(value, "no") == 0 531 || strcasecmp(value, "off") == 0 532 || strcmp (value, "0") == 0) 533 *setting = false; 534 else 535 putil_err(args, "invalid boolean in setting: %s", arg); 536 /* clang-format on */ 537 } 538 } 539 540 541 /* 542 * Given a PAM argument, convert the value portion of the argument to a number 543 * and store it in the provided location. If the value is missing or isn't a 544 * number, report an error and leave the location unchanged. 545 */ 546 static void 547 convert_number(struct pam_args *args, const char *arg, long *setting) 548 { 549 const char *value; 550 char *end; 551 long result; 552 553 value = strchr(arg, '='); 554 if (value == NULL || value[1] == '\0') { 555 putil_err(args, "value missing for option %s", arg); 556 return; 557 } 558 errno = 0; 559 result = strtol(value + 1, &end, 10); 560 if (errno != 0 || *end != '\0') { 561 putil_err(args, "invalid number in setting: %s", arg); 562 return; 563 } 564 *setting = result; 565 } 566 567 568 /* 569 * Given a PAM argument, convert the value portion of the argument from a 570 * Kerberos time string to a krb5_deltat and store it in the provided 571 * location. If the value is missing or isn't a number, report an error and 572 * leave the location unchanged. 573 */ 574 #ifdef HAVE_KRB5 575 static void 576 convert_time(struct pam_args *args, const char *arg, krb5_deltat *setting) 577 { 578 const char *value; 579 krb5_deltat result; 580 krb5_error_code retval; 581 582 value = strchr(arg, '='); 583 if (value == NULL || value[1] == '\0') { 584 putil_err(args, "value missing for option %s", arg); 585 return; 586 } 587 retval = krb5_string_to_deltat((char *) value + 1, &result); 588 if (retval != 0) 589 putil_err(args, "bad time value in setting: %s", arg); 590 else 591 *setting = result; 592 } 593 594 #else /* HAVE_KRB5 */ 595 596 static void 597 convert_time(struct pam_args *args, const char *arg, long *setting) 598 { 599 convert_number(args, arg, setting); 600 } 601 602 #endif /* !HAVE_KRB5 */ 603 604 605 /* 606 * Given a PAM argument, convert the value portion of the argument to a string 607 * and store it in the provided location. If the value is missing, report an 608 * error and leave the location unchanged, returning true since that's a 609 * non-fatal error. If memory allocation fails, return false, since PAM setup 610 * should abort. 611 */ 612 static bool 613 convert_string(struct pam_args *args, const char *arg, char **setting) 614 { 615 const char *value; 616 char *result; 617 618 value = strchr(arg, '='); 619 if (value == NULL) { 620 putil_err(args, "value missing for option %s", arg); 621 return true; 622 } 623 result = strdup(value + 1); 624 if (result == NULL) { 625 putil_crit(args, "cannot allocate memory: %s", strerror(errno)); 626 return false; 627 } 628 free(*setting); 629 *setting = result; 630 return true; 631 } 632 633 634 /* 635 * Given a PAM argument, convert the value portion of the argument to a vector 636 * and store it in the provided location. If the value is missing, report an 637 * error and leave the location unchanged, returning true since that's a 638 * non-fatal error. If memory allocation fails, return false, since PAM setup 639 * should abort. 640 */ 641 static bool 642 convert_list(struct pam_args *args, const char *arg, struct vector **setting) 643 { 644 const char *value; 645 struct vector *result; 646 647 value = strchr(arg, '='); 648 if (value == NULL) { 649 putil_err(args, "value missing for option %s", arg); 650 return true; 651 } 652 result = vector_split_multi(value + 1, " \t,", NULL); 653 if (result == NULL) { 654 putil_crit(args, "cannot allocate vector: %s", strerror(errno)); 655 return false; 656 } 657 vector_free(*setting); 658 *setting = result; 659 return true; 660 } 661 662 663 /* 664 * Parse the PAM arguments. Takes the PAM argument struct, the argument count 665 * and vector, the option table, and the number of elements in the option 666 * table. The config member of the args struct must already be allocated. 667 * Returns true on success and false on error. An error return should be 668 * considered fatal. Report errors using putil_crit(). Unknown options will 669 * also be diagnosed (to syslog at LOG_ERR using putil_err()), but are not 670 * considered fatal errors and will still return true. 671 * 672 * If options should be retrieved from krb5.conf, call putil_args_krb5() 673 * first, before calling this function. 674 */ 675 bool 676 putil_args_parse(struct pam_args *args, int argc, const char *argv[], 677 const struct option options[], size_t optlen) 678 { 679 int i; 680 const struct option *option; 681 682 /* 683 * Second pass: find each option we were given and set the corresponding 684 * configuration parameter. 685 */ 686 for (i = 0; i < argc; i++) { 687 option = bsearch(argv[i], options, optlen, sizeof(struct option), 688 option_compare); 689 if (option == NULL) { 690 putil_err(args, "unknown option %s", argv[i]); 691 continue; 692 } 693 switch (option->type) { 694 case TYPE_BOOLEAN: 695 convert_boolean(args, argv[i], 696 CONF_BOOL(args->config, option->location)); 697 break; 698 case TYPE_NUMBER: 699 convert_number(args, argv[i], 700 CONF_NUMBER(args->config, option->location)); 701 break; 702 case TYPE_TIME: 703 convert_time(args, argv[i], 704 CONF_TIME(args->config, option->location)); 705 break; 706 case TYPE_STRING: 707 if (!convert_string(args, argv[i], 708 CONF_STRING(args->config, option->location))) 709 return false; 710 break; 711 case TYPE_LIST: 712 case TYPE_STRLIST: 713 if (!convert_list(args, argv[i], 714 CONF_LIST(args->config, option->location))) 715 return false; 716 break; 717 } 718 } 719 return true; 720 } 721