Line data Source code
1 : /*
2 : * Copyright (c) 1997 - 2008 Kungliga Tekniska Högskolan
3 : * (Royal Institute of Technology, Stockholm, Sweden).
4 : * All rights reserved.
5 : *
6 : * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
7 : *
8 : * Redistribution and use in source and binary forms, with or without
9 : * modification, are permitted provided that the following conditions
10 : * are met:
11 : *
12 : * 1. Redistributions of source code must retain the above copyright
13 : * notice, this list of conditions and the following disclaimer.
14 : *
15 : * 2. Redistributions in binary form must reproduce the above copyright
16 : * notice, this list of conditions and the following disclaimer in the
17 : * documentation and/or other materials provided with the distribution.
18 : *
19 : * 3. Neither the name of the Institute nor the names of its contributors
20 : * may be used to endorse or promote products derived from this software
21 : * without specific prior written permission.
22 : *
23 : * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24 : * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 : * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 : * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27 : * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 : * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 : * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 : * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 : * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 : * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 : * SUCH DAMAGE.
34 : */
35 :
36 : #include "krb5_locl.h"
37 :
38 : typedef struct krb5_dcache{
39 : krb5_ccache fcache;
40 : char *name;
41 : char *dir;
42 : char *sub;
43 : unsigned int default_candidate:1;
44 : } krb5_dcache;
45 :
46 : #define DCACHE(X) ((krb5_dcache*)(X)->data.data)
47 : #define D2FCACHE(X) ((X)->fcache)
48 :
49 : static krb5_error_code KRB5_CALLCONV dcc_close(krb5_context, krb5_ccache);
50 : static krb5_error_code KRB5_CALLCONV dcc_get_default_name(krb5_context, char **);
51 : static krb5_error_code KRB5_CALLCONV dcc_set_default(krb5_context, krb5_ccache);
52 :
53 : /*
54 : * Make subsidiary filesystem safe by mapping / and : to -. If the subsidiary
55 : * is longer than 128 bytes, then truncate.
56 : * In all cases, "tkt." is prefixed to be compatible with the DIR requirement
57 : * that subsidiary ccache files be named tkt*.
58 : *
59 : * Thus host/foo.bar.baz@BAR.BAZ -> tkt.host-foo.bar.baz@BAR.BAZ.
60 : *
61 : * In particular, no filesystem component separators will be emitted, and . and
62 : * .. will never be traversed.
63 : */
64 : static krb5_error_code
65 0 : fs_encode_subsidiary(krb5_context context,
66 : krb5_dcache *dc,
67 : const char *subsidiary,
68 : char **res)
69 : {
70 0 : size_t len = strlen(subsidiary);
71 : size_t i;
72 :
73 0 : *res = NULL;
74 0 : if (asprintf(res, "tkt.%s", subsidiary) == -1 || *res == NULL)
75 0 : return krb5_enomem(context);
76 0 : for (i = sizeof("tkt.") - 1; i < len; i++) {
77 0 : switch ((*res)[i]) {
78 : #ifdef WIN32
79 : case '\\': (*res)[0] = '-'; break;
80 : #endif
81 0 : case '/': (*res)[0] = '-'; break;
82 0 : case ':': (*res)[0] = '-'; break;
83 0 : default: break;
84 : }
85 : }
86 :
87 : /* Hopefully this will work on all filesystems */
88 0 : if (len > 128 - sizeof("tkt.") - 1)
89 0 : (*res)[127] = '\0';
90 0 : return 0;
91 : }
92 :
93 : static char *
94 0 : primary_create(krb5_dcache *dc)
95 : {
96 0 : char *primary = NULL;
97 0 : int asprintf_ret = asprintf(&primary, "%s/primary", dc->dir);
98 0 : if (asprintf_ret == -1 || primary == NULL) {
99 0 : return NULL;
100 : }
101 :
102 0 : return primary;
103 : }
104 :
105 : static int
106 0 : is_filename_cacheish(const char *name)
107 : {
108 : size_t i;
109 :
110 0 : if (strncmp(name, "tkt", sizeof("tkt") - 1) != 0)
111 0 : return 0;
112 0 : for (i = sizeof("tkt") - 1; name[i]; i++)
113 0 : if (ISPATHSEP(name[i]))
114 0 : return 0;
115 0 : return 1;
116 : }
117 :
118 : static krb5_error_code
119 0 : set_default_cache(krb5_context context, krb5_dcache *dc, const char *residual)
120 : {
121 0 : char *path = NULL, *primary = NULL;
122 : krb5_error_code ret;
123 : struct iovec iov[2];
124 : size_t len;
125 0 : int fd = -1;
126 : int asprintf_ret;
127 :
128 0 : asprintf_ret = asprintf(&path, "%s/primary-XXXXXX", dc->dir);
129 0 : if (asprintf_ret == -1 || path == NULL) {
130 0 : return krb5_enomem(context);
131 : }
132 :
133 0 : fd = mkstemp(path);
134 0 : if (fd < 0) {
135 0 : ret = errno;
136 0 : goto out;
137 : }
138 0 : rk_cloexec(fd);
139 : #ifndef _WIN32
140 0 : if (fchmod(fd, S_IRUSR | S_IWUSR) < 0) {
141 0 : ret = errno;
142 0 : goto out;
143 : }
144 : #endif
145 0 : len = strlen(residual);
146 :
147 0 : iov[0].iov_base = rk_UNCONST(residual);
148 0 : iov[0].iov_len = len;
149 0 : iov[1].iov_base = "\n";
150 0 : iov[1].iov_len = 1;
151 :
152 0 : if (writev(fd, iov, sizeof(iov)/sizeof(iov[0])) != len + 1) {
153 0 : ret = errno;
154 0 : goto out;
155 : }
156 :
157 0 : primary = primary_create(dc);
158 0 : if (primary == NULL) {
159 0 : ret = krb5_enomem(context);
160 0 : goto out;
161 : }
162 :
163 0 : if (rename(path, primary) < 0) {
164 0 : ret = errno;
165 0 : goto out;
166 : }
167 :
168 0 : close(fd);
169 0 : fd = -1;
170 :
171 0 : ret = 0;
172 0 : out:
173 0 : if (fd >= 0) {
174 0 : (void)unlink(path);
175 0 : close(fd);
176 : }
177 0 : if (path)
178 0 : free(path);
179 0 : if (primary)
180 0 : free(primary);
181 :
182 0 : return ret;
183 : }
184 :
185 : static krb5_error_code
186 0 : get_default_cache(krb5_context context, krb5_dcache *dc,
187 : const char *subsidiary, char **residual)
188 : {
189 : krb5_error_code ret;
190 : char buf[MAXPATHLEN];
191 0 : char *primary = NULL;
192 : FILE *f;
193 :
194 0 : *residual = NULL;
195 0 : if (subsidiary)
196 0 : return fs_encode_subsidiary(context, dc, subsidiary, residual);
197 :
198 0 : primary = primary_create(dc);
199 0 : if (primary == NULL)
200 0 : return krb5_enomem(context);
201 :
202 0 : f = fopen(primary, "r");
203 0 : if (f == NULL) {
204 0 : if (errno == ENOENT) {
205 0 : free(primary);
206 0 : *residual = strdup("tkt");
207 0 : if (*residual == NULL)
208 0 : return krb5_enomem(context);
209 0 : return 0;
210 : }
211 0 : ret = errno;
212 0 : krb5_set_error_message(context, ret, "failed to open %s", primary);
213 0 : free(primary);
214 0 : return ret;
215 : }
216 :
217 0 : if (fgets(buf, sizeof(buf), f) == NULL) {
218 0 : ret = ferror(f);
219 0 : fclose(f);
220 0 : krb5_set_error_message(context, ret, "read file %s", primary);
221 0 : free(primary);
222 0 : return ret;
223 : }
224 0 : fclose(f);
225 :
226 0 : buf[strcspn(buf, "\r\n")] = '\0';
227 :
228 0 : if (!is_filename_cacheish(buf)) {
229 0 : krb5_set_error_message(context, KRB5_CC_FORMAT,
230 : "name in %s is not a cache (doesn't start with tkt)", primary);
231 0 : free(primary);
232 0 : return KRB5_CC_FORMAT;
233 : }
234 :
235 0 : free(primary);
236 :
237 0 : *residual = strdup(buf);
238 0 : if (*residual == NULL)
239 0 : return krb5_enomem(context);
240 :
241 0 : return 0;
242 : }
243 :
244 :
245 :
246 : static krb5_error_code KRB5_CALLCONV
247 0 : dcc_get_name_2(krb5_context context,
248 : krb5_ccache id,
249 : const char **name,
250 : const char **dir,
251 : const char **sub)
252 : {
253 0 : krb5_dcache *dc = DCACHE(id);
254 :
255 0 : if (name)
256 0 : *name = dc->name;
257 0 : if (dir)
258 0 : *dir = dc->dir;
259 0 : if (sub)
260 0 : *sub = dc->sub;
261 0 : return 0;
262 : }
263 :
264 :
265 : static krb5_error_code
266 0 : verify_directory(krb5_context context, const char *path)
267 : {
268 : struct stat sb;
269 :
270 0 : if (!path[0]) {
271 0 : krb5_set_error_message(context, EINVAL,
272 0 : N_("DIR empty directory component", ""));
273 0 : return EINVAL;
274 : }
275 :
276 0 : if (stat(path, &sb) != 0) {
277 0 : if (errno == ENOENT) {
278 : /* XXX should use mkdirx_np() */
279 0 : if (rk_mkdir(path, S_IRWXU) == 0)
280 0 : return 0;
281 :
282 0 : krb5_set_error_message(context, ENOENT,
283 0 : N_("DIR directory %s doesn't exists", ""), path);
284 0 : return ENOENT;
285 : } else {
286 0 : int ret = errno;
287 0 : krb5_set_error_message(context, ret,
288 0 : N_("DIR directory %s is bad: %s", ""), path, strerror(ret));
289 0 : return errno;
290 : }
291 : }
292 0 : if (!S_ISDIR(sb.st_mode)) {
293 0 : krb5_set_error_message(context, KRB5_CC_BADNAME,
294 0 : N_("DIR directory %s is not a directory", ""), path);
295 0 : return KRB5_CC_BADNAME;
296 : }
297 :
298 0 : return 0;
299 : }
300 :
301 : static void
302 0 : dcc_release(krb5_context context, krb5_dcache *dc)
303 : {
304 0 : if (dc->fcache)
305 0 : krb5_cc_close(context, dc->fcache);
306 0 : free(dc->sub);
307 0 : free(dc->dir);
308 0 : free(dc->name);
309 0 : memset(dc, 0, sizeof(*dc));
310 0 : free(dc);
311 0 : }
312 :
313 : static krb5_error_code
314 0 : get_default_dir(krb5_context context, char **res)
315 : {
316 : krb5_error_code ret;
317 : char *s;
318 :
319 0 : if ((ret = dcc_get_default_name(context, &s)))
320 0 : return ret;
321 0 : if (strncmp(s, "DIR:", sizeof("DIR:") - 1) != 0) {
322 0 : *res = s;
323 0 : s = NULL;
324 0 : } else if ((*res = strdup(s + sizeof("DIR:") - 1)) == NULL) {
325 0 : ret = krb5_enomem(context);
326 : }
327 0 : free(s);
328 0 : return ret;
329 : }
330 :
331 : static krb5_error_code KRB5_CALLCONV
332 0 : dcc_resolve_2(krb5_context context,
333 : krb5_ccache *id,
334 : const char *res,
335 : const char *sub)
336 : {
337 : krb5_error_code ret;
338 0 : krb5_dcache *dc = NULL;
339 0 : char *filename = NULL;
340 : size_t len;
341 0 : int has_pathsep = 0;
342 :
343 0 : if (sub) {
344 : /*
345 : * Here `res' has the directory name (or, if NULL, refers to the
346 : * default DIR cccol), and `sub' has the "subsidiary" name, to which
347 : * we'll prefix "tkt." (though we will insist only on "tkt" later).
348 : */
349 0 : if ((dc = calloc(1, sizeof(*dc))) == NULL ||
350 0 : asprintf(&dc->sub, "tkt.%s", sub) == -1 || dc->sub == NULL) {
351 0 : free(dc);
352 0 : return krb5_enomem(context);
353 : }
354 0 : if (res && res[0] && (dc->dir = strdup(res)) == NULL) {
355 0 : free(dc->sub);
356 0 : free(dc);
357 0 : return krb5_enomem(context);
358 0 : } else if ((!res || !res[0]) && (ret = get_default_dir(context, &dc->dir))) {
359 0 : free(dc->sub);
360 0 : free(dc);
361 0 : return ret;
362 : }
363 : } else {
364 : const char *p;
365 0 : int is_drive_letter_colon = 0;
366 :
367 : /*
368 : * Here `res' has whatever string followed "DIR:", and we need to parse
369 : * it into `dc->dir' and `dc->sub'.
370 : *
371 : * Conventions we support for DIR cache naming:
372 : *
373 : * - DIR:path:NAME ---> FILE:path/tktNAME
374 : * - DIR::path/tktNAME ---> FILE:path/tktNAME
375 : * - DIR::NAME ---> FILE:${default_DIR_cccol_path}/tktNAME
376 : * \-> FILE:/tmp/krb5cc_${uid}_dir/tktNAME
377 : * - DIR:path ---> FILE:path/$(cat primary) or FILE:path/tkt
378 : *
379 : */
380 :
381 0 : if (*res == '\0' || (res[0] == ':' && res[1] == '\0')) {
382 : /* XXX Why not? */
383 0 : krb5_set_error_message(context, KRB5_CC_FORMAT,
384 0 : N_("\"DIR:\" is not a valid ccache name", ""));
385 0 : return KRB5_CC_FORMAT;
386 : }
387 :
388 : #ifdef WIN32
389 : has_pathsep = strchr(res, '\\') != NULL;
390 : #endif
391 0 : has_pathsep |= strchr(res, '/') != NULL;
392 :
393 0 : if ((dc = calloc(1, sizeof(*dc))) == NULL)
394 0 : return krb5_enomem(context);
395 :
396 0 : p = strrchr(res, ':');
397 : #ifdef WIN32
398 : is_drive_letter_colon =
399 : p && ((res[0] == ':' && res[1] != ':' && p - res == 2) ||
400 : (res[0] != ':' && p - res == 1));
401 : #endif
402 :
403 0 : if (res[0] != ':' && p && !is_drive_letter_colon) {
404 : /* DIR:path:NAME */
405 0 : if ((dc->dir = strndup(res, (p - res))) == NULL ||
406 0 : asprintf(&dc->sub, "tkt.%s", p + 1) < 0 || dc->sub == NULL) {
407 0 : dcc_release(context, dc);
408 0 : return krb5_enomem(context);
409 : }
410 0 : } else if (res[0] == ':' && has_pathsep) {
411 : char *q;
412 :
413 : /* DIR::path/tktNAME (the "tkt" must be there; we'll check) */
414 0 : if ((dc->dir = strdup(&res[1])) == NULL) {
415 0 : dcc_release(context, dc);
416 0 : return krb5_enomem(context);
417 : }
418 : #ifdef _WIN32
419 : q = strrchr(dc->dir, '\\');
420 : if (q == NULL || ((p = strrchr(dc->dir, '/')) && q < p))
421 : #endif
422 0 : q = strrchr(dc->dir, '/');
423 0 : *q++ = '\0';
424 0 : if ((dc->sub = strdup(q)) == NULL) {
425 0 : dcc_release(context, dc);
426 0 : return krb5_enomem(context);
427 : }
428 0 : } else if (res[0] == ':') {
429 : /* DIR::NAME -- no path component separators in NAME */
430 0 : if ((ret = get_default_dir(context, &dc->dir))) {
431 0 : dcc_release(context, dc);
432 0 : return ret;
433 : }
434 0 : if (asprintf(&dc->sub, "tkt.%s", res + 1) < 0 || dc->sub == NULL) {
435 0 : dcc_release(context, dc);
436 0 : return krb5_enomem(context);
437 : }
438 : } else {
439 : /* DIR:path */
440 0 : if ((dc->dir = strdup(res)) == NULL) {
441 0 : dcc_release(context, dc);
442 0 : return krb5_enomem(context);
443 : }
444 :
445 0 : if ((ret = get_default_cache(context, dc, NULL, &dc->sub))) {
446 0 : dcc_release(context, dc);
447 0 : return ret;
448 : }
449 : }
450 : }
451 :
452 : /* Strip off extra slashes on the end */
453 0 : for (len = strlen(dc->dir);
454 0 : len && ISPATHSEP(dc->dir[len - 1]);
455 0 : len--)
456 0 : dc->dir[len - 1] = '\0';
457 :
458 : /* If we got here then `dc->dir' and `dc->sub' must both be set */
459 :
460 0 : if ((ret = verify_directory(context, dc->dir))) {
461 0 : dcc_release(context, dc);
462 0 : return ret;
463 : }
464 0 : if (!is_filename_cacheish(dc->sub)) {
465 0 : krb5_set_error_message(context, KRB5_CC_FORMAT,
466 0 : N_("Name %s is not a cache "
467 : "(doesn't start with tkt)", ""), dc->sub);
468 0 : dcc_release(context, dc);
469 0 : return KRB5_CC_FORMAT;
470 : }
471 0 : if (asprintf(&dc->name, ":%s/%s", dc->dir, dc->sub) == -1 ||
472 0 : dc->name == NULL ||
473 0 : asprintf(&filename, "FILE%s", dc->name) == -1 || filename == NULL) {
474 0 : dcc_release(context, dc);
475 0 : return krb5_enomem(context);
476 : }
477 :
478 0 : ret = krb5_cc_resolve(context, filename, &dc->fcache);
479 0 : free(filename);
480 0 : if (ret) {
481 0 : dcc_release(context, dc);
482 0 : return ret;
483 : }
484 :
485 0 : dc->default_candidate = 1;
486 0 : (*id)->data.data = dc;
487 0 : (*id)->data.length = sizeof(*dc);
488 0 : return 0;
489 : }
490 :
491 : static krb5_error_code KRB5_CALLCONV
492 0 : dcc_gen_new(krb5_context context, krb5_ccache *id)
493 : {
494 : krb5_error_code ret;
495 0 : char *def_dir = NULL;
496 0 : char *name = NULL;
497 0 : int fd = -1;
498 :
499 0 : ret = get_default_dir(context, &def_dir);
500 0 : if (ret == 0)
501 0 : ret = verify_directory(context, def_dir);
502 0 : if (ret == 0 &&
503 0 : (asprintf(&name, "DIR::%s/tktXXXXXX", def_dir) == -1 || name == NULL))
504 0 : ret = krb5_enomem(context);
505 0 : if (ret == 0 && (fd = mkstemp(name + sizeof("DIR::") - 1)) == -1)
506 0 : ret = errno;
507 0 : if (ret == 0)
508 0 : ret = dcc_resolve_2(context, id, name + sizeof("DIR:") - 1, NULL);
509 :
510 0 : free(def_dir);
511 0 : free(name);
512 0 : if (fd != -1)
513 0 : close(fd);
514 0 : return ret;
515 : }
516 :
517 : static krb5_error_code KRB5_CALLCONV
518 0 : dcc_initialize(krb5_context context,
519 : krb5_ccache id,
520 : krb5_principal primary_principal)
521 : {
522 0 : krb5_dcache *dc = DCACHE(id);
523 0 : return krb5_cc_initialize(context, D2FCACHE(dc), primary_principal);
524 : }
525 :
526 : static krb5_error_code KRB5_CALLCONV
527 0 : dcc_close(krb5_context context,
528 : krb5_ccache id)
529 : {
530 0 : krb5_dcache *dc = DCACHE(id);
531 0 : krb5_principal p = NULL;
532 : struct stat st;
533 0 : char *primary = NULL;
534 :
535 : /*
536 : * If there's no default cache, but we're closing one, and the one we're
537 : * closing has been initialized, then make it the default. This makes the
538 : * first cache created the default.
539 : *
540 : * FIXME We should check if `D2FCACHE(dc)' has live credentials.
541 : */
542 0 : if (dc->default_candidate && D2FCACHE(dc) &&
543 0 : krb5_cc_get_principal(context, D2FCACHE(dc), &p) == 0 &&
544 0 : (primary = primary_create(dc)) &&
545 0 : (stat(primary, &st) == -1 || !S_ISREG(st.st_mode) || st.st_size == 0))
546 0 : dcc_set_default(context, id);
547 0 : krb5_free_principal(context, p);
548 0 : free(primary);
549 0 : dcc_release(context, DCACHE(id));
550 0 : return 0;
551 : }
552 :
553 : static krb5_error_code KRB5_CALLCONV
554 0 : dcc_destroy(krb5_context context,
555 : krb5_ccache id)
556 : {
557 0 : krb5_dcache *dc = DCACHE(id);
558 0 : krb5_ccache fcache = D2FCACHE(dc);
559 0 : dc->fcache = NULL;
560 0 : return krb5_cc_destroy(context, fcache);
561 : }
562 :
563 : static krb5_error_code KRB5_CALLCONV
564 0 : dcc_store_cred(krb5_context context,
565 : krb5_ccache id,
566 : krb5_creds *creds)
567 : {
568 0 : krb5_dcache *dc = DCACHE(id);
569 0 : return krb5_cc_store_cred(context, D2FCACHE(dc), creds);
570 : }
571 :
572 : static krb5_error_code KRB5_CALLCONV
573 0 : dcc_get_principal(krb5_context context,
574 : krb5_ccache id,
575 : krb5_principal *principal)
576 : {
577 0 : krb5_dcache *dc = DCACHE(id);
578 0 : return krb5_cc_get_principal(context, D2FCACHE(dc), principal);
579 : }
580 :
581 : static krb5_error_code KRB5_CALLCONV
582 0 : dcc_get_first (krb5_context context,
583 : krb5_ccache id,
584 : krb5_cc_cursor *cursor)
585 : {
586 0 : krb5_dcache *dc = DCACHE(id);
587 0 : return krb5_cc_start_seq_get(context, D2FCACHE(dc), cursor);
588 : }
589 :
590 : static krb5_error_code KRB5_CALLCONV
591 0 : dcc_get_next (krb5_context context,
592 : krb5_ccache id,
593 : krb5_cc_cursor *cursor,
594 : krb5_creds *creds)
595 : {
596 0 : krb5_dcache *dc = DCACHE(id);
597 0 : return krb5_cc_next_cred(context, D2FCACHE(dc), cursor, creds);
598 : }
599 :
600 : static krb5_error_code KRB5_CALLCONV
601 0 : dcc_end_get (krb5_context context,
602 : krb5_ccache id,
603 : krb5_cc_cursor *cursor)
604 : {
605 0 : krb5_dcache *dc = DCACHE(id);
606 0 : return krb5_cc_end_seq_get(context, D2FCACHE(dc), cursor);
607 : }
608 :
609 : static krb5_error_code KRB5_CALLCONV
610 0 : dcc_remove_cred(krb5_context context,
611 : krb5_ccache id,
612 : krb5_flags which,
613 : krb5_creds *cred)
614 : {
615 0 : krb5_dcache *dc = DCACHE(id);
616 0 : return krb5_cc_remove_cred(context, D2FCACHE(dc), which, cred);
617 : }
618 :
619 : static krb5_error_code KRB5_CALLCONV
620 0 : dcc_set_flags(krb5_context context,
621 : krb5_ccache id,
622 : krb5_flags flags)
623 : {
624 0 : krb5_dcache *dc = DCACHE(id);
625 0 : return krb5_cc_set_flags(context, D2FCACHE(dc), flags);
626 : }
627 :
628 : static int KRB5_CALLCONV
629 0 : dcc_get_version(krb5_context context,
630 : krb5_ccache id)
631 : {
632 0 : krb5_dcache *dc = DCACHE(id);
633 0 : return krb5_cc_get_version(context, D2FCACHE(dc));
634 : }
635 :
636 : struct dcache_iter {
637 : char *primary;
638 : krb5_dcache *dc;
639 : DIR *d;
640 : unsigned int first:1;
641 : };
642 :
643 : static krb5_error_code KRB5_CALLCONV
644 1 : dcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
645 : {
646 1 : struct dcache_iter *iter = NULL;
647 1 : const char *name = krb5_cc_default_name(context);
648 : size_t len;
649 : char *p;
650 :
651 1 : *cursor = NULL;
652 :
653 1 : if (strncmp(name, "DIR:", sizeof("DIR:") - 1) != 0) {
654 1 : krb5_set_error_message(context, KRB5_CC_FORMAT,
655 1 : N_("Can't list DIR caches unless its the default type", ""));
656 1 : return KRB5_CC_FORMAT;
657 : }
658 :
659 0 : if ((iter = calloc(1, sizeof(*iter))) == NULL ||
660 0 : (iter->dc = calloc(1, sizeof(iter->dc[0]))) == NULL ||
661 0 : (iter->dc->dir = strdup(name + sizeof("DIR:") - 1)) == NULL) {
662 0 : if (iter)
663 0 : free(iter->dc);
664 0 : free(iter);
665 0 : return krb5_enomem(context);
666 : }
667 0 : iter->first = 1;
668 0 : p = strrchr(iter->dc->dir, ':');
669 : #ifdef WIN32
670 : if (p == iter->dc->dir + 1)
671 : p = NULL;
672 : #endif
673 0 : if (p)
674 0 : *p = '\0';
675 :
676 : /* Strip off extra slashes on the end */
677 0 : for (len = strlen(iter->dc->dir);
678 0 : len && ISPATHSEP(iter->dc->dir[len - 1]);
679 0 : len--) {
680 0 : iter->dc->dir[len - 1] = '\0';
681 : }
682 :
683 0 : if ((iter->d = opendir(iter->dc->dir)) == NULL) {
684 0 : krb5_set_error_message(context, KRB5_CC_FORMAT,
685 0 : N_("Can't open DIR %s: %s", ""),
686 0 : iter->dc->dir, strerror(errno));
687 0 : free(iter->dc->dir);
688 0 : free(iter->dc);
689 0 : free(iter);
690 0 : return KRB5_CC_FORMAT;
691 : }
692 :
693 0 : *cursor = iter;
694 0 : return 0;
695 : }
696 :
697 : static krb5_error_code KRB5_CALLCONV
698 0 : dcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
699 : {
700 0 : struct dcache_iter *iter = cursor;
701 : krb5_error_code ret;
702 : struct stat st;
703 : struct dirent *dentry;
704 0 : char *p = NULL;
705 :
706 0 : *id = NULL;
707 0 : if (iter == NULL)
708 0 : return krb5_einval(context, 2);
709 :
710 : /* Emit primary subsidiary first */
711 0 : if (iter->first &&
712 0 : get_default_cache(context, iter->dc, NULL, &iter->primary) == 0 &&
713 0 : iter->primary && is_filename_cacheish(iter->primary)) {
714 0 : iter->first = 0;
715 0 : ret = KRB5_CC_END;
716 0 : if (asprintf(&p, "FILE:%s/%s", iter->dc->dir, iter->primary) > -1 && p != NULL &&
717 0 : stat(p + sizeof("FILE:") - 1, &st) == 0 && S_ISREG(st.st_mode))
718 0 : ret = krb5_cc_resolve(context, p, id);
719 0 : if (p == NULL)
720 0 : return krb5_enomem(context);
721 0 : free(p);
722 0 : if (ret == 0)
723 0 : return ret;
724 0 : p = NULL;
725 : }
726 :
727 0 : iter->first = 0;
728 0 : for (dentry = readdir(iter->d); dentry; dentry = readdir(iter->d)) {
729 0 : if (!is_filename_cacheish(dentry->d_name) ||
730 0 : (iter->primary && strcmp(dentry->d_name, iter->primary) == 0))
731 0 : continue;
732 0 : p = NULL;
733 0 : ret = KRB5_CC_END;
734 0 : if (asprintf(&p, "FILE:%s/%s", iter->dc->dir, dentry->d_name) > -1 &&
735 0 : p != NULL &&
736 0 : stat(p + sizeof("FILE:") - 1, &st) == 0 && S_ISREG(st.st_mode))
737 0 : ret = krb5_cc_resolve(context, p, id);
738 0 : free(p);
739 0 : if (p == NULL)
740 0 : return krb5_enomem(context);
741 0 : if (ret == 0)
742 0 : return ret;
743 : }
744 0 : return KRB5_CC_END;
745 : }
746 :
747 : static krb5_error_code KRB5_CALLCONV
748 0 : dcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
749 : {
750 0 : struct dcache_iter *iter = cursor;
751 :
752 0 : if (iter == NULL)
753 0 : return krb5_einval(context, 2);
754 :
755 0 : (void) closedir(iter->d);
756 0 : free(iter->dc->dir);
757 0 : free(iter->dc);
758 0 : free(iter->primary);
759 0 : free(iter);
760 0 : return 0;
761 : }
762 :
763 : static krb5_error_code KRB5_CALLCONV
764 0 : dcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
765 : {
766 0 : krb5_dcache *dcfrom = DCACHE(from);
767 0 : krb5_dcache *dcto = DCACHE(to);
768 :
769 0 : dcfrom->default_candidate = 0;
770 0 : dcto->default_candidate = 1;
771 0 : return krb5_cc_move(context, D2FCACHE(dcfrom), D2FCACHE(dcto));
772 : }
773 :
774 : static krb5_error_code KRB5_CALLCONV
775 0 : dcc_get_default_name(krb5_context context, char **str)
776 : {
777 0 : const char *def_cc_colname =
778 : krb5_config_get_string_default(context, NULL, KRB5_DEFAULT_CCNAME_DIR,
779 : "libdefaults", "default_cc_collection",
780 : NULL);
781 :
782 : /* [libdefaults] default_cc_collection is for testing */
783 0 : if (strncmp(def_cc_colname, "DIR:", sizeof("DIR:") - 1) != 0)
784 0 : def_cc_colname = KRB5_DEFAULT_CCNAME_DIR;
785 0 : return _krb5_expand_default_cc_name(context, def_cc_colname, str);
786 : }
787 :
788 : static krb5_error_code KRB5_CALLCONV
789 0 : dcc_set_default(krb5_context context, krb5_ccache id)
790 : {
791 0 : krb5_dcache *dc = DCACHE(id);
792 :
793 0 : if (dc->sub == NULL)
794 0 : return ENOENT;
795 0 : return set_default_cache(context, dc, dc->sub);
796 : }
797 :
798 : static krb5_error_code KRB5_CALLCONV
799 0 : dcc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime)
800 : {
801 0 : krb5_dcache *dc = DCACHE(id);
802 0 : return krb5_cc_last_change_time(context, D2FCACHE(dc), mtime);
803 : }
804 :
805 : static krb5_error_code KRB5_CALLCONV
806 0 : dcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat kdc_offset)
807 : {
808 0 : krb5_dcache *dc = DCACHE(id);
809 0 : return krb5_cc_set_kdc_offset(context, D2FCACHE(dc), kdc_offset);
810 : }
811 :
812 : static krb5_error_code KRB5_CALLCONV
813 0 : dcc_get_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat *kdc_offset)
814 : {
815 0 : krb5_dcache *dc = DCACHE(id);
816 0 : return krb5_cc_get_kdc_offset(context, D2FCACHE(dc), kdc_offset);
817 : }
818 :
819 :
820 : /**
821 : * Variable containing the DIR based credential cache implemention.
822 : *
823 : * @ingroup krb5_ccache
824 : */
825 :
826 : KRB5_LIB_VARIABLE const krb5_cc_ops krb5_dcc_ops = {
827 : KRB5_CC_OPS_VERSION_5,
828 : "DIR",
829 : NULL,
830 : NULL,
831 : dcc_gen_new,
832 : dcc_initialize,
833 : dcc_destroy,
834 : dcc_close,
835 : dcc_store_cred,
836 : NULL, /* dcc_retrieve */
837 : dcc_get_principal,
838 : dcc_get_first,
839 : dcc_get_next,
840 : dcc_end_get,
841 : dcc_remove_cred,
842 : dcc_set_flags,
843 : dcc_get_version,
844 : dcc_get_cache_first,
845 : dcc_get_cache_next,
846 : dcc_end_cache_get,
847 : dcc_move,
848 : dcc_get_default_name,
849 : dcc_set_default,
850 : dcc_lastchange,
851 : dcc_set_kdc_offset,
852 : dcc_get_kdc_offset,
853 : dcc_get_name_2,
854 : dcc_resolve_2
855 : };
|