Imported Upstream version 1.17
[platform/upstream/krb5.git] / src / lib / krb5 / ccache / cc_keyring.c
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
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_timestamp changetime;
234     krb5_boolean is_legacy_type;
235 } krcc_data;
236
237 /* Global mutex */
238 k5_cc_mutex krb5int_krcc_mutex = K5_CC_MUTEX_PARTIAL_INITIALIZER;
239
240 extern const krb5_cc_ops krb5_krcc_ops;
241
242 static const char *KRB5_CALLCONV
243 krcc_get_name(krb5_context context, krb5_ccache id);
244
245 static krb5_error_code KRB5_CALLCONV
246 krcc_start_seq_get(krb5_context, krb5_ccache id, krb5_cc_cursor *cursor);
247
248 static krb5_error_code KRB5_CALLCONV
249 krcc_next_cred(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor,
250                krb5_creds *creds);
251
252 static krb5_error_code KRB5_CALLCONV
253 krcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor);
254
255 static krb5_error_code KRB5_CALLCONV
256 krcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor);
257
258 static krb5_error_code clear_cache_keyring(krb5_context context,
259                                            krb5_ccache id);
260
261 static krb5_error_code make_krcc_data(const char *anchor_name,
262                                       const char *collection_name,
263                                       const char *subsidiary_name,
264                                       key_serial_t cache_id, key_serial_t
265                                       collection_id, krcc_data **datapp);
266
267 static krb5_error_code save_principal(krb5_context context, krb5_ccache id,
268                                       krb5_principal princ);
269
270 static krb5_error_code save_time_offsets(krb5_context context, krb5_ccache id,
271                                          int32_t time_offset,
272                                          int32_t usec_offset);
273
274 static krb5_error_code get_time_offsets(krb5_context context, krb5_ccache id,
275                                         int32_t *time_offset,
276                                         int32_t *usec_offset);
277
278 static void krcc_update_change_time(krcc_data *d);
279
280 /* Note the following is a stub function for Linux */
281 extern krb5_error_code krb5_change_cache(void);
282
283 /*
284  * GET_PERSISTENT(uid) acquires the persistent keyring for uid, or falls back
285  * to the user keyring if uid matches the current effective uid.
286  */
287
288 static key_serial_t
289 get_persistent_fallback(uid_t uid)
290 {
291     return (uid == geteuid()) ? KEY_SPEC_USER_KEYRING : -1;
292 }
293
294 #ifdef HAVE_PERSISTENT_KEYRING
295 #define GET_PERSISTENT get_persistent_real
296 static key_serial_t
297 get_persistent_real(uid_t uid)
298 {
299     key_serial_t key;
300
301     key = keyctl_get_persistent(uid, KEY_SPEC_PROCESS_KEYRING);
302     return (key == -1 && errno == ENOTSUP) ? get_persistent_fallback(uid) :
303         key;
304 }
305 #else
306 #define GET_PERSISTENT get_persistent_fallback
307 #endif
308
309 /*
310  * If a process has no explicitly set session keyring, KEY_SPEC_SESSION_KEYRING
311  * will resolve to the user session keyring for ID lookup and reading, but in
312  * some kernel versions, writing to that special keyring will instead create a
313  * new empty session keyring for the process.  We do not want that; the keys we
314  * create would be invisible to other processes.  We can work around that
315  * behavior by explicitly writing to the user session keyring when it matches
316  * the session keyring.  This function returns the keyring we should write to
317  * for the session anchor.
318  */
319 static key_serial_t
320 session_write_anchor()
321 {
322     key_serial_t s, u;
323
324     s = keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 0);
325     u = keyctl_get_keyring_ID(KEY_SPEC_USER_SESSION_KEYRING, 0);
326     return (s == u) ? KEY_SPEC_USER_SESSION_KEYRING : KEY_SPEC_SESSION_KEYRING;
327 }
328
329 /*
330  * Find or create a keyring within parent with the given name.  If possess is
331  * nonzero, also make sure the key is linked from possess.  This is necessary
332  * to ensure that we have possession rights on the key when the parent is the
333  * user or persistent keyring.
334  */
335 static krb5_error_code
336 find_or_create_keyring(key_serial_t parent, key_serial_t possess,
337                        const char *name, key_serial_t *key_out)
338 {
339     key_serial_t key;
340
341     *key_out = -1;
342     key = keyctl_search(parent, KRCC_KEY_TYPE_KEYRING, name, possess);
343     if (key == -1) {
344         if (possess != 0) {
345             key = add_key(KRCC_KEY_TYPE_KEYRING, name, NULL, 0, possess);
346             if (key == -1)
347                 return errno;
348             if (keyctl_link(key, parent) == -1)
349                 return errno;
350         } else {
351             key = add_key(KRCC_KEY_TYPE_KEYRING, name, NULL, 0, parent);
352             if (key == -1)
353                 return errno;
354         }
355     }
356     *key_out = key;
357     return 0;
358 }
359
360 /* Parse a residual name into an anchor name, a collection name, and possibly a
361  * subsidiary name. */
362 static krb5_error_code
363 parse_residual(const char *residual, char **anchor_name_out,
364                char **collection_name_out, char **subsidiary_name_out)
365 {
366     krb5_error_code ret;
367     char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
368     const char *sep;
369
370     *anchor_name_out = 0;
371     *collection_name_out = NULL;
372     *subsidiary_name_out = NULL;
373
374     /* Parse out the anchor name.  Use the legacy anchor if not present. */
375     sep = strchr(residual, ':');
376     if (sep == NULL) {
377         anchor_name = strdup(KRCC_LEGACY_ANCHOR);
378         if (anchor_name == NULL)
379             goto oom;
380     } else {
381         anchor_name = k5memdup0(residual, sep - residual, &ret);
382         if (anchor_name == NULL)
383             goto oom;
384         residual = sep + 1;
385     }
386
387     /* Parse out the collection and subsidiary name. */
388     sep = strchr(residual, ':');
389     if (sep == NULL) {
390         collection_name = strdup(residual);
391         if (collection_name == NULL)
392             goto oom;
393         subsidiary_name = NULL;
394     } else {
395         collection_name = k5memdup0(residual, sep - residual, &ret);
396         if (collection_name == NULL)
397             goto oom;
398         subsidiary_name = strdup(sep + 1);
399         if (subsidiary_name == NULL)
400             goto oom;
401     }
402
403     *anchor_name_out = anchor_name;
404     *collection_name_out = collection_name;
405     *subsidiary_name_out = subsidiary_name;
406     return 0;
407
408 oom:
409     free(anchor_name);
410     free(collection_name);
411     free(subsidiary_name);
412     return ENOMEM;
413 }
414
415 /*
416  * Return true if residual identifies a subsidiary cache which should be linked
417  * into the anchor so it can be visible to old code.  This is the case if the
418  * residual has the legacy anchor and the subsidiary name matches the
419  * collection name.
420  */
421 static krb5_boolean
422 is_legacy_cache_name(const char *residual)
423 {
424     const char *sep, *aname, *cname, *sname;
425     size_t alen, clen, legacy_len = sizeof(KRCC_LEGACY_ANCHOR) - 1;
426
427     /* Get pointers to the anchor, collection, and subsidiary names. */
428     aname = residual;
429     sep = strchr(residual, ':');
430     if (sep == NULL)
431         return FALSE;
432     alen = sep - aname;
433     cname = sep + 1;
434     sep = strchr(cname, ':');
435     if (sep == NULL)
436         return FALSE;
437     clen = sep - cname;
438     sname = sep + 1;
439
440     return alen == legacy_len && clen == strlen(sname) &&
441         strncmp(aname, KRCC_LEGACY_ANCHOR, alen) == 0 &&
442         strncmp(cname, sname, clen) == 0;
443 }
444
445 /* If the default cache name for context is a KEYRING cache, parse its residual
446  * string.  Otherwise set all outputs to NULL. */
447 static krb5_error_code
448 get_default(krb5_context context, char **anchor_name_out,
449             char **collection_name_out, char **subsidiary_name_out)
450 {
451     const char *defname;
452
453     *anchor_name_out = *collection_name_out = *subsidiary_name_out = NULL;
454     defname = krb5_cc_default_name(context);
455     if (defname == NULL || strncmp(defname, "KEYRING:", 8) != 0)
456         return 0;
457     return parse_residual(defname + 8, anchor_name_out, collection_name_out,
458                           subsidiary_name_out);
459 }
460
461 /* Create a residual identifying a subsidiary cache. */
462 static krb5_error_code
463 make_subsidiary_residual(const char *anchor_name, const char *collection_name,
464                          const char *subsidiary_name, char **residual_out)
465 {
466     if (asprintf(residual_out, "%s:%s:%s", anchor_name, collection_name,
467                  subsidiary_name) < 0) {
468         *residual_out = NULL;
469         return ENOMEM;
470     }
471     return 0;
472 }
473
474 /* Retrieve or create a keyring for collection_name within the anchor, and set
475  * *collection_id_out to its serial number. */
476 static krb5_error_code
477 get_collection(const char *anchor_name, const char *collection_name,
478                key_serial_t *collection_id_out)
479 {
480     krb5_error_code ret;
481     key_serial_t persistent_id, anchor_id, possess_id = 0;
482     char *ckname, *cnend;
483     long uidnum;
484
485     *collection_id_out = 0;
486
487     if (strcmp(anchor_name, KRCC_PERSISTENT_ANCHOR) == 0) {
488         /*
489          * The collection name is a uid (or empty for the current effective
490          * uid), and we look up a fixed keyring name within the persistent
491          * keyring for that uid.  We link it to the process keyring to ensure
492          * that we have possession rights on the collection key.
493          */
494         if (*collection_name != '\0') {
495             errno = 0;
496             uidnum = strtol(collection_name, &cnend, 10);
497             if (errno || *cnend != '\0')
498                 return KRB5_KCC_INVALID_UID;
499         } else {
500             uidnum = geteuid();
501         }
502         persistent_id = GET_PERSISTENT(uidnum);
503         if (persistent_id == -1)
504             return KRB5_KCC_INVALID_UID;
505         return find_or_create_keyring(persistent_id, KEY_SPEC_PROCESS_KEYRING,
506                                       KRCC_PERSISTENT_KEYRING_NAME,
507                                       collection_id_out);
508     }
509
510     if (strcmp(anchor_name, KRCC_PROCESS_ANCHOR) == 0) {
511         anchor_id = KEY_SPEC_PROCESS_KEYRING;
512     } else if (strcmp(anchor_name, KRCC_THREAD_ANCHOR) == 0) {
513         anchor_id = KEY_SPEC_THREAD_KEYRING;
514     } else if (strcmp(anchor_name, KRCC_SESSION_ANCHOR) == 0) {
515         anchor_id = session_write_anchor();
516     } else if (strcmp(anchor_name, KRCC_USER_ANCHOR) == 0) {
517         /* The user keyring does not confer possession, so we need to link the
518          * collection to the process keyring to maintain possession rights. */
519         anchor_id = KEY_SPEC_USER_KEYRING;
520         possess_id = KEY_SPEC_PROCESS_KEYRING;
521     } else if (strcmp(anchor_name, KRCC_LEGACY_ANCHOR) == 0) {
522         anchor_id = session_write_anchor();
523     } else {
524         return KRB5_KCC_INVALID_ANCHOR;
525     }
526
527     /* Look up the collection keyring name within the anchor keyring. */
528     if (asprintf(&ckname, "%s%s", KRCC_CCCOL_PREFIX, collection_name) == -1)
529         return ENOMEM;
530     ret = find_or_create_keyring(anchor_id, possess_id, ckname,
531                                  collection_id_out);
532     free(ckname);
533     return ret;
534 }
535
536 /* Store subsidiary_name into the primary index key for collection_id. */
537 static krb5_error_code
538 set_primary_name(krb5_context context, key_serial_t collection_id,
539                  const char *subsidiary_name)
540 {
541     key_serial_t key;
542     uint32_t len = strlen(subsidiary_name), plen = 8 + len;
543     unsigned char *payload;
544
545     payload = malloc(plen);
546     if (payload == NULL)
547         return ENOMEM;
548     store_32_be(KRCC_COLLECTION_VERSION, payload);
549     store_32_be(len, payload + 4);
550     memcpy(payload + 8, subsidiary_name, len);
551     key = add_key(KRCC_KEY_TYPE_USER, KRCC_COLLECTION_PRIMARY,
552                   payload, plen, collection_id);
553     free(payload);
554     return (key == -1) ? errno : 0;
555 }
556
557 static krb5_error_code
558 parse_index(krb5_context context, int32_t *version, char **primary,
559             const unsigned char *payload, size_t psize)
560 {
561     krb5_error_code ret;
562     uint32_t len;
563
564     if (psize < 8)
565         return KRB5_CC_END;
566
567     *version = load_32_be(payload);
568     len = load_32_be(payload + 4);
569     if (len > psize - 8)
570         return KRB5_CC_END;
571     *primary = k5memdup0(payload + 8, len, &ret);
572     return (*primary == NULL) ? ret : 0;
573 }
574
575 /*
576  * Get or initialize the primary name within collection_id and set
577  * *subsidiary_out to its value.  If initializing a legacy collection, look
578  * for a legacy cache and add it to the collection.
579  */
580 static krb5_error_code
581 get_primary_name(krb5_context context, const char *anchor_name,
582                  const char *collection_name, key_serial_t collection_id,
583                  char **subsidiary_out)
584 {
585     krb5_error_code ret;
586     key_serial_t primary_id, legacy;
587     void *payload = NULL;
588     int payloadlen;
589     int32_t version;
590     char *subsidiary_name = NULL;
591
592     *subsidiary_out = NULL;
593
594     primary_id = keyctl_search(collection_id, KRCC_KEY_TYPE_USER,
595                                KRCC_COLLECTION_PRIMARY, 0);
596     if (primary_id == -1) {
597         /* Initialize the primary key using the collection name.  We can't name
598          * a key with the empty string, so map that to an arbitrary string. */
599         subsidiary_name = strdup((*collection_name == '\0') ? "tkt" :
600                                  collection_name);
601         if (subsidiary_name == NULL) {
602             ret = ENOMEM;
603             goto cleanup;
604         }
605         ret = set_primary_name(context, collection_id, subsidiary_name);
606         if (ret)
607             goto cleanup;
608
609         if (strcmp(anchor_name, KRCC_LEGACY_ANCHOR) == 0) {
610             /* Look for a cache created by old code.  If we find one, add it to
611              * the collection. */
612             legacy = keyctl_search(KEY_SPEC_SESSION_KEYRING,
613                                    KRCC_KEY_TYPE_KEYRING, subsidiary_name, 0);
614             if (legacy != -1 && keyctl_link(legacy, collection_id) == -1) {
615                 ret = errno;
616                 goto cleanup;
617             }
618         }
619     } else {
620         /* Read, parse, and free the primary key's payload. */
621         payloadlen = keyctl_read_alloc(primary_id, &payload);
622         if (payloadlen == -1) {
623             ret = errno;
624             goto cleanup;
625         }
626         ret = parse_index(context, &version, &subsidiary_name, payload,
627                           payloadlen);
628         if (ret)
629             goto cleanup;
630
631         if (version != KRCC_COLLECTION_VERSION) {
632             ret = KRB5_KCC_UNKNOWN_VERSION;
633             goto cleanup;
634         }
635     }
636
637     *subsidiary_out = subsidiary_name;
638     subsidiary_name = NULL;
639
640 cleanup:
641     free(payload);
642     free(subsidiary_name);
643     return ret;
644 }
645
646 /*
647  * Create a keyring with a unique random name within collection_id.  Set
648  * *subsidiary to its name and *cache_id_out to its key serial number.
649  */
650 static krb5_error_code
651 unique_keyring(krb5_context context, key_serial_t collection_id,
652                char **subsidiary_out, key_serial_t *cache_id_out)
653 {
654     key_serial_t key;
655     krb5_error_code ret;
656     char uniquename[sizeof(KRCC_NAME_PREFIX) + KRCC_NAME_RAND_CHARS];
657     int prefixlen = sizeof(KRCC_NAME_PREFIX) - 1;
658     int tries;
659
660     *subsidiary_out = NULL;
661     *cache_id_out = 0;
662
663     memcpy(uniquename, KRCC_NAME_PREFIX, sizeof(KRCC_NAME_PREFIX));
664     k5_cc_mutex_lock(context, &krb5int_krcc_mutex);
665
666     /* Loop until we successfully create a new ccache keyring with
667      * a unique name, or we get an error. Limit to 100 tries. */
668     tries = 100;
669     while (tries-- > 0) {
670         ret = krb5int_random_string(context, uniquename + prefixlen,
671                                     KRCC_NAME_RAND_CHARS);
672         if (ret)
673             goto cleanup;
674
675         key = keyctl_search(collection_id, KRCC_KEY_TYPE_KEYRING, uniquename,
676                             0);
677         if (key < 0) {
678             /* Name does not already exist.  Create it to reserve the name. */
679             key = add_key(KRCC_KEY_TYPE_KEYRING, uniquename, NULL, 0,
680                           collection_id);
681             if (key < 0) {
682                 ret = errno;
683                 goto cleanup;
684             }
685             break;
686         }
687     }
688
689     if (tries <= 0) {
690         ret = KRB5_CC_BADNAME;
691         goto cleanup;
692     }
693
694     *subsidiary_out = strdup(uniquename);
695     if (*subsidiary_out == NULL) {
696         ret = ENOMEM;
697         goto cleanup;
698     }
699     *cache_id_out = key;
700     ret = 0;
701 cleanup:
702     k5_cc_mutex_unlock(context, &krb5int_krcc_mutex);
703     return ret;
704 }
705
706 static krb5_error_code
707 add_cred_key(const char *name, const void *payload, size_t plen,
708              key_serial_t cache_id, krb5_boolean legacy_type,
709              key_serial_t *key_out)
710 {
711     key_serial_t key;
712
713     *key_out = -1;
714     if (!legacy_type) {
715         /* Try the preferred cred key type; fall back if no kernel support. */
716         key = add_key(KRCC_CRED_KEY_TYPE, name, payload, plen, cache_id);
717         if (key != -1) {
718             *key_out = key;
719             return 0;
720         } else if (errno != EINVAL && errno != ENODEV) {
721             return errno;
722         }
723     }
724     /* Use the user key type. */
725     key = add_key(KRCC_KEY_TYPE_USER, name, payload, plen, cache_id);
726     if (key == -1)
727         return errno;
728     *key_out = key;
729     return 0;
730 }
731
732 static void
733 update_keyring_expiration(krb5_context context, krb5_ccache id)
734 {
735     krcc_data *data = id->data;
736     krb5_cc_cursor cursor;
737     krb5_creds creds;
738     krb5_timestamp now, endtime = 0;
739     unsigned int timeout;
740
741     /*
742      * We have no way to know what is the actual timeout set on the keyring.
743      * We also cannot keep track of it in a local variable as another process
744      * can always modify the keyring independently, so just always enumerate
745      * all keys and find out the highest endtime time.
746      */
747
748     /* Find the maximum endtime of all creds in the cache. */
749     if (krcc_start_seq_get(context, id, &cursor) != 0)
750         return;
751     for (;;) {
752         if (krcc_next_cred(context, id, &cursor, &creds) != 0)
753             break;
754         if (ts_after(creds.times.endtime, endtime))
755             endtime = creds.times.endtime;
756         krb5_free_cred_contents(context, &creds);
757     }
758     (void)krcc_end_seq_get(context, id, &cursor);
759
760     if (endtime == 0)        /* No creds with end times */
761         return;
762
763     if (krb5_timeofday(context, &now) != 0)
764         return;
765
766     /* Setting the timeout to zero would reset the timeout, so we set it to one
767      * second instead if creds are already expired. */
768     timeout = ts_after(endtime, now) ? ts_delta(endtime, now) : 1;
769     (void)keyctl_set_timeout(data->cache_id, timeout);
770 }
771
772 /* Create or overwrite the cache keyring, and set the default principal. */
773 static krb5_error_code KRB5_CALLCONV
774 krcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ)
775 {
776     krcc_data *data = (krcc_data *)id->data;
777     krb5_os_context os_ctx = &context->os_context;
778     krb5_error_code ret;
779     const char *cache_name, *p;
780
781     k5_cc_mutex_lock(context, &data->lock);
782
783     ret = clear_cache_keyring(context, id);
784     if (ret)
785         goto out;
786
787     if (!data->cache_id) {
788         /* The key didn't exist at resolve time.  Check again and create the
789          * key if it still isn't there. */
790         p = strrchr(data->name, ':');
791         cache_name = (p != NULL) ? p + 1 : data->name;
792         ret = find_or_create_keyring(data->collection_id, 0, cache_name,
793                                      &data->cache_id);
794         if (ret)
795             goto out;
796     }
797
798     /* If this is the legacy cache in a legacy session collection, link it
799      * directly to the session keyring so that old code can see it. */
800     if (is_legacy_cache_name(data->name))
801         (void)keyctl_link(data->cache_id, session_write_anchor());
802
803     ret = save_principal(context, id, princ);
804
805     /* Save time offset if it is valid and this is not a legacy cache.  Legacy
806      * applications would fail to parse the new key in the cache keyring. */
807     if (!is_legacy_cache_name(data->name) &&
808         (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID)) {
809         ret = save_time_offsets(context, id, os_ctx->time_offset,
810                                 os_ctx->usec_offset);
811     }
812
813     if (ret == 0)
814         krb5_change_cache();
815
816 out:
817     k5_cc_mutex_unlock(context, &data->lock);
818     return ret;
819 }
820
821 /* Release the ccache handle. */
822 static krb5_error_code KRB5_CALLCONV
823 krcc_close(krb5_context context, krb5_ccache id)
824 {
825     krcc_data *data = id->data;
826
827     k5_cc_mutex_destroy(&data->lock);
828     free(data->name);
829     free(data);
830     free(id);
831     return 0;
832 }
833
834 /* Clear out a ccache keyring, unlinking all keys within it.  Call with the
835  * mutex locked. */
836 static krb5_error_code
837 clear_cache_keyring(krb5_context context, krb5_ccache id)
838 {
839     krcc_data *data = id->data;
840     int res;
841
842     k5_cc_mutex_assert_locked(context, &data->lock);
843
844     DEBUG_PRINT(("clear_cache_keyring: cache_id %d, princ_id %d\n",
845                  data->cache_id, data->princ_id));
846
847     if (data->cache_id) {
848         res = keyctl_clear(data->cache_id);
849         if (res != 0)
850             return errno;
851     }
852     data->princ_id = 0;
853     krcc_update_change_time(data);
854
855     return 0;
856 }
857
858 /* Destroy the cache keyring and release the handle. */
859 static krb5_error_code KRB5_CALLCONV
860 krcc_destroy(krb5_context context, krb5_ccache id)
861 {
862     krb5_error_code ret = 0;
863     krcc_data *data = id->data;
864     int res;
865
866     k5_cc_mutex_lock(context, &data->lock);
867
868     clear_cache_keyring(context, id);
869     if (data->cache_id) {
870         res = keyctl_unlink(data->cache_id, data->collection_id);
871         if (res < 0) {
872             ret = errno;
873             DEBUG_PRINT(("unlinking key %d from ring %d: %s", data->cache_id,
874                          data->collection_id, error_message(errno)));
875         }
876         /* If this is a legacy cache, unlink it from the session anchor. */
877         if (is_legacy_cache_name(data->name))
878             (void)keyctl_unlink(data->cache_id, session_write_anchor());
879     }
880
881     k5_cc_mutex_unlock(context, &data->lock);
882     k5_cc_mutex_destroy(&data->lock);
883     free(data->name);
884     free(data);
885     free(id);
886     krb5_change_cache();
887     return ret;
888 }
889
890 /* Create a cache handle for a cache ID. */
891 static krb5_error_code
892 make_cache(krb5_context context, key_serial_t collection_id,
893            key_serial_t cache_id, const char *anchor_name,
894            const char *collection_name, const char *subsidiary_name,
895            krb5_ccache *cache_out)
896 {
897     krb5_error_code ret;
898     krb5_os_context os_ctx = &context->os_context;
899     krb5_ccache ccache = NULL;
900     krcc_data *data;
901     key_serial_t pkey = 0;
902
903     /* Determine the key containing principal information, if present. */
904     pkey = keyctl_search(cache_id, KRCC_KEY_TYPE_USER, KRCC_SPEC_PRINC_KEYNAME,
905                          0);
906     if (pkey < 0)
907         pkey = 0;
908
909     ccache = malloc(sizeof(struct _krb5_ccache));
910     if (!ccache)
911         return ENOMEM;
912
913     ret = make_krcc_data(anchor_name, collection_name, subsidiary_name,
914                          cache_id, collection_id, &data);
915     if (ret) {
916         free(ccache);
917         return ret;
918     }
919
920     data->princ_id = pkey;
921     ccache->ops = &krb5_krcc_ops;
922     ccache->data = data;
923     ccache->magic = KV5M_CCACHE;
924     *cache_out = ccache;
925
926     /* Look up time offsets if necessary. */
927     if ((context->library_options & KRB5_LIBOPT_SYNC_KDCTIME) &&
928         !(os_ctx->os_flags & KRB5_OS_TOFFSET_VALID)) {
929         if (get_time_offsets(context, ccache, &os_ctx->time_offset,
930                              &os_ctx->usec_offset) == 0) {
931             os_ctx->os_flags &= ~KRB5_OS_TOFFSET_TIME;
932             os_ctx->os_flags |= KRB5_OS_TOFFSET_VALID;
933         }
934     }
935
936     return 0;
937 }
938
939 /* Create a keyring ccache handle for the given residual string. */
940 static krb5_error_code KRB5_CALLCONV
941 krcc_resolve(krb5_context context, krb5_ccache *id, const char *residual)
942 {
943     krb5_error_code ret;
944     key_serial_t collection_id, cache_id;
945     char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
946
947     ret = parse_residual(residual, &anchor_name, &collection_name,
948                          &subsidiary_name);
949     if (ret)
950         goto cleanup;
951     ret = get_collection(anchor_name, collection_name, &collection_id);
952     if (ret)
953         goto cleanup;
954
955     if (subsidiary_name == NULL) {
956         /* Retrieve or initialize the primary name for the collection. */
957         ret = get_primary_name(context, anchor_name, collection_name,
958                                collection_id, &subsidiary_name);
959         if (ret)
960             goto cleanup;
961     }
962
963     /* Look up the cache keyring ID, if the cache is already initialized. */
964     cache_id = keyctl_search(collection_id, KRCC_KEY_TYPE_KEYRING,
965                              subsidiary_name, 0);
966     if (cache_id < 0)
967         cache_id = 0;
968
969     ret = make_cache(context, collection_id, cache_id, anchor_name,
970                      collection_name, subsidiary_name, id);
971     if (ret)
972         goto cleanup;
973
974 cleanup:
975     free(anchor_name);
976     free(collection_name);
977     free(subsidiary_name);
978     return ret;
979 }
980
981 /* Prepare for a sequential iteration over the cache keyring. */
982 static krb5_error_code KRB5_CALLCONV
983 krcc_start_seq_get(krb5_context context, krb5_ccache id,
984                    krb5_cc_cursor *cursor)
985 {
986     krcc_cursor krcursor;
987     krcc_data *data = id->data;
988     void *keys;
989     long size;
990
991     k5_cc_mutex_lock(context, &data->lock);
992
993     if (!data->cache_id) {
994         k5_cc_mutex_unlock(context, &data->lock);
995         return KRB5_FCC_NOFILE;
996     }
997
998     size = keyctl_read_alloc(data->cache_id, &keys);
999     if (size == -1) {
1000         DEBUG_PRINT(("Error getting from keyring: %s\n", strerror(errno)));
1001         k5_cc_mutex_unlock(context, &data->lock);
1002         return KRB5_CC_IO;
1003     }
1004
1005     krcursor = calloc(1, sizeof(*krcursor));
1006     if (krcursor == NULL) {
1007         free(keys);
1008         k5_cc_mutex_unlock(context, &data->lock);
1009         return KRB5_CC_NOMEM;
1010     }
1011
1012     krcursor->princ_id = data->princ_id;
1013     krcursor->offsets_id = keyctl_search(data->cache_id, KRCC_KEY_TYPE_USER,
1014                                          KRCC_TIME_OFFSETS, 0);
1015     krcursor->numkeys = size / sizeof(key_serial_t);
1016     krcursor->keys = keys;
1017
1018     k5_cc_mutex_unlock(context, &data->lock);
1019     *cursor = krcursor;
1020     return 0;
1021 }
1022
1023 /* Get the next credential from the cache keyring. */
1024 static krb5_error_code KRB5_CALLCONV
1025 krcc_next_cred(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor,
1026                krb5_creds *creds)
1027 {
1028     krcc_cursor krcursor;
1029     krb5_error_code ret;
1030     int psize;
1031     void *payload = NULL;
1032
1033     memset(creds, 0, sizeof(krb5_creds));
1034
1035     /* The cursor has the entire list of keys.  (Note that we don't support
1036      * remove_cred.) */
1037     krcursor = *cursor;
1038     if (krcursor == NULL)
1039         return KRB5_CC_END;
1040
1041     /* If we're pointing past the end of the keys array, there are no more. */
1042     if (krcursor->currkey >= krcursor->numkeys)
1043         return KRB5_CC_END;
1044
1045     /* If we're pointing at the entry with the principal, or at the key
1046      * with the time offsets, skip it. */
1047     while (krcursor->keys[krcursor->currkey] == krcursor->princ_id ||
1048            krcursor->keys[krcursor->currkey] == krcursor->offsets_id) {
1049         krcursor->currkey++;
1050         /* Check if we have now reached the end */
1051         if (krcursor->currkey >= krcursor->numkeys)
1052             return KRB5_CC_END;
1053     }
1054
1055     /* Read the key; the right size buffer will be allocated and returned. */
1056     psize = keyctl_read_alloc(krcursor->keys[krcursor->currkey], &payload);
1057     if (psize == -1) {
1058         DEBUG_PRINT(("Error reading key %d: %s\n",
1059                      krcursor->keys[krcursor->currkey],
1060                      strerror(errno)));
1061         return KRB5_FCC_NOFILE;
1062     }
1063     krcursor->currkey++;
1064
1065     /* Unmarshal the credential using the file ccache version 4 format. */
1066     ret = k5_unmarshal_cred(payload, psize, 4, creds);
1067     free(payload);
1068     return ret;
1069 }
1070
1071 /* Release an iteration cursor. */
1072 static krb5_error_code KRB5_CALLCONV
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
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->changetime = 0;
1117     data->is_legacy_type = (strcmp(anchor_name, KRCC_LEGACY_ANCHOR) == 0);
1118     krcc_update_change_time(data);
1119
1120     *data_out = data;
1121     return 0;
1122 }
1123
1124 /* Create a new keyring cache with a unique name. */
1125 static krb5_error_code KRB5_CALLCONV
1126 krcc_generate_new(krb5_context context, krb5_ccache *id_out)
1127 {
1128     krb5_ccache id = NULL;
1129     krb5_error_code ret;
1130     char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
1131     char *new_subsidiary_name = NULL, *new_residual = NULL;
1132     krcc_data *data;
1133     key_serial_t collection_id;
1134     key_serial_t cache_id = 0;
1135
1136     *id_out = NULL;
1137
1138     /* Determine the collection in which we will create the cache.*/
1139     ret = get_default(context, &anchor_name, &collection_name,
1140                       &subsidiary_name);
1141     if (ret)
1142         return ret;
1143     if (anchor_name == NULL) {
1144         ret = parse_residual(KRCC_DEFAULT_UNIQUE_COLLECTION, &anchor_name,
1145                              &collection_name, &subsidiary_name);
1146         if (ret)
1147             return ret;
1148     }
1149     if (subsidiary_name != NULL) {
1150         k5_setmsg(context, KRB5_DCC_CANNOT_CREATE,
1151                   _("Can't create new subsidiary cache because default cache "
1152                     "is already a subsidiary"));
1153         ret = KRB5_DCC_CANNOT_CREATE;
1154         goto cleanup;
1155     }
1156
1157     /* Allocate memory */
1158     id = malloc(sizeof(struct _krb5_ccache));
1159     if (id == NULL) {
1160         ret = ENOMEM;
1161         goto cleanup;
1162     }
1163
1164     id->ops = &krb5_krcc_ops;
1165
1166     /* Make a unique keyring within the chosen collection. */
1167     ret = get_collection(anchor_name, collection_name, &collection_id);
1168     if (ret)
1169         goto cleanup;
1170     ret = unique_keyring(context, collection_id, &new_subsidiary_name,
1171                          &cache_id);
1172     if (ret)
1173         goto cleanup;
1174
1175     ret = make_krcc_data(anchor_name, collection_name, new_subsidiary_name,
1176                          cache_id, collection_id, &data);
1177     if (ret)
1178         goto cleanup;
1179
1180     id->data = data;
1181     krb5_change_cache();
1182
1183 cleanup:
1184     free(anchor_name);
1185     free(collection_name);
1186     free(subsidiary_name);
1187     free(new_subsidiary_name);
1188     free(new_residual);
1189     if (ret) {
1190         free(id);
1191         return ret;
1192     }
1193     *id_out = id;
1194     return 0;
1195 }
1196
1197 /* Return an alias to the residual string of the cache. */
1198 static const char *KRB5_CALLCONV
1199 krcc_get_name(krb5_context context, krb5_ccache id)
1200 {
1201     return ((krcc_data *)id->data)->name;
1202 }
1203
1204 /* Retrieve a copy of the default principal, if the cache is initialized. */
1205 static krb5_error_code KRB5_CALLCONV
1206 krcc_get_principal(krb5_context context, krb5_ccache id,
1207                    krb5_principal *princ_out)
1208 {
1209     krcc_data *data = id->data;
1210     krb5_error_code ret;
1211     void *payload = NULL;
1212     int psize;
1213
1214     *princ_out = NULL;
1215     k5_cc_mutex_lock(context, &data->lock);
1216
1217     if (!data->cache_id || !data->princ_id) {
1218         ret = KRB5_FCC_NOFILE;
1219         k5_setmsg(context, ret, _("Credentials cache keyring '%s' not found"),
1220                   data->name);
1221         goto errout;
1222     }
1223
1224     psize = keyctl_read_alloc(data->princ_id, &payload);
1225     if (psize == -1) {
1226         DEBUG_PRINT(("Reading principal key %d: %s\n",
1227                      data->princ_id, strerror(errno)));
1228         ret = KRB5_CC_IO;
1229         goto errout;
1230     }
1231
1232     /* Unmarshal the principal using the file ccache version 4 format. */
1233     ret = k5_unmarshal_princ(payload, psize, 4, princ_out);
1234
1235 errout:
1236     free(payload);
1237     k5_cc_mutex_unlock(context, &data->lock);
1238     return ret;
1239 }
1240
1241 /* Search for a credential within the cache keyring. */
1242 static krb5_error_code KRB5_CALLCONV
1243 krcc_retrieve(krb5_context context, krb5_ccache id,
1244               krb5_flags whichfields, krb5_creds *mcreds,
1245               krb5_creds *creds)
1246 {
1247     return k5_cc_retrieve_cred_default(context, id, whichfields, mcreds,
1248                                        creds);
1249 }
1250
1251 /* Non-functional stub for removing a cred from the cache keyring. */
1252 static krb5_error_code KRB5_CALLCONV
1253 krcc_remove_cred(krb5_context context, krb5_ccache cache,
1254                  krb5_flags flags, krb5_creds *creds)
1255 {
1256     return KRB5_CC_NOSUPP;
1257 }
1258
1259 /* Set flags on the cache.  (We don't care about any flags.) */
1260 static krb5_error_code KRB5_CALLCONV
1261 krcc_set_flags(krb5_context context, krb5_ccache id, krb5_flags flags)
1262 {
1263     return 0;
1264 }
1265
1266 /* Get the current operational flags (of which we have none) for the cache. */
1267 static krb5_error_code KRB5_CALLCONV
1268 krcc_get_flags(krb5_context context, krb5_ccache id, krb5_flags *flags_out)
1269 {
1270     *flags_out = 0;
1271     return 0;
1272 }
1273
1274 /* Store a credential in the cache keyring. */
1275 static krb5_error_code KRB5_CALLCONV
1276 krcc_store(krb5_context context, krb5_ccache id, krb5_creds *creds)
1277 {
1278     krb5_error_code ret;
1279     krcc_data *data = id->data;
1280     struct k5buf buf = EMPTY_K5BUF;
1281     char *keyname = NULL;
1282     key_serial_t cred_key;
1283     krb5_timestamp now;
1284
1285     k5_cc_mutex_lock(context, &data->lock);
1286
1287     if (!data->cache_id) {
1288         k5_cc_mutex_unlock(context, &data->lock);
1289         return KRB5_FCC_NOFILE;
1290     }
1291
1292     /* Get the service principal name and use it as the key name */
1293     ret = krb5_unparse_name(context, creds->server, &keyname);
1294     if (ret)
1295         goto errout;
1296
1297     /* Serialize credential using the file ccache version 4 format. */
1298     k5_buf_init_dynamic_zap(&buf);
1299     k5_marshal_cred(&buf, 4, creds);
1300     ret = k5_buf_status(&buf);
1301     if (ret)
1302         goto errout;
1303
1304     /* Add new key (credentials) into keyring */
1305     DEBUG_PRINT(("krcc_store: adding new key '%s' to keyring %d\n",
1306                  keyname, data->cache_id));
1307     ret = add_cred_key(keyname, buf.data, buf.len, data->cache_id,
1308                        data->is_legacy_type, &cred_key);
1309     if (ret)
1310         goto errout;
1311
1312     krcc_update_change_time(data);
1313
1314     /* Set appropriate timeouts on cache keys. */
1315     ret = krb5_timeofday(context, &now);
1316     if (ret)
1317         goto errout;
1318
1319     if (ts_after(creds->times.endtime, now)) {
1320         (void)keyctl_set_timeout(cred_key,
1321                                  ts_delta(creds->times.endtime, now));
1322     }
1323
1324     update_keyring_expiration(context, id);
1325
1326 errout:
1327     k5_buf_free(&buf);
1328     krb5_free_unparsed_name(context, keyname);
1329     k5_cc_mutex_unlock(context, &data->lock);
1330     return ret;
1331 }
1332
1333 /* Get the cache's last modification time.  (This is currently broken; it
1334  * returns only the last change made using this handle.) */
1335 static krb5_error_code KRB5_CALLCONV
1336 krcc_last_change_time(krb5_context context, krb5_ccache id,
1337                       krb5_timestamp *change_time)
1338 {
1339     krcc_data *data = id->data;
1340
1341     k5_cc_mutex_lock(context, &data->lock);
1342     *change_time = data->changetime;
1343     k5_cc_mutex_unlock(context, &data->lock);
1344     return 0;
1345 }
1346
1347 /* Lock the cache handle against other threads.  (This does not lock the cache
1348  * keyring against other processes.) */
1349 static krb5_error_code KRB5_CALLCONV
1350 krcc_lock(krb5_context context, krb5_ccache id)
1351 {
1352     krcc_data *data = id->data;
1353
1354     k5_cc_mutex_lock(context, &data->lock);
1355     return 0;
1356 }
1357
1358 /* Unlock the cache handle. */
1359 static krb5_error_code KRB5_CALLCONV
1360 krcc_unlock(krb5_context context, krb5_ccache id)
1361 {
1362     krcc_data *data = id->data;
1363
1364     k5_cc_mutex_unlock(context, &data->lock);
1365     return 0;
1366 }
1367
1368 static krb5_error_code
1369 save_principal(krb5_context context, krb5_ccache id, krb5_principal princ)
1370 {
1371     krcc_data *data = id->data;
1372     krb5_error_code ret;
1373     struct k5buf buf;
1374     key_serial_t newkey;
1375
1376     k5_cc_mutex_assert_locked(context, &data->lock);
1377
1378     /* Serialize princ using the file ccache version 4 format. */
1379     k5_buf_init_dynamic(&buf);
1380     k5_marshal_princ(&buf, 4, princ);
1381     if (k5_buf_status(&buf) != 0)
1382         return ENOMEM;
1383
1384     /* Add new key into keyring */
1385 #ifdef KRCC_DEBUG
1386     {
1387         krb5_error_code rc;
1388         char *princname = NULL;
1389         rc = krb5_unparse_name(context, princ, &princname);
1390         DEBUG_PRINT(("save_principal: adding new key '%s' "
1391                      "to keyring %d for principal '%s'\n",
1392                      KRCC_SPEC_PRINC_KEYNAME, data->cache_id,
1393                      rc ? "<unknown>" : princname));
1394         if (rc == 0)
1395             krb5_free_unparsed_name(context, princname);
1396     }
1397 #endif
1398     newkey = add_key(KRCC_KEY_TYPE_USER, KRCC_SPEC_PRINC_KEYNAME, buf.data,
1399                      buf.len, data->cache_id);
1400     if (newkey < 0) {
1401         ret = errno;
1402         DEBUG_PRINT(("Error adding principal key: %s\n", strerror(ret)));
1403     } else {
1404         data->princ_id = newkey;
1405         ret = 0;
1406         krcc_update_change_time(data);
1407     }
1408
1409     k5_buf_free(&buf);
1410     return ret;
1411 }
1412
1413 /* Add a key to the cache keyring containing the given time offsets. */
1414 static krb5_error_code
1415 save_time_offsets(krb5_context context, krb5_ccache id, int32_t time_offset,
1416                   int32_t usec_offset)
1417 {
1418     krcc_data *data = id->data;
1419     key_serial_t newkey;
1420     unsigned char payload[8];
1421
1422     k5_cc_mutex_assert_locked(context, &data->lock);
1423
1424     /* Prepare the payload. */
1425     store_32_be(time_offset, payload);
1426     store_32_be(usec_offset, payload + 4);
1427
1428     /* Add new key into keyring. */
1429     newkey = add_key(KRCC_KEY_TYPE_USER, KRCC_TIME_OFFSETS, payload, 8,
1430                      data->cache_id);
1431     if (newkey == -1)
1432         return errno;
1433     krcc_update_change_time(data);
1434     return 0;
1435 }
1436
1437 /* Retrieve and parse the key in the cache keyring containing time offsets. */
1438 static krb5_error_code
1439 get_time_offsets(krb5_context context, krb5_ccache id, int32_t *time_offset,
1440                  int32_t *usec_offset)
1441 {
1442     krcc_data *data = id->data;
1443     krb5_error_code ret = 0;
1444     key_serial_t key;
1445     void *payload = NULL;
1446     int psize;
1447
1448     k5_cc_mutex_lock(context, &data->lock);
1449
1450     if (!data->cache_id) {
1451         ret = KRB5_FCC_NOFILE;
1452         goto errout;
1453     }
1454
1455     key = keyctl_search(data->cache_id, KRCC_KEY_TYPE_USER, KRCC_TIME_OFFSETS,
1456                         0);
1457     if (key == -1) {
1458         ret = ENOENT;
1459         goto errout;
1460     }
1461
1462     psize = keyctl_read_alloc(key, &payload);
1463     if (psize == -1) {
1464         DEBUG_PRINT(("Reading time offsets key %d: %s\n",
1465                      key, strerror(errno)));
1466         ret = KRB5_CC_IO;
1467         goto errout;
1468     }
1469
1470     if (psize < 8) {
1471         ret = KRB5_CC_END;
1472         goto errout;
1473     }
1474     *time_offset = load_32_be(payload);
1475     *usec_offset = load_32_be((char *)payload + 4);
1476
1477 errout:
1478     free(payload);
1479     k5_cc_mutex_unlock(context, &data->lock);
1480     return ret;
1481 }
1482
1483 struct krcc_ptcursor_data {
1484     key_serial_t collection_id;
1485     char *anchor_name;
1486     char *collection_name;
1487     char *subsidiary_name;
1488     char *primary_name;
1489     krb5_boolean first;
1490     long num_keys;
1491     long next_key;
1492     key_serial_t *keys;
1493 };
1494
1495 static krb5_error_code KRB5_CALLCONV
1496 krcc_ptcursor_new(krb5_context context, krb5_cc_ptcursor *cursor_out)
1497 {
1498     struct krcc_ptcursor_data *ptd;
1499     krb5_cc_ptcursor cursor;
1500     krb5_error_code ret;
1501     void *keys;
1502     long size;
1503
1504     *cursor_out = NULL;
1505
1506     cursor = k5alloc(sizeof(*cursor), &ret);
1507     if (cursor == NULL)
1508         return ENOMEM;
1509     ptd = k5alloc(sizeof(*ptd), &ret);
1510     if (ptd == NULL)
1511         goto error;
1512     cursor->ops = &krb5_krcc_ops;
1513     cursor->data = ptd;
1514     ptd->first = TRUE;
1515
1516     ret = get_default(context, &ptd->anchor_name, &ptd->collection_name,
1517                       &ptd->subsidiary_name);
1518     if (ret)
1519         goto error;
1520
1521     /* If there is no default collection, return an empty cursor. */
1522     if (ptd->anchor_name == NULL) {
1523         *cursor_out = cursor;
1524         return 0;
1525     }
1526
1527     ret = get_collection(ptd->anchor_name, ptd->collection_name,
1528                          &ptd->collection_id);
1529     if (ret)
1530         goto error;
1531
1532     if (ptd->subsidiary_name == NULL) {
1533         ret = get_primary_name(context, ptd->anchor_name,
1534                                ptd->collection_name, ptd->collection_id,
1535                                &ptd->primary_name);
1536         if (ret)
1537             goto error;
1538
1539         size = keyctl_read_alloc(ptd->collection_id, &keys);
1540         if (size == -1) {
1541             ret = errno;
1542             goto error;
1543         }
1544         ptd->keys = keys;
1545         ptd->num_keys = size / sizeof(key_serial_t);
1546     }
1547
1548     *cursor_out = cursor;
1549     return 0;
1550
1551 error:
1552     krcc_ptcursor_free(context, &cursor);
1553     return ret;
1554 }
1555
1556 static krb5_error_code KRB5_CALLCONV
1557 krcc_ptcursor_next(krb5_context context, krb5_cc_ptcursor cursor,
1558                    krb5_ccache *cache_out)
1559 {
1560     krb5_error_code ret;
1561     struct krcc_ptcursor_data *ptd = cursor->data;
1562     key_serial_t key, cache_id = 0;
1563     const char *first_name, *keytype, *sep, *subsidiary_name;
1564     size_t keytypelen;
1565     char *description = NULL;
1566
1567     *cache_out = NULL;
1568
1569     /* No keyring available */
1570     if (ptd->collection_id == 0)
1571         return 0;
1572
1573     if (ptd->first) {
1574         /* Look for the primary cache for a collection cursor, or the
1575          * subsidiary cache for a subsidiary cursor. */
1576         ptd->first = FALSE;
1577         first_name = (ptd->primary_name != NULL) ? ptd->primary_name :
1578             ptd->subsidiary_name;
1579         cache_id = keyctl_search(ptd->collection_id, KRCC_KEY_TYPE_KEYRING,
1580                                  first_name, 0);
1581         if (cache_id != -1) {
1582             return make_cache(context, ptd->collection_id, cache_id,
1583                               ptd->anchor_name, ptd->collection_name,
1584                               first_name, cache_out);
1585         }
1586     }
1587
1588     /* A subsidiary cursor yields at most the first cache. */
1589     if (ptd->subsidiary_name != NULL)
1590         return 0;
1591
1592     keytype = KRCC_KEY_TYPE_KEYRING ";";
1593     keytypelen = strlen(keytype);
1594
1595     for (; ptd->next_key < ptd->num_keys; ptd->next_key++) {
1596         /* Free any previously retrieved key description. */
1597         free(description);
1598         description = NULL;
1599
1600         /*
1601          * Get the key description, which should have the form:
1602          *   typename;UID;GID;permissions;description
1603          */
1604         key = ptd->keys[ptd->next_key];
1605         if (keyctl_describe_alloc(key, &description) < 0)
1606             continue;
1607         sep = strrchr(description, ';');
1608         if (sep == NULL)
1609             continue;
1610         subsidiary_name = sep + 1;
1611
1612         /* Skip this key if it isn't a keyring. */
1613         if (strncmp(description, keytype, keytypelen) != 0)
1614             continue;
1615
1616         /* Don't repeat the primary cache. */
1617         if (strcmp(subsidiary_name, ptd->primary_name) == 0)
1618             continue;
1619
1620         /* We found a valid key */
1621         ptd->next_key++;
1622         ret = make_cache(context, ptd->collection_id, key, ptd->anchor_name,
1623                          ptd->collection_name, subsidiary_name, cache_out);
1624         free(description);
1625         return ret;
1626     }
1627
1628     free(description);
1629     return 0;
1630 }
1631
1632 static krb5_error_code KRB5_CALLCONV
1633 krcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor)
1634 {
1635     struct krcc_ptcursor_data *ptd = (*cursor)->data;
1636
1637     if (ptd != NULL) {
1638         free(ptd->anchor_name);
1639         free(ptd->collection_name);
1640         free(ptd->subsidiary_name);
1641         free(ptd->primary_name);
1642         free(ptd->keys);
1643         free(ptd);
1644     }
1645     free(*cursor);
1646     *cursor = NULL;
1647     return 0;
1648 }
1649
1650 static krb5_error_code KRB5_CALLCONV
1651 krcc_switch_to(krb5_context context, krb5_ccache cache)
1652 {
1653     krcc_data *data = cache->data;
1654     krb5_error_code ret;
1655     char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
1656     key_serial_t collection_id;
1657
1658     ret = parse_residual(data->name, &anchor_name, &collection_name,
1659                          &subsidiary_name);
1660     if (ret)
1661         goto cleanup;
1662     ret = get_collection(anchor_name, collection_name, &collection_id);
1663     if (ret)
1664         goto cleanup;
1665     ret = set_primary_name(context, collection_id, subsidiary_name);
1666
1667 cleanup:
1668     free(anchor_name);
1669     free(collection_name);
1670     free(subsidiary_name);
1671     return ret;
1672 }
1673
1674 /*
1675  * Utility routine: called by krcc_* functions to keep
1676  * result of krcc_last_change_time up to date.
1677  * Value monotonically increases -- based on but not guaranteed to be actual
1678  * system time.
1679  */
1680
1681 static void
1682 krcc_update_change_time(krcc_data *data)
1683 {
1684     krb5_timestamp now_time = time(NULL);
1685     data->changetime = ts_after(now_time, data->changetime) ?
1686         now_time : ts_incr(data->changetime, 1);
1687 }
1688
1689 /*
1690  * ccache implementation storing credentials in the Linux keyring facility
1691  * The default is to put them at the session keyring level.
1692  * If "KEYRING:process:" or "KEYRING:thread:" is specified, then they will
1693  * be stored at the process or thread level respectively.
1694  */
1695 const krb5_cc_ops krb5_krcc_ops = {
1696     0,
1697     "KEYRING",
1698     krcc_get_name,
1699     krcc_resolve,
1700     krcc_generate_new,
1701     krcc_initialize,
1702     krcc_destroy,
1703     krcc_close,
1704     krcc_store,
1705     krcc_retrieve,
1706     krcc_get_principal,
1707     krcc_start_seq_get,
1708     krcc_next_cred,
1709     krcc_end_seq_get,
1710     krcc_remove_cred,
1711     krcc_set_flags,
1712     krcc_get_flags,        /* added after 1.4 release */
1713     krcc_ptcursor_new,
1714     krcc_ptcursor_next,
1715     krcc_ptcursor_free,
1716     NULL, /* move */
1717     krcc_last_change_time, /* lastchange */
1718     NULL, /* wasdefault */
1719     krcc_lock,
1720     krcc_unlock,
1721     krcc_switch_to,
1722 };
1723
1724 #else /* !USE_KEYRING_CCACHE */
1725
1726 /*
1727  * Export this, but it shouldn't be used.
1728  */
1729 const krb5_cc_ops krb5_krcc_ops = {
1730     0,
1731     "KEYRING",
1732     NULL,
1733     NULL,
1734     NULL,
1735     NULL,
1736     NULL,
1737     NULL,
1738     NULL,
1739     NULL,
1740     NULL,
1741     NULL,
1742     NULL,
1743     NULL,
1744     NULL,
1745     NULL,
1746     NULL,                       /* added after 1.4 release */
1747     NULL,
1748     NULL,
1749     NULL,
1750     NULL,
1751     NULL,
1752     NULL,
1753     NULL,
1754     NULL,
1755     NULL,
1756 };
1757 #endif  /* USE_KEYRING_CCACHE */