1 /*
2 * Ticket cache initialization.
3 *
4 * Provides functions for creating ticket caches, used by pam_authenticate,
5 * pam_setcred, and pam_chauthtok after changing an expired password.
6 *
7 * Copyright 2005-2009, 2014, 2020 Russ Allbery <eagle@eyrie.org>
8 * Copyright 2011-2012
9 * The Board of Trustees of the Leland Stanford Junior University
10 * Copyright 2005 Andres Salomon <dilinger@debian.org>
11 * Copyright 1999-2000 Frank Cusack <fcusack@fcusack.com>
12 *
13 * SPDX-License-Identifier: BSD-3-clause or GPL-1+
14 */
15
16 #include <config.h>
17 #include <portable/krb5.h>
18 #include <portable/pam.h>
19 #include <portable/system.h>
20
21 #include <errno.h>
22
23 #include <module/internal.h>
24 #include <pam-util/args.h>
25 #include <pam-util/logging.h>
26
27
28 /*
29 * Get the name of a cache. Takes the name of the environment variable that
30 * should be set to indicate which cache to use, either the permanent cache
31 * (KRB5CCNAME) or the temporary cache (PAM_KRB5CCNAME).
32 *
33 * Treat an empty environment variable setting the same as if the variable
34 * was not set, since on FreeBSD we can't delete the environment variable,
35 * only set it to an empty value.
36 */
37 const char *
pamk5_get_krb5ccname(struct pam_args * args,const char * key)38 pamk5_get_krb5ccname(struct pam_args *args, const char *key)
39 {
40 const char *name;
41
42 /* When refreshing a cache, we need to try the regular environment. */
43 name = pam_getenv(args->pamh, key);
44 if (name == NULL || *name == '\0')
45 name = getenv(key);
46 if (name == NULL || *name == '\0')
47 return NULL;
48 else
49 return name;
50 }
51
52
53 /*
54 * Put the ticket cache information into the environment. Takes the path and
55 * the environment variable to set, since this is used both for the permanent
56 * cache (KRB5CCNAME) and the temporary cache (PAM_KRB5CCNAME). Returns a PAM
57 * status code.
58 */
59 int
pamk5_set_krb5ccname(struct pam_args * args,const char * name,const char * key)60 pamk5_set_krb5ccname(struct pam_args *args, const char *name, const char *key)
61 {
62 char *env_name = NULL;
63 int pamret;
64
65 if (asprintf(&env_name, "%s=%s", key, name) < 0) {
66 putil_crit(args, "asprintf failed: %s", strerror(errno));
67 pamret = PAM_BUF_ERR;
68 goto done;
69 }
70 pamret = pam_putenv(args->pamh, env_name);
71 if (pamret != PAM_SUCCESS) {
72 putil_err_pam(args, pamret, "pam_putenv failed");
73 pamret = PAM_SERVICE_ERR;
74 goto done;
75 }
76 pamret = PAM_SUCCESS;
77
78 done:
79 free(env_name);
80 return pamret;
81 }
82
83
84 /*
85 * Given the template for a ticket cache name, initialize that file securely
86 * mkstemp. Returns a PAM success or error code.
87 */
88 int
pamk5_cache_mkstemp(struct pam_args * args,char * template)89 pamk5_cache_mkstemp(struct pam_args *args, char *template)
90 {
91 int ccfd, oerrno;
92
93 ccfd = mkstemp(template);
94 if (ccfd < 0) {
95 oerrno = errno;
96 putil_crit(args, "mkstemp(\"%s\") failed: %s", template,
97 strerror(errno));
98 errno = oerrno;
99 return PAM_SERVICE_ERR;
100 }
101 close(ccfd);
102 return PAM_SUCCESS;
103 }
104
105
106 /*
107 * Given a cache name and the initial credentials, initialize the cache, store
108 * the credentials in that cache, and return a pointer to the new cache in the
109 * cache argument. Returns a PAM success or error code.
110 */
111 int
pamk5_cache_init(struct pam_args * args,const char * ccname,krb5_creds * creds,krb5_ccache * cache)112 pamk5_cache_init(struct pam_args *args, const char *ccname, krb5_creds *creds,
113 krb5_ccache *cache)
114 {
115 struct context *ctx;
116 int retval;
117
118 if (args == NULL || args->config == NULL || args->config->ctx == NULL
119 || args->config->ctx->context == NULL)
120 return PAM_SERVICE_ERR;
121 ctx = args->config->ctx;
122 retval = krb5_cc_resolve(ctx->context, ccname, cache);
123 if (retval != 0) {
124 putil_err_krb5(args, retval, "cannot resolve ticket cache %s", ccname);
125 retval = PAM_SERVICE_ERR;
126 goto done;
127 }
128 retval = krb5_cc_initialize(ctx->context, *cache, ctx->princ);
129 if (retval != 0) {
130 putil_err_krb5(args, retval, "cannot initialize ticket cache %s",
131 ccname);
132 retval = PAM_SERVICE_ERR;
133 goto done;
134 }
135 retval = krb5_cc_store_cred(ctx->context, *cache, creds);
136 if (retval != 0) {
137 putil_err_krb5(args, retval, "cannot store credentials in %s", ccname);
138 retval = PAM_SERVICE_ERR;
139 goto done;
140 }
141
142 done:
143 if (retval != PAM_SUCCESS && *cache != NULL) {
144 krb5_cc_destroy(ctx->context, *cache);
145 *cache = NULL;
146 }
147 return retval;
148 }
149
150
151 /*
152 * Initialize an internal ticket cache with a random name, store the given
153 * credentials in the cache, and store the cache in the context. Put the path
154 * in PAM_KRB5CCNAME where it can be picked up later by pam_setcred. Returns
155 * a PAM success or error code.
156 */
157 int
pamk5_cache_init_random(struct pam_args * args,krb5_creds * creds)158 pamk5_cache_init_random(struct pam_args *args, krb5_creds *creds)
159 {
160 char *cache_name = NULL;
161 const char *dir;
162 int pamret;
163
164 /* Store the obtained credentials in a temporary cache. */
165 dir = args->config->ccache_dir;
166 if (strncmp("FILE:", args->config->ccache_dir, strlen("FILE:")) == 0)
167 dir += strlen("FILE:");
168 if (asprintf(&cache_name, "%s/krb5cc_pam_XXXXXX", dir) < 0) {
169 putil_crit(args, "malloc failure: %s", strerror(errno));
170 return PAM_SERVICE_ERR;
171 }
172 pamret = pamk5_cache_mkstemp(args, cache_name);
173 if (pamret != PAM_SUCCESS)
174 goto done;
175 pamret =
176 pamk5_cache_init(args, cache_name, creds, &args->config->ctx->cache);
177 if (pamret != PAM_SUCCESS)
178 goto done;
179 putil_debug(args, "temporarily storing credentials in %s", cache_name);
180 pamret = pamk5_set_krb5ccname(args, cache_name, "PAM_KRB5CCNAME");
181
182 done:
183 free(cache_name);
184 return pamret;
185 }
186