1 /*
2 * Tests for the pam-krb5 module with an expired password.
3 *
4 * This test case checks correct handling of an account whose password has
5 * expired and the multiple different paths the module can take for handling
6 * that case.
7 *
8 * Written by Russ Allbery <eagle@eyrie.org>
9 * Copyright 2020 Russ Allbery <eagle@eyrie.org>
10 * Copyright 2011-2012
11 * The Board of Trustees of the Leland Stanford Junior University
12 *
13 * SPDX-License-Identifier: BSD-3-clause or GPL-1+
14 */
15
16 #include <config.h>
17 #include <portable/system.h>
18
19 #include <pwd.h>
20 #include <time.h>
21
22 #include <tests/fakepam/pam.h>
23 #include <tests/fakepam/script.h>
24 #include <tests/tap/basic.h>
25 #include <tests/tap/kadmin.h>
26 #include <tests/tap/kerberos.h>
27 #include <tests/tap/process.h>
28 #include <tests/tap/string.h>
29
30
31 int
main(void)32 main(void)
33 {
34 struct script_config config;
35 struct kerberos_config *krbconf;
36 char *newpass, *date;
37 struct passwd pwd;
38 time_t now;
39
40 /* Load the Kerberos principal and password from a file. */
41 krbconf = kerberos_setup(TAP_KRB_NEEDS_PASSWORD);
42 memset(&config, 0, sizeof(config));
43 config.user = krbconf->username;
44 config.password = krbconf->password;
45 config.extra[0] = krbconf->userprinc;
46
47 /*
48 * Ensure we can expire the password. Heimdal has a prompt for the
49 * expiration time, so save that to use as a substitution in the script.
50 */
51 now = time(NULL) - 1;
52 if (!kerberos_expire_password(krbconf->userprinc, now))
53 skip_all("kadmin not configured or kadmin mismatch");
54 date = bstrdup(ctime(&now));
55 date[strlen(date) - 1] = '\0';
56 config.extra[1] = date;
57
58 /* Generate a testing krb5.conf file. */
59 kerberos_generate_conf(krbconf->realm);
60
61 /* Create a fake passwd struct for our user. */
62 memset(&pwd, 0, sizeof(pwd));
63 pwd.pw_name = krbconf->username;
64 pwd.pw_uid = getuid();
65 pwd.pw_gid = getgid();
66 basprintf(&pwd.pw_dir, "%s/tmp", getenv("BUILD"));
67 pam_set_pwd(&pwd);
68
69 /*
70 * We'll be changing the password to something new. This needs to be
71 * sufficiently random that it's unlikely to fall afoul of password
72 * strength checking.
73 */
74 basprintf(&newpass, "ngh1,a%lu nn9af6", (unsigned long) getpid());
75 config.newpass = newpass;
76
77 plan_lazy();
78
79 /*
80 * Default behavior. We have to distinguish between two versions of
81 * Heimdal for testing because the prompts changed substantially. Use the
82 * existence of krb5_principal_set_comp_string to distinguish because it
83 * was introduced at the same time.
84 */
85 #ifdef HAVE_KRB5_HEIMDAL
86 # ifdef HAVE_KRB5_PRINCIPAL_SET_COMP_STRING
87 run_script("data/scripts/expired/basic-heimdal", &config);
88 config.newpass = krbconf->password;
89 config.password = newpass;
90 kerberos_expire_password(krbconf->userprinc, now);
91 run_script("data/scripts/expired/basic-heimdal-debug", &config);
92 # else
93 run_script("data/scripts/expired/basic-heimdal-old", &config);
94 config.newpass = krbconf->password;
95 config.password = newpass;
96 kerberos_expire_password(krbconf->userprinc, now);
97 run_script("data/scripts/expired/basic-heimdal-old-debug", &config);
98 # endif
99 #else
100 run_script("data/scripts/expired/basic-mit", &config);
101 config.newpass = krbconf->password;
102 config.password = newpass;
103 kerberos_expire_password(krbconf->userprinc, now);
104 run_script("data/scripts/expired/basic-mit-debug", &config);
105 #endif
106
107 /* Test again with PAM_SILENT, specified two ways. */
108 #ifdef HAVE_KRB5_HEIMDAL
109 config.newpass = newpass;
110 config.password = krbconf->password;
111 kerberos_expire_password(krbconf->userprinc, now);
112 run_script("data/scripts/expired/basic-heimdal-silent", &config);
113 config.newpass = krbconf->password;
114 config.password = newpass;
115 kerberos_expire_password(krbconf->userprinc, now);
116 run_script("data/scripts/expired/basic-heimdal-flag-silent", &config);
117 #else
118 config.newpass = newpass;
119 config.password = krbconf->password;
120 kerberos_expire_password(krbconf->userprinc, now);
121 run_script("data/scripts/expired/basic-mit-silent", &config);
122 config.newpass = krbconf->password;
123 config.password = newpass;
124 kerberos_expire_password(krbconf->userprinc, now);
125 run_script("data/scripts/expired/basic-mit-flag-silent", &config);
126 #endif
127
128 /*
129 * We can only run the remaining checks if we can suppress the Kerberos
130 * library behavior of prompting for a new password when the password has
131 * expired.
132 */
133 #ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_CHANGE_PASSWORD_PROMPT
134
135 /* Check the forced failure behavior. */
136 run_script("data/scripts/expired/fail", &config);
137 run_script("data/scripts/expired/fail-debug", &config);
138
139 /*
140 * Defer the error to the account management check.
141 *
142 * Skip this check on Heimdal currently (Heimdal 7.4.0) because its
143 * implementation of krb5_get_init_creds_opt_set_change_password_prompt is
144 * incomplete. See <https://github.com/heimdal/heimdal/issues/322>.
145 */
146 # ifdef HAVE_KRB5_HEIMDAL
147 skip_block(2, "deferring password changes broken in Heimdal");
148 # else
149 config.newpass = newpass;
150 config.password = krbconf->password;
151 config.authtok = krbconf->password;
152 kerberos_expire_password(krbconf->userprinc, now);
153 run_script("data/scripts/expired/defer-mit", &config);
154 config.newpass = krbconf->password;
155 config.password = newpass;
156 config.authtok = newpass;
157 kerberos_expire_password(krbconf->userprinc, now);
158 run_script("data/scripts/expired/defer-mit-debug", &config);
159 # endif
160
161 #else /* !HAVE_KRB5_GET_INIT_CREDS_OPT_SET_CHANGE_PASSWORD_PROMPT */
162
163 /* Mention that we skipped something for the record. */
164 skip_block(4, "cannot disable library password prompting");
165
166 #endif /* HAVE_KRB5_GET_INIT_CREDS_OPT_SET_CHANGE_PASSWORD_PROMPT */
167
168 /* In case we ran into some error, try to unexpire the password. */
169 kerberos_expire_password(krbconf->userprinc, 0);
170
171 free(date);
172 free(newpass);
173 free(pwd.pw_dir);
174 return 0;
175 }
176