1 /* $NetBSD: dcache.c,v 1.2 2017/01/28 21:31:49 christos Exp $ */
2
3 /*
4 * Copyright (c) 1997 - 2008 Kungliga Tekniska Högskolan
5 * (Royal Institute of Technology, Stockholm, Sweden).
6 * All rights reserved.
7 *
8 * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 *
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 *
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 *
21 * 3. Neither the name of the Institute nor the names of its contributors
22 * may be used to endorse or promote products derived from this software
23 * without specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 * SUCH DAMAGE.
36 */
37
38 #include "krb5_locl.h"
39
40 typedef struct krb5_dcache{
41 krb5_ccache fcache;
42 char *dir;
43 char *name;
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
52
53 static char *
primary_create(krb5_dcache * dc)54 primary_create(krb5_dcache *dc)
55 {
56 char *primary = NULL;
57
58 asprintf(&primary, "%s/primary", dc->dir);
59 if (primary == NULL)
60 return NULL;
61
62 return primary;
63 }
64
65 static int
is_filename_cacheish(const char * name)66 is_filename_cacheish(const char *name)
67 {
68 return strncmp(name, "tkt", 3) == 0;
69
70 }
71
72 static krb5_error_code
set_default_cache(krb5_context context,krb5_dcache * dc,const char * residual)73 set_default_cache(krb5_context context, krb5_dcache *dc, const char *residual)
74 {
75 char *path = NULL, *primary = NULL;
76 krb5_error_code ret;
77 struct iovec iov[2];
78 size_t len;
79 int fd = -1;
80
81 if (!is_filename_cacheish(residual)) {
82 krb5_set_error_message(context, KRB5_CC_FORMAT,
83 "name %s is not a cache (doesn't start with tkt)", residual);
84 return KRB5_CC_FORMAT;
85 }
86
87 asprintf(&path, "%s/primary-XXXXXX", dc->dir);
88 if (path == NULL)
89 return krb5_enomem(context);
90
91 fd = mkstemp(path);
92 if (fd < 0) {
93 ret = errno;
94 goto out;
95 }
96 rk_cloexec(fd);
97 #ifndef _WIN32
98 if (fchmod(fd, S_IRUSR | S_IWUSR) < 0) {
99 ret = errno;
100 goto out;
101 }
102 #endif
103 len = strlen(residual);
104
105 iov[0].iov_base = rk_UNCONST(residual);
106 iov[0].iov_len = len;
107 iov[1].iov_base = "\n";
108 iov[1].iov_len = 1;
109
110 if (writev(fd, iov, sizeof(iov)/sizeof(iov[0])) != len + 1) {
111 ret = errno;
112 goto out;
113 }
114
115 primary = primary_create(dc);
116 if (primary == NULL) {
117 ret = krb5_enomem(context);
118 goto out;
119 }
120
121 if (rename(path, primary) < 0) {
122 ret = errno;
123 goto out;
124 }
125
126 close(fd);
127 fd = -1;
128
129 ret = 0;
130 out:
131 if (fd >= 0) {
132 (void)unlink(path);
133 close(fd);
134 }
135 if (path)
136 free(path);
137 if (primary)
138 free(primary);
139
140 return ret;
141 }
142
143 static krb5_error_code
get_default_cache(krb5_context context,krb5_dcache * dc,char ** residual)144 get_default_cache(krb5_context context, krb5_dcache *dc, char **residual)
145 {
146 krb5_error_code ret;
147 char buf[MAXPATHLEN];
148 char *primary;
149 FILE *f;
150
151 *residual = NULL;
152 primary = primary_create(dc);
153 if (primary == NULL)
154 return krb5_enomem(context);
155
156 f = fopen(primary, "r");
157 if (f == NULL) {
158 if (errno == ENOENT) {
159 free(primary);
160 *residual = strdup("tkt");
161 if (*residual == NULL)
162 return krb5_enomem(context);
163 return 0;
164 }
165 ret = errno;
166 krb5_set_error_message(context, ret, "failed to open %s", primary);
167 free(primary);
168 return ret;
169 }
170
171 if (fgets(buf, sizeof(buf), f) == NULL) {
172 ret = ferror(f);
173 fclose(f);
174 krb5_set_error_message(context, ret, "read file %s", primary);
175 free(primary);
176 return ret;
177 }
178 fclose(f);
179
180 buf[strcspn(buf, "\r\n")] = '\0';
181
182 if (!is_filename_cacheish(buf)) {
183 krb5_set_error_message(context, KRB5_CC_FORMAT,
184 "name in %s is not a cache (doesn't start with tkt)", primary);
185 free(primary);
186 return KRB5_CC_FORMAT;
187 }
188
189 free(primary);
190
191 *residual = strdup(buf);
192 if (*residual == NULL)
193 return krb5_enomem(context);
194
195 return 0;
196 }
197
198
199
200 static const char* KRB5_CALLCONV
dcc_get_name(krb5_context context,krb5_ccache id)201 dcc_get_name(krb5_context context,
202 krb5_ccache id)
203 {
204 krb5_dcache *dc = DCACHE(id);
205 return dc->name;
206 }
207
208
209 static krb5_error_code
verify_directory(krb5_context context,const char * path)210 verify_directory(krb5_context context, const char *path)
211 {
212 struct stat sb;
213
214 if (stat(path, &sb) != 0) {
215 if (errno == ENOENT) {
216 /* XXX should use mkdirx_np() */
217 if (rk_mkdir(path, S_IRWXU) == 0)
218 return 0;
219
220 krb5_set_error_message(context, ENOENT,
221 N_("DIR directory %s doesn't exists", ""), path);
222 return ENOENT;
223 } else {
224 int ret = errno;
225 krb5_set_error_message(context, ret,
226 N_("DIR directory %s is bad: %s", ""), path, strerror(ret));
227 return errno;
228 }
229 }
230 if (!S_ISDIR(sb.st_mode)) {
231 krb5_set_error_message(context, KRB5_CC_BADNAME,
232 N_("DIR directory %s is not a directory", ""), path);
233 return KRB5_CC_BADNAME;
234 }
235
236 return 0;
237 }
238
239 static void
dcc_release(krb5_context context,krb5_dcache * dc)240 dcc_release(krb5_context context, krb5_dcache *dc)
241 {
242 if (dc->fcache)
243 krb5_cc_close(context, dc->fcache);
244 if (dc->dir)
245 free(dc->dir);
246 if (dc->name)
247 free(dc->name);
248 memset(dc, 0, sizeof(*dc));
249 free(dc);
250 }
251
252 static krb5_error_code KRB5_CALLCONV
dcc_resolve(krb5_context context,krb5_ccache * id,const char * res)253 dcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
254 {
255 char *filename = NULL;
256 krb5_error_code ret;
257 krb5_dcache *dc;
258 const char *p;
259
260 p = res;
261 do {
262 p = strstr(p, "..");
263 if (p && (p == res || ISPATHSEP(p[-1])) && (ISPATHSEP(p[2]) || p[2] == '\0')) {
264 krb5_set_error_message(context, KRB5_CC_FORMAT,
265 N_("Path contains a .. component", ""));
266 return KRB5_CC_FORMAT;
267 }
268 if (p)
269 p += 3;
270 } while (p);
271
272 dc = calloc(1, sizeof(*dc));
273 if (dc == NULL) {
274 krb5_set_error_message(context, KRB5_CC_NOMEM,
275 N_("malloc: out of memory", ""));
276 return KRB5_CC_NOMEM;
277 }
278
279 /* check for explicit component */
280 if (res[0] == ':') {
281 char *q;
282
283 dc->dir = strdup(&res[1]);
284 #ifdef _WIN32
285 q = strrchr(dc->dir, '\\');
286 if (q == NULL)
287 #endif
288 q = strrchr(dc->dir, '/');
289 if (q) {
290 *q++ = '\0';
291 } else {
292 krb5_set_error_message(context, KRB5_CC_FORMAT, N_("Cache not an absolute path: %s", ""), dc->dir);
293 dcc_release(context, dc);
294 return KRB5_CC_FORMAT;
295 }
296
297 if (!is_filename_cacheish(q)) {
298 krb5_set_error_message(context, KRB5_CC_FORMAT,
299 N_("Name %s is not a cache (doesn't start with tkt)", ""), q);
300 dcc_release(context, dc);
301 return KRB5_CC_FORMAT;
302 }
303
304 ret = verify_directory(context, dc->dir);
305 if (ret) {
306 dcc_release(context, dc);
307 return ret;
308 }
309
310 dc->name = strdup(res);
311 if (dc->name == NULL) {
312 dcc_release(context, dc);
313 return krb5_enomem(context);
314 }
315
316 } else {
317 char *residual;
318 size_t len;
319
320 dc->dir = strdup(res);
321 if (dc->dir == NULL) {
322 dcc_release(context, dc);
323 return krb5_enomem(context);
324 }
325
326 len = strlen(dc->dir);
327
328 if (ISPATHSEP(dc->dir[len - 1]))
329 dc->dir[len - 1] = '\0';
330
331 ret = verify_directory(context, dc->dir);
332 if (ret) {
333 dcc_release(context, dc);
334 return ret;
335 }
336
337 ret = get_default_cache(context, dc, &residual);
338 if (ret) {
339 dcc_release(context, dc);
340 return ret;
341 }
342 asprintf(&dc->name, ":%s/%s", dc->dir, residual);
343 free(residual);
344 if (dc->name == NULL) {
345 dcc_release(context, dc);
346 return krb5_enomem(context);
347 }
348 }
349
350 asprintf(&filename, "FILE%s", dc->name);
351 if (filename == NULL) {
352 dcc_release(context, dc);
353 return krb5_enomem(context);
354 }
355
356 ret = krb5_cc_resolve(context, filename, &dc->fcache);
357 free(filename);
358 if (ret) {
359 dcc_release(context, dc);
360 return ret;
361 }
362
363
364 (*id)->data.data = dc;
365 (*id)->data.length = sizeof(*dc);
366 return 0;
367 }
368
369 static char *
copy_default_dcc_cache(krb5_context context)370 copy_default_dcc_cache(krb5_context context)
371 {
372 const char *defname;
373 krb5_error_code ret;
374 char *name = NULL;
375 size_t len;
376
377 len = strlen(krb5_dcc_ops.prefix);
378
379 defname = krb5_cc_default_name(context);
380 if (defname == NULL ||
381 strncmp(defname, krb5_dcc_ops.prefix, len) != 0 ||
382 defname[len] != ':')
383 {
384 ret = dcc_get_default_name(context, &name);
385 if (ret)
386 return NULL;
387
388 return name;
389 } else {
390 return strdup(&defname[len + 1]);
391 }
392 }
393
394
395 static krb5_error_code KRB5_CALLCONV
dcc_gen_new(krb5_context context,krb5_ccache * id)396 dcc_gen_new(krb5_context context, krb5_ccache *id)
397 {
398 krb5_error_code ret;
399 char *name = NULL;
400 krb5_dcache *dc;
401 int fd;
402 size_t len;
403
404 name = copy_default_dcc_cache(context);
405 if (name == NULL) {
406 krb5_set_error_message(context, KRB5_CC_FORMAT,
407 N_("Can't generate DIR caches unless its the default type", ""));
408 return KRB5_CC_FORMAT;
409 }
410
411 len = strlen(krb5_dcc_ops.prefix);
412 if (strncmp(name, krb5_dcc_ops.prefix, len) == 0 && name[len] == ':')
413 ++len;
414 else
415 len = 0;
416
417 ret = dcc_resolve(context, id, name + len);
418 free(name);
419 name = NULL;
420 if (ret)
421 return ret;
422
423 dc = DCACHE((*id));
424
425 asprintf(&name, ":%s/tktXXXXXX", dc->dir);
426 if (name == NULL) {
427 dcc_close(context, *id);
428 return krb5_enomem(context);
429 }
430
431 fd = mkstemp(&name[1]);
432 if (fd < 0) {
433 dcc_close(context, *id);
434 return krb5_enomem(context);
435 }
436 close(fd);
437
438 free(dc->name);
439 dc->name = name;
440
441 return 0;
442 }
443
444 static krb5_error_code KRB5_CALLCONV
dcc_initialize(krb5_context context,krb5_ccache id,krb5_principal primary_principal)445 dcc_initialize(krb5_context context,
446 krb5_ccache id,
447 krb5_principal primary_principal)
448 {
449 krb5_dcache *dc = DCACHE(id);
450 return krb5_cc_initialize(context, D2FCACHE(dc), primary_principal);
451 }
452
453 static krb5_error_code KRB5_CALLCONV
dcc_close(krb5_context context,krb5_ccache id)454 dcc_close(krb5_context context,
455 krb5_ccache id)
456 {
457 dcc_release(context, DCACHE(id));
458 return 0;
459 }
460
461 static krb5_error_code KRB5_CALLCONV
dcc_destroy(krb5_context context,krb5_ccache id)462 dcc_destroy(krb5_context context,
463 krb5_ccache id)
464 {
465 krb5_dcache *dc = DCACHE(id);
466 krb5_ccache fcache = D2FCACHE(dc);
467 dc->fcache = NULL;
468 return krb5_cc_destroy(context, fcache);
469 }
470
471 static krb5_error_code KRB5_CALLCONV
dcc_store_cred(krb5_context context,krb5_ccache id,krb5_creds * creds)472 dcc_store_cred(krb5_context context,
473 krb5_ccache id,
474 krb5_creds *creds)
475 {
476 krb5_dcache *dc = DCACHE(id);
477 return krb5_cc_store_cred(context, D2FCACHE(dc), creds);
478 }
479
480 static krb5_error_code KRB5_CALLCONV
dcc_get_principal(krb5_context context,krb5_ccache id,krb5_principal * principal)481 dcc_get_principal(krb5_context context,
482 krb5_ccache id,
483 krb5_principal *principal)
484 {
485 krb5_dcache *dc = DCACHE(id);
486 return krb5_cc_get_principal(context, D2FCACHE(dc), principal);
487 }
488
489 static krb5_error_code KRB5_CALLCONV
dcc_get_first(krb5_context context,krb5_ccache id,krb5_cc_cursor * cursor)490 dcc_get_first (krb5_context context,
491 krb5_ccache id,
492 krb5_cc_cursor *cursor)
493 {
494 krb5_dcache *dc = DCACHE(id);
495 return krb5_cc_start_seq_get(context, D2FCACHE(dc), cursor);
496 }
497
498 static krb5_error_code KRB5_CALLCONV
dcc_get_next(krb5_context context,krb5_ccache id,krb5_cc_cursor * cursor,krb5_creds * creds)499 dcc_get_next (krb5_context context,
500 krb5_ccache id,
501 krb5_cc_cursor *cursor,
502 krb5_creds *creds)
503 {
504 krb5_dcache *dc = DCACHE(id);
505 return krb5_cc_next_cred(context, D2FCACHE(dc), cursor, creds);
506 }
507
508 static krb5_error_code KRB5_CALLCONV
dcc_end_get(krb5_context context,krb5_ccache id,krb5_cc_cursor * cursor)509 dcc_end_get (krb5_context context,
510 krb5_ccache id,
511 krb5_cc_cursor *cursor)
512 {
513 krb5_dcache *dc = DCACHE(id);
514 return krb5_cc_end_seq_get(context, D2FCACHE(dc), cursor);
515 }
516
517 static krb5_error_code KRB5_CALLCONV
dcc_remove_cred(krb5_context context,krb5_ccache id,krb5_flags which,krb5_creds * cred)518 dcc_remove_cred(krb5_context context,
519 krb5_ccache id,
520 krb5_flags which,
521 krb5_creds *cred)
522 {
523 krb5_dcache *dc = DCACHE(id);
524 return krb5_cc_remove_cred(context, D2FCACHE(dc), which, cred);
525 }
526
527 static krb5_error_code KRB5_CALLCONV
dcc_set_flags(krb5_context context,krb5_ccache id,krb5_flags flags)528 dcc_set_flags(krb5_context context,
529 krb5_ccache id,
530 krb5_flags flags)
531 {
532 krb5_dcache *dc = DCACHE(id);
533 return krb5_cc_set_flags(context, D2FCACHE(dc), flags);
534 }
535
536 static int KRB5_CALLCONV
dcc_get_version(krb5_context context,krb5_ccache id)537 dcc_get_version(krb5_context context,
538 krb5_ccache id)
539 {
540 krb5_dcache *dc = DCACHE(id);
541 return krb5_cc_get_version(context, D2FCACHE(dc));
542 }
543
544 struct dcache_iter {
545 int first;
546 krb5_dcache *dc;
547 };
548
549 static krb5_error_code KRB5_CALLCONV
dcc_get_cache_first(krb5_context context,krb5_cc_cursor * cursor)550 dcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
551 {
552 struct dcache_iter *iter;
553 krb5_error_code ret;
554 char *name;
555
556 *cursor = NULL;
557 iter = calloc(1, sizeof(*iter));
558 if (iter == NULL)
559 return krb5_enomem(context);
560 iter->first = 1;
561
562 name = copy_default_dcc_cache(context);
563 if (name == NULL) {
564 free(iter);
565 krb5_set_error_message(context, KRB5_CC_FORMAT,
566 N_("Can't generate DIR caches unless its the default type", ""));
567 return KRB5_CC_FORMAT;
568 }
569
570 ret = dcc_resolve(context, NULL, name);
571 free(name);
572 if (ret) {
573 free(iter);
574 return ret;
575 }
576
577 /* XXX We need to opendir() here */
578
579 *cursor = iter;
580 return 0;
581 }
582
583 static krb5_error_code KRB5_CALLCONV
dcc_get_cache_next(krb5_context context,krb5_cc_cursor cursor,krb5_ccache * id)584 dcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
585 {
586 struct dcache_iter *iter = cursor;
587
588 if (iter == NULL)
589 return krb5_einval(context, 2);
590
591 if (!iter->first) {
592 krb5_clear_error_message(context);
593 return KRB5_CC_END;
594 }
595
596 /* XXX We need to readdir() here */
597 iter->first = 0;
598
599 return KRB5_CC_END;
600 }
601
602 static krb5_error_code KRB5_CALLCONV
dcc_end_cache_get(krb5_context context,krb5_cc_cursor cursor)603 dcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
604 {
605 struct dcache_iter *iter = cursor;
606
607 if (iter == NULL)
608 return krb5_einval(context, 2);
609
610 /* XXX We need to closedir() here */
611 if (iter->dc)
612 dcc_release(context, iter->dc);
613 free(iter);
614 return 0;
615 }
616
617 static krb5_error_code KRB5_CALLCONV
dcc_move(krb5_context context,krb5_ccache from,krb5_ccache to)618 dcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
619 {
620 krb5_dcache *dcfrom = DCACHE(from);
621 krb5_dcache *dcto = DCACHE(to);
622 return krb5_cc_move(context, D2FCACHE(dcfrom), D2FCACHE(dcto));
623 }
624
625 static krb5_error_code KRB5_CALLCONV
dcc_get_default_name(krb5_context context,char ** str)626 dcc_get_default_name(krb5_context context, char **str)
627 {
628 return _krb5_expand_default_cc_name(context,
629 KRB5_DEFAULT_CCNAME_DIR,
630 str);
631 }
632
633 static krb5_error_code KRB5_CALLCONV
dcc_set_default(krb5_context context,krb5_ccache id)634 dcc_set_default(krb5_context context, krb5_ccache id)
635 {
636 krb5_dcache *dc = DCACHE(id);
637 const char *name;
638
639 name = krb5_cc_get_name(context, D2FCACHE(dc));
640 if (name == NULL)
641 return ENOENT;
642
643 return set_default_cache(context, dc, name);
644 }
645
646 static krb5_error_code KRB5_CALLCONV
dcc_lastchange(krb5_context context,krb5_ccache id,krb5_timestamp * mtime)647 dcc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime)
648 {
649 krb5_dcache *dc = DCACHE(id);
650 return krb5_cc_last_change_time(context, D2FCACHE(dc), mtime);
651 }
652
653 static krb5_error_code KRB5_CALLCONV
dcc_set_kdc_offset(krb5_context context,krb5_ccache id,krb5_deltat kdc_offset)654 dcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat kdc_offset)
655 {
656 krb5_dcache *dc = DCACHE(id);
657 return krb5_cc_set_kdc_offset(context, D2FCACHE(dc), kdc_offset);
658 }
659
660 static krb5_error_code KRB5_CALLCONV
dcc_get_kdc_offset(krb5_context context,krb5_ccache id,krb5_deltat * kdc_offset)661 dcc_get_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat *kdc_offset)
662 {
663 krb5_dcache *dc = DCACHE(id);
664 return krb5_cc_get_kdc_offset(context, D2FCACHE(dc), kdc_offset);
665 }
666
667
668 /**
669 * Variable containing the DIR based credential cache implemention.
670 *
671 * @ingroup krb5_ccache
672 */
673
674 KRB5_LIB_VARIABLE const krb5_cc_ops krb5_dcc_ops = {
675 KRB5_CC_OPS_VERSION,
676 "DIR",
677 dcc_get_name,
678 dcc_resolve,
679 dcc_gen_new,
680 dcc_initialize,
681 dcc_destroy,
682 dcc_close,
683 dcc_store_cred,
684 NULL, /* dcc_retrieve */
685 dcc_get_principal,
686 dcc_get_first,
687 dcc_get_next,
688 dcc_end_get,
689 dcc_remove_cred,
690 dcc_set_flags,
691 dcc_get_version,
692 dcc_get_cache_first,
693 dcc_get_cache_next,
694 dcc_end_cache_get,
695 dcc_move,
696 dcc_get_default_name,
697 dcc_set_default,
698 dcc_lastchange,
699 dcc_set_kdc_offset,
700 dcc_get_kdc_offset
701 };
702