/* * Option handling for pam-krb5. * * Responsible for initializing the args struct that's passed to nearly all * internal functions. Retrieves configuration information from krb5.conf and * parses the PAM configuration. * * Copyright 2005-2010, 2014, 2020 Russ Allbery * Copyright 2011-2012 * The Board of Trustees of the Leland Stanford Junior University * Copyright 2005 Andres Salomon * Copyright 1999-2000 Frank Cusack * * SPDX-License-Identifier: BSD-3-clause or GPL-1+ */ #include #include #include #include #include #include #include #include #include /* Our option definition. Must be sorted. */ #define K(name) (#name), offsetof(struct pam_config, name) /* clang-format off */ static const struct option options[] = { { K(alt_auth_map), true, STRING (NULL) }, { K(anon_fast), true, BOOL (false) }, { K(banner), true, STRING ("Kerberos") }, { K(ccache), true, STRING (NULL) }, { K(ccache_dir), true, STRING ("FILE:/tmp") }, { K(clear_on_fail), true, BOOL (false) }, { K(debug), true, BOOL (false) }, { K(defer_pwchange), true, BOOL (false) }, { K(expose_account), true, BOOL (false) }, { K(fail_pwchange), true, BOOL (false) }, { K(fast_ccache), true, STRING (NULL) }, { K(force_alt_auth), true, BOOL (false) }, { K(force_first_pass), false, BOOL (false) }, { K(force_pwchange), true, BOOL (false) }, { K(forwardable), true, BOOL (false) }, { K(ignore_k5login), true, BOOL (false) }, { K(ignore_root), true, BOOL (false) }, { K(keytab), true, STRING (NULL) }, { K(minimum_uid), true, NUMBER (0) }, { K(no_ccache), false, BOOL (false) }, { K(no_prompt), true, BOOL (false) }, { K(no_update_user), true, BOOL (false) }, { K(only_alt_auth), true, BOOL (false) }, { K(pkinit_anchors), true, STRING (NULL) }, { K(pkinit_prompt), true, BOOL (false) }, { K(pkinit_user), true, STRING (NULL) }, { K(preauth_opt), true, LIST (NULL) }, { K(prompt_principal), true, BOOL (false) }, { K(realm), false, STRING (NULL) }, { K(renew_lifetime), true, TIME (0) }, { K(retain_after_close), true, BOOL (false) }, { K(search_k5login), true, BOOL (false) }, { K(silent), false, BOOL (false) }, { K(ticket_lifetime), true, TIME (0) }, { K(trace), false, STRING (NULL) }, { K(try_first_pass), false, BOOL (false) }, { K(try_pkinit), true, BOOL (false) }, { K(use_authtok), false, BOOL (false) }, { K(use_first_pass), false, BOOL (false) }, { K(use_pkinit), true, BOOL (false) }, { K(user_realm), true, STRING (NULL) }, }; /* clang-format on */ static const size_t optlen = sizeof(options) / sizeof(options[0]); /* * Allocate a new struct pam_args and initialize its data members, including * parsing the arguments and getting settings from krb5.conf. Check the * resulting options for consistency. */ struct pam_args * pamk5_init(pam_handle_t *pamh, int flags, int argc, const char **argv) { int i; struct pam_args *args; struct pam_config *config = NULL; args = putil_args_new(pamh, flags); if (args == NULL) { return NULL; } config = calloc(1, sizeof(struct pam_config)); if (config == NULL) { goto nomem; } args->config = config; /* * Do an initial scan to see if the realm is already set in our options. * If so, make sure that's set before we start loading option values, * since it affects what comes out of krb5.conf. * * We will then ignore args->config->realm, set later by option parsing, * in favor of using args->realm extracted here. However, the latter must * exist to avoid throwing unknown option errors. */ for (i = 0; i < argc; i++) { if (strncmp(argv[i], "realm=", 6) != 0) continue; free(args->realm); args->realm = strdup(&argv[i][strlen("realm=")]); if (args->realm == NULL) goto nomem; } if (!putil_args_defaults(args, options, optlen)) { free(config); putil_args_free(args); return NULL; } if (!putil_args_krb5(args, "pam", options, optlen)) { goto fail; } if (!putil_args_parse(args, argc, argv, options, optlen)) { goto fail; } if (config->debug) { args->debug = true; } if (config->silent) { args->silent = true; } /* An empty banner should be treated the same as not having one. */ if (config->banner != NULL && config->banner[0] == '\0') { free(config->banner); config->banner = NULL; } /* Sanity-check try_first_pass, use_first_pass, and force_first_pass. */ if (config->force_first_pass && config->try_first_pass) { putil_err(args, "force_first_pass set, ignoring try_first_pass"); config->try_first_pass = 0; } if (config->force_first_pass && config->use_first_pass) { putil_err(args, "force_first_pass set, ignoring use_first_pass"); config->use_first_pass = 0; } if (config->use_first_pass && config->try_first_pass) { putil_err(args, "use_first_pass set, ignoring try_first_pass"); config->try_first_pass = 0; } /* * Don't set expose_account if we're using search_k5login. The user will * get a principal formed from the account into which they're logging in, * which isn't the password they'll use (that's the whole point of * search_k5login). */ if (config->search_k5login) { config->expose_account = 0; } /* UIDs are unsigned on some systems. */ if (config->minimum_uid < 0) { config->minimum_uid = 0; } /* * Warn if PKINIT options were set and PKINIT isn't supported. The MIT * method (krb5_get_init_creds_opt_set_pa) can't support use_pkinit. */ #ifndef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_PKINIT # ifndef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_PA if (config->try_pkinit) { putil_err(args, "try_pkinit requested but PKINIT not available"); } else if (config->use_pkinit) { putil_err(args, "use_pkinit requested but PKINIT not available"); } # endif # ifndef HAVE_KRB5_GET_PROMPT_TYPES if (config->use_pkinit) { putil_err(args, "use_pkinit requested but PKINIT cannot be enforced"); } # endif #endif /* Warn if the FAST option was set and FAST isn't supported. */ #ifndef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_FAST_CCACHE_NAME if (config->fast_ccache || config->anon_fast) { putil_err(args, "fast_ccache or anon_fast requested but FAST not" " supported by Kerberos libraries"); } #endif /* If tracing was requested enable it if possible. */ #ifdef HAVE_KRB5_SET_TRACE_FILENAME if (config->trace != NULL) { krb5_error_code retval; retval = krb5_set_trace_filename(args->ctx, config->trace); if (retval == 0) putil_debug(args, "enabled trace logging to %s", config->trace); else putil_err_krb5(args, retval, "cannot enable trace logging to %s", config->trace); } #else if (config->trace != NULL) { putil_err(args, "trace logging requested but not supported"); } #endif return args; nomem: putil_crit(args, "cannot allocate memory: %s", strerror(errno)); free(config); putil_args_free(args); return NULL; fail: pamk5_free(args); return NULL; } /* * Free the allocated args struct and any memory it points to. */ void pamk5_free(struct pam_args *args) { struct pam_config *config; if (args == NULL) return; config = args->config; if (config != NULL) { free(config->alt_auth_map); free(config->banner); free(config->ccache); free(config->ccache_dir); free(config->fast_ccache); free(config->keytab); free(config->pkinit_anchors); free(config->pkinit_user); vector_free(config->preauth_opt); free(config->realm); free(config->trace); free(config->user_realm); free(args->config); args->config = NULL; } putil_args_free(args); }