xref: /freebsd/crypto/krb5/src/lib/krb5/ccache/cc_keyring.c (revision f1c4c3daccbaf3820f0e2224de53df12fc952fcc)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/ccache/cc_keyring.c */
3 /*
4  * Copyright (c) 2006
5  * The Regents of the University of Michigan
6  * ALL RIGHTS RESERVED
7  *
8  * Permission is granted to use, copy, create derivative works
9  * and redistribute this software and such derivative works
10  * for any purpose, so long as the name of The University of
11  * Michigan is not used in any advertising or publicity
12  * pertaining to the use of distribution of this software
13  * without specific, written prior authorization.  If the
14  * above copyright notice or any other identification of the
15  * University of Michigan is included in any copy of any
16  * portion of this software, then the disclaimer below must
17  * also be included.
18  *
19  * THIS SOFTWARE IS PROVIDED AS IS, WITHOUT REPRESENTATION
20  * FROM THE UNIVERSITY OF MICHIGAN AS TO ITS FITNESS FOR ANY
21  * PURPOSE, AND WITHOUT WARRANTY BY THE UNIVERSITY OF
22  * MICHIGAN OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
23  * WITHOUT LIMITATION THE IMPLIED WARRANTIES OF
24  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
25  * REGENTS OF THE UNIVERSITY OF MICHIGAN SHALL NOT BE LIABLE
26  * FOR ANY DAMAGES, INCLUDING SPECIAL, INDIRECT, INCIDENTAL, OR
27  * CONSEQUENTIAL DAMAGES, WITH RESPECT TO ANY CLAIM ARISING
28  * OUT OF OR IN CONNECTION WITH THE USE OF THE SOFTWARE, EVEN
29  * IF IT HAS BEEN OR IS HEREAFTER ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGES.
31  */
32 /*
33  * Copyright 1990,1991,1992,1993,1994,2000,2004 Massachusetts Institute of
34  * Technology.  All Rights Reserved.
35  *
36  * Original stdio support copyright 1995 by Cygnus Support.
37  *
38  * Export of this software from the United States of America may
39  *   require a specific license from the United States Government.
40  *   It is the responsibility of any person or organization contemplating
41  *   export to obtain such a license before exporting.
42  *
43  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
44  * distribute this software and its documentation for any purpose and
45  * without fee is hereby granted, provided that the above copyright
46  * notice appear in all copies and that both that copyright notice and
47  * this permission notice appear in supporting documentation, and that
48  * the name of M.I.T. not be used in advertising or publicity pertaining
49  * to distribution of the software without specific, written prior
50  * permission.  Furthermore if you modify this software you must label
51  * your software as modified software and not distribute it in such a
52  * fashion that it might be confused with the original M.I.T. software.
53  * M.I.T. makes no representations about the suitability of
54  * this software for any purpose.  It is provided "as is" without express
55  * or implied warranty.
56  */
57 
58 /*
59  * This file implements a collection-enabled credential cache type where the
60  * credentials are stored in the Linux keyring facility.
61  *
62  * A residual of this type can have three forms:
63  *    anchor:collection:subsidiary
64  *    anchor:collection
65  *    collection
66  *
67  * The anchor name is "process", "thread", or "legacy" and determines where we
68  * search for keyring collections.  In the third form, the anchor name is
69  * presumed to be "legacy".  The anchor keyring for legacy caches is the
70  * session keyring.
71  *
72  * If the subsidiary name is present, the residual identifies a single cache
73  * within a collection.  Otherwise, the residual identifies the collection
74  * itself.  When a residual identifying a collection is resolved, the
75  * collection's primary key is looked up (or initialized, using the collection
76  * name as the subsidiary name), and the resulting cache's name will use the
77  * first name form and will identify the primary cache.
78  *
79  * Keyring collections are named "_krb_<collection>" and are linked from the
80  * anchor keyring.  The keys within a keyring collection are links to cache
81  * keyrings, plus a link to one user key named "krb_ccache:primary" which
82  * contains a serialized representation of the collection version (currently 1)
83  * and the primary name of the collection.
84  *
85  * Cache keyrings contain one user key per credential which contains a
86  * serialized representation of the credential.  There is also one user key
87  * named "__krb5_princ__" which contains a serialized representation of the
88  * cache's default principal.
89  *
90  * If the anchor name is "legacy", then the initial primary cache (the one
91  * named with the collection name) is also linked to the session keyring, and
92  * we look for a cache in that location when initializing the collection.  This
93  * extra link allows that cache to be visible to old versions of the KEYRING
94  * cache type, and allows us to see caches created by that code.
95  */
96 
97 #include "cc-int.h"
98 
99 #ifdef USE_KEYRING_CCACHE
100 
101 #include <errno.h>
102 #include <keyutils.h>
103 
104 #ifdef DEBUG
105 #define KRCC_DEBUG          1
106 #endif
107 
108 #if KRCC_DEBUG
109 void debug_print(char *fmt, ...);       /* prototype to silence warning */
110 #include <syslog.h>
111 #define DEBUG_PRINT(x) debug_print x
112 void
debug_print(char * fmt,...)113 debug_print(char *fmt, ...)
114 {
115     va_list ap;
116     va_start(ap, fmt);
117 #ifdef DEBUG_STDERR
118     vfprintf(stderr, fmt, ap);
119 #else
120     vsyslog(LOG_ERR, fmt, ap);
121 #endif
122     va_end(ap);
123 }
124 #else
125 #define DEBUG_PRINT(x)
126 #endif
127 
128 /*
129  * We try to use the big_key key type for credentials except in legacy caches.
130  * We fall back to the user key type if the kernel does not support big_key.
131  * If the library doesn't support keyctl_get_persistent(), we don't even try
132  * big_key since the two features were added at the same time.
133  */
134 #ifdef HAVE_PERSISTENT_KEYRING
135 #define KRCC_CRED_KEY_TYPE "big_key"
136 #else
137 #define KRCC_CRED_KEY_TYPE "user"
138 #endif
139 
140 /*
141  * We use the "user" key type for collection primary names, for cache principal
142  * names, and for credentials in legacy caches.
143  */
144 #define KRCC_KEY_TYPE_USER "user"
145 
146 /*
147  * We create ccaches as separate keyrings
148  */
149 #define KRCC_KEY_TYPE_KEYRING "keyring"
150 
151 /*
152  * Special name of the key within a ccache keyring
153  * holding principal information
154  */
155 #define KRCC_SPEC_PRINC_KEYNAME "__krb5_princ__"
156 
157 /*
158  * Special name for the key to communicate the name(s)
159  * of credentials caches to be used for requests.
160  * This should currently contain a single name, but
161  * in the future may contain a list that may be
162  * intelligently chosen from.
163  */
164 #define KRCC_SPEC_CCACHE_SET_KEYNAME "__krb5_cc_set__"
165 
166 /*
167  * This name identifies the key containing the name of the current primary
168  * cache within a collection.
169  */
170 #define KRCC_COLLECTION_PRIMARY "krb_ccache:primary"
171 
172 /*
173  * If the library context does not specify a keyring collection, unique ccaches
174  * will be created within this collection.
175  */
176 #define KRCC_DEFAULT_UNIQUE_COLLECTION "session:__krb5_unique__"
177 
178 /*
179  * Collection keyring names begin with this prefix.  We use a prefix so that a
180  * cache keyring with the collection name itself can be linked directly into
181  * the anchor, for legacy session keyring compatibility.
182  */
183 #define KRCC_CCCOL_PREFIX "_krb_"
184 
185 /*
186  * For the "persistent" anchor type, we look up or create this fixed keyring
187  * name within the per-UID persistent keyring.
188  */
189 #define KRCC_PERSISTENT_KEYRING_NAME "_krb"
190 
191 /*
192  * Name of the key holding time offsets for the individual cache
193  */
194 #define KRCC_TIME_OFFSETS "__krb5_time_offsets__"
195 
196 /*
197  * Keyring name prefix and length of random name part
198  */
199 #define KRCC_NAME_PREFIX "krb_ccache_"
200 #define KRCC_NAME_RAND_CHARS 8
201 
202 #define KRCC_COLLECTION_VERSION 1
203 
204 #define KRCC_PERSISTENT_ANCHOR "persistent"
205 #define KRCC_PROCESS_ANCHOR "process"
206 #define KRCC_THREAD_ANCHOR "thread"
207 #define KRCC_SESSION_ANCHOR "session"
208 #define KRCC_USER_ANCHOR "user"
209 #define KRCC_LEGACY_ANCHOR "legacy"
210 
211 typedef struct _krcc_cursor
212 {
213     int numkeys;
214     int currkey;
215     key_serial_t princ_id;
216     key_serial_t offsets_id;
217     key_serial_t *keys;
218 } *krcc_cursor;
219 
220 /*
221  * This represents a credentials cache "file"
222  * where cache_id is the keyring serial number for
223  * this credentials cache "file".  Each key
224  * in the keyring contains a separate key.
225  */
226 typedef struct _krcc_data
227 {
228     char *name;                 /* Name for this credentials cache */
229     k5_cc_mutex lock;           /* synchronization */
230     key_serial_t collection_id; /* collection containing this cache keyring */
231     key_serial_t cache_id;      /* keyring representing ccache */
232     key_serial_t princ_id;      /* key holding principal info */
233     krb5_boolean is_legacy_type;
234 } krcc_data;
235 
236 /* Global mutex */
237 k5_cc_mutex krb5int_krcc_mutex = K5_CC_MUTEX_PARTIAL_INITIALIZER;
238 
239 extern const krb5_cc_ops krb5_krcc_ops;
240 
241 static const char *KRB5_CALLCONV
242 krcc_get_name(krb5_context context, krb5_ccache id);
243 
244 static krb5_error_code KRB5_CALLCONV
245 krcc_start_seq_get(krb5_context, krb5_ccache id, krb5_cc_cursor *cursor);
246 
247 static krb5_error_code KRB5_CALLCONV
248 krcc_next_cred(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor,
249                krb5_creds *creds);
250 
251 static krb5_error_code KRB5_CALLCONV
252 krcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor);
253 
254 static krb5_error_code KRB5_CALLCONV
255 krcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor);
256 
257 static krb5_error_code clear_cache_keyring(krb5_context context,
258                                            krb5_ccache id);
259 
260 static krb5_error_code make_krcc_data(const char *anchor_name,
261                                       const char *collection_name,
262                                       const char *subsidiary_name,
263                                       key_serial_t cache_id, key_serial_t
264                                       collection_id, krcc_data **datapp);
265 
266 static krb5_error_code save_principal(krb5_context context, krb5_ccache id,
267                                       krb5_principal princ);
268 
269 static krb5_error_code save_time_offsets(krb5_context context, krb5_ccache id,
270                                          int32_t time_offset,
271                                          int32_t usec_offset);
272 
273 static krb5_error_code get_time_offsets(krb5_context context, krb5_ccache id,
274                                         int32_t *time_offset,
275                                         int32_t *usec_offset);
276 
277 /* Note the following is a stub function for Linux */
278 extern krb5_error_code krb5_change_cache(void);
279 
280 /*
281  * GET_PERSISTENT(uid) acquires the persistent keyring for uid, or falls back
282  * to the user keyring if uid matches the current effective uid.
283  */
284 
285 static key_serial_t
get_persistent_fallback(uid_t uid)286 get_persistent_fallback(uid_t uid)
287 {
288     return (uid == geteuid()) ? KEY_SPEC_USER_KEYRING : -1;
289 }
290 
291 #ifdef HAVE_PERSISTENT_KEYRING
292 #define GET_PERSISTENT get_persistent_real
293 static key_serial_t
get_persistent_real(uid_t uid)294 get_persistent_real(uid_t uid)
295 {
296     key_serial_t key;
297 
298     key = keyctl_get_persistent(uid, KEY_SPEC_PROCESS_KEYRING);
299     return (key == -1 && errno == ENOTSUP) ? get_persistent_fallback(uid) :
300         key;
301 }
302 #else
303 #define GET_PERSISTENT get_persistent_fallback
304 #endif
305 
306 /*
307  * If a process has no explicitly set session keyring, KEY_SPEC_SESSION_KEYRING
308  * will resolve to the user session keyring for ID lookup and reading, but in
309  * some kernel versions, writing to that special keyring will instead create a
310  * new empty session keyring for the process.  We do not want that; the keys we
311  * create would be invisible to other processes.  We can work around that
312  * behavior by explicitly writing to the user session keyring when it matches
313  * the session keyring.  This function returns the keyring we should write to
314  * for the session anchor.
315  */
316 static key_serial_t
session_write_anchor(void)317 session_write_anchor(void)
318 {
319     key_serial_t s, u;
320 
321     s = keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 0);
322     u = keyctl_get_keyring_ID(KEY_SPEC_USER_SESSION_KEYRING, 0);
323     return (s == u) ? KEY_SPEC_USER_SESSION_KEYRING : KEY_SPEC_SESSION_KEYRING;
324 }
325 
326 /*
327  * Find or create a keyring within parent with the given name.  If possess is
328  * nonzero, also make sure the key is linked from possess.  This is necessary
329  * to ensure that we have possession rights on the key when the parent is the
330  * user or persistent keyring.
331  */
332 static krb5_error_code
find_or_create_keyring(key_serial_t parent,key_serial_t possess,const char * name,key_serial_t * key_out)333 find_or_create_keyring(key_serial_t parent, key_serial_t possess,
334                        const char *name, key_serial_t *key_out)
335 {
336     key_serial_t key;
337 
338     *key_out = -1;
339     key = keyctl_search(parent, KRCC_KEY_TYPE_KEYRING, name, possess);
340     if (key == -1) {
341         if (possess != 0) {
342             key = add_key(KRCC_KEY_TYPE_KEYRING, name, NULL, 0, possess);
343             if (key == -1)
344                 return errno;
345             if (keyctl_link(key, parent) == -1)
346                 return errno;
347         } else {
348             key = add_key(KRCC_KEY_TYPE_KEYRING, name, NULL, 0, parent);
349             if (key == -1)
350                 return errno;
351         }
352     }
353     *key_out = key;
354     return 0;
355 }
356 
357 /* Parse a residual name into an anchor name, a collection name, and possibly a
358  * subsidiary name. */
359 static krb5_error_code
parse_residual(const char * residual,char ** anchor_name_out,char ** collection_name_out,char ** subsidiary_name_out)360 parse_residual(const char *residual, char **anchor_name_out,
361                char **collection_name_out, char **subsidiary_name_out)
362 {
363     krb5_error_code ret;
364     char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
365     const char *sep;
366 
367     *anchor_name_out = 0;
368     *collection_name_out = NULL;
369     *subsidiary_name_out = NULL;
370 
371     /* Parse out the anchor name.  Use the legacy anchor if not present. */
372     sep = strchr(residual, ':');
373     if (sep == NULL) {
374         anchor_name = strdup(KRCC_LEGACY_ANCHOR);
375         if (anchor_name == NULL)
376             goto oom;
377     } else {
378         anchor_name = k5memdup0(residual, sep - residual, &ret);
379         if (anchor_name == NULL)
380             goto oom;
381         residual = sep + 1;
382     }
383 
384     /* Parse out the collection and subsidiary name. */
385     sep = strchr(residual, ':');
386     if (sep == NULL) {
387         collection_name = strdup(residual);
388         if (collection_name == NULL)
389             goto oom;
390         subsidiary_name = NULL;
391     } else {
392         collection_name = k5memdup0(residual, sep - residual, &ret);
393         if (collection_name == NULL)
394             goto oom;
395         subsidiary_name = strdup(sep + 1);
396         if (subsidiary_name == NULL)
397             goto oom;
398     }
399 
400     *anchor_name_out = anchor_name;
401     *collection_name_out = collection_name;
402     *subsidiary_name_out = subsidiary_name;
403     return 0;
404 
405 oom:
406     free(anchor_name);
407     free(collection_name);
408     free(subsidiary_name);
409     return ENOMEM;
410 }
411 
412 /*
413  * Return true if residual identifies a subsidiary cache which should be linked
414  * into the anchor so it can be visible to old code.  This is the case if the
415  * residual has the legacy anchor and the subsidiary name matches the
416  * collection name.
417  */
418 static krb5_boolean
is_legacy_cache_name(const char * residual)419 is_legacy_cache_name(const char *residual)
420 {
421     const char *sep, *aname, *cname, *sname;
422     size_t alen, clen, legacy_len = sizeof(KRCC_LEGACY_ANCHOR) - 1;
423 
424     /* Get pointers to the anchor, collection, and subsidiary names. */
425     aname = residual;
426     sep = strchr(residual, ':');
427     if (sep == NULL)
428         return FALSE;
429     alen = sep - aname;
430     cname = sep + 1;
431     sep = strchr(cname, ':');
432     if (sep == NULL)
433         return FALSE;
434     clen = sep - cname;
435     sname = sep + 1;
436 
437     return alen == legacy_len && clen == strlen(sname) &&
438         strncmp(aname, KRCC_LEGACY_ANCHOR, alen) == 0 &&
439         strncmp(cname, sname, clen) == 0;
440 }
441 
442 /* If the default cache name for context is a KEYRING cache, parse its residual
443  * string.  Otherwise set all outputs to NULL. */
444 static krb5_error_code
get_default(krb5_context context,char ** anchor_name_out,char ** collection_name_out,char ** subsidiary_name_out)445 get_default(krb5_context context, char **anchor_name_out,
446             char **collection_name_out, char **subsidiary_name_out)
447 {
448     const char *defname;
449 
450     *anchor_name_out = *collection_name_out = *subsidiary_name_out = NULL;
451     defname = krb5_cc_default_name(context);
452     if (defname == NULL || strncmp(defname, "KEYRING:", 8) != 0)
453         return 0;
454     return parse_residual(defname + 8, anchor_name_out, collection_name_out,
455                           subsidiary_name_out);
456 }
457 
458 /* Create a residual identifying a subsidiary cache. */
459 static krb5_error_code
make_subsidiary_residual(const char * anchor_name,const char * collection_name,const char * subsidiary_name,char ** residual_out)460 make_subsidiary_residual(const char *anchor_name, const char *collection_name,
461                          const char *subsidiary_name, char **residual_out)
462 {
463     if (asprintf(residual_out, "%s:%s:%s", anchor_name, collection_name,
464                  subsidiary_name) < 0) {
465         *residual_out = NULL;
466         return ENOMEM;
467     }
468     return 0;
469 }
470 
471 /* Retrieve or create a keyring for collection_name within the anchor, and set
472  * *collection_id_out to its serial number. */
473 static krb5_error_code
get_collection(const char * anchor_name,const char * collection_name,key_serial_t * collection_id_out)474 get_collection(const char *anchor_name, const char *collection_name,
475                key_serial_t *collection_id_out)
476 {
477     krb5_error_code ret;
478     key_serial_t persistent_id, anchor_id, possess_id = 0;
479     char *ckname, *cnend;
480     long uidnum;
481 
482     *collection_id_out = 0;
483 
484     if (strcmp(anchor_name, KRCC_PERSISTENT_ANCHOR) == 0) {
485         /*
486          * The collection name is a uid (or empty for the current effective
487          * uid), and we look up a fixed keyring name within the persistent
488          * keyring for that uid.  We link it to the process keyring to ensure
489          * that we have possession rights on the collection key.
490          */
491         if (*collection_name != '\0') {
492             errno = 0;
493             uidnum = strtol(collection_name, &cnend, 10);
494             if (errno || *cnend != '\0')
495                 return KRB5_KCC_INVALID_UID;
496         } else {
497             uidnum = geteuid();
498         }
499         persistent_id = GET_PERSISTENT(uidnum);
500         if (persistent_id == -1)
501             return KRB5_KCC_INVALID_UID;
502         return find_or_create_keyring(persistent_id, KEY_SPEC_PROCESS_KEYRING,
503                                       KRCC_PERSISTENT_KEYRING_NAME,
504                                       collection_id_out);
505     }
506 
507     if (strcmp(anchor_name, KRCC_PROCESS_ANCHOR) == 0) {
508         anchor_id = KEY_SPEC_PROCESS_KEYRING;
509     } else if (strcmp(anchor_name, KRCC_THREAD_ANCHOR) == 0) {
510         anchor_id = KEY_SPEC_THREAD_KEYRING;
511     } else if (strcmp(anchor_name, KRCC_SESSION_ANCHOR) == 0) {
512         anchor_id = session_write_anchor();
513     } else if (strcmp(anchor_name, KRCC_USER_ANCHOR) == 0) {
514         /* The user keyring does not confer possession, so we need to link the
515          * collection to the process keyring to maintain possession rights. */
516         anchor_id = KEY_SPEC_USER_KEYRING;
517         possess_id = KEY_SPEC_PROCESS_KEYRING;
518     } else if (strcmp(anchor_name, KRCC_LEGACY_ANCHOR) == 0) {
519         anchor_id = session_write_anchor();
520     } else {
521         return KRB5_KCC_INVALID_ANCHOR;
522     }
523 
524     /* Look up the collection keyring name within the anchor keyring. */
525     if (asprintf(&ckname, "%s%s", KRCC_CCCOL_PREFIX, collection_name) == -1)
526         return ENOMEM;
527     ret = find_or_create_keyring(anchor_id, possess_id, ckname,
528                                  collection_id_out);
529     free(ckname);
530     return ret;
531 }
532 
533 /* Store subsidiary_name into the primary index key for collection_id. */
534 static krb5_error_code
set_primary_name(krb5_context context,key_serial_t collection_id,const char * subsidiary_name)535 set_primary_name(krb5_context context, key_serial_t collection_id,
536                  const char *subsidiary_name)
537 {
538     key_serial_t key;
539     uint32_t len = strlen(subsidiary_name), plen = 8 + len;
540     unsigned char *payload;
541 
542     payload = malloc(plen);
543     if (payload == NULL)
544         return ENOMEM;
545     store_32_be(KRCC_COLLECTION_VERSION, payload);
546     store_32_be(len, payload + 4);
547     memcpy(payload + 8, subsidiary_name, len);
548     key = add_key(KRCC_KEY_TYPE_USER, KRCC_COLLECTION_PRIMARY,
549                   payload, plen, collection_id);
550     free(payload);
551     return (key == -1) ? errno : 0;
552 }
553 
554 static krb5_error_code
parse_index(krb5_context context,int32_t * version,char ** primary,const unsigned char * payload,size_t psize)555 parse_index(krb5_context context, int32_t *version, char **primary,
556             const unsigned char *payload, size_t psize)
557 {
558     krb5_error_code ret;
559     uint32_t len;
560 
561     if (psize < 8)
562         return KRB5_CC_END;
563 
564     *version = load_32_be(payload);
565     len = load_32_be(payload + 4);
566     if (len > psize - 8)
567         return KRB5_CC_END;
568     *primary = k5memdup0(payload + 8, len, &ret);
569     return (*primary == NULL) ? ret : 0;
570 }
571 
572 /*
573  * Get or initialize the primary name within collection_id and set
574  * *subsidiary_out to its value.  If initializing a legacy collection, look
575  * for a legacy cache and add it to the collection.
576  */
577 static krb5_error_code
get_primary_name(krb5_context context,const char * anchor_name,const char * collection_name,key_serial_t collection_id,char ** subsidiary_out)578 get_primary_name(krb5_context context, const char *anchor_name,
579                  const char *collection_name, key_serial_t collection_id,
580                  char **subsidiary_out)
581 {
582     krb5_error_code ret;
583     key_serial_t primary_id, legacy;
584     void *payload = NULL;
585     int payloadlen;
586     int32_t version;
587     char *subsidiary_name = NULL;
588 
589     *subsidiary_out = NULL;
590 
591     primary_id = keyctl_search(collection_id, KRCC_KEY_TYPE_USER,
592                                KRCC_COLLECTION_PRIMARY, 0);
593     if (primary_id == -1) {
594         /* Initialize the primary key using the collection name.  We can't name
595          * a key with the empty string, so map that to an arbitrary string. */
596         subsidiary_name = strdup((*collection_name == '\0') ? "tkt" :
597                                  collection_name);
598         if (subsidiary_name == NULL) {
599             ret = ENOMEM;
600             goto cleanup;
601         }
602         ret = set_primary_name(context, collection_id, subsidiary_name);
603         if (ret)
604             goto cleanup;
605 
606         if (strcmp(anchor_name, KRCC_LEGACY_ANCHOR) == 0) {
607             /* Look for a cache created by old code.  If we find one, add it to
608              * the collection. */
609             legacy = keyctl_search(KEY_SPEC_SESSION_KEYRING,
610                                    KRCC_KEY_TYPE_KEYRING, subsidiary_name, 0);
611             if (legacy != -1 && keyctl_link(legacy, collection_id) == -1) {
612                 ret = errno;
613                 goto cleanup;
614             }
615         }
616     } else {
617         /* Read, parse, and free the primary key's payload. */
618         payloadlen = keyctl_read_alloc(primary_id, &payload);
619         if (payloadlen == -1) {
620             ret = errno;
621             goto cleanup;
622         }
623         ret = parse_index(context, &version, &subsidiary_name, payload,
624                           payloadlen);
625         if (ret)
626             goto cleanup;
627 
628         if (version != KRCC_COLLECTION_VERSION) {
629             ret = KRB5_KCC_UNKNOWN_VERSION;
630             goto cleanup;
631         }
632     }
633 
634     *subsidiary_out = subsidiary_name;
635     subsidiary_name = NULL;
636 
637 cleanup:
638     free(payload);
639     free(subsidiary_name);
640     return ret;
641 }
642 
643 /*
644  * Create a keyring with a unique random name within collection_id.  Set
645  * *subsidiary to its name and *cache_id_out to its key serial number.
646  */
647 static krb5_error_code
unique_keyring(krb5_context context,key_serial_t collection_id,char ** subsidiary_out,key_serial_t * cache_id_out)648 unique_keyring(krb5_context context, key_serial_t collection_id,
649                char **subsidiary_out, key_serial_t *cache_id_out)
650 {
651     key_serial_t key;
652     krb5_error_code ret;
653     char uniquename[sizeof(KRCC_NAME_PREFIX) + KRCC_NAME_RAND_CHARS];
654     int prefixlen = sizeof(KRCC_NAME_PREFIX) - 1;
655     int tries;
656 
657     *subsidiary_out = NULL;
658     *cache_id_out = 0;
659 
660     memcpy(uniquename, KRCC_NAME_PREFIX, sizeof(KRCC_NAME_PREFIX));
661     k5_cc_mutex_lock(context, &krb5int_krcc_mutex);
662 
663     /* Loop until we successfully create a new ccache keyring with
664      * a unique name, or we get an error. Limit to 100 tries. */
665     tries = 100;
666     while (tries-- > 0) {
667         ret = krb5int_random_string(context, uniquename + prefixlen,
668                                     KRCC_NAME_RAND_CHARS);
669         if (ret)
670             goto cleanup;
671 
672         key = keyctl_search(collection_id, KRCC_KEY_TYPE_KEYRING, uniquename,
673                             0);
674         if (key < 0) {
675             /* Name does not already exist.  Create it to reserve the name. */
676             key = add_key(KRCC_KEY_TYPE_KEYRING, uniquename, NULL, 0,
677                           collection_id);
678             if (key < 0) {
679                 ret = errno;
680                 goto cleanup;
681             }
682             break;
683         }
684     }
685 
686     if (tries <= 0) {
687         ret = KRB5_CC_BADNAME;
688         goto cleanup;
689     }
690 
691     *subsidiary_out = strdup(uniquename);
692     if (*subsidiary_out == NULL) {
693         ret = ENOMEM;
694         goto cleanup;
695     }
696     *cache_id_out = key;
697     ret = 0;
698 cleanup:
699     k5_cc_mutex_unlock(context, &krb5int_krcc_mutex);
700     return ret;
701 }
702 
703 static krb5_error_code
add_cred_key(const char * name,const void * payload,size_t plen,key_serial_t cache_id,krb5_boolean legacy_type,key_serial_t * key_out)704 add_cred_key(const char *name, const void *payload, size_t plen,
705              key_serial_t cache_id, krb5_boolean legacy_type,
706              key_serial_t *key_out)
707 {
708     key_serial_t key;
709 
710     *key_out = -1;
711     if (!legacy_type) {
712         /* Try the preferred cred key type; fall back if no kernel support. */
713         key = add_key(KRCC_CRED_KEY_TYPE, name, payload, plen, cache_id);
714         if (key != -1) {
715             *key_out = key;
716             return 0;
717         } else if (errno != EINVAL && errno != ENODEV) {
718             return errno;
719         }
720     }
721     /* Use the user key type. */
722     key = add_key(KRCC_KEY_TYPE_USER, name, payload, plen, cache_id);
723     if (key == -1)
724         return errno;
725     *key_out = key;
726     return 0;
727 }
728 
729 static void
update_keyring_expiration(krb5_context context,krb5_ccache id)730 update_keyring_expiration(krb5_context context, krb5_ccache id)
731 {
732     krcc_data *data = id->data;
733     krb5_cc_cursor cursor;
734     krb5_creds creds;
735     krb5_timestamp now, endtime = 0;
736     unsigned int timeout;
737 
738     /*
739      * We have no way to know what is the actual timeout set on the keyring.
740      * We also cannot keep track of it in a local variable as another process
741      * can always modify the keyring independently, so just always enumerate
742      * all keys and find out the highest endtime time.
743      */
744 
745     /* Find the maximum endtime of all creds in the cache. */
746     if (krcc_start_seq_get(context, id, &cursor) != 0)
747         return;
748     for (;;) {
749         if (krcc_next_cred(context, id, &cursor, &creds) != 0)
750             break;
751         if (ts_after(creds.times.endtime, endtime))
752             endtime = creds.times.endtime;
753         krb5_free_cred_contents(context, &creds);
754     }
755     (void)krcc_end_seq_get(context, id, &cursor);
756 
757     if (endtime == 0)        /* No creds with end times */
758         return;
759 
760     if (krb5_timeofday(context, &now) != 0)
761         return;
762 
763     /* Setting the timeout to zero would reset the timeout, so we set it to one
764      * second instead if creds are already expired. */
765     timeout = ts_after(endtime, now) ? ts_interval(now, endtime) : 1;
766     (void)keyctl_set_timeout(data->cache_id, timeout);
767 }
768 
769 /* Create or overwrite the cache keyring, and set the default principal. */
770 static krb5_error_code KRB5_CALLCONV
krcc_initialize(krb5_context context,krb5_ccache id,krb5_principal princ)771 krcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ)
772 {
773     krcc_data *data = (krcc_data *)id->data;
774     krb5_os_context os_ctx = &context->os_context;
775     krb5_error_code ret;
776     const char *cache_name, *p;
777 
778     k5_cc_mutex_lock(context, &data->lock);
779 
780     ret = clear_cache_keyring(context, id);
781     if (ret)
782         goto out;
783 
784     if (!data->cache_id) {
785         /* The key didn't exist at resolve time.  Check again and create the
786          * key if it still isn't there. */
787         p = strrchr(data->name, ':');
788         cache_name = (p != NULL) ? p + 1 : data->name;
789         ret = find_or_create_keyring(data->collection_id, 0, cache_name,
790                                      &data->cache_id);
791         if (ret)
792             goto out;
793     }
794 
795     /* If this is the legacy cache in a legacy session collection, link it
796      * directly to the session keyring so that old code can see it. */
797     if (is_legacy_cache_name(data->name))
798         (void)keyctl_link(data->cache_id, session_write_anchor());
799 
800     ret = save_principal(context, id, princ);
801 
802     /* Save time offset if it is valid and this is not a legacy cache.  Legacy
803      * applications would fail to parse the new key in the cache keyring. */
804     if (!is_legacy_cache_name(data->name) &&
805         (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID)) {
806         ret = save_time_offsets(context, id, os_ctx->time_offset,
807                                 os_ctx->usec_offset);
808     }
809 
810     if (ret == 0)
811         krb5_change_cache();
812 
813 out:
814     k5_cc_mutex_unlock(context, &data->lock);
815     return ret;
816 }
817 
818 /* Release the ccache handle. */
819 static krb5_error_code KRB5_CALLCONV
krcc_close(krb5_context context,krb5_ccache id)820 krcc_close(krb5_context context, krb5_ccache id)
821 {
822     krcc_data *data = id->data;
823 
824     k5_cc_mutex_destroy(&data->lock);
825     free(data->name);
826     free(data);
827     free(id);
828     return 0;
829 }
830 
831 /* Clear out a ccache keyring, unlinking all keys within it.  Call with the
832  * mutex locked. */
833 static krb5_error_code
clear_cache_keyring(krb5_context context,krb5_ccache id)834 clear_cache_keyring(krb5_context context, krb5_ccache id)
835 {
836     krcc_data *data = id->data;
837     int res;
838 
839     k5_cc_mutex_assert_locked(context, &data->lock);
840 
841     DEBUG_PRINT(("clear_cache_keyring: cache_id %d, princ_id %d\n",
842                  data->cache_id, data->princ_id));
843 
844     if (data->cache_id) {
845         res = keyctl_clear(data->cache_id);
846         if (res != 0)
847             return errno;
848     }
849     data->princ_id = 0;
850 
851     return 0;
852 }
853 
854 /* Destroy the cache keyring and release the handle. */
855 static krb5_error_code KRB5_CALLCONV
krcc_destroy(krb5_context context,krb5_ccache id)856 krcc_destroy(krb5_context context, krb5_ccache id)
857 {
858     krb5_error_code ret = 0;
859     krcc_data *data = id->data;
860     int res;
861 
862     k5_cc_mutex_lock(context, &data->lock);
863 
864     clear_cache_keyring(context, id);
865     if (data->cache_id) {
866         res = keyctl_unlink(data->cache_id, data->collection_id);
867         if (res < 0) {
868             ret = errno;
869             DEBUG_PRINT(("unlinking key %d from ring %d: %s", data->cache_id,
870                          data->collection_id, error_message(errno)));
871         }
872         /* If this is a legacy cache, unlink it from the session anchor. */
873         if (is_legacy_cache_name(data->name))
874             (void)keyctl_unlink(data->cache_id, session_write_anchor());
875     }
876 
877     k5_cc_mutex_unlock(context, &data->lock);
878     k5_cc_mutex_destroy(&data->lock);
879     free(data->name);
880     free(data);
881     free(id);
882     krb5_change_cache();
883     return ret;
884 }
885 
886 /* Create a cache handle for a cache ID. */
887 static krb5_error_code
make_cache(krb5_context context,key_serial_t collection_id,key_serial_t cache_id,const char * anchor_name,const char * collection_name,const char * subsidiary_name,krb5_ccache * cache_out)888 make_cache(krb5_context context, key_serial_t collection_id,
889            key_serial_t cache_id, const char *anchor_name,
890            const char *collection_name, const char *subsidiary_name,
891            krb5_ccache *cache_out)
892 {
893     krb5_error_code ret;
894     krb5_os_context os_ctx = &context->os_context;
895     krb5_ccache ccache = NULL;
896     krcc_data *data;
897     key_serial_t pkey = 0;
898 
899     /* Determine the key containing principal information, if present. */
900     pkey = keyctl_search(cache_id, KRCC_KEY_TYPE_USER, KRCC_SPEC_PRINC_KEYNAME,
901                          0);
902     if (pkey < 0)
903         pkey = 0;
904 
905     ccache = malloc(sizeof(struct _krb5_ccache));
906     if (!ccache)
907         return ENOMEM;
908 
909     ret = make_krcc_data(anchor_name, collection_name, subsidiary_name,
910                          cache_id, collection_id, &data);
911     if (ret) {
912         free(ccache);
913         return ret;
914     }
915 
916     data->princ_id = pkey;
917     ccache->ops = &krb5_krcc_ops;
918     ccache->data = data;
919     ccache->magic = KV5M_CCACHE;
920     *cache_out = ccache;
921 
922     /* Look up time offsets if necessary. */
923     if ((context->library_options & KRB5_LIBOPT_SYNC_KDCTIME) &&
924         !(os_ctx->os_flags & KRB5_OS_TOFFSET_VALID)) {
925         if (get_time_offsets(context, ccache, &os_ctx->time_offset,
926                              &os_ctx->usec_offset) == 0) {
927             os_ctx->os_flags &= ~KRB5_OS_TOFFSET_TIME;
928             os_ctx->os_flags |= KRB5_OS_TOFFSET_VALID;
929         }
930     }
931 
932     return 0;
933 }
934 
935 /* Create a keyring ccache handle for the given residual string. */
936 static krb5_error_code KRB5_CALLCONV
krcc_resolve(krb5_context context,krb5_ccache * id,const char * residual)937 krcc_resolve(krb5_context context, krb5_ccache *id, const char *residual)
938 {
939     krb5_error_code ret;
940     key_serial_t collection_id, cache_id;
941     char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
942 
943     ret = parse_residual(residual, &anchor_name, &collection_name,
944                          &subsidiary_name);
945     if (ret)
946         goto cleanup;
947     ret = get_collection(anchor_name, collection_name, &collection_id);
948     if (ret)
949         goto cleanup;
950 
951     if (subsidiary_name == NULL) {
952         /* Retrieve or initialize the primary name for the collection. */
953         ret = get_primary_name(context, anchor_name, collection_name,
954                                collection_id, &subsidiary_name);
955         if (ret)
956             goto cleanup;
957     }
958 
959     /* Look up the cache keyring ID, if the cache is already initialized. */
960     cache_id = keyctl_search(collection_id, KRCC_KEY_TYPE_KEYRING,
961                              subsidiary_name, 0);
962     if (cache_id < 0)
963         cache_id = 0;
964 
965     ret = make_cache(context, collection_id, cache_id, anchor_name,
966                      collection_name, subsidiary_name, id);
967     if (ret)
968         goto cleanup;
969 
970 cleanup:
971     free(anchor_name);
972     free(collection_name);
973     free(subsidiary_name);
974     return ret;
975 }
976 
977 /* Prepare for a sequential iteration over the cache keyring. */
978 static krb5_error_code KRB5_CALLCONV
krcc_start_seq_get(krb5_context context,krb5_ccache id,krb5_cc_cursor * cursor)979 krcc_start_seq_get(krb5_context context, krb5_ccache id,
980                    krb5_cc_cursor *cursor)
981 {
982     krcc_cursor krcursor;
983     krcc_data *data = id->data;
984     void *keys;
985     long size;
986 
987     k5_cc_mutex_lock(context, &data->lock);
988 
989     if (!data->cache_id) {
990         k5_cc_mutex_unlock(context, &data->lock);
991         return KRB5_FCC_NOFILE;
992     }
993 
994     size = keyctl_read_alloc(data->cache_id, &keys);
995     if (size == -1) {
996         DEBUG_PRINT(("Error getting from keyring: %s\n", strerror(errno)));
997         k5_cc_mutex_unlock(context, &data->lock);
998         return KRB5_CC_IO;
999     }
1000 
1001     krcursor = calloc(1, sizeof(*krcursor));
1002     if (krcursor == NULL) {
1003         free(keys);
1004         k5_cc_mutex_unlock(context, &data->lock);
1005         return KRB5_CC_NOMEM;
1006     }
1007 
1008     krcursor->princ_id = data->princ_id;
1009     krcursor->offsets_id = keyctl_search(data->cache_id, KRCC_KEY_TYPE_USER,
1010                                          KRCC_TIME_OFFSETS, 0);
1011     krcursor->numkeys = size / sizeof(key_serial_t);
1012     krcursor->keys = keys;
1013 
1014     k5_cc_mutex_unlock(context, &data->lock);
1015     *cursor = krcursor;
1016     return 0;
1017 }
1018 
1019 /* Get the next credential from the cache keyring. */
1020 static krb5_error_code KRB5_CALLCONV
krcc_next_cred(krb5_context context,krb5_ccache id,krb5_cc_cursor * cursor,krb5_creds * creds)1021 krcc_next_cred(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor,
1022                krb5_creds *creds)
1023 {
1024     krcc_cursor krcursor;
1025     krb5_error_code ret;
1026     int psize;
1027     void *payload = NULL;
1028 
1029     memset(creds, 0, sizeof(krb5_creds));
1030 
1031     /* The cursor has the entire list of keys. */
1032     krcursor = *cursor;
1033     if (krcursor == NULL)
1034         return KRB5_CC_END;
1035 
1036     while (krcursor->currkey < krcursor->numkeys) {
1037         /* If we're pointing at the entry with the principal, or at the key
1038          * with the time offsets, skip it. */
1039         if (krcursor->keys[krcursor->currkey] == krcursor->princ_id ||
1040             krcursor->keys[krcursor->currkey] == krcursor->offsets_id) {
1041             krcursor->currkey++;
1042             continue;
1043         }
1044 
1045         /* Read the key; the right size buffer will be allocated and
1046          * returned. */
1047         psize = keyctl_read_alloc(krcursor->keys[krcursor->currkey],
1048                                   &payload);
1049         if (psize != -1) {
1050             krcursor->currkey++;
1051 
1052             /* Unmarshal the cred using the file ccache version 4 format. */
1053             ret = k5_unmarshal_cred(payload, psize, 4, creds);
1054             free(payload);
1055             return ret;
1056         } else if (errno != ENOKEY && errno != EACCES) {
1057             DEBUG_PRINT(("Error reading key %d: %s\n",
1058                          krcursor->keys[krcursor->currkey], strerror(errno)));
1059             return KRB5_FCC_NOFILE;
1060         }
1061 
1062         /* The current key was unlinked, probably by a remove_cred call; move
1063          * on to the next one. */
1064         krcursor->currkey++;
1065     }
1066 
1067     /* No more keys in keyring. */
1068     return KRB5_CC_END;
1069 }
1070 
1071 /* Release an iteration cursor. */
1072 static krb5_error_code KRB5_CALLCONV
krcc_end_seq_get(krb5_context context,krb5_ccache id,krb5_cc_cursor * cursor)1073 krcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
1074 {
1075     krcc_cursor krcursor = *cursor;
1076 
1077     if (krcursor != NULL) {
1078         free(krcursor->keys);
1079         free(krcursor);
1080     }
1081     *cursor = NULL;
1082     return 0;
1083 }
1084 
1085 /* Create keyring data for a credential cache. */
1086 static krb5_error_code
make_krcc_data(const char * anchor_name,const char * collection_name,const char * subsidiary_name,key_serial_t cache_id,key_serial_t collection_id,krcc_data ** data_out)1087 make_krcc_data(const char *anchor_name, const char *collection_name,
1088                const char *subsidiary_name, key_serial_t cache_id,
1089                key_serial_t collection_id, krcc_data **data_out)
1090 {
1091     krb5_error_code ret;
1092     krcc_data *data;
1093 
1094     *data_out = NULL;
1095 
1096     data = malloc(sizeof(krcc_data));
1097     if (data == NULL)
1098         return KRB5_CC_NOMEM;
1099 
1100     ret = k5_cc_mutex_init(&data->lock);
1101     if (ret) {
1102         free(data);
1103         return ret;
1104     }
1105 
1106     ret = make_subsidiary_residual(anchor_name, collection_name,
1107                                    subsidiary_name, &data->name);
1108     if (ret) {
1109         k5_cc_mutex_destroy(&data->lock);
1110         free(data);
1111         return ret;
1112     }
1113     data->princ_id = 0;
1114     data->cache_id = cache_id;
1115     data->collection_id = collection_id;
1116     data->is_legacy_type = (strcmp(anchor_name, KRCC_LEGACY_ANCHOR) == 0);
1117 
1118     *data_out = data;
1119     return 0;
1120 }
1121 
1122 /* Create a new keyring cache with a unique name. */
1123 static krb5_error_code KRB5_CALLCONV
krcc_generate_new(krb5_context context,krb5_ccache * id_out)1124 krcc_generate_new(krb5_context context, krb5_ccache *id_out)
1125 {
1126     krb5_ccache id = NULL;
1127     krb5_error_code ret;
1128     char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
1129     char *new_subsidiary_name = NULL, *new_residual = NULL;
1130     krcc_data *data;
1131     key_serial_t collection_id;
1132     key_serial_t cache_id = 0;
1133 
1134     *id_out = NULL;
1135 
1136     /* Determine the collection in which we will create the cache.*/
1137     ret = get_default(context, &anchor_name, &collection_name,
1138                       &subsidiary_name);
1139     if (ret)
1140         return ret;
1141     if (anchor_name == NULL) {
1142         ret = parse_residual(KRCC_DEFAULT_UNIQUE_COLLECTION, &anchor_name,
1143                              &collection_name, &subsidiary_name);
1144         if (ret)
1145             return ret;
1146     }
1147     if (subsidiary_name != NULL) {
1148         k5_setmsg(context, KRB5_DCC_CANNOT_CREATE,
1149                   _("Can't create new subsidiary cache because default cache "
1150                     "is already a subsidiary"));
1151         ret = KRB5_DCC_CANNOT_CREATE;
1152         goto cleanup;
1153     }
1154 
1155     /* Allocate memory */
1156     id = malloc(sizeof(struct _krb5_ccache));
1157     if (id == NULL) {
1158         ret = ENOMEM;
1159         goto cleanup;
1160     }
1161 
1162     id->ops = &krb5_krcc_ops;
1163 
1164     /* Make a unique keyring within the chosen collection. */
1165     ret = get_collection(anchor_name, collection_name, &collection_id);
1166     if (ret)
1167         goto cleanup;
1168     ret = unique_keyring(context, collection_id, &new_subsidiary_name,
1169                          &cache_id);
1170     if (ret)
1171         goto cleanup;
1172 
1173     ret = make_krcc_data(anchor_name, collection_name, new_subsidiary_name,
1174                          cache_id, collection_id, &data);
1175     if (ret)
1176         goto cleanup;
1177 
1178     id->data = data;
1179     krb5_change_cache();
1180 
1181 cleanup:
1182     free(anchor_name);
1183     free(collection_name);
1184     free(subsidiary_name);
1185     free(new_subsidiary_name);
1186     free(new_residual);
1187     if (ret) {
1188         free(id);
1189         return ret;
1190     }
1191     *id_out = id;
1192     return 0;
1193 }
1194 
1195 /* Return an alias to the residual string of the cache. */
1196 static const char *KRB5_CALLCONV
krcc_get_name(krb5_context context,krb5_ccache id)1197 krcc_get_name(krb5_context context, krb5_ccache id)
1198 {
1199     return ((krcc_data *)id->data)->name;
1200 }
1201 
1202 /* Retrieve a copy of the default principal, if the cache is initialized. */
1203 static krb5_error_code KRB5_CALLCONV
krcc_get_principal(krb5_context context,krb5_ccache id,krb5_principal * princ_out)1204 krcc_get_principal(krb5_context context, krb5_ccache id,
1205                    krb5_principal *princ_out)
1206 {
1207     krcc_data *data = id->data;
1208     krb5_error_code ret;
1209     void *payload = NULL;
1210     int psize;
1211 
1212     *princ_out = NULL;
1213     k5_cc_mutex_lock(context, &data->lock);
1214 
1215     if (!data->cache_id || !data->princ_id) {
1216         ret = KRB5_FCC_NOFILE;
1217         k5_setmsg(context, ret, _("Credentials cache keyring '%s' not found"),
1218                   data->name);
1219         goto errout;
1220     }
1221 
1222     psize = keyctl_read_alloc(data->princ_id, &payload);
1223     if (psize == -1) {
1224         DEBUG_PRINT(("Reading principal key %d: %s\n",
1225                      data->princ_id, strerror(errno)));
1226         ret = KRB5_CC_IO;
1227         goto errout;
1228     }
1229 
1230     /* Unmarshal the principal using the file ccache version 4 format. */
1231     ret = k5_unmarshal_princ(payload, psize, 4, princ_out);
1232 
1233 errout:
1234     free(payload);
1235     k5_cc_mutex_unlock(context, &data->lock);
1236     return ret;
1237 }
1238 
1239 /* Search for a credential within the cache keyring. */
1240 static krb5_error_code KRB5_CALLCONV
krcc_retrieve(krb5_context context,krb5_ccache id,krb5_flags whichfields,krb5_creds * mcreds,krb5_creds * creds)1241 krcc_retrieve(krb5_context context, krb5_ccache id,
1242               krb5_flags whichfields, krb5_creds *mcreds,
1243               krb5_creds *creds)
1244 {
1245     return k5_cc_retrieve_cred_default(context, id, whichfields, mcreds,
1246                                        creds);
1247 }
1248 
1249 /* Remove a credential from the cache keyring. */
1250 static krb5_error_code KRB5_CALLCONV
krcc_remove_cred(krb5_context context,krb5_ccache cache,krb5_flags flags,krb5_creds * creds)1251 krcc_remove_cred(krb5_context context, krb5_ccache cache,
1252                  krb5_flags flags, krb5_creds *creds)
1253 {
1254     krb5_error_code ret;
1255     krcc_data *data = cache->data;
1256     krb5_cc_cursor cursor;
1257     krb5_creds c;
1258     krcc_cursor krcursor;
1259     key_serial_t key;
1260     krb5_boolean match;
1261 
1262     ret = krcc_start_seq_get(context, cache, &cursor);
1263     if (ret)
1264         return ret;
1265 
1266     for (;;) {
1267         ret = krcc_next_cred(context, cache, &cursor, &c);
1268         if (ret)
1269             break;
1270         match = krb5int_cc_creds_match_request(context, flags, creds, &c);
1271         krb5_free_cred_contents(context, &c);
1272         if (match) {
1273             krcursor = cursor;
1274             key = krcursor->keys[krcursor->currkey - 1];
1275             if (keyctl_unlink(key, data->cache_id) == -1) {
1276                 ret = errno;
1277                 break;
1278             }
1279         }
1280     }
1281 
1282     krcc_end_seq_get(context, cache, &cursor);
1283     return (ret == KRB5_CC_END) ? 0 : ret;
1284 }
1285 
1286 /* Set flags on the cache.  (We don't care about any flags.) */
1287 static krb5_error_code KRB5_CALLCONV
krcc_set_flags(krb5_context context,krb5_ccache id,krb5_flags flags)1288 krcc_set_flags(krb5_context context, krb5_ccache id, krb5_flags flags)
1289 {
1290     return 0;
1291 }
1292 
1293 /* Get the current operational flags (of which we have none) for the cache. */
1294 static krb5_error_code KRB5_CALLCONV
krcc_get_flags(krb5_context context,krb5_ccache id,krb5_flags * flags_out)1295 krcc_get_flags(krb5_context context, krb5_ccache id, krb5_flags *flags_out)
1296 {
1297     *flags_out = 0;
1298     return 0;
1299 }
1300 
1301 /* Store a credential in the cache keyring. */
1302 static krb5_error_code KRB5_CALLCONV
krcc_store(krb5_context context,krb5_ccache id,krb5_creds * creds)1303 krcc_store(krb5_context context, krb5_ccache id, krb5_creds *creds)
1304 {
1305     krb5_error_code ret;
1306     krcc_data *data = id->data;
1307     struct k5buf buf = EMPTY_K5BUF;
1308     char *keyname = NULL;
1309     key_serial_t cred_key;
1310     krb5_timestamp now;
1311 
1312     k5_cc_mutex_lock(context, &data->lock);
1313 
1314     if (!data->cache_id) {
1315         k5_cc_mutex_unlock(context, &data->lock);
1316         return KRB5_FCC_NOFILE;
1317     }
1318 
1319     /* Get the service principal name and use it as the key name */
1320     ret = krb5_unparse_name(context, creds->server, &keyname);
1321     if (ret)
1322         goto errout;
1323 
1324     /* Serialize credential using the file ccache version 4 format. */
1325     k5_buf_init_dynamic_zap(&buf);
1326     k5_marshal_cred(&buf, 4, creds);
1327     ret = k5_buf_status(&buf);
1328     if (ret)
1329         goto errout;
1330 
1331     /* Add new key (credentials) into keyring */
1332     DEBUG_PRINT(("krcc_store: adding new key '%s' to keyring %d\n",
1333                  keyname, data->cache_id));
1334     ret = add_cred_key(keyname, buf.data, buf.len, data->cache_id,
1335                        data->is_legacy_type, &cred_key);
1336     if (ret)
1337         goto errout;
1338 
1339     /* Set appropriate timeouts on cache keys. */
1340     ret = krb5_timeofday(context, &now);
1341     if (ret)
1342         goto errout;
1343 
1344     if (ts_after(creds->times.endtime, now)) {
1345         (void)keyctl_set_timeout(cred_key,
1346                                  ts_interval(now, creds->times.endtime));
1347     }
1348 
1349     update_keyring_expiration(context, id);
1350 
1351 errout:
1352     k5_buf_free(&buf);
1353     krb5_free_unparsed_name(context, keyname);
1354     k5_cc_mutex_unlock(context, &data->lock);
1355     return ret;
1356 }
1357 
1358 /* Lock the cache handle against other threads.  (This does not lock the cache
1359  * keyring against other processes.) */
1360 static krb5_error_code KRB5_CALLCONV
krcc_lock(krb5_context context,krb5_ccache id)1361 krcc_lock(krb5_context context, krb5_ccache id)
1362 {
1363     krcc_data *data = id->data;
1364 
1365     k5_cc_mutex_lock(context, &data->lock);
1366     return 0;
1367 }
1368 
1369 /* Unlock the cache handle. */
1370 static krb5_error_code KRB5_CALLCONV
krcc_unlock(krb5_context context,krb5_ccache id)1371 krcc_unlock(krb5_context context, krb5_ccache id)
1372 {
1373     krcc_data *data = id->data;
1374 
1375     k5_cc_mutex_unlock(context, &data->lock);
1376     return 0;
1377 }
1378 
1379 static krb5_error_code
save_principal(krb5_context context,krb5_ccache id,krb5_principal princ)1380 save_principal(krb5_context context, krb5_ccache id, krb5_principal princ)
1381 {
1382     krcc_data *data = id->data;
1383     krb5_error_code ret;
1384     struct k5buf buf;
1385     key_serial_t newkey;
1386 
1387     k5_cc_mutex_assert_locked(context, &data->lock);
1388 
1389     /* Serialize princ using the file ccache version 4 format. */
1390     k5_buf_init_dynamic(&buf);
1391     k5_marshal_princ(&buf, 4, princ);
1392     if (k5_buf_status(&buf) != 0)
1393         return ENOMEM;
1394 
1395     /* Add new key into keyring */
1396 #ifdef KRCC_DEBUG
1397     {
1398         krb5_error_code rc;
1399         char *princname = NULL;
1400         rc = krb5_unparse_name(context, princ, &princname);
1401         DEBUG_PRINT(("save_principal: adding new key '%s' "
1402                      "to keyring %d for principal '%s'\n",
1403                      KRCC_SPEC_PRINC_KEYNAME, data->cache_id,
1404                      rc ? "<unknown>" : princname));
1405         if (rc == 0)
1406             krb5_free_unparsed_name(context, princname);
1407     }
1408 #endif
1409     newkey = add_key(KRCC_KEY_TYPE_USER, KRCC_SPEC_PRINC_KEYNAME, buf.data,
1410                      buf.len, data->cache_id);
1411     if (newkey < 0) {
1412         ret = errno;
1413         DEBUG_PRINT(("Error adding principal key: %s\n", strerror(ret)));
1414     } else {
1415         data->princ_id = newkey;
1416         ret = 0;
1417     }
1418 
1419     k5_buf_free(&buf);
1420     return ret;
1421 }
1422 
1423 /* Add a key to the cache keyring containing the given time offsets. */
1424 static krb5_error_code
save_time_offsets(krb5_context context,krb5_ccache id,int32_t time_offset,int32_t usec_offset)1425 save_time_offsets(krb5_context context, krb5_ccache id, int32_t time_offset,
1426                   int32_t usec_offset)
1427 {
1428     krcc_data *data = id->data;
1429     key_serial_t newkey;
1430     unsigned char payload[8];
1431 
1432     k5_cc_mutex_assert_locked(context, &data->lock);
1433 
1434     /* Prepare the payload. */
1435     store_32_be(time_offset, payload);
1436     store_32_be(usec_offset, payload + 4);
1437 
1438     /* Add new key into keyring. */
1439     newkey = add_key(KRCC_KEY_TYPE_USER, KRCC_TIME_OFFSETS, payload, 8,
1440                      data->cache_id);
1441     if (newkey == -1)
1442         return errno;
1443     return 0;
1444 }
1445 
1446 /* Retrieve and parse the key in the cache keyring containing time offsets. */
1447 static krb5_error_code
get_time_offsets(krb5_context context,krb5_ccache id,int32_t * time_offset,int32_t * usec_offset)1448 get_time_offsets(krb5_context context, krb5_ccache id, int32_t *time_offset,
1449                  int32_t *usec_offset)
1450 {
1451     krcc_data *data = id->data;
1452     krb5_error_code ret = 0;
1453     key_serial_t key;
1454     void *payload = NULL;
1455     int psize;
1456 
1457     k5_cc_mutex_lock(context, &data->lock);
1458 
1459     if (!data->cache_id) {
1460         ret = KRB5_FCC_NOFILE;
1461         goto errout;
1462     }
1463 
1464     key = keyctl_search(data->cache_id, KRCC_KEY_TYPE_USER, KRCC_TIME_OFFSETS,
1465                         0);
1466     if (key == -1) {
1467         ret = ENOENT;
1468         goto errout;
1469     }
1470 
1471     psize = keyctl_read_alloc(key, &payload);
1472     if (psize == -1) {
1473         DEBUG_PRINT(("Reading time offsets key %d: %s\n",
1474                      key, strerror(errno)));
1475         ret = KRB5_CC_IO;
1476         goto errout;
1477     }
1478 
1479     if (psize < 8) {
1480         ret = KRB5_CC_END;
1481         goto errout;
1482     }
1483     *time_offset = load_32_be(payload);
1484     *usec_offset = load_32_be((char *)payload + 4);
1485 
1486 errout:
1487     free(payload);
1488     k5_cc_mutex_unlock(context, &data->lock);
1489     return ret;
1490 }
1491 
1492 struct krcc_ptcursor_data {
1493     key_serial_t collection_id;
1494     char *anchor_name;
1495     char *collection_name;
1496     char *subsidiary_name;
1497     char *primary_name;
1498     krb5_boolean first;
1499     long num_keys;
1500     long next_key;
1501     key_serial_t *keys;
1502 };
1503 
1504 static krb5_error_code KRB5_CALLCONV
krcc_ptcursor_new(krb5_context context,krb5_cc_ptcursor * cursor_out)1505 krcc_ptcursor_new(krb5_context context, krb5_cc_ptcursor *cursor_out)
1506 {
1507     struct krcc_ptcursor_data *ptd;
1508     krb5_cc_ptcursor cursor;
1509     krb5_error_code ret;
1510     void *keys;
1511     long size;
1512 
1513     *cursor_out = NULL;
1514 
1515     cursor = k5alloc(sizeof(*cursor), &ret);
1516     if (cursor == NULL)
1517         return ENOMEM;
1518     ptd = k5alloc(sizeof(*ptd), &ret);
1519     if (ptd == NULL)
1520         goto error;
1521     cursor->ops = &krb5_krcc_ops;
1522     cursor->data = ptd;
1523     ptd->first = TRUE;
1524 
1525     ret = get_default(context, &ptd->anchor_name, &ptd->collection_name,
1526                       &ptd->subsidiary_name);
1527     if (ret)
1528         goto error;
1529 
1530     /* If there is no default collection, return an empty cursor. */
1531     if (ptd->anchor_name == NULL) {
1532         *cursor_out = cursor;
1533         return 0;
1534     }
1535 
1536     ret = get_collection(ptd->anchor_name, ptd->collection_name,
1537                          &ptd->collection_id);
1538     if (ret)
1539         goto error;
1540 
1541     if (ptd->subsidiary_name == NULL) {
1542         ret = get_primary_name(context, ptd->anchor_name,
1543                                ptd->collection_name, ptd->collection_id,
1544                                &ptd->primary_name);
1545         if (ret)
1546             goto error;
1547 
1548         size = keyctl_read_alloc(ptd->collection_id, &keys);
1549         if (size == -1) {
1550             ret = errno;
1551             goto error;
1552         }
1553         ptd->keys = keys;
1554         ptd->num_keys = size / sizeof(key_serial_t);
1555     }
1556 
1557     *cursor_out = cursor;
1558     return 0;
1559 
1560 error:
1561     krcc_ptcursor_free(context, &cursor);
1562     return ret;
1563 }
1564 
1565 static krb5_error_code KRB5_CALLCONV
krcc_ptcursor_next(krb5_context context,krb5_cc_ptcursor cursor,krb5_ccache * cache_out)1566 krcc_ptcursor_next(krb5_context context, krb5_cc_ptcursor cursor,
1567                    krb5_ccache *cache_out)
1568 {
1569     krb5_error_code ret;
1570     struct krcc_ptcursor_data *ptd = cursor->data;
1571     key_serial_t key, cache_id = 0;
1572     const char *first_name, *keytype, *sep, *subsidiary_name;
1573     size_t keytypelen;
1574     char *description = NULL;
1575 
1576     *cache_out = NULL;
1577 
1578     /* No keyring available */
1579     if (ptd->collection_id == 0)
1580         return 0;
1581 
1582     if (ptd->first) {
1583         /* Look for the primary cache for a collection cursor, or the
1584          * subsidiary cache for a subsidiary cursor. */
1585         ptd->first = FALSE;
1586         first_name = (ptd->primary_name != NULL) ? ptd->primary_name :
1587             ptd->subsidiary_name;
1588         cache_id = keyctl_search(ptd->collection_id, KRCC_KEY_TYPE_KEYRING,
1589                                  first_name, 0);
1590         if (cache_id != -1) {
1591             return make_cache(context, ptd->collection_id, cache_id,
1592                               ptd->anchor_name, ptd->collection_name,
1593                               first_name, cache_out);
1594         }
1595     }
1596 
1597     /* A subsidiary cursor yields at most the first cache. */
1598     if (ptd->subsidiary_name != NULL)
1599         return 0;
1600 
1601     keytype = KRCC_KEY_TYPE_KEYRING ";";
1602     keytypelen = strlen(keytype);
1603 
1604     for (; ptd->next_key < ptd->num_keys; ptd->next_key++) {
1605         /* Free any previously retrieved key description. */
1606         free(description);
1607         description = NULL;
1608 
1609         /*
1610          * Get the key description, which should have the form:
1611          *   typename;UID;GID;permissions;description
1612          */
1613         key = ptd->keys[ptd->next_key];
1614         if (keyctl_describe_alloc(key, &description) < 0)
1615             continue;
1616         sep = strrchr(description, ';');
1617         if (sep == NULL)
1618             continue;
1619         subsidiary_name = sep + 1;
1620 
1621         /* Skip this key if it isn't a keyring. */
1622         if (strncmp(description, keytype, keytypelen) != 0)
1623             continue;
1624 
1625         /* Don't repeat the primary cache. */
1626         if (strcmp(subsidiary_name, ptd->primary_name) == 0)
1627             continue;
1628 
1629         /* We found a valid key */
1630         ptd->next_key++;
1631         ret = make_cache(context, ptd->collection_id, key, ptd->anchor_name,
1632                          ptd->collection_name, subsidiary_name, cache_out);
1633         free(description);
1634         return ret;
1635     }
1636 
1637     free(description);
1638     return 0;
1639 }
1640 
1641 static krb5_error_code KRB5_CALLCONV
krcc_ptcursor_free(krb5_context context,krb5_cc_ptcursor * cursor)1642 krcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor)
1643 {
1644     struct krcc_ptcursor_data *ptd = (*cursor)->data;
1645 
1646     if (ptd != NULL) {
1647         free(ptd->anchor_name);
1648         free(ptd->collection_name);
1649         free(ptd->subsidiary_name);
1650         free(ptd->primary_name);
1651         free(ptd->keys);
1652         free(ptd);
1653     }
1654     free(*cursor);
1655     *cursor = NULL;
1656     return 0;
1657 }
1658 
1659 static krb5_error_code KRB5_CALLCONV
krcc_switch_to(krb5_context context,krb5_ccache cache)1660 krcc_switch_to(krb5_context context, krb5_ccache cache)
1661 {
1662     krcc_data *data = cache->data;
1663     krb5_error_code ret;
1664     char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
1665     key_serial_t collection_id;
1666 
1667     ret = parse_residual(data->name, &anchor_name, &collection_name,
1668                          &subsidiary_name);
1669     if (ret)
1670         goto cleanup;
1671     ret = get_collection(anchor_name, collection_name, &collection_id);
1672     if (ret)
1673         goto cleanup;
1674     ret = set_primary_name(context, collection_id, subsidiary_name);
1675 
1676 cleanup:
1677     free(anchor_name);
1678     free(collection_name);
1679     free(subsidiary_name);
1680     return ret;
1681 }
1682 
1683 /*
1684  * ccache implementation storing credentials in the Linux keyring facility
1685  * The default is to put them at the session keyring level.
1686  * If "KEYRING:process:" or "KEYRING:thread:" is specified, then they will
1687  * be stored at the process or thread level respectively.
1688  */
1689 const krb5_cc_ops krb5_krcc_ops = {
1690     0,
1691     "KEYRING",
1692     krcc_get_name,
1693     krcc_resolve,
1694     krcc_generate_new,
1695     krcc_initialize,
1696     krcc_destroy,
1697     krcc_close,
1698     krcc_store,
1699     krcc_retrieve,
1700     krcc_get_principal,
1701     krcc_start_seq_get,
1702     krcc_next_cred,
1703     krcc_end_seq_get,
1704     krcc_remove_cred,
1705     krcc_set_flags,
1706     krcc_get_flags,        /* added after 1.4 release */
1707     krcc_ptcursor_new,
1708     krcc_ptcursor_next,
1709     krcc_ptcursor_free,
1710     NULL, /* move */
1711     NULL, /* wasdefault */
1712     krcc_lock,
1713     krcc_unlock,
1714     krcc_switch_to,
1715 };
1716 
1717 #else /* !USE_KEYRING_CCACHE */
1718 
1719 /*
1720  * Export this, but it shouldn't be used.
1721  */
1722 const krb5_cc_ops krb5_krcc_ops = {
1723     0,
1724     "KEYRING",
1725     NULL,
1726     NULL,
1727     NULL,
1728     NULL,
1729     NULL,
1730     NULL,
1731     NULL,
1732     NULL,
1733     NULL,
1734     NULL,
1735     NULL,
1736     NULL,
1737     NULL,
1738     NULL,
1739     NULL,                       /* added after 1.4 release */
1740     NULL,
1741     NULL,
1742     NULL,
1743     NULL,
1744     NULL,
1745     NULL,
1746     NULL,
1747     NULL,
1748 };
1749 #endif  /* USE_KEYRING_CCACHE */
1750