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
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;
+}