1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/ccache/cc_dir.c - Directory-based credential cache collection */
4 * Copyright (C) 2011 by the Massachusetts Institute of Technology.
7 * Export of this software from the United States of America may
8 * require a specific license from the United States Government.
9 * It is the responsibility of any person or organization contemplating
10 * export to obtain such a license before exporting.
12 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13 * distribute this software and its documentation for any purpose and
14 * without fee is hereby granted, provided that the above copyright
15 * notice appear in all copies and that both that copyright notice and
16 * this permission notice appear in supporting documentation, and that
17 * the name of M.I.T. not be used in advertising or publicity pertaining
18 * to distribution of the software without specific, written prior
19 * permission. Furthermore if you modify this software you must label
20 * your software as modified software and not distribute it in such a
21 * fashion that it might be confused with the original M.I.T. software.
22 * M.I.T. makes no representations about the suitability of
23 * this software for any purpose. It is provided "as is" without express
24 * or implied warranty.
28 * This credential cache type represents a set of file-based caches with a
29 * switchable primary cache. An alternate form of the type represents a
30 * subsidiary file cache within the directory.
32 * A cache name of the form DIR:dirname identifies a directory containing the
33 * cache set. Resolving a name of this form results in dirname's primary
34 * cache. If a context's default cache is of this form, the global cache
35 * collection will contain dirname's cache set, and new unique caches of type
36 * DIR will be created within dirname.
38 * A cache name of the form DIR::filepath represents a single cache within the
39 * directory. Switching to a ccache of this type causes the directory's
40 * primary cache to be set to the named cache.
42 * Within the directory, cache names begin with 'tkt'. The file "primary"
43 * contains a single line naming the primary cache. The directory must already
44 * exist when the DIR ccache is resolved, but the primary file will be created
45 * automatically if it does not exist.
58 /* This is Unix-only for now. To work on Windows, we will need opendir/readdir
59 * replacements and possibly more flexible newline handling. */
64 extern const krb5_cc_ops krb5_dcc_ops;
65 extern const krb5_cc_ops krb5_fcc_ops;
67 /* Fields are not modified after creation, so no lock is necessary. */
68 typedef struct dcc_data_st {
69 char *residual; /* dirname or :filename */
70 krb5_ccache fcc; /* File cache for actual cache ops */
73 static inline krb5_boolean
74 filename_is_cache(const char *filename)
76 return (strncmp(filename, "tkt", 3) == 0);
79 /* Compose the pathname of the primary file within a cache directory. */
80 static inline krb5_error_code
81 primary_pathname(const char *dirname, char **path_out)
83 return k5_path_join(dirname, "primary", path_out);
86 /* Compose a residual string for a subsidiary path with the specified directory
87 * name and filename. */
88 static krb5_error_code
89 subsidiary_residual(const char *dirname, const char *filename, char **out)
92 char *path, *residual;
95 ret = k5_path_join(dirname, filename, &path);
98 ret = asprintf(&residual, ":%s", path);
106 static inline krb5_error_code
107 split_path(krb5_context context, const char *path, char **dirname_out,
111 char *dirname, *filename;
114 *filename_out = NULL;
115 ret = k5_path_split(path, &dirname, &filename);
119 if (*dirname == '\0') {
120 ret = KRB5_CC_BADNAME;
121 k5_setmsg(context, ret,
122 _("Subsidiary cache path %s has no parent directory"), path);
125 if (!filename_is_cache(filename)) {
126 ret = KRB5_CC_BADNAME;
127 k5_setmsg(context, ret,
128 _("Subsidiary cache path %s filename does not begin with "
133 *dirname_out = dirname;
134 *filename_out = filename;
143 /* Read the primary file and compose the residual string for the primary
144 * subsidiary cache file. */
145 static krb5_error_code
146 read_primary_file(krb5_context context, const char *primary_path,
147 const char *dirname, char **residual_out)
153 *residual_out = NULL;
155 /* Open the file and read its first line. */
156 fp = fopen(primary_path, "r");
159 ret = fgets(buf, sizeof(buf), fp);
165 /* Check if line is too long, doesn't look like a subsidiary cache
166 * filename, or isn't a single-component filename. */
167 if (buf[len - 1] != '\n' || !filename_is_cache(buf) ||
168 strchr(buf, '/') || strchr(buf, '\\')) {
169 k5_setmsg(context, KRB5_CC_FORMAT, _("%s contains invalid filename"),
171 return KRB5_CC_FORMAT;
175 return subsidiary_residual(dirname, buf, residual_out);
178 /* Create or update the primary file with a line containing contents. */
179 static krb5_error_code
180 write_primary_file(const char *primary_path, const char *contents)
182 krb5_error_code ret = KRB5_CC_IO;
183 char *newpath = NULL;
187 if (asprintf(&newpath, "%s.XXXXXX", primary_path) < 0)
189 fd = mkstemp(newpath);
193 chmod(newpath, S_IRUSR | S_IWUSR);
195 fp = fdopen(fd, "w");
199 if (fprintf(fp, "%s\n", contents) < 0)
206 if (rename(newpath, primary_path) != 0)
219 /* Verify or create a cache directory path. */
220 static krb5_error_code
221 verify_dir(krb5_context context, const char *dirname)
225 if (stat(dirname, &st) < 0) {
226 if (errno == ENOENT && mkdir(dirname, S_IRWXU) == 0)
228 k5_setmsg(context, KRB5_FCC_NOFILE,
229 _("Credential cache directory %s does not exist"),
231 return KRB5_FCC_NOFILE;
233 if (!S_ISDIR(st.st_mode)) {
234 k5_setmsg(context, KRB5_CC_FORMAT,
235 _("Credential cache directory %s exists but is not a "
236 "directory"), dirname);
237 return KRB5_CC_FORMAT;
243 * If the default ccache name for context is a directory collection, set
244 * *dirname_out to the directory name for that collection. Otherwise set
245 * *dirname_out to NULL.
247 static krb5_error_code
248 get_context_default_dir(krb5_context context, char **dirname_out)
254 defname = krb5_cc_default_name(context);
257 if (strncmp(defname, "DIR:", 4) != 0 ||
258 defname[4] == ':' || defname[4] == '\0')
260 dirname = strdup(defname + 4);
263 *dirname_out = dirname;
268 * If the default ccache name for context is a subsidiary file in a directory
269 * collection, set *subsidiary_out to the residual value. Otherwise set
270 * *subsidiary_out to NULL.
272 static krb5_error_code
273 get_context_subsidiary_file(krb5_context context, char **subsidiary_out)
278 *subsidiary_out = NULL;
279 defname = krb5_cc_default_name(context);
280 if (defname == NULL || strncmp(defname, "DIR::", 5) != 0)
282 residual = strdup(defname + 4);
283 if (residual == NULL)
285 *subsidiary_out = residual;
289 static const char * KRB5_CALLCONV
290 dcc_get_name(krb5_context context, krb5_ccache cache)
292 dcc_data *data = cache->data;
294 return data->residual;
297 /* Construct a cache object given a residual string and file ccache. Take
298 * ownership of fcc on success. */
299 static krb5_error_code
300 make_cache(const char *residual, krb5_ccache fcc, krb5_ccache *cache_out)
302 krb5_ccache cache = NULL;
303 dcc_data *data = NULL;
304 char *residual_copy = NULL;
306 cache = malloc(sizeof(*cache));
309 data = malloc(sizeof(*data));
312 residual_copy = strdup(residual);
313 if (residual_copy == NULL)
316 data->residual = residual_copy;
318 cache->ops = &krb5_dcc_ops;
320 cache->magic = KV5M_CCACHE;
331 static krb5_error_code KRB5_CALLCONV
332 dcc_resolve(krb5_context context, krb5_ccache *cache_out, const char *residual)
336 char *primary_path = NULL, *sresidual = NULL, *dirname, *filename;
340 if (*residual == ':') {
341 /* This is a subsidiary cache within the directory. */
342 ret = split_path(context, residual + 1, &dirname, &filename);
346 ret = verify_dir(context, dirname);
352 /* This is the directory itself; resolve to the primary cache. */
353 ret = verify_dir(context, residual);
357 ret = primary_pathname(residual, &primary_path);
361 ret = read_primary_file(context, primary_path, residual, &sresidual);
363 /* Create an initial primary file. */
364 ret = write_primary_file(primary_path, "tkt");
367 ret = subsidiary_residual(residual, "tkt", &sresidual);
371 residual = sresidual;
374 ret = krb5_fcc_ops.resolve(context, &fcc, residual + 1);
377 ret = make_cache(residual, fcc, cache_out);
379 krb5_fcc_ops.close(context, fcc);
387 static krb5_error_code KRB5_CALLCONV
388 dcc_gen_new(krb5_context context, krb5_ccache *cache_out)
391 char *dirname = NULL, *template = NULL, *residual = NULL;
392 krb5_ccache fcc = NULL;
395 ret = get_context_default_dir(context, &dirname);
398 if (dirname == NULL) {
399 k5_setmsg(context, KRB5_DCC_CANNOT_CREATE,
400 _("Can't create new subsidiary cache because default cache "
401 "is not a directory collection"));
402 return KRB5_DCC_CANNOT_CREATE;
404 ret = verify_dir(context, dirname);
407 ret = k5_path_join(dirname, "tktXXXXXX", &template);
410 ret = krb5int_fcc_new_unique(context, template, &fcc);
413 if (asprintf(&residual, ":%s", template) < 0) {
417 ret = make_cache(residual, fcc, cache_out);
424 krb5_fcc_ops.destroy(context, fcc);
431 static krb5_error_code KRB5_CALLCONV
432 dcc_init(krb5_context context, krb5_ccache cache, krb5_principal princ)
434 dcc_data *data = cache->data;
436 return krb5_fcc_ops.init(context, data->fcc, princ);
439 static krb5_error_code KRB5_CALLCONV
440 dcc_destroy(krb5_context context, krb5_ccache cache)
442 dcc_data *data = cache->data;
445 ret = krb5_fcc_ops.destroy(context, data->fcc);
446 free(data->residual);
452 static krb5_error_code KRB5_CALLCONV
453 dcc_close(krb5_context context, krb5_ccache cache)
455 dcc_data *data = cache->data;
458 ret = krb5_fcc_ops.close(context, data->fcc);
459 free(data->residual);
465 static krb5_error_code KRB5_CALLCONV
466 dcc_store(krb5_context context, krb5_ccache cache, krb5_creds *creds)
468 dcc_data *data = cache->data;
470 return krb5_fcc_ops.store(context, data->fcc, creds);
473 static krb5_error_code KRB5_CALLCONV
474 dcc_retrieve(krb5_context context, krb5_ccache cache, krb5_flags flags,
475 krb5_creds *mcreds, krb5_creds *creds)
477 dcc_data *data = cache->data;
479 return krb5_fcc_ops.retrieve(context, data->fcc, flags, mcreds,
483 static krb5_error_code KRB5_CALLCONV
484 dcc_get_princ(krb5_context context, krb5_ccache cache,
485 krb5_principal *princ_out)
487 dcc_data *data = cache->data;
489 return krb5_fcc_ops.get_princ(context, data->fcc, princ_out);
492 static krb5_error_code KRB5_CALLCONV
493 dcc_get_first(krb5_context context, krb5_ccache cache, krb5_cc_cursor *cursor)
495 dcc_data *data = cache->data;
497 return krb5_fcc_ops.get_first(context, data->fcc, cursor);
500 static krb5_error_code KRB5_CALLCONV
501 dcc_get_next(krb5_context context, krb5_ccache cache, krb5_cc_cursor *cursor,
504 dcc_data *data = cache->data;
506 return krb5_fcc_ops.get_next(context, data->fcc, cursor, creds);
509 static krb5_error_code KRB5_CALLCONV
510 dcc_end_get(krb5_context context, krb5_ccache cache, krb5_cc_cursor *cursor)
512 dcc_data *data = cache->data;
514 return krb5_fcc_ops.end_get(context, data->fcc, cursor);
517 static krb5_error_code KRB5_CALLCONV
518 dcc_remove_cred(krb5_context context, krb5_ccache cache, krb5_flags flags,
521 dcc_data *data = cache->data;
523 return krb5_fcc_ops.remove_cred(context, data->fcc, flags, creds);
526 static krb5_error_code KRB5_CALLCONV
527 dcc_set_flags(krb5_context context, krb5_ccache cache, krb5_flags flags)
529 dcc_data *data = cache->data;
531 return krb5_fcc_ops.set_flags(context, data->fcc, flags);
534 static krb5_error_code KRB5_CALLCONV
535 dcc_get_flags(krb5_context context, krb5_ccache cache, krb5_flags *flags_out)
537 dcc_data *data = cache->data;
539 return krb5_fcc_ops.get_flags(context, data->fcc, flags_out);
542 struct dcc_ptcursor_data {
549 /* Construct a cursor, taking ownership of dirname, primary, and dir on
551 static krb5_error_code
552 make_cursor(char *dirname, char *primary, DIR *dir,
553 krb5_cc_ptcursor *cursor_out)
555 krb5_cc_ptcursor cursor;
556 struct dcc_ptcursor_data *data;
560 data = malloc(sizeof(*data));
563 cursor = malloc(sizeof(*cursor));
564 if (cursor == NULL) {
569 data->dirname = dirname;
570 data->primary = primary;
573 cursor->ops = &krb5_dcc_ops;
575 *cursor_out = cursor;
579 static krb5_error_code KRB5_CALLCONV
580 dcc_ptcursor_new(krb5_context context, krb5_cc_ptcursor *cursor_out)
583 char *dirname = NULL, *primary_path = NULL, *primary = NULL;
588 /* If the default cache is a subsidiary file, make a cursor with the
589 * specified file as the primary but with no directory collection. */
590 ret = get_context_subsidiary_file(context, &primary);
593 if (primary != NULL) {
594 ret = make_cursor(NULL, primary, NULL, cursor_out);
600 /* Open the directory for the context's default cache. */
601 ret = get_context_default_dir(context, &dirname);
602 if (ret || dirname == NULL)
604 dir = opendir(dirname);
608 /* Fetch the primary cache name if possible. */
609 ret = primary_pathname(dirname, &primary_path);
612 ret = read_primary_file(context, primary_path, dirname, &primary);
614 krb5_clear_error_message(context);
616 ret = make_cursor(dirname, primary, dir, cursor_out);
619 dirname = primary = NULL;
628 /* Return an empty cursor if we fail for any reason. */
629 if (*cursor_out == NULL)
630 return make_cursor(NULL, NULL, NULL, cursor_out);
634 static krb5_error_code KRB5_CALLCONV
635 dcc_ptcursor_next(krb5_context context, krb5_cc_ptcursor cursor,
636 krb5_ccache *cache_out)
638 struct dcc_ptcursor_data *data = cursor->data;
646 /* Return the primary or specified subsidiary cache if we haven't yet. */
649 if (data->primary != NULL && stat(data->primary + 1, &sb) == 0)
650 return dcc_resolve(context, cache_out, data->primary);
653 if (data->dir == NULL) /* No directory collection */
656 /* Look for the next filename of the correct form, without repeating the
658 while ((ent = readdir(data->dir)) != NULL) {
659 if (!filename_is_cache(ent->d_name))
661 ret = subsidiary_residual(data->dirname, ent->d_name, &residual);
664 if (data->primary != NULL && strcmp(residual, data->primary) == 0) {
668 ret = dcc_resolve(context, cache_out, residual);
673 /* We exhausted the directory without finding a cache to yield. */
679 static krb5_error_code KRB5_CALLCONV
680 dcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor)
682 struct dcc_ptcursor_data *data = (*cursor)->data;
694 static krb5_error_code KRB5_CALLCONV
695 dcc_lastchange(krb5_context context, krb5_ccache cache,
696 krb5_timestamp *time_out)
698 dcc_data *data = cache->data;
700 return krb5_fcc_ops.lastchange(context, data->fcc, time_out);
703 static krb5_error_code KRB5_CALLCONV
704 dcc_lock(krb5_context context, krb5_ccache cache)
706 dcc_data *data = cache->data;
708 return krb5_fcc_ops.lock(context, data->fcc);
711 static krb5_error_code KRB5_CALLCONV
712 dcc_unlock(krb5_context context, krb5_ccache cache)
714 dcc_data *data = cache->data;
716 return krb5_fcc_ops.unlock(context, data->fcc);
719 static krb5_error_code KRB5_CALLCONV
720 dcc_switch_to(krb5_context context, krb5_ccache cache)
722 dcc_data *data = cache->data;
723 char *primary_path = NULL, *dirname = NULL, *filename = NULL;
726 ret = split_path(context, data->residual + 1, &dirname, &filename);
730 ret = primary_pathname(dirname, &primary_path);
734 ret = write_primary_file(primary_path, filename);
743 const krb5_cc_ops krb5_dcc_ops = {
766 NULL, /* wasdefault */
772 #endif /* not _WIN32 */