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