aboutsummaryrefslogtreecommitdiff
path: root/tests/module
diff options
context:
space:
mode:
authorCy Schubert <cy@FreeBSD.org>2025-04-17 02:13:41 +0000
committerCy Schubert <cy@FreeBSD.org>2025-05-27 16:20:06 +0000
commit24f0b4ca2d565cdbb4fe7839ff28320706bf2386 (patch)
treebc9ce87edb73f767f5580887d0fc8c643b9d7a49 /tests/module
pam-krb5: Import/add pam-krb5 from eyeire.orgvendor/pam-krb5/4.11vendor/pam-krb5
From https://www.eyrie.org/~eagle/software/pam-krb5/: pam-krb5 provides a Kerberos PAM module that supports authentication, user ticket cache handling, simple authorization (via .k5login or checking Kerberos principals against local usernames), and password changing. It can be configured through either options in the PAM configuration itself or through entries in the system krb5.conf file, and it tries to work around PAM implementation flaws in commonly-used PAM-enabled applications such as OpenSSH and xdm. It supports both PKINIT and FAST to the extent that the underlying Kerberos libraries support these features. The reason for this import is to provide an MIT KRB5 compatible pam_krb5 PAM module. The existing pam_krb5 in FreeBS only works with Heimdal. Sponsored by: The FreeBSD Foundation
Diffstat (limited to 'tests/module')
-rw-r--r--tests/module/alt-auth-t.c117
-rw-r--r--tests/module/bad-authtok-t.c53
-rw-r--r--tests/module/basic-t.c67
-rw-r--r--tests/module/cache-cleanup-t.c104
-rw-r--r--tests/module/cache-t.c210
-rw-r--r--tests/module/expired-t.c175
-rw-r--r--tests/module/fast-anon-t.c108
-rw-r--r--tests/module/fast-t.c57
-rw-r--r--tests/module/long-t.c46
-rw-r--r--tests/module/no-cache-t.c47
-rw-r--r--tests/module/pam-user-t.c80
-rw-r--r--tests/module/password-t.c152
-rw-r--r--tests/module/pkinit-t.c98
-rw-r--r--tests/module/realm-t.c87
-rw-r--r--tests/module/stacked-t.c50
-rw-r--r--tests/module/trace-t.c48
16 files changed, 1499 insertions, 0 deletions
diff --git a/tests/module/alt-auth-t.c b/tests/module/alt-auth-t.c
new file mode 100644
index 000000000000..df32ff941001
--- /dev/null
+++ b/tests/module/alt-auth-t.c
@@ -0,0 +1,117 @@
+/*
+ * Tests for the alt_auth_map functionality in libpam-krb5.
+ *
+ * This test case tests the variations of the alt_auth_map functionality for
+ * both authentication and account management. It requires a Kerberos
+ * configuration, but does not attempt to save a session ticket cache (to
+ * avoid requiring user configuration).
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2020 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2012
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * SPDX-License-Identifier: BSD-3-clause or GPL-1+
+ */
+
+#include <config.h>
+#include <portable/system.h>
+
+#include <tests/fakepam/script.h>
+#include <tests/tap/kerberos.h>
+#include <tests/tap/process.h>
+#include <tests/tap/string.h>
+
+
+int
+main(void)
+{
+ struct script_config config;
+ struct kerberos_config *krbconf;
+ char *user;
+
+ /*
+ * Load the Kerberos principal and password from a file, but set the
+ * principal as extra[0] and use something else bogus as the user. We
+ * want to test that alt_auth_map works when there's no relationship
+ * between the mapped principal and the user.
+ */
+ krbconf = kerberos_setup(TAP_KRB_NEEDS_PASSWORD);
+ memset(&config, 0, sizeof(config));
+ config.user = "bogus-nonexistent-account";
+ config.authtok = krbconf->password;
+ config.extra[0] = krbconf->username;
+ config.extra[1] = krbconf->userprinc;
+
+ /*
+ * Generate a testing krb5.conf file with a nonexistent default realm so
+ * that we can be sure that our principals will stay fully-qualified in
+ * the logs.
+ */
+ kerberos_generate_conf("bogus.example.com");
+ config.extra[2] = "bogus.example.com";
+
+ /* Test without password prompting. */
+ plan_lazy();
+ run_script("data/scripts/alt-auth/basic", &config);
+ run_script("data/scripts/alt-auth/basic-debug", &config);
+ run_script("data/scripts/alt-auth/fail", &config);
+ run_script("data/scripts/alt-auth/fail-debug", &config);
+ run_script("data/scripts/alt-auth/force", &config);
+ run_script("data/scripts/alt-auth/only", &config);
+
+ /*
+ * If the alternate account exists but the password is incorrect, we
+ * should not fall back to the regular account. Test with debug so that
+ * we don't need two principals configured.
+ */
+ config.authtok = "bogus incorrect password";
+ run_script("data/scripts/alt-auth/force-fail-debug", &config);
+
+ /*
+ * Switch to our correct user (but wrong realm) realm to test username
+ * mapping to a different realm.
+ */
+ config.authtok = krbconf->password;
+ config.user = krbconf->username;
+ config.extra[2] = krbconf->realm;
+ run_script("data/scripts/alt-auth/username-map", &config);
+
+ /*
+ * Split the username into two parts, one in the PAM configuration and one
+ * in the real username, so that we can test interpolation of the username
+ * when %s isn't the first token.
+ */
+ config.user = &krbconf->username[1];
+ user = bstrndup(krbconf->username, 1);
+ config.extra[3] = user;
+ run_script("data/scripts/alt-auth/username-map-prefix", &config);
+ free(user);
+ config.extra[3] = NULL;
+
+ /*
+ * Ensure that we don't add the realm of the authentication username when
+ * the alt_auth_map already includes a realm.
+ */
+ basprintf(&user, "%s@foo.example.com", krbconf->username);
+ config.user = user;
+ diag("re-running username-map with fully-qualified PAM user");
+ run_script("data/scripts/alt-auth/username-map", &config);
+ free(user);
+
+ /*
+ * Add the password and make the user match our authentication principal,
+ * and then test fallback to normal authentication when alternative
+ * authentication fails.
+ */
+ config.user = krbconf->userprinc;
+ config.password = krbconf->password;
+ config.extra[2] = krbconf->realm;
+ run_script("data/scripts/alt-auth/fallback", &config);
+ run_script("data/scripts/alt-auth/fallback-debug", &config);
+ run_script("data/scripts/alt-auth/fallback-realm", &config);
+ run_script("data/scripts/alt-auth/force-fallback", &config);
+ run_script("data/scripts/alt-auth/only-fail", &config);
+
+ return 0;
+}
diff --git a/tests/module/bad-authtok-t.c b/tests/module/bad-authtok-t.c
new file mode 100644
index 000000000000..385dd5946849
--- /dev/null
+++ b/tests/module/bad-authtok-t.c
@@ -0,0 +1,53 @@
+/*
+ * Authentication tests for the pam-krb5 module with an incorrect AUTHTOK.
+ *
+ * This test case includes tests that require Kerberos to be configured and a
+ * username and password available and that run with an incorrect AUTHTOK
+ * already set. They test various prompting fallback cases. They don't write
+ * a ticket cache (which requires additional work to test the cache
+ * ownership).
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2020 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2011-2012
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * SPDX-License-Identifier: BSD-3-clause or GPL-1+
+ */
+
+#include <config.h>
+#include <portable/system.h>
+
+#include <tests/fakepam/script.h>
+#include <tests/tap/kerberos.h>
+#include <tests/tap/process.h>
+#include <tests/tap/string.h>
+
+
+int
+main(void)
+{
+ struct script_config config;
+ struct kerberos_config *krbconf;
+
+ /* Load the Kerberos principal and password from a file. */
+ krbconf = kerberos_setup(TAP_KRB_NEEDS_PASSWORD);
+ memset(&config, 0, sizeof(config));
+ config.user = krbconf->userprinc;
+ config.password = krbconf->password;
+
+ /* Set the authtok to something bogus. */
+ config.authtok = "BAD PASSWORD THAT WILL NOT WORK";
+
+ /*
+ * Generate a testing krb5.conf file with a nonexistent default realm so
+ * that we can be sure that our principals will stay fully-qualified in
+ * the logs.
+ */
+ kerberos_generate_conf("bogus.example.com");
+
+ plan_lazy();
+ run_script_dir("data/scripts/bad-authtok", &config);
+
+ return 0;
+}
diff --git a/tests/module/basic-t.c b/tests/module/basic-t.c
new file mode 100644
index 000000000000..cacad5906ffb
--- /dev/null
+++ b/tests/module/basic-t.c
@@ -0,0 +1,67 @@
+/*
+ * Basic tests for the pam-krb5 module.
+ *
+ * This test case includes all tests that can be done without having Kerberos
+ * configured and a username and password available, and without any special
+ * configuration.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2020 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2011
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * SPDX-License-Identifier: BSD-3-clause or GPL-1+
+ */
+
+#include <config.h>
+#include <portable/system.h>
+
+#include <pwd.h>
+
+#include <tests/fakepam/pam.h>
+#include <tests/fakepam/script.h>
+#include <tests/tap/basic.h>
+#include <tests/tap/kerberos.h>
+#include <tests/tap/string.h>
+
+
+int
+main(void)
+{
+ struct script_config config;
+ struct passwd pwd;
+ char *uid;
+ char *uidplus;
+
+ plan_lazy();
+
+ /*
+ * Generate a testing krb5.conf file with a nonexistent default realm so
+ * that this test will run on any system.
+ */
+ kerberos_generate_conf("bogus.example.com");
+
+ /* Create a fake passwd struct for our user. */
+ memset(&pwd, 0, sizeof(pwd));
+ pwd.pw_name = (char *) "root";
+ pwd.pw_uid = getuid();
+ pwd.pw_gid = getgid();
+ pam_set_pwd(&pwd);
+
+ /*
+ * Attempt login as the root user to test ignore_root. Set our current
+ * UID and a UID one larger for testing minimum_uid.
+ */
+ basprintf(&uid, "%lu", (unsigned long) pwd.pw_uid);
+ basprintf(&uidplus, "%lu", (unsigned long) pwd.pw_uid + 1);
+ memset(&config, 0, sizeof(config));
+ config.user = "root";
+ config.extra[0] = uid;
+ config.extra[1] = uidplus;
+
+ run_script_dir("data/scripts/basic", &config);
+
+ free(uid);
+ free(uidplus);
+ return 0;
+}
diff --git a/tests/module/cache-cleanup-t.c b/tests/module/cache-cleanup-t.c
new file mode 100644
index 000000000000..8b5012fc3507
--- /dev/null
+++ b/tests/module/cache-cleanup-t.c
@@ -0,0 +1,104 @@
+/*
+ * Test for properly cleaning up ticket caches.
+ *
+ * Verify that the temporary Kerberos ticket cache generated during
+ * authentication is cleaned up on pam_end, even if no session was opened.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2020 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2012
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * SPDX-License-Identifier: BSD-3-clause or GPL-1+
+ */
+
+#include <config.h>
+#include <portable/system.h>
+
+#include <dirent.h>
+
+#include <tests/fakepam/pam.h>
+#include <tests/fakepam/script.h>
+#include <tests/tap/basic.h>
+#include <tests/tap/kerberos.h>
+#include <tests/tap/string.h>
+
+
+int
+main(void)
+{
+ struct script_config config;
+ struct kerberos_config *krbconf;
+ DIR *tmpdir;
+ struct dirent *file;
+ char *tmppath, *path;
+
+ /* Load the Kerberos principal and password from a file. */
+ krbconf = kerberos_setup(TAP_KRB_NEEDS_PASSWORD);
+ memset(&config, 0, sizeof(config));
+ config.user = krbconf->username;
+ config.authtok = krbconf->password;
+ config.extra[0] = krbconf->userprinc;
+
+ /* Generate a testing krb5.conf file. */
+ kerberos_generate_conf(krbconf->realm);
+
+ /* Get the temporary directory and store that as the %1 substitution. */
+ tmppath = test_tmpdir();
+ config.extra[1] = tmppath;
+
+ plan_lazy();
+
+ /*
+ * We need to ensure that the only thing in the test temporary directory
+ * is the krb5.conf file that we generated and any valgrind logs, since
+ * we're going to check for cleanup by looking for any out-of-place files.
+ */
+ tmpdir = opendir(tmppath);
+ if (tmpdir == NULL)
+ sysbail("cannot open directory %s", tmppath);
+ while ((file = readdir(tmpdir)) != NULL) {
+ if (strcmp(file->d_name, ".") == 0 || strcmp(file->d_name, "..") == 0)
+ continue;
+ if (strcmp(file->d_name, "krb5.conf") == 0)
+ continue;
+ if (strcmp(file->d_name, "valgrind") == 0)
+ continue;
+ basprintf(&path, "%s/%s", tmppath, file->d_name);
+ if (unlink(path) < 0)
+ sysbail("cannot delete temporary file %s", path);
+ free(path);
+ }
+ closedir(tmpdir);
+
+ /*
+ * Authenticate only, call pam_end, and be sure the ticket cache is
+ * gone. The auth-only script sets ccache_dir to the temporary directory,
+ * so the module will create a temporary ticket cache there and then
+ * should clean it up.
+ */
+ run_script("data/scripts/cache-cleanup/auth-only", &config);
+ path = NULL;
+ tmpdir = opendir(tmppath);
+ if (tmpdir == NULL)
+ sysbail("cannot open directory %s", tmppath);
+ while ((file = readdir(tmpdir)) != NULL) {
+ if (strcmp(file->d_name, ".") == 0 || strcmp(file->d_name, "..") == 0)
+ continue;
+ if (strcmp(file->d_name, "krb5.conf") == 0)
+ continue;
+ if (strcmp(file->d_name, "valgrind") == 0)
+ continue;
+ if (path == NULL)
+ basprintf(&path, "%s/%s", tmppath, file->d_name);
+ }
+ closedir(tmpdir);
+ if (path != NULL)
+ diag("found stray temporary file %s", path);
+ ok(path == NULL, "ticket cache cleaned up");
+ if (path != NULL)
+ free(path);
+
+ test_tmpdir_free(tmppath);
+ return 0;
+}
diff --git a/tests/module/cache-t.c b/tests/module/cache-t.c
new file mode 100644
index 000000000000..8ec82df7c460
--- /dev/null
+++ b/tests/module/cache-t.c
@@ -0,0 +1,210 @@
+/*
+ * Authentication tests for the pam-krb5 module with ticket cache.
+ *
+ * This test case includes all tests that require Kerberos to be configured, a
+ * username and password available, and a ticket cache created, but with the
+ * PAM module running as the same user for which the ticket cache will be
+ * created (so without setuid and with chown doing nothing).
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2017, 2020-2021 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2011, 2012
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * SPDX-License-Identifier: BSD-3-clause or GPL-1+
+ */
+
+#include <config.h>
+#include <portable/krb5.h>
+#include <portable/system.h>
+
+#include <pwd.h>
+#include <sys/stat.h>
+#include <time.h>
+
+#include <tests/fakepam/pam.h>
+#include <tests/fakepam/script.h>
+#include <tests/tap/basic.h>
+#include <tests/tap/kerberos.h>
+#include <tests/tap/process.h>
+#include <tests/tap/string.h>
+
+/* Additional data used by the cache check callback. */
+struct extra {
+ char *realm;
+ char *cache_path;
+};
+
+
+/*
+ * PAM test callback to check whether we created a ticket cache and the ticket
+ * cache is for the correct user.
+ */
+static void
+check_cache(const char *file, const struct script_config *config,
+ const struct extra *extra)
+{
+ struct stat st;
+ krb5_error_code code;
+ krb5_context ctx = NULL;
+ krb5_ccache ccache = NULL;
+ krb5_principal princ = NULL;
+ krb5_principal tgtprinc = NULL;
+ krb5_creds in, out;
+ char *principal = NULL;
+
+ /* Check ownership and permissions. */
+ is_int(0, stat(file, &st), "cache exists");
+ is_int(getuid(), st.st_uid, "...with correct UID");
+ is_int(getgid(), st.st_gid, "...with correct GID");
+ is_int(0600, (st.st_mode & 0777), "...with correct permissions");
+
+ /* Check the existence of the ticket cache and its principal. */
+ code = krb5_init_context(&ctx);
+ if (code != 0)
+ bail("cannot create Kerberos context");
+ code = krb5_cc_resolve(ctx, file, &ccache);
+ is_int(0, code, "able to resolve Kerberos ticket cache");
+ code = krb5_cc_get_principal(ctx, ccache, &princ);
+ is_int(0, code, "able to get principal");
+ code = krb5_unparse_name(ctx, princ, &principal);
+ is_int(0, code, "...and principal is valid");
+ is_string(config->extra[0], principal, "...and matches our principal");
+
+ /* Retrieve the krbtgt for the realm and check properties. */
+ code = krb5_build_principal_ext(
+ ctx, &tgtprinc, (unsigned int) strlen(extra->realm), extra->realm,
+ KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME, strlen(extra->realm), extra->realm,
+ NULL);
+ if (code != 0)
+ bail("cannot create krbtgt principal name");
+ memset(&in, 0, sizeof(in));
+ memset(&out, 0, sizeof(out));
+ in.server = tgtprinc;
+ in.client = princ;
+ code = krb5_cc_retrieve_cred(ctx, ccache, KRB5_TC_MATCH_SRV_NAMEONLY, &in,
+ &out);
+ is_int(0, code, "able to get krbtgt credentials");
+ ok(out.times.endtime > time(NULL) + 30 * 60, "...good for 30 minutes");
+ krb5_free_cred_contents(ctx, &out);
+
+ /* Close things and release memory. */
+ krb5_free_principal(ctx, tgtprinc);
+ krb5_free_unparsed_name(ctx, principal);
+ krb5_free_principal(ctx, princ);
+ krb5_cc_close(ctx, ccache);
+ krb5_free_context(ctx);
+}
+
+
+/*
+ * Same as check_cache except unlink the ticket cache afterwards. Used to
+ * check the ticket cache in cases where the PAM module will not clean it up
+ * afterwards, such as calling pam_end with PAM_DATA_SILENT.
+ */
+static void
+check_cache_callback(pam_handle_t *pamh, const struct script_config *config,
+ void *data)
+{
+ struct extra *extra = data;
+ const char *cache, *file;
+ char *prefix;
+
+ cache = pam_getenv(pamh, "KRB5CCNAME");
+ ok(cache != NULL, "KRB5CCNAME is set in PAM environment");
+ if (cache == NULL)
+ return;
+ basprintf(&prefix, "FILE:/tmp/krb5cc_%lu_", (unsigned long) getuid());
+ diag("KRB5CCNAME = %s", cache);
+ ok(strncmp(prefix, cache, strlen(prefix)) == 0,
+ "cache file name prefix is correct");
+ free(prefix);
+ file = cache + strlen("FILE:");
+ extra->cache_path = bstrdup(file);
+ check_cache(file, config, extra);
+}
+
+
+int
+main(void)
+{
+ struct script_config config;
+ struct kerberos_config *krbconf;
+ char *k5login;
+ struct extra extra;
+ struct passwd pwd;
+ FILE *file;
+
+ /* Load the Kerberos principal and password from a file. */
+ krbconf = kerberos_setup(TAP_KRB_NEEDS_PASSWORD);
+ memset(&config, 0, sizeof(config));
+ config.user = krbconf->username;
+ extra.realm = krbconf->realm;
+ extra.cache_path = NULL;
+ config.authtok = krbconf->password;
+ config.extra[0] = krbconf->userprinc;
+
+ /* Generate a testing krb5.conf file. */
+ kerberos_generate_conf(krbconf->realm);
+
+ /* Create a fake passwd struct for our user. */
+ memset(&pwd, 0, sizeof(pwd));
+ pwd.pw_name = krbconf->username;
+ pwd.pw_uid = getuid();
+ pwd.pw_gid = getgid();
+ basprintf(&pwd.pw_dir, "%s/tmp", getenv("BUILD"));
+ pam_set_pwd(&pwd);
+
+ plan_lazy();
+
+ /* Basic test. */
+ run_script("data/scripts/cache/basic", &config);
+
+ /* Check the cache status before the session is closed. */
+ config.callback = check_cache_callback;
+ config.data = &extra;
+ run_script("data/scripts/cache/open-session", &config);
+ free(extra.cache_path);
+ extra.cache_path = NULL;
+
+ /*
+ * Try again but passing PAM_DATA_SILENT to pam_end. This should leave
+ * the ticket cache intact.
+ */
+ run_script("data/scripts/cache/end-data-silent", &config);
+ check_cache(extra.cache_path, &config, &extra);
+ if (unlink(extra.cache_path) < 0)
+ sysdiag("unable to unlink temporary cache %s", extra.cache_path);
+ free(extra.cache_path);
+ extra.cache_path = NULL;
+
+ /* Change the authenticating user and test search_k5login. */
+ pwd.pw_name = (char *) "testuser";
+ pam_set_pwd(&pwd);
+ config.user = "testuser";
+ basprintf(&k5login, "%s/.k5login", pwd.pw_dir);
+ file = fopen(k5login, "w");
+ if (file == NULL)
+ sysbail("cannot create %s", k5login);
+ if (fprintf(file, "%s\n", krbconf->userprinc) < 0)
+ sysbail("cannot write to %s", k5login);
+ if (fclose(file) < 0)
+ sysbail("cannot flush %s", k5login);
+ run_script("data/scripts/cache/search-k5login", &config);
+ free(extra.cache_path);
+ extra.cache_path = NULL;
+ config.callback = NULL;
+ run_script("data/scripts/cache/search-k5login-debug", &config);
+ unlink(k5login);
+ free(k5login);
+
+ /* Test search_k5login when no .k5login file exists. */
+ pwd.pw_name = krbconf->username;
+ pam_set_pwd(&pwd);
+ config.user = krbconf->username;
+ diag("testing search_k5login with no .k5login file");
+ run_script("data/scripts/cache/search-k5login", &config);
+
+ free(pwd.pw_dir);
+ return 0;
+}
diff --git a/tests/module/expired-t.c b/tests/module/expired-t.c
new file mode 100644
index 000000000000..01a1892a0d04
--- /dev/null
+++ b/tests/module/expired-t.c
@@ -0,0 +1,175 @@
+/*
+ * Tests for the pam-krb5 module with an expired password.
+ *
+ * This test case checks correct handling of an account whose password has
+ * expired and the multiple different paths the module can take for handling
+ * that case.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2020 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2011-2012
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * SPDX-License-Identifier: BSD-3-clause or GPL-1+
+ */
+
+#include <config.h>
+#include <portable/system.h>
+
+#include <pwd.h>
+#include <time.h>
+
+#include <tests/fakepam/pam.h>
+#include <tests/fakepam/script.h>
+#include <tests/tap/basic.h>
+#include <tests/tap/kadmin.h>
+#include <tests/tap/kerberos.h>
+#include <tests/tap/process.h>
+#include <tests/tap/string.h>
+
+
+int
+main(void)
+{
+ struct script_config config;
+ struct kerberos_config *krbconf;
+ char *newpass, *date;
+ struct passwd pwd;
+ time_t now;
+
+ /* Load the Kerberos principal and password from a file. */
+ krbconf = kerberos_setup(TAP_KRB_NEEDS_PASSWORD);
+ memset(&config, 0, sizeof(config));
+ config.user = krbconf->username;
+ config.password = krbconf->password;
+ config.extra[0] = krbconf->userprinc;
+
+ /*
+ * Ensure we can expire the password. Heimdal has a prompt for the
+ * expiration time, so save that to use as a substitution in the script.
+ */
+ now = time(NULL) - 1;
+ if (!kerberos_expire_password(krbconf->userprinc, now))
+ skip_all("kadmin not configured or kadmin mismatch");
+ date = bstrdup(ctime(&now));
+ date[strlen(date) - 1] = '\0';
+ config.extra[1] = date;
+
+ /* Generate a testing krb5.conf file. */
+ kerberos_generate_conf(krbconf->realm);
+
+ /* Create a fake passwd struct for our user. */
+ memset(&pwd, 0, sizeof(pwd));
+ pwd.pw_name = krbconf->username;
+ pwd.pw_uid = getuid();
+ pwd.pw_gid = getgid();
+ basprintf(&pwd.pw_dir, "%s/tmp", getenv("BUILD"));
+ pam_set_pwd(&pwd);
+
+ /*
+ * We'll be changing the password to something new. This needs to be
+ * sufficiently random that it's unlikely to fall afoul of password
+ * strength checking.
+ */
+ basprintf(&newpass, "ngh1,a%lu nn9af6", (unsigned long) getpid());
+ config.newpass = newpass;
+
+ plan_lazy();
+
+ /*
+ * Default behavior. We have to distinguish between two versions of
+ * Heimdal for testing because the prompts changed substantially. Use the
+ * existence of krb5_principal_set_comp_string to distinguish because it
+ * was introduced at the same time.
+ */
+#ifdef HAVE_KRB5_HEIMDAL
+# ifdef HAVE_KRB5_PRINCIPAL_SET_COMP_STRING
+ run_script("data/scripts/expired/basic-heimdal", &config);
+ config.newpass = krbconf->password;
+ config.password = newpass;
+ kerberos_expire_password(krbconf->userprinc, now);
+ run_script("data/scripts/expired/basic-heimdal-debug", &config);
+# else
+ run_script("data/scripts/expired/basic-heimdal-old", &config);
+ config.newpass = krbconf->password;
+ config.password = newpass;
+ kerberos_expire_password(krbconf->userprinc, now);
+ run_script("data/scripts/expired/basic-heimdal-old-debug", &config);
+# endif
+#else
+ run_script("data/scripts/expired/basic-mit", &config);
+ config.newpass = krbconf->password;
+ config.password = newpass;
+ kerberos_expire_password(krbconf->userprinc, now);
+ run_script("data/scripts/expired/basic-mit-debug", &config);
+#endif
+
+ /* Test again with PAM_SILENT, specified two ways. */
+#ifdef HAVE_KRB5_HEIMDAL
+ config.newpass = newpass;
+ config.password = krbconf->password;
+ kerberos_expire_password(krbconf->userprinc, now);
+ run_script("data/scripts/expired/basic-heimdal-silent", &config);
+ config.newpass = krbconf->password;
+ config.password = newpass;
+ kerberos_expire_password(krbconf->userprinc, now);
+ run_script("data/scripts/expired/basic-heimdal-flag-silent", &config);
+#else
+ config.newpass = newpass;
+ config.password = krbconf->password;
+ kerberos_expire_password(krbconf->userprinc, now);
+ run_script("data/scripts/expired/basic-mit-silent", &config);
+ config.newpass = krbconf->password;
+ config.password = newpass;
+ kerberos_expire_password(krbconf->userprinc, now);
+ run_script("data/scripts/expired/basic-mit-flag-silent", &config);
+#endif
+
+ /*
+ * We can only run the remaining checks if we can suppress the Kerberos
+ * library behavior of prompting for a new password when the password has
+ * expired.
+ */
+#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_CHANGE_PASSWORD_PROMPT
+
+ /* Check the forced failure behavior. */
+ run_script("data/scripts/expired/fail", &config);
+ run_script("data/scripts/expired/fail-debug", &config);
+
+ /*
+ * Defer the error to the account management check.
+ *
+ * Skip this check on Heimdal currently (Heimdal 7.4.0) because its
+ * implementation of krb5_get_init_creds_opt_set_change_password_prompt is
+ * incomplete. See <https://github.com/heimdal/heimdal/issues/322>.
+ */
+# ifdef HAVE_KRB5_HEIMDAL
+ skip_block(2, "deferring password changes broken in Heimdal");
+# else
+ config.newpass = newpass;
+ config.password = krbconf->password;
+ config.authtok = krbconf->password;
+ kerberos_expire_password(krbconf->userprinc, now);
+ run_script("data/scripts/expired/defer-mit", &config);
+ config.newpass = krbconf->password;
+ config.password = newpass;
+ config.authtok = newpass;
+ kerberos_expire_password(krbconf->userprinc, now);
+ run_script("data/scripts/expired/defer-mit-debug", &config);
+# endif
+
+#else /* !HAVE_KRB5_GET_INIT_CREDS_OPT_SET_CHANGE_PASSWORD_PROMPT */
+
+ /* Mention that we skipped something for the record. */
+ skip_block(4, "cannot disable library password prompting");
+
+#endif /* HAVE_KRB5_GET_INIT_CREDS_OPT_SET_CHANGE_PASSWORD_PROMPT */
+
+ /* In case we ran into some error, try to unexpire the password. */
+ kerberos_expire_password(krbconf->userprinc, 0);
+
+ free(date);
+ free(newpass);
+ free(pwd.pw_dir);
+ return 0;
+}
diff --git a/tests/module/fast-anon-t.c b/tests/module/fast-anon-t.c
new file mode 100644
index 000000000000..6355a5154f69
--- /dev/null
+++ b/tests/module/fast-anon-t.c
@@ -0,0 +1,108 @@
+/*
+ * Tests for anonymous FAST support in pam-krb5.
+ *
+ * Tests for anonymous Flexible Authentication Secure Tunneling, a mechanism
+ * for improving the preauthentication part of the Kerberos protocol and
+ * protecting it against various attacks.
+ *
+ * This is broken out from the other FAST tests because it uses PKINIT, and
+ * PKINIT code cannot be tested under valgrind with MIT Kerberos due to some
+ * bug in valgrind.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2017, 2020 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2012
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * SPDX-License-Identifier: BSD-3-clause or GPL-1+
+ */
+
+#include <config.h>
+#include <portable/krb5.h>
+#include <portable/system.h>
+
+#include <tests/fakepam/script.h>
+#include <tests/tap/kerberos.h>
+
+
+/*
+ * Test whether anonymous authentication works. If this doesn't, we need to
+ * skip the tests of anonymous FAST.
+ */
+static bool
+anon_fast_works(void)
+{
+ krb5_context ctx;
+ krb5_error_code retval;
+ krb5_principal princ = NULL;
+ char *realm;
+ krb5_creds creds;
+ krb5_get_init_creds_opt *opts = NULL;
+
+ /* Construct the anonymous principal name. */
+ retval = krb5_init_context(&ctx);
+ if (retval != 0)
+ bail("cannot initialize Kerberos");
+ retval = krb5_get_default_realm(ctx, &realm);
+ if (retval != 0)
+ bail("cannot get default realm");
+ retval = krb5_build_principal_ext(
+ ctx, &princ, (unsigned int) strlen(realm), realm,
+ strlen(KRB5_WELLKNOWN_NAME), KRB5_WELLKNOWN_NAME,
+ strlen(KRB5_ANON_NAME), KRB5_ANON_NAME, NULL);
+ if (retval != 0)
+ bail("cannot construct anonymous principal");
+ krb5_free_default_realm(ctx, realm);
+
+ /* Obtain the credentials. */
+ memset(&creds, 0, sizeof(creds));
+ retval = krb5_get_init_creds_opt_alloc(ctx, &opts);
+ if (retval != 0)
+ bail("cannot create credential options");
+ krb5_get_init_creds_opt_set_anonymous(opts, 1);
+ krb5_get_init_creds_opt_set_tkt_life(opts, 60);
+ retval = krb5_get_init_creds_password(ctx, &creds, princ, NULL, NULL, NULL,
+ 0, NULL, opts);
+
+ /* Clean up. */
+ if (princ != NULL)
+ krb5_free_principal(ctx, princ);
+ if (opts != NULL)
+ krb5_get_init_creds_opt_free(ctx, opts);
+ krb5_free_cred_contents(ctx, &creds);
+
+ /* Return whether authentication succeeded. */
+ return (retval == 0);
+}
+
+
+int
+main(void)
+{
+ struct script_config config;
+ struct kerberos_config *krbconf;
+
+ /* Skip the test if FAST is not available. */
+#ifndef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_FAST_CCACHE_NAME
+ skip_all("FAST support not available");
+#endif
+
+ /* Initialize Kerberos configuration. */
+ krbconf = kerberos_setup(TAP_KRB_NEEDS_PASSWORD);
+ memset(&config, 0, sizeof(config));
+ config.user = krbconf->username;
+ config.authtok = krbconf->password;
+ config.extra[0] = krbconf->userprinc;
+ kerberos_generate_conf(krbconf->realm);
+
+ /* Skip the test if anonymous PKINIT doesn't work. */
+ if (!anon_fast_works())
+ skip_all("anonymous PKINIT failed");
+
+ /* Test anonymous FAST. */
+ plan_lazy();
+ run_script("data/scripts/fast/anonymous", &config);
+ run_script("data/scripts/fast/anonymous-debug", &config);
+
+ return 0;
+}
diff --git a/tests/module/fast-t.c b/tests/module/fast-t.c
new file mode 100644
index 000000000000..51fee27098c8
--- /dev/null
+++ b/tests/module/fast-t.c
@@ -0,0 +1,57 @@
+/*
+ * Tests for authenticated FAST support in pam-krb5.
+ *
+ * Tests for Flexible Authentication Secure Tunneling, a mechanism for
+ * improving the preauthentication part of the Kerberos protocol and
+ * protecting it against various attacks. This tests authenticated FAST;
+ * anonymous FAST is tested separately.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2017, 2020 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2012
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * SPDX-License-Identifier: BSD-3-clause or GPL-1+
+ */
+
+#include <config.h>
+#include <portable/system.h>
+
+#include <tests/fakepam/script.h>
+#include <tests/tap/kerberos.h>
+
+
+int
+main(void)
+{
+ struct script_config config;
+ struct kerberos_config *krbconf;
+
+ /* Skip the test if FAST is not available. */
+#ifndef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_FAST_CCACHE_NAME
+ skip_all("FAST support not available");
+#endif
+
+ /* Initialize Kerberos configuration. */
+ krbconf = kerberos_setup(TAP_KRB_NEEDS_BOTH);
+ memset(&config, 0, sizeof(config));
+ config.user = krbconf->userprinc;
+ config.authtok = krbconf->password;
+ config.extra[0] = krbconf->cache;
+
+ /*
+ * Generate a testing krb5.conf file with a nonexistent default realm so
+ * that we can be sure that our principals will stay fully-qualified in
+ * the logs.
+ */
+ kerberos_generate_conf("bogus.example.com");
+
+ /* Test fast_ccache */
+ plan_lazy();
+ run_script("data/scripts/fast/ccache", &config);
+ run_script("data/scripts/fast/ccache-debug", &config);
+ run_script("data/scripts/fast/no-ccache", &config);
+ run_script("data/scripts/fast/no-ccache-debug", &config);
+
+ return 0;
+}
diff --git a/tests/module/long-t.c b/tests/module/long-t.c
new file mode 100644
index 000000000000..73614b0f6ec9
--- /dev/null
+++ b/tests/module/long-t.c
@@ -0,0 +1,46 @@
+/*
+ * Excessively long password tests for the pam-krb5 module.
+ *
+ * This test case includes all tests for excessively long passwords that can
+ * be done without having Kerberos configured and a username and password
+ * available.
+ *
+ * Copyright 2020 Russ Allbery <eagle@eyrie.org>
+ *
+ * SPDX-License-Identifier: BSD-3-clause or GPL-1+
+ */
+
+#include <config.h>
+#include <portable/system.h>
+
+#include <tests/fakepam/script.h>
+#include <tests/tap/basic.h>
+
+
+int
+main(void)
+{
+ struct script_config config;
+ char *password;
+
+ plan_lazy();
+
+ memset(&config, 0, sizeof(config));
+ config.user = "test";
+
+ /* Test a password that is too long. */
+ password = bcalloc_type(PAM_MAX_RESP_SIZE + 1, char);
+ memset(password, 'a', PAM_MAX_RESP_SIZE);
+ config.password = password;
+ run_script("data/scripts/long/password", &config);
+ run_script("data/scripts/long/password-debug", &config);
+
+ /* Test a stored authtok that's too long. */
+ config.authtok = password;
+ config.password = "testing";
+ run_script("data/scripts/long/use-first", &config);
+ run_script("data/scripts/long/use-first-debug", &config);
+
+ free(password);
+ return 0;
+}
diff --git a/tests/module/no-cache-t.c b/tests/module/no-cache-t.c
new file mode 100644
index 000000000000..8b282d1de397
--- /dev/null
+++ b/tests/module/no-cache-t.c
@@ -0,0 +1,47 @@
+/*
+ * Authentication tests for the pam-krb5 module without a ticket cache.
+ *
+ * This test case includes tests that require Kerberos to be configured and a
+ * username and password available, but which don't write a ticket cache
+ * (which requires additional work to test the cache ownership). This test
+ * does not set AUTHTOK.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2020 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2011, 2012
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * SPDX-License-Identifier: BSD-3-clause or GPL-1+
+ */
+
+#include <config.h>
+#include <portable/system.h>
+
+#include <tests/fakepam/script.h>
+#include <tests/tap/kerberos.h>
+
+
+int
+main(void)
+{
+ struct script_config config;
+ struct kerberos_config *krbconf;
+
+ /* Load the Kerberos principal and password from a file. */
+ krbconf = kerberos_setup(TAP_KRB_NEEDS_PASSWORD);
+ memset(&config, 0, sizeof(config));
+ config.user = krbconf->userprinc;
+ config.password = krbconf->password;
+
+ /*
+ * Generate a testing krb5.conf file with a nonexistent default realm so
+ * that we can be sure that our principals will stay fully-qualified in
+ * the logs.
+ */
+ kerberos_generate_conf("bogus.example.com");
+
+ plan_lazy();
+ run_script_dir("data/scripts/no-cache", &config);
+
+ return 0;
+}
diff --git a/tests/module/pam-user-t.c b/tests/module/pam-user-t.c
new file mode 100644
index 000000000000..72cc21eebae3
--- /dev/null
+++ b/tests/module/pam-user-t.c
@@ -0,0 +1,80 @@
+/*
+ * Tests for PAM_USER handling.
+ *
+ * This test case includes tests that require Kerberos to be configured and a
+ * username and password available, but which don't write a ticket cache
+ * (which requires additional work to test the cache ownership).
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2014, 2020 Russ Allbery <eagle@eyrie.org>
+ *
+ * SPDX-License-Identifier: BSD-3-clause or GPL-1+
+ */
+
+#include <config.h>
+#include <portable/system.h>
+
+#include <tests/fakepam/script.h>
+#include <tests/tap/kerberos.h>
+#include <tests/tap/macros.h>
+
+
+/*
+ * Callback to check that PAM_USER matches the desired value, passed in as the
+ * data parameter.
+ */
+static void
+check_pam_user(pam_handle_t *pamh, const struct script_config *config UNUSED,
+ void *data)
+{
+ int retval;
+ const char *name = NULL;
+ const char *expected = data;
+
+ retval = pam_get_item(pamh, PAM_USER, (PAM_CONST void **) &name);
+ is_int(PAM_SUCCESS, retval, "Found PAM_USER");
+ is_string(expected, name, "...matching %s", expected);
+}
+
+
+int
+main(void)
+{
+ struct script_config config;
+ struct kerberos_config *krbconf;
+
+ /* Load the Kerberos principal and password from a file. */
+ krbconf = kerberos_setup(TAP_KRB_NEEDS_PASSWORD);
+ memset(&config, 0, sizeof(config));
+ config.password = krbconf->password;
+ config.callback = check_pam_user;
+ config.extra[0] = krbconf->username;
+ config.extra[1] = krbconf->userprinc;
+
+ /*
+ * Generate a testing krb5.conf file matching the realm of the Kerberos
+ * configuration so that canonicalization will work.
+ */
+ kerberos_generate_conf(krbconf->realm);
+
+ /* Declare our plan. */
+ plan_lazy();
+
+ /* Authentication without a realm. No canonicalization. */
+ config.user = krbconf->username;
+ config.data = krbconf->username;
+ run_script("data/scripts/pam-user/update", &config);
+
+ /* Authentication with the local realm. Should be canonicalized. */
+ config.user = krbconf->userprinc;
+ run_script("data/scripts/pam-user/update", &config);
+
+ /*
+ * Now, test again with user updates disabled. The PAM_USER value should
+ * now not be canonicalized.
+ */
+ config.data = krbconf->userprinc;
+ run_script("data/scripts/pam-user/no-update", &config);
+
+ return 0;
+}
diff --git a/tests/module/password-t.c b/tests/module/password-t.c
new file mode 100644
index 000000000000..bdf9762bc6cb
--- /dev/null
+++ b/tests/module/password-t.c
@@ -0,0 +1,152 @@
+/*
+ * Authentication tests for the pam-krb5 module with ticket cache.
+ *
+ * This test case includes all tests that require Kerberos to be configured, a
+ * username and password available, and a ticket cache created, but with the
+ * PAM module running as the same user for which the ticket cache will be
+ * created (so without setuid and with chown doing nothing).
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2020 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2011-2012, 2014
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * SPDX-License-Identifier: BSD-3-clause or GPL-1+
+ */
+
+#include <config.h>
+#include <portable/krb5.h>
+#include <portable/pam.h>
+#include <portable/system.h>
+
+#include <pwd.h>
+#include <sys/stat.h>
+#include <time.h>
+
+#include <tests/fakepam/pam.h>
+#include <tests/fakepam/script.h>
+#include <tests/tap/basic.h>
+#include <tests/tap/kerberos.h>
+#include <tests/tap/macros.h>
+#include <tests/tap/process.h>
+#include <tests/tap/string.h>
+
+
+static void
+check_authtok(pam_handle_t *pamh, const struct script_config *config,
+ void *data UNUSED)
+{
+ int retval;
+ const char *authtok;
+
+ retval = pam_get_item(pamh, PAM_AUTHTOK, (PAM_CONST void **) &authtok);
+ is_int(PAM_SUCCESS, retval, "Found PAM_AUTHTOK");
+ is_string(config->newpass, authtok, "...and it is correct");
+}
+
+
+int
+main(void)
+{
+ struct script_config config;
+ struct kerberos_config *krbconf;
+ char *newpass;
+
+ /* Load the Kerberos principal and password from a file. */
+ krbconf = kerberos_setup(TAP_KRB_NEEDS_PASSWORD);
+ memset(&config, 0, sizeof(config));
+ config.user = krbconf->username;
+ config.password = krbconf->password;
+ config.extra[0] = krbconf->userprinc;
+
+ /* Generate a testing krb5.conf file. */
+ kerberos_generate_conf(krbconf->realm);
+
+ plan_lazy();
+
+ /*
+ * First test trying to change the password to something that's
+ * excessively long.
+ */
+ newpass = bcalloc_type(PAM_MAX_RESP_SIZE + 1, char);
+ memset(newpass, 'a', PAM_MAX_RESP_SIZE);
+ config.newpass = newpass;
+ run_script("data/scripts/password/too-long", &config);
+ run_script("data/scripts/password/too-long-debug", &config);
+
+ /* Test use_authtok with an excessively long password. */
+ config.newpass = NULL;
+ config.authtok = newpass;
+ run_script("data/scripts/password/authtok-too-long", &config);
+ run_script("data/scripts/password/authtok-too-long-debug", &config);
+
+ /*
+ * Change the password to something new. This needs to be sufficiently
+ * random that it's unlikely to fall afoul of password strength checking.
+ */
+ free(newpass);
+ config.authtok = NULL;
+ basprintf(&newpass, "ngh1,a%lu nn9af6%lu", (unsigned long) getpid(),
+ (unsigned long) time(NULL));
+ config.newpass = newpass;
+ run_script("data/scripts/password/basic", &config);
+ config.password = newpass;
+ config.newpass = krbconf->password;
+ run_script("data/scripts/password/basic-debug", &config);
+
+ /* Test prompt_principal with password change. */
+ config.password = krbconf->password;
+ config.newpass = newpass;
+ run_script("data/scripts/password/prompt-principal", &config);
+
+ /* Change the password back and test expose-account. */
+ config.password = newpass;
+ config.newpass = krbconf->password;
+ run_script("data/scripts/password/expose", &config);
+
+ /*
+ * Test two banner settings by changing the password and then changing it
+ * back again.
+ */
+ config.password = krbconf->password;
+ config.newpass = newpass;
+ run_script("data/scripts/password/banner", &config);
+ config.password = newpass;
+ config.newpass = krbconf->password;
+ run_script("data/scripts/password/no-banner", &config);
+
+ /* Do the same, but with expose_account set as well. */
+ config.password = krbconf->password;
+ config.newpass = newpass;
+ run_script("data/scripts/password/banner-expose", &config);
+ config.password = newpass;
+ config.newpass = krbconf->password;
+ run_script("data/scripts/password/no-banner-expose", &config);
+
+ /* Test use_authtok. */
+ config.password = krbconf->password;
+ config.newpass = NULL;
+ config.authtok = newpass;
+ run_script("data/scripts/password/authtok", &config);
+
+ /* Test use_authtok with force_first_pass. */
+ config.password = NULL;
+ config.authtok = krbconf->password;
+ config.oldauthtok = newpass;
+ run_script("data/scripts/password/authtok-force", &config);
+
+ /*
+ * Ensure PAM_AUTHTOK and PAM_OLDAUTHTOK are set even if the user is
+ * ignored.
+ */
+ config.user = "root";
+ config.authtok = NULL;
+ config.oldauthtok = NULL;
+ config.password = "old-password";
+ config.newpass = "new-password";
+ config.callback = check_authtok;
+ run_script("data/scripts/password/ignore", &config);
+
+ free(newpass);
+ return 0;
+}
diff --git a/tests/module/pkinit-t.c b/tests/module/pkinit-t.c
new file mode 100644
index 000000000000..6bbb6993b2af
--- /dev/null
+++ b/tests/module/pkinit-t.c
@@ -0,0 +1,98 @@
+/*
+ * PKINIT authentication tests for the pam-krb5 module.
+ *
+ * This test case includes tests that require a PKINIT certificate, but which
+ * don't write a Kerberos ticket cache.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2020 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2012
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * SPDX-License-Identifier: BSD-3-clause or GPL-1+
+ */
+
+#include <config.h>
+#include <portable/system.h>
+
+#include <tests/fakepam/script.h>
+#include <tests/tap/kerberos.h>
+#include <tests/tap/process.h>
+#include <tests/tap/string.h>
+
+
+int
+main(void)
+{
+ struct script_config config;
+ struct kerberos_config *krbconf;
+#if defined(HAVE_KRB5_MIT) && defined(PATH_OPENSSL)
+ const char **generate_pkcs12;
+ char *tmpdir, *pkcs12_path;
+#endif
+
+ /* Load the Kerberos principal and certificate path. */
+ krbconf = kerberos_setup(TAP_KRB_NEEDS_PKINIT);
+ memset(&config, 0, sizeof(config));
+ config.user = krbconf->pkinit_principal;
+ config.extra[0] = krbconf->pkinit_cert;
+
+ /*
+ * Generate a testing krb5.conf file with a nonexistent default realm so
+ * that we can be sure that our principals will stay fully-qualified in
+ * the logs.
+ */
+ kerberos_generate_conf("bogus.example.com");
+
+ /* Check things that are the same with both Kerberos implementations. */
+ plan_lazy();
+ run_script("data/scripts/pkinit/basic", &config);
+ run_script("data/scripts/pkinit/basic-debug", &config);
+ run_script("data/scripts/pkinit/prompt-use", &config);
+ run_script("data/scripts/pkinit/prompt-try", &config);
+ run_script("data/scripts/pkinit/try-pkinit", &config);
+
+ /* Debugging output is a little different between the implementations. */
+#ifdef HAVE_KRB5_HEIMDAL
+ run_script("data/scripts/pkinit/try-pkinit-debug", &config);
+#else
+ run_script("data/scripts/pkinit/try-pkinit-debug-mit", &config);
+#endif
+
+ /* Only MIT Kerberos supports setting preauth options. */
+#ifdef HAVE_KRB5_MIT
+ run_script("data/scripts/pkinit/preauth-opt-mit", &config);
+#endif
+
+ /*
+ * If OpenSSL is available, test prompting with MIT Kerberos since we have
+ * to implement the prompting for the use_pkinit case ourselves. To do
+ * this, convert the input PKINIT certificate to a PKCS12 file with a
+ * password.
+ */
+#if defined(HAVE_KRB5_MIT) && defined(PATH_OPENSSL)
+ tmpdir = test_tmpdir();
+ basprintf(&pkcs12_path, "%s/%s", tmpdir, "pkinit-pkcs12");
+ generate_pkcs12 = bcalloc_type(10, const char *);
+ generate_pkcs12[0] = PATH_OPENSSL;
+ generate_pkcs12[1] = "pkcs12";
+ generate_pkcs12[2] = "-export";
+ generate_pkcs12[3] = "-in";
+ generate_pkcs12[4] = krbconf->pkinit_cert;
+ generate_pkcs12[5] = "-password";
+ generate_pkcs12[6] = "pass:some-password";
+ generate_pkcs12[7] = "-out";
+ generate_pkcs12[8] = pkcs12_path;
+ generate_pkcs12[9] = NULL;
+ run_setup(generate_pkcs12);
+ free(generate_pkcs12);
+ config.extra[0] = pkcs12_path;
+ config.extra[1] = "some-password";
+ run_script("data/scripts/pkinit/pin-mit", &config);
+ unlink(pkcs12_path);
+ free(pkcs12_path);
+ test_tmpdir_free(tmpdir);
+#endif /* HAVE_KRB5_MIT && PATH_OPENSSL */
+
+ return 0;
+}
diff --git a/tests/module/realm-t.c b/tests/module/realm-t.c
new file mode 100644
index 000000000000..d5643ca1f3e5
--- /dev/null
+++ b/tests/module/realm-t.c
@@ -0,0 +1,87 @@
+/*
+ * Authentication tests for realm support in pam-krb5.
+ *
+ * Test the realm and user_realm option in the PAM configuration, which is
+ * special in several ways since it influences krb5.conf parsing and is read
+ * out of order in the initial configuration.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2020 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2011-2012
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * SPDX-License-Identifier: BSD-3-clause or GPL-1+
+ */
+
+#include <config.h>
+#include <portable/krb5.h>
+#include <portable/system.h>
+
+#include <pwd.h>
+
+#include <tests/fakepam/pam.h>
+#include <tests/fakepam/script.h>
+#include <tests/tap/basic.h>
+#include <tests/tap/kerberos.h>
+#include <tests/tap/string.h>
+
+
+int
+main(void)
+{
+ struct script_config config;
+ struct kerberos_config *krbconf;
+ struct passwd pwd;
+ FILE *file;
+ char *k5login;
+
+ /* Load the Kerberos principal and password from a file. */
+ krbconf = kerberos_setup(TAP_KRB_NEEDS_PASSWORD);
+ memset(&config, 0, sizeof(config));
+ config.user = krbconf->username;
+ config.authtok = krbconf->password;
+
+ /* Don't keep track of the tests in each script. */
+ plan_lazy();
+
+ /* Start with a nonexistent default realm for authentication failure. */
+ kerberos_generate_conf("bogus.example.com");
+ config.extra[0] = "bogus.example.com";
+ run_script("data/scripts/realm/fail-no-realm", &config);
+ run_script("data/scripts/realm/fail-no-realm-debug", &config);
+
+ /* Running a script that sets realm properly should pass. */
+ config.extra[0] = krbconf->realm;
+ run_script("data/scripts/realm/pass-realm", &config);
+
+ /* Setting user_realm should continue to fail due to no .k5login file. */
+ run_script("data/scripts/realm/fail-user-realm", &config);
+
+ /* If we add a .k5login file for the user, user_realm should work. */
+ pwd.pw_name = krbconf->username;
+ pwd.pw_uid = getuid();
+ pwd.pw_gid = getgid();
+ pwd.pw_dir = test_tmpdir();
+ pam_set_pwd(&pwd);
+ basprintf(&k5login, "%s/.k5login", pwd.pw_dir);
+ file = fopen(k5login, "w");
+ if (file == NULL)
+ sysbail("cannot create %s", k5login);
+ if (fprintf(file, "%s\n", krbconf->userprinc) < 0)
+ sysbail("cannot write to %s", k5login);
+ if (fclose(file) < 0)
+ sysbail("cannot flush %s", k5login);
+ run_script("data/scripts/realm/pass-user-realm", &config);
+ pam_set_pwd(NULL);
+ unlink(k5login);
+ free(k5login);
+ test_tmpdir_free(pwd.pw_dir);
+
+ /* Switch to the correct realm, but set the wrong realm in PAM. */
+ kerberos_generate_conf(krbconf->realm);
+ config.extra[0] = "bogus.example.com";
+ run_script("data/scripts/realm/fail-realm", &config);
+ run_script("data/scripts/realm/fail-bad-user-realm", &config);
+
+ return 0;
+}
diff --git a/tests/module/stacked-t.c b/tests/module/stacked-t.c
new file mode 100644
index 000000000000..ef8e70885ecb
--- /dev/null
+++ b/tests/module/stacked-t.c
@@ -0,0 +1,50 @@
+/*
+ * Authentication tests for the pam-krb5 module with an existing AUTHTOK.
+ *
+ * This test case includes tests that require Kerberos to be configured and a
+ * username and password available and that run with AUTHTOK already set, but
+ * which don't write a ticket cache (which requires additional work to test
+ * the cache ownership).
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2020 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2011-2012
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * SPDX-License-Identifier: BSD-3-clause or GPL-1+
+ */
+
+#include <config.h>
+#include <portable/system.h>
+
+#include <tests/fakepam/script.h>
+#include <tests/tap/kerberos.h>
+#include <tests/tap/process.h>
+#include <tests/tap/string.h>
+
+
+int
+main(void)
+{
+ struct script_config config;
+ struct kerberos_config *krbconf;
+
+ /* Load the Kerberos principal and password from a file. */
+ krbconf = kerberos_setup(TAP_KRB_NEEDS_PASSWORD);
+ memset(&config, 0, sizeof(config));
+ config.user = krbconf->userprinc;
+ config.password = krbconf->password;
+ config.authtok = krbconf->password;
+
+ /*
+ * Generate a testing krb5.conf file with a nonexistent default realm so
+ * that we can be sure that our principals will stay fully-qualified in
+ * the logs.
+ */
+ kerberos_generate_conf("bogus.example.com");
+
+ plan_lazy();
+ run_script_dir("data/scripts/stacked", &config);
+
+ return 0;
+}
diff --git a/tests/module/trace-t.c b/tests/module/trace-t.c
new file mode 100644
index 000000000000..db3aa67f9e24
--- /dev/null
+++ b/tests/module/trace-t.c
@@ -0,0 +1,48 @@
+/*
+ * Tests for trace logging in the pam-krb5 module.
+ *
+ * Checks that trace logging is handled properly. This is currently very
+ * simple and just checks that the file is created.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2020 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2012
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * SPDX-License-Identifier: BSD-3-clause or GPL-1+
+ */
+
+#include <config.h>
+#include <portable/system.h>
+
+#include <tests/fakepam/script.h>
+#include <tests/tap/basic.h>
+#include <tests/tap/string.h>
+
+
+int
+main(void)
+{
+ struct script_config config;
+ char *tmpdir, *trace;
+
+ plan_lazy();
+
+ memset(&config, 0, sizeof(config));
+ config.user = "testuser";
+ tmpdir = test_tmpdir();
+ basprintf(&trace, "%s/trace", tmpdir);
+ config.extra[0] = trace;
+#ifdef HAVE_KRB5_SET_TRACE_FILENAME
+ run_script("data/scripts/trace/supported", &config);
+ is_int(0, access(trace, F_OK), "Trace file was created");
+ unlink(trace);
+#else
+ run_script("data/scripts/trace/unsupported", &config);
+ is_int(-1, access(trace, F_OK), "Trace file does not exist");
+#endif
+
+ free(trace);
+ test_tmpdir_free(tmpdir);
+ return 0;
+}