xref: /freebsd/contrib/pam-krb5/pam-util/options.c (revision 24e4dcf4ba5e9dedcf89efd358ea3e1fe5867020)
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