xref: /freebsd/contrib/pam-krb5/module/cache.c (revision bf6873c5786e333d679a7838d28812febf479a8a)
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