xref: /freebsd/contrib/pam-krb5/tests/module/cache-t.c (revision bf6873c5786e333d679a7838d28812febf479a8a)
1 /*
2  * Authentication tests for the pam-krb5 module with ticket cache.
3  *
4  * This test case includes all tests that require Kerberos to be configured, a
5  * username and password available, and a ticket cache created, but with the
6  * PAM module running as the same user for which the ticket cache will be
7  * created (so without setuid and with chown doing nothing).
8  *
9  * Written by Russ Allbery <eagle@eyrie.org>
10  * Copyright 2017, 2020-2021 Russ Allbery <eagle@eyrie.org>
11  * Copyright 2011, 2012
12  *     The Board of Trustees of the Leland Stanford Junior University
13  *
14  * SPDX-License-Identifier: BSD-3-clause or GPL-1+
15  */
16 
17 #include <config.h>
18 #include <portable/krb5.h>
19 #include <portable/system.h>
20 
21 #include <pwd.h>
22 #include <sys/stat.h>
23 #include <time.h>
24 
25 #include <tests/fakepam/pam.h>
26 #include <tests/fakepam/script.h>
27 #include <tests/tap/basic.h>
28 #include <tests/tap/kerberos.h>
29 #include <tests/tap/process.h>
30 #include <tests/tap/string.h>
31 
32 /* Additional data used by the cache check callback. */
33 struct extra {
34     char *realm;
35     char *cache_path;
36 };
37 
38 
39 /*
40  * PAM test callback to check whether we created a ticket cache and the ticket
41  * cache is for the correct user.
42  */
43 static void
check_cache(const char * file,const struct script_config * config,const struct extra * extra)44 check_cache(const char *file, const struct script_config *config,
45             const struct extra *extra)
46 {
47     struct stat st;
48     krb5_error_code code;
49     krb5_context ctx = NULL;
50     krb5_ccache ccache = NULL;
51     krb5_principal princ = NULL;
52     krb5_principal tgtprinc = NULL;
53     krb5_creds in, out;
54     char *principal = NULL;
55 
56     /* Check ownership and permissions. */
57     is_int(0, stat(file, &st), "cache exists");
58     is_int(getuid(), st.st_uid, "...with correct UID");
59     is_int(getgid(), st.st_gid, "...with correct GID");
60     is_int(0600, (st.st_mode & 0777), "...with correct permissions");
61 
62     /* Check the existence of the ticket cache and its principal. */
63     code = krb5_init_context(&ctx);
64     if (code != 0)
65         bail("cannot create Kerberos context");
66     code = krb5_cc_resolve(ctx, file, &ccache);
67     is_int(0, code, "able to resolve Kerberos ticket cache");
68     code = krb5_cc_get_principal(ctx, ccache, &princ);
69     is_int(0, code, "able to get principal");
70     code = krb5_unparse_name(ctx, princ, &principal);
71     is_int(0, code, "...and principal is valid");
72     is_string(config->extra[0], principal, "...and matches our principal");
73 
74     /* Retrieve the krbtgt for the realm and check properties. */
75     code = krb5_build_principal_ext(
76         ctx, &tgtprinc, (unsigned int) strlen(extra->realm), extra->realm,
77         KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME, strlen(extra->realm), extra->realm,
78         NULL);
79     if (code != 0)
80         bail("cannot create krbtgt principal name");
81     memset(&in, 0, sizeof(in));
82     memset(&out, 0, sizeof(out));
83     in.server = tgtprinc;
84     in.client = princ;
85     code = krb5_cc_retrieve_cred(ctx, ccache, KRB5_TC_MATCH_SRV_NAMEONLY, &in,
86                                  &out);
87     is_int(0, code, "able to get krbtgt credentials");
88     ok(out.times.endtime > time(NULL) + 30 * 60, "...good for 30 minutes");
89     krb5_free_cred_contents(ctx, &out);
90 
91     /* Close things and release memory. */
92     krb5_free_principal(ctx, tgtprinc);
93     krb5_free_unparsed_name(ctx, principal);
94     krb5_free_principal(ctx, princ);
95     krb5_cc_close(ctx, ccache);
96     krb5_free_context(ctx);
97 }
98 
99 
100 /*
101  * Same as check_cache except unlink the ticket cache afterwards.  Used to
102  * check the ticket cache in cases where the PAM module will not clean it up
103  * afterwards, such as calling pam_end with PAM_DATA_SILENT.
104  */
105 static void
check_cache_callback(pam_handle_t * pamh,const struct script_config * config,void * data)106 check_cache_callback(pam_handle_t *pamh, const struct script_config *config,
107                      void *data)
108 {
109     struct extra *extra = data;
110     const char *cache, *file;
111     char *prefix;
112 
113     cache = pam_getenv(pamh, "KRB5CCNAME");
114     ok(cache != NULL, "KRB5CCNAME is set in PAM environment");
115     if (cache == NULL)
116         return;
117     basprintf(&prefix, "FILE:/tmp/krb5cc_%lu_", (unsigned long) getuid());
118     diag("KRB5CCNAME = %s", cache);
119     ok(strncmp(prefix, cache, strlen(prefix)) == 0,
120        "cache file name prefix is correct");
121     free(prefix);
122     file = cache + strlen("FILE:");
123     extra->cache_path = bstrdup(file);
124     check_cache(file, config, extra);
125 }
126 
127 
128 int
main(void)129 main(void)
130 {
131     struct script_config config;
132     struct kerberos_config *krbconf;
133     char *k5login;
134     struct extra extra;
135     struct passwd pwd;
136     FILE *file;
137 
138     /* Load the Kerberos principal and password from a file. */
139     krbconf = kerberos_setup(TAP_KRB_NEEDS_PASSWORD);
140     memset(&config, 0, sizeof(config));
141     config.user = krbconf->username;
142     extra.realm = krbconf->realm;
143     extra.cache_path = NULL;
144     config.authtok = krbconf->password;
145     config.extra[0] = krbconf->userprinc;
146 
147     /* Generate a testing krb5.conf file. */
148     kerberos_generate_conf(krbconf->realm);
149 
150     /* Create a fake passwd struct for our user. */
151     memset(&pwd, 0, sizeof(pwd));
152     pwd.pw_name = krbconf->username;
153     pwd.pw_uid = getuid();
154     pwd.pw_gid = getgid();
155     basprintf(&pwd.pw_dir, "%s/tmp", getenv("BUILD"));
156     pam_set_pwd(&pwd);
157 
158     plan_lazy();
159 
160     /* Basic test. */
161     run_script("data/scripts/cache/basic", &config);
162 
163     /* Check the cache status before the session is closed. */
164     config.callback = check_cache_callback;
165     config.data = &extra;
166     run_script("data/scripts/cache/open-session", &config);
167     free(extra.cache_path);
168     extra.cache_path = NULL;
169 
170     /*
171      * Try again but passing PAM_DATA_SILENT to pam_end.  This should leave
172      * the ticket cache intact.
173      */
174     run_script("data/scripts/cache/end-data-silent", &config);
175     check_cache(extra.cache_path, &config, &extra);
176     if (unlink(extra.cache_path) < 0)
177         sysdiag("unable to unlink temporary cache %s", extra.cache_path);
178     free(extra.cache_path);
179     extra.cache_path = NULL;
180 
181     /* Change the authenticating user and test search_k5login. */
182     pwd.pw_name = (char *) "testuser";
183     pam_set_pwd(&pwd);
184     config.user = "testuser";
185     basprintf(&k5login, "%s/.k5login", pwd.pw_dir);
186     file = fopen(k5login, "w");
187     if (file == NULL)
188         sysbail("cannot create %s", k5login);
189     if (fprintf(file, "%s\n", krbconf->userprinc) < 0)
190         sysbail("cannot write to %s", k5login);
191     if (fclose(file) < 0)
192         sysbail("cannot flush %s", k5login);
193     run_script("data/scripts/cache/search-k5login", &config);
194     free(extra.cache_path);
195     extra.cache_path = NULL;
196     config.callback = NULL;
197     run_script("data/scripts/cache/search-k5login-debug", &config);
198     unlink(k5login);
199     free(k5login);
200 
201     /* Test search_k5login when no .k5login file exists. */
202     pwd.pw_name = krbconf->username;
203     pam_set_pwd(&pwd);
204     config.user = krbconf->username;
205     diag("testing search_k5login with no .k5login file");
206     run_script("data/scripts/cache/search-k5login", &config);
207 
208     free(pwd.pw_dir);
209     return 0;
210 }
211