Line data Source code
1 : /*
2 : * Copyright (c) 1997 - 2017 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_fcache{
39 : char *filename;
40 : char *res;
41 : char *sub;
42 : char *tmpfn;
43 : int version;
44 : }krb5_fcache;
45 :
46 : struct fcc_cursor {
47 : int fd;
48 : off_t cred_start;
49 : off_t cred_end;
50 : krb5_storage *sp;
51 : };
52 :
53 : #define KRB5_FCC_FVNO_1 1
54 : #define KRB5_FCC_FVNO_2 2
55 : #define KRB5_FCC_FVNO_3 3
56 : #define KRB5_FCC_FVNO_4 4
57 :
58 : #define FCC_TAG_DELTATIME 1
59 :
60 : #define FCACHE(X) ((krb5_fcache*)(X)->data.data)
61 :
62 : #define FILENAME(X) (FCACHE(X)->filename)
63 : #define TMPFILENAME(X) (FCACHE(X)->tmpfn)
64 : #define RESFILENAME(X) (FCACHE(X)->res)
65 : #define SUBFILENAME(X) (FCACHE(X)->sub)
66 :
67 : #define FCC_CURSOR(C) ((struct fcc_cursor*)(C))
68 :
69 : static krb5_error_code KRB5_CALLCONV
70 1918 : fcc_get_name_2(krb5_context context,
71 : krb5_ccache id,
72 : const char **name,
73 : const char **colname,
74 : const char **sub)
75 : {
76 1918 : if (FCACHE(id) == NULL)
77 0 : return KRB5_CC_NOTFOUND;
78 :
79 1918 : if (name)
80 1918 : *name = FILENAME(id);
81 1918 : if (colname)
82 0 : *colname = FILENAME(id);
83 1918 : if (sub)
84 0 : *sub = NULL;
85 1918 : return 0;
86 : }
87 :
88 : KRB5_LIB_FUNCTION int KRB5_LIB_CALL
89 59847 : _krb5_xlock(krb5_context context, int fd, krb5_boolean exclusive,
90 : const char *filename)
91 : {
92 1379 : int ret;
93 : #ifdef HAVE_FCNTL
94 : struct flock l;
95 :
96 : l.l_start = 0;
97 : l.l_len = 0;
98 : l.l_type = exclusive ? F_WRLCK : F_RDLCK;
99 : l.l_whence = SEEK_SET;
100 : ret = fcntl(fd, F_SETLKW, &l);
101 : #else
102 60968 : ret = flock(fd, exclusive ? LOCK_EX : LOCK_SH);
103 : #endif
104 59847 : if(ret < 0)
105 0 : ret = errno;
106 59847 : if(ret == EACCES) /* fcntl can return EACCES instead of EAGAIN */
107 0 : ret = EAGAIN;
108 :
109 59847 : switch (ret) {
110 58468 : case 0:
111 58468 : break;
112 0 : case EINVAL: /* filesystem doesn't support locking, let the user have it */
113 0 : ret = 0;
114 0 : break;
115 0 : case EAGAIN:
116 0 : krb5_set_error_message(context, ret,
117 0 : N_("timed out locking cache file %s", "file"),
118 : filename);
119 0 : break;
120 0 : default: {
121 0 : char buf[128];
122 0 : rk_strerror_r(ret, buf, sizeof(buf));
123 0 : krb5_set_error_message(context, ret,
124 0 : N_("error locking cache file %s: %s",
125 : "file, error"), filename, buf);
126 0 : break;
127 : }
128 : }
129 59847 : return ret;
130 : }
131 :
132 : KRB5_LIB_FUNCTION int KRB5_LIB_CALL
133 0 : _krb5_xunlock(krb5_context context, int fd)
134 : {
135 0 : int ret;
136 : #ifdef HAVE_FCNTL
137 : struct flock l;
138 : l.l_start = 0;
139 : l.l_len = 0;
140 : l.l_type = F_UNLCK;
141 : l.l_whence = SEEK_SET;
142 : ret = fcntl(fd, F_SETLKW, &l);
143 : #else
144 0 : ret = flock(fd, LOCK_UN);
145 : #endif
146 0 : if (ret < 0)
147 0 : ret = errno;
148 0 : switch (ret) {
149 0 : case 0:
150 0 : break;
151 0 : case EINVAL: /* filesystem doesn't support locking, let the user have it */
152 0 : ret = 0;
153 0 : break;
154 0 : default: {
155 0 : char buf[128];
156 0 : rk_strerror_r(ret, buf, sizeof(buf));
157 0 : krb5_set_error_message(context, ret,
158 0 : N_("Failed to unlock file: %s", ""), buf);
159 0 : break;
160 : }
161 : }
162 0 : return ret;
163 : }
164 :
165 : static krb5_error_code
166 1571 : write_storage(krb5_context context, krb5_storage *sp, int fd)
167 : {
168 0 : krb5_error_code ret;
169 0 : krb5_data data;
170 0 : ssize_t sret;
171 :
172 1571 : ret = krb5_storage_to_data(sp, &data);
173 1571 : if (ret) {
174 0 : krb5_set_error_message(context, ret, N_("malloc: out of memory", ""));
175 0 : return ret;
176 : }
177 1571 : sret = write(fd, data.data, data.length);
178 1571 : ret = (sret != (ssize_t)data.length);
179 1571 : krb5_data_free(&data);
180 1571 : if (ret) {
181 0 : ret = errno;
182 0 : krb5_set_error_message(context, ret,
183 0 : N_("Failed to write FILE credential data", ""));
184 0 : return ret;
185 : }
186 1571 : return 0;
187 : }
188 :
189 :
190 : static krb5_error_code KRB5_CALLCONV
191 8421 : fcc_lock(krb5_context context, krb5_ccache id,
192 : int fd, krb5_boolean exclusive)
193 : {
194 0 : krb5_error_code ret;
195 0 : const char *name;
196 :
197 8421 : if (exclusive == FALSE)
198 6850 : return 0;
199 1571 : ret = fcc_get_name_2(context, id, &name, NULL, NULL);
200 1571 : if (ret == 0)
201 1571 : ret = _krb5_xlock(context, fd, exclusive, name);
202 1571 : return ret;
203 : }
204 :
205 : static krb5_error_code KRB5_CALLCONV
206 : fcc_get_default_name(krb5_context, char **);
207 :
208 : /*
209 : * This is the character used to separate the residual from the subsidiary name
210 : * when both are given. It's tempting to use ':' just as we do in the ccache
211 : * names, but we can't on Windows.
212 : */
213 : #define FILESUBSEP "+"
214 : #define FILESUBSEPCHR ((FILESUBSEP)[0])
215 :
216 : static krb5_error_code KRB5_CALLCONV
217 54530 : fcc_resolve_2(krb5_context context,
218 : krb5_ccache *id,
219 : const char *res,
220 : const char *sub)
221 : {
222 437 : krb5_fcache *f;
223 54530 : char *freeme = NULL;
224 :
225 54530 : if (res == NULL && sub == NULL)
226 0 : return krb5_einval(context, 3);
227 54530 : if (res == NULL) {
228 0 : krb5_error_code ret;
229 :
230 0 : if ((ret = fcc_get_default_name(context, &freeme)))
231 0 : return ret;
232 0 : res = freeme + sizeof("FILE:") - 1;
233 54530 : } else if (!sub && (sub = strchr(res, FILESUBSEPCHR))) {
234 0 : if (sub[1] == '\0') {
235 0 : sub = NULL;
236 : } else {
237 : /* `res' has a subsidiary component, so split on it */
238 0 : if ((freeme = strndup(res, sub - res)) == NULL)
239 0 : return krb5_enomem(context);
240 0 : res = freeme;
241 0 : sub++;
242 : }
243 : }
244 :
245 54530 : if ((f = calloc(1, sizeof(*f))) == NULL ||
246 109060 : (f->res = strdup(res)) == NULL ||
247 108623 : (f->sub = sub ? strdup(sub) : NULL) == (sub ? NULL : "") ||
248 55404 : asprintf(&f->filename, "%s%s%s",
249 54530 : res, sub ? FILESUBSEP : "", sub ? sub : "") == -1 ||
250 54530 : f->filename == NULL) {
251 0 : if (f) {
252 0 : free(f->filename);
253 0 : free(f->res);
254 0 : free(f->sub);
255 : }
256 0 : free(f);
257 0 : free(freeme);
258 0 : return krb5_enomem(context);
259 : }
260 54530 : f->tmpfn = NULL;
261 54530 : f->version = 0;
262 54530 : (*id)->data.data = f;
263 54530 : (*id)->data.length = sizeof(*f);
264 :
265 54530 : free(freeme);
266 54530 : return 0;
267 : }
268 :
269 : /*
270 : * Try to scrub the contents of `filename' safely.
271 : */
272 :
273 : static int
274 172 : scrub_file (int fd)
275 : {
276 0 : off_t pos;
277 0 : char buf[128];
278 :
279 172 : pos = lseek(fd, 0, SEEK_END);
280 172 : if (pos < 0)
281 0 : return errno;
282 172 : if (lseek(fd, 0, SEEK_SET) < 0)
283 0 : return errno;
284 172 : memset(buf, 0, sizeof(buf));
285 3497 : while(pos > 0) {
286 0 : ssize_t tmp;
287 3325 : size_t wr = sizeof(buf);
288 3325 : if (wr > pos)
289 172 : wr = (size_t)pos;
290 3325 : tmp = write(fd, buf, wr);
291 :
292 3325 : if (tmp < 0)
293 0 : return errno;
294 3325 : pos -= tmp;
295 : }
296 : #ifdef _MSC_VER
297 : _commit (fd);
298 : #else
299 172 : fsync (fd);
300 : #endif
301 172 : return 0;
302 : }
303 :
304 : /*
305 : * Erase `filename' if it exists, trying to remove the contents if
306 : * it's `safe'. We always try to remove the file, it it exists. It's
307 : * only overwritten if it's a regular file (not a symlink and not a
308 : * hardlink)
309 : */
310 :
311 : KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
312 172 : _krb5_erase_file(krb5_context context, const char *filename)
313 : {
314 0 : int fd;
315 0 : struct stat sb1, sb2;
316 0 : int ret;
317 :
318 172 : ret = lstat (filename, &sb1);
319 172 : if (ret < 0) {
320 0 : if(errno == ENOENT)
321 0 : return 0;
322 : else
323 0 : return errno;
324 : }
325 :
326 172 : fd = open(filename, O_RDWR | O_BINARY | O_CLOEXEC | O_NOFOLLOW);
327 172 : if(fd < 0) {
328 0 : if(errno == ENOENT)
329 0 : return 0;
330 : else
331 0 : return errno;
332 : }
333 172 : rk_cloexec(fd);
334 172 : ret = _krb5_xlock(context, fd, 1, filename);
335 172 : if (ret) {
336 0 : close(fd);
337 0 : return ret;
338 : }
339 172 : if (unlink(filename) < 0) {
340 0 : ret = errno;
341 0 : close (fd);
342 0 : krb5_set_error_message(context, errno,
343 0 : N_("krb5_cc_destroy: unlinking \"%s\": %s", ""),
344 : filename, strerror(ret));
345 0 : return ret;
346 : }
347 172 : ret = fstat(fd, &sb2);
348 172 : if (ret < 0) {
349 0 : ret = errno;
350 0 : close (fd);
351 0 : return ret;
352 : }
353 :
354 : /* check if someone was playing with symlinks */
355 :
356 172 : if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) {
357 0 : close(fd);
358 0 : return EPERM;
359 : }
360 :
361 : /* there are still hard links to this file */
362 :
363 172 : if (sb2.st_nlink != 0) {
364 0 : close(fd);
365 0 : return 0;
366 : }
367 :
368 172 : ret = scrub_file(fd);
369 172 : close(fd);
370 172 : return ret;
371 : }
372 :
373 : static krb5_error_code KRB5_CALLCONV
374 172 : fcc_gen_new(krb5_context context, krb5_ccache *id)
375 : {
376 172 : char *file = NULL, *exp_file = NULL;
377 0 : krb5_error_code ret;
378 0 : krb5_fcache *f;
379 0 : int fd;
380 :
381 172 : f = calloc(1, sizeof(*f));
382 172 : if(f == NULL) {
383 0 : krb5_set_error_message(context, KRB5_CC_NOMEM,
384 0 : N_("malloc: out of memory", ""));
385 0 : return KRB5_CC_NOMEM;
386 : }
387 172 : f->tmpfn = NULL;
388 : /*
389 : * XXX We should asprintf(&file, "%s:XXXXXX", KRB5_DEFAULT_CCNAME_FILE)
390 : * instead so that new unique FILE ccaches can be found in the user's
391 : * default collection.
392 : * */
393 172 : ret = asprintf(&file, "%sXXXXXX", KRB5_DEFAULT_CCFILE_ROOT);
394 172 : if(ret < 0 || file == NULL) {
395 0 : free(f);
396 0 : krb5_set_error_message(context, KRB5_CC_NOMEM,
397 0 : N_("malloc: out of memory", ""));
398 0 : return KRB5_CC_NOMEM;
399 : }
400 172 : ret = _krb5_expand_path_tokens(context, file, 1, &exp_file);
401 172 : free(file);
402 172 : if (ret) {
403 0 : free(f);
404 0 : return ret;
405 : }
406 :
407 172 : file = exp_file;
408 :
409 172 : fd = mkostemp(exp_file, O_CLOEXEC);
410 172 : if(fd < 0) {
411 0 : ret = (krb5_error_code)errno;
412 0 : krb5_set_error_message(context, ret, N_("mkstemp %s failed", ""), exp_file);
413 0 : free(f);
414 0 : free(exp_file);
415 0 : return ret;
416 : }
417 172 : close(fd);
418 172 : f->filename = exp_file;
419 172 : f->res = strdup(exp_file); /* XXX See above commentary about collection */
420 172 : f->sub = NULL;
421 172 : f->version = 0;
422 172 : (*id)->data.data = f;
423 172 : (*id)->data.length = sizeof(*f);
424 172 : return 0;
425 : }
426 :
427 : static void
428 8423 : storage_set_flags(krb5_context context, krb5_storage *sp, int vno)
429 : {
430 8423 : int flags = 0;
431 8423 : switch(vno) {
432 0 : case KRB5_FCC_FVNO_1:
433 0 : flags |= KRB5_STORAGE_PRINCIPAL_WRONG_NUM_COMPONENTS;
434 0 : flags |= KRB5_STORAGE_PRINCIPAL_NO_NAME_TYPE;
435 0 : flags |= KRB5_STORAGE_HOST_BYTEORDER;
436 0 : break;
437 0 : case KRB5_FCC_FVNO_2:
438 0 : flags |= KRB5_STORAGE_HOST_BYTEORDER;
439 0 : break;
440 0 : case KRB5_FCC_FVNO_3:
441 0 : flags |= KRB5_STORAGE_KEYBLOCK_KEYTYPE_TWICE;
442 0 : break;
443 8423 : case KRB5_FCC_FVNO_4:
444 8423 : break;
445 0 : default:
446 0 : krb5_abortx(context,
447 : "storage_set_flags called with bad vno (%x)", vno);
448 : }
449 8423 : krb5_storage_set_flags(sp, flags);
450 8423 : }
451 :
452 : static krb5_error_code KRB5_CALLCONV
453 60180 : fcc_open(krb5_context context,
454 : krb5_ccache id,
455 : const char *operation,
456 : int *fd_ret,
457 : int flags,
458 : mode_t mode)
459 : {
460 118818 : krb5_boolean exclusive = ((flags | O_WRONLY) == flags ||
461 59075 : (flags | O_RDWR) == flags);
462 437 : krb5_error_code ret;
463 437 : const char *filename;
464 437 : struct stat sb1, sb2;
465 : #ifndef _WIN32
466 437 : struct stat sb3;
467 60180 : size_t tries = 3;
468 : #endif
469 437 : int strict_checking;
470 437 : int fd;
471 :
472 60180 : flags |= O_BINARY | O_CLOEXEC | O_NOFOLLOW;
473 :
474 60180 : *fd_ret = -1;
475 :
476 60180 : if (FCACHE(id) == NULL)
477 0 : return krb5_einval(context, 2);
478 :
479 60180 : if ((flags & O_EXCL)) {
480 : /*
481 : * FIXME Instead of mkostemp()... we could instead try to use a .new
482 : * file... with care. Or the O_TMPFILE / linkat() extensions. We need
483 : * a roken / heimbase abstraction for that.
484 : */
485 464 : if (TMPFILENAME(id))
486 137 : (void) unlink(TMPFILENAME(id));
487 464 : free(TMPFILENAME(id));
488 464 : TMPFILENAME(id) = NULL;
489 464 : if (asprintf(&TMPFILENAME(id), "%s-XXXXXX", FILENAME(id)) < 0 ||
490 464 : TMPFILENAME(id) == NULL)
491 0 : return krb5_enomem(context);
492 464 : if ((fd = mkostemp(TMPFILENAME(id), O_CLOEXEC)) == -1) {
493 0 : krb5_set_error_message(context, ret = errno,
494 0 : N_("Could not make temp ccache FILE:%s", ""),
495 0 : TMPFILENAME(id));
496 0 : free(TMPFILENAME(id));
497 0 : TMPFILENAME(id) = NULL;
498 0 : return ret;
499 : }
500 464 : goto out;
501 : }
502 :
503 59716 : filename = TMPFILENAME(id) ? TMPFILENAME(id) : FILENAME(id);
504 119432 : strict_checking = (flags & O_CREAT) == 0 &&
505 59716 : (context->flags & KRB5_CTX_F_FCACHE_STRICT_CHECKING) != 0;
506 :
507 : #ifndef WIN32
508 59716 : again:
509 : #endif
510 59716 : memset(&sb1, 0, sizeof(sb1));
511 59716 : ret = lstat(filename, &sb1);
512 59716 : if (ret == 0) {
513 7957 : if (!S_ISREG(sb1.st_mode)) {
514 0 : krb5_set_error_message(context, EPERM,
515 0 : N_("Refuses to open symlinks for caches FILE:%s", ""), filename);
516 0 : return EPERM;
517 : }
518 51759 : } else if (errno != ENOENT || !(flags & O_CREAT)) {
519 51759 : krb5_set_error_message(context, errno, N_("%s lstat(%s)", "file, error"),
520 : operation, filename);
521 51759 : return errno;
522 : }
523 :
524 7957 : fd = open(filename, flags, mode);
525 7957 : if(fd < 0) {
526 0 : char buf[128];
527 0 : ret = errno;
528 0 : rk_strerror_r(ret, buf, sizeof(buf));
529 0 : krb5_set_error_message(context, ret, N_("%s open(%s): %s", "file, error"),
530 : operation, filename, buf);
531 0 : return ret;
532 : }
533 7957 : rk_cloexec(fd);
534 :
535 7957 : ret = fstat(fd, &sb2);
536 7957 : if (ret < 0) {
537 0 : krb5_clear_error_message(context);
538 0 : close(fd);
539 0 : return errno;
540 : }
541 :
542 7957 : if (!S_ISREG(sb2.st_mode)) {
543 0 : krb5_set_error_message(context, EPERM, N_("Refuses to open non files caches: FILE:%s", ""), filename);
544 0 : close(fd);
545 0 : return EPERM;
546 : }
547 :
548 : #ifndef _WIN32
549 7957 : if (sb1.st_dev && sb1.st_ino &&
550 7957 : (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino)) {
551 : /*
552 : * Perhaps we raced with a rename(). To complain about
553 : * symlinks in that case would cause unnecessary concern, so
554 : * we check for that possibility and loop. This has no
555 : * TOCTOU problems because we redo the open(). We could also
556 : * not do any of this checking if O_NOFOLLOW != 0...
557 : */
558 0 : close(fd);
559 0 : ret = lstat(filename, &sb3);
560 0 : if (ret || sb1.st_dev != sb2.st_dev ||
561 0 : sb3.st_dev != sb2.st_dev || sb3.st_ino != sb2.st_ino) {
562 0 : krb5_set_error_message(context, EPERM, N_("Refuses to open possible symlink for caches: FILE:%s", ""), filename);
563 0 : return EPERM;
564 : }
565 0 : if (--tries == 0) {
566 0 : krb5_set_error_message(context, EPERM, N_("Raced too many times with renames of FILE:%s", ""), filename);
567 0 : return EPERM;
568 : }
569 0 : goto again;
570 : }
571 : #endif
572 :
573 : /*
574 : * /tmp (or wherever default ccaches go) might not be on its own
575 : * filesystem, or on a filesystem different /etc, say, and even if
576 : * it were, suppose a user hard-links another's ccache to her
577 : * default ccache, then runs a set-uid program that will user her
578 : * default ccache (even if it ignores KRB5CCNAME)...
579 : *
580 : * Default ccache locations should really be on per-user non-tmp
581 : * locations on tmpfs "run" directories. But we don't know here
582 : * that this is the case. Thus: no hard-links, no symlinks.
583 : */
584 7957 : if (sb2.st_nlink > 1) {
585 0 : krb5_set_error_message(context, EPERM, N_("Refuses to open hardlinks for caches FILE:%s", ""), filename);
586 0 : close(fd);
587 0 : return EPERM;
588 : }
589 :
590 7957 : if (strict_checking) {
591 : #ifndef _WIN32
592 : /*
593 : * XXX WIN32: Needs to have ACL checking code!
594 : * st_mode comes out as 100666, and st_uid is no use.
595 : */
596 : /*
597 : * XXX Should probably add options to improve control over this
598 : * check. We might want strict checking of everything except
599 : * this.
600 : */
601 0 : if (sb2.st_uid != geteuid()) {
602 0 : krb5_set_error_message(context, EPERM, N_("Refuses to open cache files not own by myself FILE:%s (owned by %d)", ""), filename, (int)sb2.st_uid);
603 0 : close(fd);
604 0 : return EPERM;
605 : }
606 0 : if ((sb2.st_mode & 077) != 0) {
607 0 : krb5_set_error_message(context, EPERM,
608 0 : N_("Refuses to open group/other readable files FILE:%s", ""), filename);
609 0 : close(fd);
610 0 : return EPERM;
611 : }
612 : #endif
613 : }
614 :
615 7957 : out:
616 8421 : if((ret = fcc_lock(context, id, fd, exclusive)) != 0) {
617 0 : close(fd);
618 0 : return ret;
619 : }
620 8421 : *fd_ret = fd;
621 8421 : return 0;
622 : }
623 :
624 : static krb5_error_code KRB5_CALLCONV
625 464 : fcc_initialize(krb5_context context,
626 : krb5_ccache id,
627 : krb5_principal primary_principal)
628 : {
629 464 : krb5_fcache *f = FCACHE(id);
630 464 : int ret = 0;
631 0 : int fd;
632 :
633 464 : if (f == NULL)
634 0 : return krb5_einval(context, 2);
635 :
636 : /*
637 : * fcc_open() will notice the O_EXCL and will make a temporary file that
638 : * will later be renamed into place.
639 : */
640 464 : ret = fcc_open(context, id, "initialize", &fd, O_RDWR | O_CREAT | O_EXCL, 0600);
641 464 : if(ret)
642 0 : return ret;
643 : {
644 0 : krb5_storage *sp;
645 464 : sp = krb5_storage_emem();
646 464 : if (sp == NULL)
647 0 : return krb5_enomem(context);
648 464 : krb5_storage_set_eof_code(sp, KRB5_CC_END);
649 464 : if(context->fcache_vno != 0)
650 0 : f->version = context->fcache_vno;
651 : else
652 464 : f->version = KRB5_FCC_FVNO_4;
653 464 : if (ret == 0)
654 464 : ret = krb5_store_int8(sp, 5);
655 464 : if (ret == 0)
656 464 : ret = krb5_store_int8(sp, f->version);
657 464 : storage_set_flags(context, sp, f->version);
658 464 : if(f->version == KRB5_FCC_FVNO_4 && ret == 0) {
659 : /* V4 stuff */
660 464 : if (context->kdc_sec_offset) {
661 0 : if (ret == 0)
662 0 : ret = krb5_store_int16 (sp, 12); /* length */
663 0 : if (ret == 0)
664 0 : ret = krb5_store_int16 (sp, FCC_TAG_DELTATIME); /* Tag */
665 0 : if (ret == 0)
666 0 : ret = krb5_store_int16 (sp, 8); /* length of data */
667 0 : if (ret == 0)
668 0 : ret = krb5_store_int32 (sp, context->kdc_sec_offset);
669 0 : if (ret == 0)
670 0 : ret = krb5_store_int32 (sp, context->kdc_usec_offset);
671 : } else {
672 464 : if (ret == 0)
673 464 : ret = krb5_store_int16 (sp, 0);
674 : }
675 : }
676 464 : if (ret == 0)
677 464 : ret = krb5_store_principal(sp, primary_principal);
678 :
679 464 : if (ret == 0)
680 464 : ret = write_storage(context, sp, fd);
681 :
682 464 : krb5_storage_free(sp);
683 : }
684 464 : if (close(fd) < 0)
685 0 : if (ret == 0) {
686 0 : char buf[128];
687 0 : ret = errno;
688 0 : rk_strerror_r(ret, buf, sizeof(buf));
689 0 : krb5_set_error_message(context, ret, N_("close %s: %s", ""),
690 0 : FILENAME(id), buf);
691 : }
692 464 : return ret;
693 : }
694 :
695 : static krb5_error_code KRB5_CALLCONV
696 54618 : fcc_close(krb5_context context,
697 : krb5_ccache id)
698 : {
699 54618 : if (FCACHE(id) == NULL)
700 0 : return krb5_einval(context, 2);
701 :
702 54618 : if (TMPFILENAME(id))
703 0 : (void) unlink(TMPFILENAME(id));
704 54618 : free(TMPFILENAME(id));
705 54618 : free(RESFILENAME(id));
706 54618 : free(SUBFILENAME(id));
707 54618 : free(FILENAME(id));
708 54618 : krb5_data_free(&id->data);
709 54618 : return 0;
710 : }
711 :
712 : static krb5_error_code KRB5_CALLCONV
713 172 : fcc_destroy(krb5_context context,
714 : krb5_ccache id)
715 : {
716 172 : if (FCACHE(id) == NULL)
717 0 : return krb5_einval(context, 2);
718 :
719 172 : if (TMPFILENAME(id))
720 0 : (void) _krb5_erase_file(context, TMPFILENAME(id));
721 172 : return _krb5_erase_file(context, FILENAME(id));
722 : }
723 :
724 : static krb5_error_code KRB5_CALLCONV
725 1105 : fcc_store_cred(krb5_context context,
726 : krb5_ccache id,
727 : krb5_creds *creds)
728 : {
729 0 : int ret;
730 0 : int fd;
731 :
732 1105 : ret = fcc_open(context, id, "store", &fd, O_WRONLY | O_APPEND, 0);
733 1105 : if(ret)
734 0 : return ret;
735 : {
736 0 : krb5_storage *sp;
737 :
738 1105 : sp = krb5_storage_emem();
739 1105 : if (sp == NULL)
740 0 : return krb5_enomem(context);
741 1105 : krb5_storage_set_eof_code(sp, KRB5_CC_END);
742 1105 : storage_set_flags(context, sp, FCACHE(id)->version);
743 1105 : ret = krb5_store_creds(sp, creds);
744 1105 : if (ret == 0)
745 1105 : ret = write_storage(context, sp, fd);
746 1105 : krb5_storage_free(sp);
747 : }
748 1105 : if (close(fd) < 0) {
749 0 : if (ret == 0) {
750 0 : char buf[128];
751 0 : ret = errno;
752 0 : rk_strerror_r(ret, buf, sizeof(buf));
753 0 : krb5_set_error_message(context, ret, N_("close %s: %s", ""),
754 0 : FILENAME(id), buf);
755 : }
756 : }
757 1432 : if (ret == 0 && TMPFILENAME(id) &&
758 327 : !krb5_is_config_principal(context, creds->server)) {
759 :
760 : /*
761 : * Portability note: there's no need to have WIN32 or other code here
762 : * for odd rename cases because rk_rename() is meant to handle that.
763 : */
764 327 : ret = rk_rename(TMPFILENAME(id), FILENAME(id));
765 327 : if (ret == 0) {
766 327 : free(TMPFILENAME(id));
767 327 : TMPFILENAME(id) = NULL;
768 : } else {
769 0 : ret = errno;
770 : }
771 : }
772 1105 : return ret;
773 : }
774 :
775 : static krb5_error_code
776 58609 : init_fcc(krb5_context context,
777 : krb5_ccache id,
778 : const char *operation,
779 : krb5_storage **ret_sp,
780 : int *ret_fd,
781 : krb5_deltat *kdc_offset)
782 : {
783 437 : int fd;
784 437 : int8_t pvno, tag;
785 437 : krb5_storage *sp;
786 437 : krb5_error_code ret;
787 :
788 58609 : *ret_fd = -1;
789 58609 : *ret_sp = NULL;
790 58609 : if (kdc_offset)
791 197 : *kdc_offset = 0;
792 :
793 58609 : ret = fcc_open(context, id, operation, &fd, O_RDONLY, 0);
794 58609 : if(ret)
795 51322 : return ret;
796 :
797 6850 : sp = krb5_storage_stdio_from_fd(fd, "r");
798 6850 : if(sp == NULL) {
799 0 : krb5_clear_error_message(context);
800 0 : ret = ENOMEM;
801 0 : goto out;
802 : }
803 6850 : krb5_storage_set_eof_code(sp, KRB5_CC_END);
804 6850 : ret = krb5_ret_int8(sp, &pvno);
805 6850 : if (ret != 0) {
806 0 : if(ret == KRB5_CC_END) {
807 0 : ret = ENOENT;
808 0 : krb5_set_error_message(context, ret,
809 0 : N_("Empty credential cache file: %s", ""),
810 0 : FILENAME(id));
811 : } else
812 0 : krb5_set_error_message(context, ret, N_("Error reading pvno "
813 : "in cache file: %s", ""),
814 0 : FILENAME(id));
815 0 : goto out;
816 : }
817 6850 : if (pvno != 5) {
818 0 : ret = KRB5_CCACHE_BADVNO;
819 0 : krb5_set_error_message(context, ret, N_("Bad version number in credential "
820 : "cache file: %s", ""),
821 0 : FILENAME(id));
822 0 : goto out;
823 : }
824 6850 : ret = krb5_ret_int8(sp, &tag); /* should not be host byte order */
825 6850 : if (ret != 0) {
826 0 : ret = KRB5_CC_FORMAT;
827 0 : krb5_set_error_message(context, ret, "Error reading tag in "
828 0 : "cache file: %s", FILENAME(id));
829 0 : goto out;
830 : }
831 6850 : FCACHE(id)->version = tag;
832 6850 : storage_set_flags(context, sp, FCACHE(id)->version);
833 6850 : switch (tag) {
834 6850 : case KRB5_FCC_FVNO_4: {
835 0 : int16_t length;
836 :
837 6850 : ret = krb5_ret_int16 (sp, &length);
838 6850 : if(ret) {
839 0 : ret = KRB5_CC_FORMAT;
840 0 : krb5_set_error_message(context, ret,
841 0 : N_("Error reading tag length in "
842 0 : "cache file: %s", ""), FILENAME(id));
843 0 : goto out;
844 : }
845 7980 : while(length > 0) {
846 0 : int16_t dtag, data_len;
847 0 : int i;
848 0 : int8_t dummy;
849 :
850 1130 : ret = krb5_ret_int16 (sp, &dtag);
851 1130 : if(ret) {
852 0 : ret = KRB5_CC_FORMAT;
853 0 : krb5_set_error_message(context, ret, N_("Error reading dtag in "
854 : "cache file: %s", ""),
855 0 : FILENAME(id));
856 0 : goto out;
857 : }
858 1130 : ret = krb5_ret_int16 (sp, &data_len);
859 1130 : if(ret) {
860 0 : ret = KRB5_CC_FORMAT;
861 0 : krb5_set_error_message(context, ret,
862 0 : N_("Error reading dlength "
863 : "in cache file: %s",""),
864 0 : FILENAME(id));
865 0 : goto out;
866 : }
867 1130 : switch (dtag) {
868 1130 : case FCC_TAG_DELTATIME : {
869 0 : int32_t offset;
870 :
871 1130 : ret = krb5_ret_int32 (sp, &offset);
872 1130 : ret |= krb5_ret_int32 (sp, &context->kdc_usec_offset);
873 1130 : if(ret) {
874 0 : ret = KRB5_CC_FORMAT;
875 0 : krb5_set_error_message(context, ret,
876 0 : N_("Error reading kdc_sec in "
877 : "cache file: %s", ""),
878 0 : FILENAME(id));
879 0 : goto out;
880 : }
881 1130 : context->kdc_sec_offset = offset;
882 1130 : if (kdc_offset)
883 18 : *kdc_offset = offset;
884 1130 : break;
885 : }
886 0 : default :
887 0 : for (i = 0; i < data_len; ++i) {
888 0 : ret = krb5_ret_int8 (sp, &dummy);
889 0 : if(ret) {
890 0 : ret = KRB5_CC_FORMAT;
891 0 : krb5_set_error_message(context, ret,
892 0 : N_("Error reading unknown "
893 : "tag in cache file: %s", ""),
894 0 : FILENAME(id));
895 0 : goto out;
896 : }
897 : }
898 0 : break;
899 : }
900 1130 : length -= 4 + data_len;
901 : }
902 6850 : break;
903 : }
904 0 : case KRB5_FCC_FVNO_3:
905 : case KRB5_FCC_FVNO_2:
906 : case KRB5_FCC_FVNO_1:
907 0 : break;
908 0 : default :
909 0 : ret = KRB5_CCACHE_BADVNO;
910 0 : krb5_set_error_message(context, ret,
911 0 : N_("Unknown version number (%d) in "
912 : "credential cache file: %s", ""),
913 0 : (int)tag, FILENAME(id));
914 0 : goto out;
915 : }
916 6850 : *ret_sp = sp;
917 6850 : *ret_fd = fd;
918 :
919 6850 : return 0;
920 0 : out:
921 0 : if(sp != NULL)
922 0 : krb5_storage_free(sp);
923 0 : close(fd);
924 0 : return ret;
925 : }
926 :
927 : static krb5_error_code KRB5_CALLCONV
928 55435 : fcc_get_principal(krb5_context context,
929 : krb5_ccache id,
930 : krb5_principal *principal)
931 : {
932 437 : krb5_error_code ret;
933 437 : int fd;
934 437 : krb5_storage *sp;
935 :
936 55435 : ret = init_fcc (context, id, "get-principal", &sp, &fd, NULL);
937 55435 : if (ret)
938 51322 : return ret;
939 3676 : ret = krb5_ret_principal(sp, principal);
940 3676 : if (ret)
941 0 : krb5_clear_error_message(context);
942 3676 : krb5_storage_free(sp);
943 3676 : close(fd);
944 3676 : return ret;
945 : }
946 :
947 : static krb5_error_code KRB5_CALLCONV
948 : fcc_end_get(krb5_context context,
949 : krb5_ccache id,
950 : krb5_cc_cursor *cursor);
951 :
952 : static krb5_error_code KRB5_CALLCONV
953 2977 : fcc_get_first(krb5_context context,
954 : krb5_ccache id,
955 : krb5_cc_cursor *cursor)
956 : {
957 0 : krb5_error_code ret;
958 0 : krb5_principal principal;
959 :
960 2977 : if (FCACHE(id) == NULL)
961 0 : return krb5_einval(context, 2);
962 :
963 2977 : *cursor = calloc(1, sizeof(struct fcc_cursor));
964 2977 : if (*cursor == NULL) {
965 0 : krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
966 0 : return ENOMEM;
967 : }
968 :
969 2977 : ret = init_fcc(context, id, "get-first", &FCC_CURSOR(*cursor)->sp,
970 2977 : &FCC_CURSOR(*cursor)->fd, NULL);
971 2977 : if (ret) {
972 0 : free(*cursor);
973 0 : *cursor = NULL;
974 0 : return ret;
975 : }
976 2977 : ret = krb5_ret_principal (FCC_CURSOR(*cursor)->sp, &principal);
977 2977 : if(ret) {
978 0 : krb5_clear_error_message(context);
979 0 : fcc_end_get(context, id, cursor);
980 0 : return ret;
981 : }
982 2977 : krb5_free_principal (context, principal);
983 2977 : return 0;
984 : }
985 :
986 : static krb5_error_code KRB5_CALLCONV
987 8622 : fcc_get_next (krb5_context context,
988 : krb5_ccache id,
989 : krb5_cc_cursor *cursor,
990 : krb5_creds *creds)
991 : {
992 0 : krb5_error_code ret;
993 :
994 8622 : if (FCACHE(id) == NULL)
995 0 : return krb5_einval(context, 2);
996 :
997 8622 : if (FCC_CURSOR(*cursor) == NULL)
998 0 : return krb5_einval(context, 3);
999 :
1000 17244 : FCC_CURSOR(*cursor)->cred_start =
1001 8622 : krb5_storage_seek(FCC_CURSOR(*cursor)->sp, 0, SEEK_CUR);
1002 :
1003 8622 : ret = krb5_ret_creds(FCC_CURSOR(*cursor)->sp, creds);
1004 8622 : if (ret)
1005 1915 : krb5_clear_error_message(context);
1006 :
1007 17244 : FCC_CURSOR(*cursor)->cred_end =
1008 8622 : krb5_storage_seek(FCC_CURSOR(*cursor)->sp, 0, SEEK_CUR);
1009 :
1010 8622 : return ret;
1011 : }
1012 :
1013 : static krb5_error_code KRB5_CALLCONV
1014 2977 : fcc_end_get (krb5_context context,
1015 : krb5_ccache id,
1016 : krb5_cc_cursor *cursor)
1017 : {
1018 :
1019 2977 : if (FCACHE(id) == NULL)
1020 0 : return krb5_einval(context, 2);
1021 :
1022 2977 : if (FCC_CURSOR(*cursor) == NULL)
1023 0 : return krb5_einval(context, 3);
1024 :
1025 2977 : krb5_storage_free(FCC_CURSOR(*cursor)->sp);
1026 2977 : close (FCC_CURSOR(*cursor)->fd);
1027 2977 : free(*cursor);
1028 2977 : *cursor = NULL;
1029 2977 : return 0;
1030 : }
1031 :
1032 : static void KRB5_CALLCONV
1033 2 : cred_delete(krb5_context context,
1034 : krb5_ccache id,
1035 : krb5_cc_cursor *cursor,
1036 : krb5_creds *cred)
1037 : {
1038 0 : krb5_error_code ret;
1039 0 : krb5_storage *sp;
1040 0 : krb5_data orig_cred_data;
1041 2 : unsigned char *cred_data_in_file = NULL;
1042 0 : off_t new_cred_sz;
1043 0 : struct stat sb1, sb2;
1044 2 : int fd = -1;
1045 0 : ssize_t bytes;
1046 2 : krb5_const_realm srealm = krb5_principal_get_realm(context, cred->server);
1047 :
1048 : /* This is best-effort code; if we lose track of errors here it's OK */
1049 :
1050 2 : heim_assert(FCC_CURSOR(*cursor)->cred_start < FCC_CURSOR(*cursor)->cred_end,
1051 : "fcache internal error");
1052 :
1053 2 : krb5_data_zero(&orig_cred_data);
1054 :
1055 2 : sp = krb5_storage_emem();
1056 2 : if (sp == NULL)
1057 0 : return;
1058 2 : krb5_storage_set_eof_code(sp, KRB5_CC_END);
1059 2 : storage_set_flags(context, sp, FCACHE(id)->version);
1060 :
1061 : /* Get a copy of what the cred should look like in the file; see below */
1062 2 : ret = krb5_store_creds(sp, cred);
1063 2 : if (ret)
1064 0 : goto out;
1065 :
1066 2 : ret = krb5_storage_to_data(sp, &orig_cred_data);
1067 2 : if (ret)
1068 0 : goto out;
1069 2 : krb5_storage_free(sp);
1070 :
1071 2 : cred_data_in_file = malloc(orig_cred_data.length);
1072 2 : if (cred_data_in_file == NULL)
1073 0 : goto out;
1074 :
1075 : /*
1076 : * Mark the cred expired; krb5_cc_retrieve_cred() callers should use
1077 : * KRB5_TC_MATCH_TIMES, so this should be good enough...
1078 : */
1079 2 : cred->times.endtime = 0;
1080 :
1081 : /* ...except for config creds because we don't check their endtimes */
1082 2 : if (srealm && strcmp(srealm, "X-CACHECONF:") == 0) {
1083 0 : ret = krb5_principal_set_realm(context, cred->server, "X-RMED-CONF:");
1084 0 : if (ret)
1085 0 : goto out;
1086 : }
1087 :
1088 2 : sp = krb5_storage_emem();
1089 2 : if (sp == NULL)
1090 0 : goto out;
1091 2 : krb5_storage_set_eof_code(sp, KRB5_CC_END);
1092 2 : storage_set_flags(context, sp, FCACHE(id)->version);
1093 :
1094 2 : ret = krb5_store_creds(sp, cred);
1095 :
1096 : /* The new cred must be the same size as the old cred */
1097 2 : new_cred_sz = krb5_storage_seek(sp, 0, SEEK_END);
1098 2 : if (new_cred_sz != orig_cred_data.length || new_cred_sz !=
1099 2 : (FCC_CURSOR(*cursor)->cred_end - FCC_CURSOR(*cursor)->cred_start)) {
1100 : /* XXX This really can't happen. Assert like above? */
1101 0 : krb5_set_error_message(context, EINVAL,
1102 0 : N_("Credential deletion failed on ccache "
1103 : "FILE:%s: new credential size did not "
1104 : "match old credential size", ""),
1105 0 : FILENAME(id));
1106 0 : goto out;
1107 : }
1108 :
1109 2 : ret = fcc_open(context, id, "remove_cred", &fd, O_RDWR, 0);
1110 2 : if (ret)
1111 0 : goto out;
1112 :
1113 : /*
1114 : * Check that we're updating the same file where we got the
1115 : * cred's offset, else we'd be corrupting a new ccache.
1116 : */
1117 4 : if (fstat(FCC_CURSOR(*cursor)->fd, &sb1) == -1 ||
1118 2 : fstat(fd, &sb2) == -1)
1119 0 : goto out;
1120 2 : if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino)
1121 0 : goto out;
1122 :
1123 : /*
1124 : * Make sure what we overwrite is what we expected.
1125 : *
1126 : * FIXME: We *really* need the ccache v4 tag for ccache ID. This
1127 : * check that we're only overwriting something that looks exactly
1128 : * like what we want to is probably good enough in practice, but
1129 : * it's not guaranteed to work.
1130 : */
1131 2 : if (lseek(fd, FCC_CURSOR(*cursor)->cred_start, SEEK_SET) == (off_t)-1)
1132 0 : goto out;
1133 2 : bytes = read(fd, cred_data_in_file, orig_cred_data.length);
1134 2 : if (bytes != orig_cred_data.length)
1135 0 : goto out;
1136 2 : if (memcmp(orig_cred_data.data, cred_data_in_file, bytes) != 0)
1137 0 : goto out;
1138 2 : if (lseek(fd, FCC_CURSOR(*cursor)->cred_start, SEEK_SET) == (off_t)-1)
1139 0 : goto out;
1140 2 : ret = write_storage(context, sp, fd);
1141 2 : out:
1142 2 : if (fd > -1) {
1143 2 : if (close(fd) < 0 && ret == 0) {
1144 0 : krb5_set_error_message(context, errno, N_("close %s", ""),
1145 0 : FILENAME(id));
1146 : }
1147 : }
1148 2 : krb5_data_free(&orig_cred_data);
1149 2 : free(cred_data_in_file);
1150 2 : krb5_storage_free(sp);
1151 2 : return;
1152 : }
1153 :
1154 : static krb5_error_code KRB5_CALLCONV
1155 456 : fcc_remove_cred(krb5_context context,
1156 : krb5_ccache id,
1157 : krb5_flags which,
1158 : krb5_creds *mcred)
1159 : {
1160 0 : krb5_error_code ret, ret2;
1161 0 : krb5_cc_cursor cursor;
1162 0 : krb5_creds found_cred;
1163 :
1164 456 : if (FCACHE(id) == NULL)
1165 0 : return krb5_einval(context, 2);
1166 :
1167 456 : ret = krb5_cc_start_seq_get(context, id, &cursor);
1168 456 : if (ret)
1169 0 : return ret;
1170 1072 : while ((ret = krb5_cc_next_cred(context, id, &cursor, &found_cred)) == 0) {
1171 616 : if (!krb5_compare_creds(context, which, mcred, &found_cred)) {
1172 614 : krb5_free_cred_contents(context, &found_cred);
1173 614 : continue;
1174 : }
1175 2 : cred_delete(context, id, &cursor, &found_cred);
1176 2 : krb5_free_cred_contents(context, &found_cred);
1177 : }
1178 456 : ret2 = krb5_cc_end_seq_get(context, id, &cursor);
1179 456 : if (ret2) /* not expected to fail */
1180 0 : return ret2;
1181 456 : if (ret == KRB5_CC_END)
1182 456 : return 0;
1183 0 : return ret;
1184 : }
1185 :
1186 : static krb5_error_code KRB5_CALLCONV
1187 0 : fcc_set_flags(krb5_context context,
1188 : krb5_ccache id,
1189 : krb5_flags flags)
1190 : {
1191 0 : if (FCACHE(id) == NULL)
1192 0 : return krb5_einval(context, 2);
1193 :
1194 0 : return 0; /* XXX */
1195 : }
1196 :
1197 : static int KRB5_CALLCONV
1198 0 : fcc_get_version(krb5_context context,
1199 : krb5_ccache id)
1200 : {
1201 0 : if (FCACHE(id) == NULL)
1202 0 : return -1;
1203 :
1204 0 : return FCACHE(id)->version;
1205 : }
1206 :
1207 : static const char *
1208 0 : my_basename(const char *fn)
1209 : {
1210 0 : const char *base, *p;
1211 :
1212 0 : if (strncmp(fn, "FILE:", sizeof("FILE:") - 1) == 0)
1213 0 : fn += sizeof("FILE:") - 1;
1214 0 : for (p = base = fn; *p; p++) {
1215 : #ifdef WIN32
1216 : if (*p == '/' || *p == '\\')
1217 : base = p + 1;
1218 : #else
1219 0 : if (*p == '/')
1220 0 : base = p + 1;
1221 : #endif
1222 : }
1223 0 : return base;
1224 : }
1225 :
1226 : /* We could use an rk_dirname()... */
1227 : static char *
1228 0 : my_dirname(const char *fn)
1229 : {
1230 0 : size_t len, i;
1231 0 : char *dname;
1232 :
1233 0 : if (strncmp(fn, "FILE:", sizeof("FILE:") - 1) == 0)
1234 0 : fn += sizeof("FILE:") - 1;
1235 :
1236 0 : if ((dname = strdup(fn)) == NULL)
1237 0 : return NULL;
1238 0 : len = strlen(dname);
1239 0 : for (i = 0; i < len; i++) {
1240 : #ifdef WIN32
1241 : if (dname[len - i] == '\\' ||
1242 : dname[len - i] == '/') {
1243 : dname[len - i] = '\0';
1244 : break;
1245 : }
1246 : #else
1247 0 : if (dname[len - i] == '/') {
1248 0 : dname[len - i] = '\0';
1249 0 : break;
1250 : }
1251 : #endif
1252 : }
1253 0 : if (i < len)
1254 0 : return dname;
1255 0 : free(dname);
1256 0 : return strdup(".");
1257 : }
1258 :
1259 : /*
1260 : * This checks that a directory entry matches a required basename and has a
1261 : * non-empty subsidiary component.
1262 : */
1263 : static int
1264 0 : matchbase(const char *fn, const char *base, size_t baselen)
1265 : {
1266 0 : return strncmp(fn, base, baselen) == 0 &&
1267 0 : (fn[baselen] == FILESUBSEPCHR && fn[baselen + 1] != '\0');
1268 : }
1269 :
1270 : /*
1271 : * Check if `def_locs' contains `name' (which must be the default ccache name),
1272 : * in which case the caller may look for subsidiaries of all of `def_locs'.
1273 : *
1274 : * This is needed because the collection iterators don't take a base location
1275 : * as an argument, so we can only search default locations, but only if the
1276 : * current default ccache name is indeed a default (as opposed to from
1277 : * KRB5CCNAME being set in the environment pointing to a non-default name).
1278 : */
1279 : static krb5_error_code
1280 52 : is_default_collection(krb5_context context, const char *name,
1281 : const char * const *def_locs, int *res)
1282 : {
1283 0 : krb5_error_code ret;
1284 52 : const char *def_loc[2] = { KRB5_DEFAULT_CCNAME_FILE, NULL };
1285 0 : const char *sep;
1286 0 : size_t namelen;
1287 0 : size_t i;
1288 :
1289 52 : *res = 0;
1290 52 : if (name == NULL) {
1291 52 : *res = 1;
1292 52 : return 0;
1293 : }
1294 0 : if ((sep = strchr(name, FILESUBSEPCHR)))
1295 0 : namelen = (size_t)(sep - name);
1296 : else
1297 0 : namelen = strlen(name);
1298 0 : if (def_locs == NULL)
1299 0 : def_locs = def_loc;
1300 0 : for (i = 0; !(*res) && def_locs[i]; i++) {
1301 0 : char *e = NULL;
1302 :
1303 0 : if ((ret = _krb5_expand_default_cc_name(context, def_locs[i], &e)))
1304 0 : return ret;
1305 0 : *res = strncmp(e, name, namelen) == 0 &&
1306 0 : (sep == NULL || e[namelen] == FILESUBSEPCHR || e[namelen] == '\0');
1307 0 : free(e);
1308 : }
1309 0 : return 0;
1310 : }
1311 :
1312 : /*
1313 : * Collection iterator cursor.
1314 : *
1315 : * There may be an array of locations, and for each location we'll try
1316 : * resolving it, as well as doing a readdir() of the dirname of it and output
1317 : * all ccache names in that directory that begin with the current location and
1318 : * end in "+${subsidiary}".
1319 : */
1320 : struct fcache_iter {
1321 : const char *curr_location;
1322 : char *def_ccname; /* The default ccname */
1323 : char **locations; /* All the other places we'll look for a ccache */
1324 : char *dname; /* dirname() of curr_location */
1325 : DIR *d;
1326 : struct dirent *dentry;
1327 : int location; /* Index of `locations' */
1328 : unsigned int first:1;
1329 : unsigned int dead:1;
1330 : };
1331 :
1332 : /* Initiate FILE collection iteration */
1333 : static krb5_error_code KRB5_CALLCONV
1334 52 : fcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
1335 : {
1336 52 : struct fcache_iter *iter = NULL;
1337 0 : krb5_error_code ret;
1338 52 : const char *def_ccname = NULL;
1339 52 : char **def_locs = NULL;
1340 52 : int is_def_coll = 0;
1341 :
1342 52 : if (krb5_config_get_bool_default(context, NULL, FALSE, "libdefaults",
1343 : "enable_file_cache_iteration", NULL)) {
1344 0 : def_ccname = krb5_cc_default_name(context);
1345 0 : def_locs = krb5_config_get_strings(context, NULL, "libdefaults",
1346 : "default_file_cache_collections",
1347 : NULL);
1348 : }
1349 :
1350 : /*
1351 : * Note: do not allow krb5_cc_default_name() to recurse via
1352 : * krb5_cc_cache_match().
1353 : * Note that context->default_cc_name will be NULL even though
1354 : * KRB5CCNAME is set in the environment if neither krb5_cc_default_name()
1355 : * nor krb5_cc_set_default_name() have been called.
1356 : */
1357 :
1358 : /*
1359 : * Figure out if the current default ccache name is a really a default one
1360 : * so we know whether to search any other default FILE collection
1361 : * locations.
1362 : */
1363 52 : if ((ret = is_default_collection(context, def_ccname,
1364 : (const char **)def_locs,
1365 : &is_def_coll)))
1366 0 : goto out;
1367 :
1368 : /* Setup the cursor */
1369 52 : if ((iter = calloc(1, sizeof(*iter))) == NULL ||
1370 0 : (def_ccname && (iter->def_ccname = strdup(def_ccname)) == NULL)) {
1371 0 : ret = krb5_enomem(context);
1372 0 : goto out;
1373 : }
1374 :
1375 52 : if (is_def_coll) {
1376 : /* Since def_ccname is in the `def_locs', we'll include those */
1377 52 : iter->locations = def_locs;
1378 52 : free(iter->def_ccname);
1379 52 : iter->def_ccname = NULL;
1380 52 : def_locs = NULL;
1381 : } else {
1382 : /* Since def_ccname is NOT in the `def_locs', we'll exclude those */
1383 0 : iter->locations = NULL;
1384 : }
1385 52 : iter->curr_location = NULL;
1386 52 : iter->location = -1; /* Pre-incremented */
1387 52 : iter->first = 1;
1388 52 : iter->dname = NULL;
1389 52 : iter->d = NULL;
1390 52 : *cursor = iter;
1391 52 : iter = NULL;
1392 52 : ret = 0;
1393 :
1394 52 : out:
1395 52 : krb5_config_free_strings(def_locs);
1396 52 : free(iter);
1397 52 : return ret;
1398 : }
1399 :
1400 : /* Pick the next location as the `iter->curr_location' */
1401 : static krb5_error_code
1402 52 : next_location(krb5_context context, struct fcache_iter *iter)
1403 : {
1404 52 : if (iter->first && iter->def_ccname) {
1405 0 : iter->curr_location = iter->def_ccname;
1406 0 : iter->first = 0;
1407 0 : return 0;
1408 : }
1409 52 : iter->first = 0;
1410 :
1411 52 : if (iter->d)
1412 0 : closedir(iter->d);
1413 52 : iter->d = NULL;
1414 52 : iter->curr_location = NULL;
1415 52 : if (iter->locations &&
1416 0 : (iter->curr_location = iter->locations[++(iter->location)]))
1417 0 : return 0;
1418 :
1419 52 : iter->dead = 1; /* Do not run off the end of iter->locations */
1420 52 : return KRB5_CC_END;
1421 : }
1422 :
1423 : /* Output the next match for `iter->curr_location' from readdir() */
1424 : static krb5_error_code
1425 0 : next_dir_match(krb5_context context, struct fcache_iter *iter, char **fn)
1426 : {
1427 0 : struct stat st;
1428 0 : const char *base = my_basename(iter->curr_location);
1429 0 : size_t baselen = strlen(base);
1430 0 : char *s;
1431 :
1432 0 : *fn = NULL;
1433 0 : if (iter->d == NULL)
1434 0 : return 0;
1435 0 : for (iter->dentry = readdir(iter->d);
1436 0 : iter->dentry;
1437 0 : iter->dentry = readdir(iter->d)) {
1438 0 : if (!matchbase(iter->dentry->d_name, base, baselen))
1439 0 : continue;
1440 0 : if (asprintf(&s, "FILE:%s/%s", iter->dname, iter->dentry->d_name) == -1 ||
1441 0 : s == NULL)
1442 0 : return krb5_enomem(context);
1443 0 : if (stat(s + sizeof("FILE:") - 1, &st) == 0 && S_ISREG(st.st_mode)) {
1444 0 : *fn = s;
1445 0 : return 0;
1446 : }
1447 0 : free(s);
1448 : }
1449 0 : iter->curr_location = NULL;
1450 0 : closedir(iter->d);
1451 0 : iter->d = NULL;
1452 0 : return 0;
1453 : }
1454 :
1455 : /* See if the given `ccname' is a FILE ccache we can resolve */
1456 : static krb5_error_code
1457 0 : try1(krb5_context context, const char *ccname, krb5_ccache *id)
1458 : {
1459 0 : krb5_error_code ret;
1460 0 : krb5_ccache cc;
1461 :
1462 0 : ret = krb5_cc_resolve(context, ccname, &cc);
1463 0 : if (ret == ENOMEM)
1464 0 : return ret;
1465 0 : if (ret == 0) {
1466 0 : if (strcmp(krb5_cc_get_type(context, cc), "FILE") == 0) {
1467 0 : *id = cc;
1468 0 : cc = NULL;
1469 : }
1470 0 : krb5_cc_close(context, cc);
1471 : }
1472 0 : return 0;
1473 : }
1474 :
1475 : /* Output the next FILE ccache in the FILE ccache collection */
1476 : static krb5_error_code KRB5_CALLCONV
1477 52 : fcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
1478 : {
1479 52 : struct fcache_iter *iter = cursor;
1480 0 : krb5_error_code ret;
1481 52 : char *name = NULL;
1482 :
1483 52 : *id = NULL;
1484 52 : if (iter == NULL)
1485 0 : return krb5_einval(context, 2);
1486 :
1487 : /* Do not run off the end of iter->locations */
1488 52 : if (iter->dead)
1489 0 : return KRB5_CC_END;
1490 :
1491 52 : if (!iter->curr_location) {
1492 : /* Next base location */
1493 52 : if ((ret = next_location(context, iter)))
1494 52 : return ret;
1495 : /* Output the current base location */
1496 0 : if ((ret = try1(context, iter->curr_location, id)) || *id)
1497 0 : return ret;
1498 : }
1499 :
1500 : /* Look for subsidiaries of iter->curr_location */
1501 0 : if (!iter->d) {
1502 0 : free(iter->dname);
1503 0 : if ((iter->dname = my_dirname(iter->curr_location)) == NULL)
1504 0 : return krb5_enomem(context);
1505 0 : if ((iter->d = opendir(iter->dname)) == NULL) {
1506 : /* Dirname ENOENT -> next location */
1507 0 : if ((ret = next_location(context, iter)))
1508 0 : return ret;
1509 : /* Tail-recurse */
1510 0 : return fcc_get_cache_next(context, cursor, id);
1511 : }
1512 : }
1513 0 : for (ret = next_dir_match(context, iter, &name);
1514 0 : ret == 0 && name != NULL;
1515 0 : ret = next_dir_match(context, iter, &name)) {
1516 0 : if ((ret = try1(context, name, id)) || *id) {
1517 0 : free(name);
1518 0 : return ret;
1519 : }
1520 0 : free(name);
1521 : }
1522 :
1523 : /* Directory listing exhausted -> go to next location, tail-recurse */
1524 0 : if ((ret = next_location(context, iter)))
1525 0 : return ret;
1526 0 : return fcc_get_cache_next(context, cursor, id);
1527 : }
1528 :
1529 : static krb5_error_code KRB5_CALLCONV
1530 52 : fcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
1531 : {
1532 52 : struct fcache_iter *iter = cursor;
1533 :
1534 52 : if (iter == NULL)
1535 0 : return krb5_einval(context, 2);
1536 :
1537 52 : krb5_config_free_strings(iter->locations);
1538 52 : if (iter->d)
1539 0 : closedir(iter->d);
1540 52 : free(iter->def_ccname);
1541 52 : free(iter->dname);
1542 52 : free(iter);
1543 52 : return 0;
1544 : }
1545 :
1546 : static krb5_error_code KRB5_CALLCONV
1547 137 : fcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
1548 : {
1549 137 : krb5_error_code ret = 0;
1550 137 : krb5_fcache *f = FCACHE(from);
1551 137 : krb5_fcache *t = FCACHE(to);
1552 :
1553 137 : if (f->tmpfn) {
1554 : /*
1555 : * If `from' has a temp file and we haven't renamed it into place yet,
1556 : * then we should rename TMPFILENAME(from) to FILENAME(to).
1557 : *
1558 : * This can only happen if we're moving a ccache where only cc config
1559 : * entries, or no entries, have been written. That's not likely.
1560 : */
1561 0 : if (rk_rename(f->tmpfn, t->filename)) {
1562 0 : ret = errno;
1563 : } else {
1564 0 : free(f->tmpfn);
1565 0 : f->tmpfn = NULL;
1566 : }
1567 137 : } else if (rk_rename(f->filename, t->filename)) {
1568 137 : ret = errno;
1569 : }
1570 : /*
1571 : * We need only close from -- we can't destroy it since the rename
1572 : * succeeded, which "destroyed" it at its old name.
1573 : */
1574 137 : if (ret == 0)
1575 0 : krb5_cc_close(context, from);
1576 137 : return ret;
1577 : }
1578 :
1579 : static krb5_error_code KRB5_CALLCONV
1580 0 : fcc_get_default_name(krb5_context context, char **str)
1581 : {
1582 0 : return _krb5_expand_default_cc_name(context,
1583 : KRB5_DEFAULT_CCNAME_FILE,
1584 : str);
1585 : }
1586 :
1587 : static krb5_error_code KRB5_CALLCONV
1588 113 : fcc_set_default_cache(krb5_context context, krb5_ccache id)
1589 : {
1590 0 : krb5_error_code ret;
1591 0 : krb5_ccache dest;
1592 113 : char *s = NULL;
1593 :
1594 113 : if (SUBFILENAME(id) == NULL)
1595 113 : return 0; /* Already a primary */
1596 0 : if (asprintf(&s, "FILE:%s", RESFILENAME(id)) == -1 || s == NULL)
1597 0 : return krb5_enomem(context);
1598 :
1599 : /*
1600 : * We can't hard-link, since we refuse to open ccaches with st_nlink > 1,
1601 : * and we can't rename() the ccache because the old name should remain
1602 : * available. Ergo, we copy the ccache.
1603 : */
1604 0 : ret = krb5_cc_resolve(context, s, &dest);
1605 0 : if (ret == 0)
1606 0 : ret = krb5_cc_copy_cache(context, id, dest);
1607 0 : free(s);
1608 0 : if (ret)
1609 0 : krb5_set_error_message(context, ret,
1610 0 : N_("Failed to copy subsidiary cache file %s to "
1611 0 : "default %s", ""), FILENAME(id),
1612 0 : RESFILENAME(id));
1613 0 : return ret;
1614 : }
1615 :
1616 : static krb5_error_code KRB5_CALLCONV
1617 0 : fcc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime)
1618 : {
1619 0 : krb5_error_code ret;
1620 0 : struct stat sb;
1621 0 : int fd;
1622 :
1623 0 : ret = fcc_open(context, id, "lastchange", &fd, O_RDONLY, 0);
1624 0 : if(ret)
1625 0 : return ret;
1626 0 : ret = fstat(fd, &sb);
1627 0 : close(fd);
1628 0 : if (ret) {
1629 0 : ret = errno;
1630 0 : krb5_set_error_message(context, ret, N_("Failed to stat cache file", ""));
1631 0 : return ret;
1632 : }
1633 0 : *mtime = sb.st_mtime;
1634 0 : return 0;
1635 : }
1636 :
1637 : static krb5_error_code KRB5_CALLCONV
1638 0 : fcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat kdc_offset)
1639 : {
1640 0 : return 0;
1641 : }
1642 :
1643 : static krb5_error_code KRB5_CALLCONV
1644 197 : fcc_get_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat *kdc_offset)
1645 : {
1646 0 : krb5_error_code ret;
1647 197 : krb5_storage *sp = NULL;
1648 0 : int fd;
1649 197 : ret = init_fcc(context, id, "get-kdc-offset", &sp, &fd, kdc_offset);
1650 197 : if (sp)
1651 197 : krb5_storage_free(sp);
1652 197 : close(fd);
1653 :
1654 197 : return ret;
1655 : }
1656 :
1657 :
1658 : /**
1659 : * Variable containing the FILE based credential cache implemention.
1660 : *
1661 : * @ingroup krb5_ccache
1662 : */
1663 :
1664 : KRB5_LIB_VARIABLE const krb5_cc_ops krb5_fcc_ops = {
1665 : KRB5_CC_OPS_VERSION_5,
1666 : "FILE",
1667 : NULL,
1668 : NULL,
1669 : fcc_gen_new,
1670 : fcc_initialize,
1671 : fcc_destroy,
1672 : fcc_close,
1673 : fcc_store_cred,
1674 : NULL, /* fcc_retrieve */
1675 : fcc_get_principal,
1676 : fcc_get_first,
1677 : fcc_get_next,
1678 : fcc_end_get,
1679 : fcc_remove_cred,
1680 : fcc_set_flags,
1681 : fcc_get_version,
1682 : fcc_get_cache_first,
1683 : fcc_get_cache_next,
1684 : fcc_end_cache_get,
1685 : fcc_move,
1686 : fcc_get_default_name,
1687 : fcc_set_default_cache,
1688 : fcc_lastchange,
1689 : fcc_set_kdc_offset,
1690 : fcc_get_kdc_offset,
1691 : fcc_get_name_2,
1692 : fcc_resolve_2
1693 : };
|