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
copy_default_list(struct pam_args * args,struct vector ** setting,const struct vector * defval)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
default_list_string(struct pam_args * args,struct vector ** setting,const char * defval)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
putil_args_defaults(struct pam_args * args,const struct option options[],size_t optlen)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
default_boolean(struct pam_args * args,const char * section,const char * realm,const char * opt,bool * result)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
default_number(struct pam_args * args,const char * section,const char * realm,const char * opt,long * result)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
default_time(struct pam_args * args,const char * section,const char * realm,const char * opt,krb5_deltat * result)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
default_string(struct pam_args * args,const char * section,const char * realm,const char * opt,char ** result)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
default_list(struct pam_args * args,const char * section,const char * realm,const char * opt,struct vector ** result)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
putil_args_krb5(struct pam_args * args,const char * section,const struct option options[],size_t optlen)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
putil_args_krb5(struct pam_args * args UNUSED,const char * section UNUSED,const struct option options[]UNUSED,size_t optlen UNUSED)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
option_compare(const void * key,const void * member)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
convert_boolean(struct pam_args * args,const char * arg,bool * setting)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
convert_number(struct pam_args * args,const char * arg,long * setting)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
convert_time(struct pam_args * args,const char * arg,krb5_deltat * setting)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
convert_time(struct pam_args * args,const char * arg,long * setting)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
convert_string(struct pam_args * args,const char * arg,char ** setting)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
convert_list(struct pam_args * args,const char * arg,struct vector ** setting)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
putil_args_parse(struct pam_args * args,int argc,const char * argv[],const struct option options[],size_t optlen)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