1 /* -*- mode: c; indent-tabs-mode: nil -*- */
3 * lib/krb5/rcache/rc_io.c
5 * This file of the Kerberos V5 software is derived from public-domain code
6 * contributed by Daniel J. Bernstein, <brnstnd@acf10.nyu.edu>.
11 * I/O functions for the replay cache default implementation.
15 # define PATH_SEPARATOR "\\"
17 # define PATH_SEPARATOR "/"
20 #define KRB5_RC_VNO 0x0501 /* krb5, rcache v 1 */
26 #include <stdio.h> /* for P_tmpdir */
35 #ifdef HAVE_NETINET_IN_H
36 #if !defined(_WINSOCKAPI_)
37 #include <netinet/in.h>
40 #error find some way to use net-byte-order file version numbers.
43 #define UNIQUE getpid() /* hopefully unique number */
45 #define GETDIR (dir = getdir(), dirlen = strlen(dir) + sizeof(PATH_SEPARATOR) - 1)
52 if (!(dir = getenv("KRB5RCACHEDIR"))) {
54 if (!(dir = getenv("TEMP")))
55 if (!(dir = getenv("TMP")))
58 if (!(dir = getenv("TMPDIR"))) {
71 * Called from krb5_rc_io_creat(); calls mkstemp() and does some
72 * sanity checking on the file modes in case some broken mkstemp()
73 * implementation creates the file with overly permissive modes. To
74 * avoid race conditions, do not fchmod() a file for which mkstemp set
77 static krb5_error_code
78 krb5_rc_io_mkstemp(krb5_context context, krb5_rc_iostuff *d, char *dir)
80 krb5_error_code retval = 0;
85 memset(&stbuf, 0, sizeof(stbuf));
86 if (asprintf(&d->fn, "%s%skrb5_RCXXXXXX",
87 dir, PATH_SEPARATOR) < 0) {
89 return KRB5_RC_IO_MALLOC;
91 d->fd = mkstemp(d->fn);
94 * This return value is deliberate because d->fd == -1 causes
95 * caller to go into errno interpretation code.
101 * Be paranoid and check that mkstemp made the file accessible
104 retval = fstat(d->fd, &stbuf);
106 krb5_set_error_message(context, retval,
107 "Cannot fstat replay cache file %s: %s",
108 d->fn, strerror(errno));
109 return KRB5_RC_IO_UNKNOWN;
111 if (stbuf.st_mode & 077) {
112 krb5_set_error_message(context, retval,
113 "Insecure mkstemp() file mode "
114 "for replay cache file %s; "
115 "try running this program "
116 "with umask 077 ", d->fn);
117 return KRB5_RC_IO_UNKNOWN;
124 static krb5_error_code rc_map_errno (int) __attribute__((cold));
127 static krb5_error_code
128 rc_map_errno (krb5_context context, int e, const char *fn,
129 const char *operation)
137 return KRB5_RC_IO_SPACE;
140 return KRB5_RC_IO_IO;
146 krb5_set_error_message(context, KRB5_RC_IO_PERM,
147 "Cannot %s replay cache file %s: %s",
148 operation, fn, strerror(e));
149 return KRB5_RC_IO_PERM;
152 krb5_set_error_message(context, KRB5_RC_IO_UNKNOWN,
153 "Cannot %s replay cache: %s",
154 operation, strerror(e));
155 return KRB5_RC_IO_UNKNOWN;
161 krb5_rc_io_creat(krb5_context context, krb5_rc_iostuff *d, char **fn)
163 krb5_int16 rc_vno = htons(KRB5_RC_VNO);
164 krb5_error_code retval = 0;
165 int do_not_unlink = 0;
171 if (asprintf(&d->fn, "%s%s%s", dir, PATH_SEPARATOR, *fn) < 0)
172 return KRB5_RC_IO_MALLOC;
174 d->fd = THREEPARAMOPEN(d->fn, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL |
177 retval = krb5_rc_io_mkstemp(context, d, dir);
180 if (d->fd != -1 && fn) {
181 *fn = strdup(d->fn + dirlen);
184 return KRB5_RC_IO_MALLOC;
189 retval = rc_map_errno(context, errno, d->fn, "create");
190 if (retval == KRB5_RC_IO_PERM)
194 set_cloexec_fd(d->fd);
195 retval = krb5_rc_io_write(context, d, (krb5_pointer)&rc_vno,
200 retval = krb5_rc_io_sync(context, d);
206 (void) unlink(d->fn);
217 static krb5_error_code
218 krb5_rc_io_open_internal(krb5_context context, krb5_rc_iostuff *d, char *fn,
222 krb5_error_code retval = 0;
223 int do_not_unlink = 1;
225 struct stat sb1, sb2;
232 if (!(d->fn = strdup(full_pathname)))
233 return KRB5_RC_IO_MALLOC;
235 if (asprintf(&d->fn, "%s%s%s", dir, PATH_SEPARATOR, fn) < 0)
236 return KRB5_RC_IO_MALLOC;
240 d->fd = THREEPARAMOPEN(d->fn, O_RDWR | O_BINARY, 0600);
242 retval = rc_map_errno(context, errno, d->fn, "open");
247 retval = lstat(d->fn, &sb1);
249 retval = rc_map_errno(context, errno, d->fn, "lstat");
252 d->fd = THREEPARAMOPEN(d->fn, O_RDWR | O_BINARY, 0600);
254 retval = rc_map_errno(context, errno, d->fn, "open");
257 retval = fstat(d->fd, &sb2);
259 retval = rc_map_errno(context, errno, d->fn, "fstat");
262 /* check if someone was playing with symlinks */
263 if ((sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino)
264 || (sb1.st_mode & S_IFMT) != S_IFREG)
266 retval = KRB5_RC_IO_PERM;
267 krb5_set_error_message(context, retval,
268 "rcache not a file %s", d->fn);
271 /* check that non other can read/write/execute the file */
272 if (sb1.st_mode & 077) {
273 krb5_set_error_message(context, retval, "Insecure file mode "
274 "for replay cache file %s", d->fn);
275 return KRB5_RC_IO_UNKNOWN;
278 if (sb1.st_uid != geteuid()) {
279 retval = KRB5_RC_IO_PERM;
280 krb5_set_error_message(context, retval, "rcache not owned by %d",
285 set_cloexec_fd(d->fd);
288 retval = krb5_rc_io_read(context, d, (krb5_pointer) &rc_vno,
293 if (ntohs(rc_vno) != KRB5_RC_VNO)
294 retval = KRB5_RCACHE_BADVNO;
300 (void) unlink(d->fn);
311 krb5_rc_io_open(krb5_context context, krb5_rc_iostuff *d, char *fn)
313 return krb5_rc_io_open_internal(context, d, fn, NULL);
317 krb5_rc_io_move(krb5_context context, krb5_rc_iostuff *new1,
318 krb5_rc_iostuff *old)
320 #if defined(_WIN32) || defined(__CYGWIN__)
324 krb5_error_code retval = 0;
326 * Initial work around provided by Tom Sanfilippo to work around
327 * poor Windows emulation of POSIX functions. Rename and dup has
328 * different semantics!
330 * Additional fixes and explanation provided by dalmeida@mit.edu:
332 * First, we save the offset of "old". Then, we close and remove
333 * the "new" file so we can do the rename. We also close "old" to
334 * make sure the rename succeeds (though that might not be
335 * necessary on some systems).
337 * Next, we do the rename. If all goes well, we seek the "new"
338 * file to the position "old" was at.
342 * Since "old" is now gone, we mourn its disappearance, but we
343 * cannot emulate that Unix behavior... THIS BEHAVIOR IS
344 * DIFFERENT FROM UNIX. However, it is ok because this function
345 * gets called such that "old" gets closed right afterwards.
347 offset = lseek(old->fd, 0, SEEK_CUR);
361 if (rename(old_fn, new_fn) == -1) { /* MUST be atomic! */
362 retval = KRB5_RC_IO_UNKNOWN;
366 retval = krb5_rc_io_open_internal(context, new1, 0, new_fn);
370 if (lseek(new1->fd, offset, SEEK_SET) == -1) {
371 retval = KRB5_RC_IO_UNKNOWN;
381 if (rename(old->fn, new1->fn) == -1) /* MUST be atomic! */
382 return KRB5_RC_IO_UNKNOWN;
384 new1->fn = NULL; /* avoid clobbering */
385 (void) krb5_rc_io_close(context, new1);
387 new1->fd = dup(old->fd);
388 set_cloexec_fd(new1->fd);
394 krb5_rc_io_write(krb5_context context, krb5_rc_iostuff *d, krb5_pointer buf,
397 if (write(d->fd, (char *) buf, num) == -1)
405 krb5_set_error_message (context, KRB5_RC_IO_SPACE,
406 "Can't write to replay cache: %s",
408 return KRB5_RC_IO_SPACE;
410 krb5_set_error_message (context, KRB5_RC_IO_IO,
411 "Can't write to replay cache: %s",
413 return KRB5_RC_IO_IO;
416 krb5_set_error_message (context, KRB5_RC_IO_UNKNOWN,
417 "Can't write to replay cache: %s",
419 return KRB5_RC_IO_UNKNOWN;
425 krb5_rc_io_sync(krb5_context context, krb5_rc_iostuff *d)
429 #define fsync _commit
432 if (fsync(d->fd) == -1) {
435 case EBADF: return KRB5_RC_IO_UNKNOWN;
436 case EIO: return KRB5_RC_IO_IO;
438 krb5_set_error_message(context, KRB5_RC_IO_UNKNOWN,
439 "Cannot sync replay cache file: %s",
441 return KRB5_RC_IO_UNKNOWN;
448 krb5_rc_io_read(krb5_context context, krb5_rc_iostuff *d, krb5_pointer buf,
452 if ((count = read(d->fd, (char *) buf, num)) == -1)
455 case EIO: return KRB5_RC_IO_IO;
458 krb5_set_error_message(context, KRB5_RC_IO_UNKNOWN,
459 "Can't read from replay cache: %s",
461 return KRB5_RC_IO_UNKNOWN;
463 if (count < 0 || (unsigned int)count != num)
464 return KRB5_RC_IO_EOF;
469 krb5_rc_io_close(krb5_context context, krb5_rc_iostuff *d)
476 if (close(d->fd) == -1) /* can't happen */
477 return KRB5_RC_IO_UNKNOWN;
484 krb5_rc_io_destroy(krb5_context context, krb5_rc_iostuff *d)
486 if (unlink(d->fn) == -1)
490 krb5_set_error_message(context, KRB5_RC_IO_IO,
491 "Can't destroy replay cache: %s",
493 return KRB5_RC_IO_IO;
497 krb5_set_error_message(context, KRB5_RC_IO_PERM,
498 "Can't destroy replay cache: %s",
500 return KRB5_RC_IO_PERM;
503 krb5_set_error_message(context, KRB5_RC_IO_UNKNOWN,
504 "Can't destroy replay cache: %s",
506 return KRB5_RC_IO_UNKNOWN;
512 krb5_rc_io_mark(krb5_context context, krb5_rc_iostuff *d)
514 d->mark = lseek(d->fd, (off_t) 0, SEEK_CUR); /* can't fail */
519 krb5_rc_io_unmark(krb5_context context, krb5_rc_iostuff *d)
521 (void) lseek(d->fd, d->mark, SEEK_SET); /* if it fails, tough luck */
526 krb5_rc_io_size(krb5_context context, krb5_rc_iostuff *d)
530 if (fstat(d->fd, &statb) == 0)
531 return statb.st_size;