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 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 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 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