Imported Upstream version 1.17
[platform/upstream/krb5.git] / src / lib / krb5 / rcache / rc_io.c
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/rcache/rc_io.c */
3 /*
4  * This file of the Kerberos V5 software is derived from public-domain code
5  * contributed by Daniel J. Bernstein, <brnstnd@acf10.nyu.edu>.
6  *
7  */
8
9 /*
10  * I/O functions for the replay cache default implementation.
11  */
12
13 #if defined(_WIN32)
14 #  define PATH_SEPARATOR "\\"
15 #else
16 #  define PATH_SEPARATOR "/"
17 #endif
18
19 #define KRB5_RC_VNO     0x0501          /* krb5, rcache v 1 */
20
21 #if HAVE_SYS_STAT_H
22 #include <sys/stat.h>
23 #endif
24 #include "k5-int.h"
25 #include <stdio.h> /* for P_tmpdir */
26 #include "rc_base.h"
27 #include "rc_dfl.h"
28 #include "rc_io.h"
29
30 #ifndef O_BINARY
31 #define O_BINARY    0
32 #endif
33
34 #ifdef HAVE_NETINET_IN_H
35 #if !defined(_WINSOCKAPI_)
36 #include <netinet/in.h>
37 #endif
38 #else
39 #error find some way to use net-byte-order file version numbers.
40 #endif
41
42 #define UNIQUE getpid() /* hopefully unique number */
43
44 #define GETDIR (dir = getdir(), dirlen = strlen(dir) + sizeof(PATH_SEPARATOR) - 1)
45
46 static char *
47 getdir(void)
48 {
49     char *dir;
50
51     if (!(dir = getenv("KRB5RCACHEDIR"))) {
52 #if defined(_WIN32)
53         if (!(dir = getenv("TEMP")))
54             if (!(dir = getenv("TMP")))
55                 dir = "C:";
56 #else
57         if (!(dir = getenv("TMPDIR"))) {
58 #ifdef RCTMPDIR
59             dir = RCTMPDIR;
60 #else
61             dir = "/tmp";
62 #endif
63         }
64 #endif
65     }
66     return dir;
67 }
68
69 /*
70  * Called from krb5_rc_io_creat(); calls mkstemp() and does some
71  * sanity checking on the file modes in case some broken mkstemp()
72  * implementation creates the file with overly permissive modes.  To
73  * avoid race conditions, do not fchmod() a file for which mkstemp set
74  * incorrect modes.
75  */
76 static krb5_error_code
77 krb5_rc_io_mkstemp(krb5_context context, krb5_rc_iostuff *d, char *dir)
78 {
79     krb5_error_code retval = 0;
80 #if HAVE_SYS_STAT_H
81     struct stat stbuf;
82
83     memset(&stbuf, 0, sizeof(stbuf));
84 #endif
85     if (asprintf(&d->fn, "%s%skrb5_RCXXXXXX",
86                  dir, PATH_SEPARATOR) < 0) {
87         d->fn = NULL;
88         return KRB5_RC_IO_MALLOC;
89     }
90     d->fd = mkstemp(d->fn);
91     if (d->fd == -1) {
92         /*
93          * This return value is deliberate because d->fd == -1 causes
94          * caller to go into errno interpretation code.
95          */
96         return 0;
97     }
98 #if HAVE_SYS_STAT_H
99     /*
100      * Be paranoid and check that mkstemp made the file accessible
101      * only to the user.
102      */
103     retval = fstat(d->fd, &stbuf);
104     if (retval) {
105         k5_setmsg(context, retval,
106                   _("Cannot fstat replay cache file %s: %s"),
107                   d->fn, strerror(errno));
108         return KRB5_RC_IO_UNKNOWN;
109     }
110     if (stbuf.st_mode & 077) {
111         k5_setmsg(context, retval,
112                   _("Insecure mkstemp() file mode for replay cache file %s; "
113                     "try running this program with umask 077"), d->fn);
114         return KRB5_RC_IO_UNKNOWN;
115     }
116 #endif
117     return 0;
118 }
119
120 static krb5_error_code
121 rc_map_errno (krb5_context context, int e, const char *fn,
122               const char *operation)
123 {
124     switch (e) {
125     case EFBIG:
126 #ifdef EDQUOT
127     case EDQUOT:
128 #endif
129     case ENOSPC:
130         return KRB5_RC_IO_SPACE;
131
132     case EIO:
133         return KRB5_RC_IO_IO;
134
135     case EPERM:
136     case EACCES:
137     case EROFS:
138     case EEXIST:
139         k5_setmsg(context, KRB5_RC_IO_PERM,
140                   _("Cannot %s replay cache file %s: %s"),
141                   operation, fn, strerror(e));
142         return KRB5_RC_IO_PERM;
143
144     default:
145         k5_setmsg(context, KRB5_RC_IO_UNKNOWN, _("Cannot %s replay cache: %s"),
146                   operation, strerror(e));
147         return KRB5_RC_IO_UNKNOWN;
148     }
149 }
150
151
152 krb5_error_code
153 krb5_rc_io_creat(krb5_context context, krb5_rc_iostuff *d, char **fn)
154 {
155     krb5_int16 rc_vno = htons(KRB5_RC_VNO);
156     krb5_error_code retval = 0;
157     int flags, do_not_unlink = 0;
158     char *dir;
159     size_t dirlen;
160
161     GETDIR;
162     if (fn && *fn) {
163         if (asprintf(&d->fn, "%s%s%s", dir, PATH_SEPARATOR, *fn) < 0)
164             return KRB5_RC_IO_MALLOC;
165         d->fd = -1;
166         do {
167             if (unlink(d->fn) == -1 && errno != ENOENT)
168                 break;
169             flags = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | O_BINARY;
170             d->fd = THREEPARAMOPEN(d->fn, flags, 0600);
171         } while (d->fd == -1 && errno == EEXIST);
172     } else {
173         retval = krb5_rc_io_mkstemp(context, d, dir);
174         if (retval)
175             goto cleanup;
176         if (d->fd != -1 && fn) {
177             *fn = strdup(d->fn + dirlen);
178             if (*fn == NULL) {
179                 free(d->fn);
180                 return KRB5_RC_IO_MALLOC;
181             }
182         }
183     }
184     if (d->fd == -1) {
185         retval = rc_map_errno(context, errno, d->fn, "create");
186         if (retval == KRB5_RC_IO_PERM)
187             do_not_unlink = 1;
188         goto cleanup;
189     }
190     set_cloexec_fd(d->fd);
191     retval = krb5_rc_io_write(context, d, (krb5_pointer)&rc_vno,
192                               sizeof(rc_vno));
193     if (retval)
194         goto cleanup;
195
196     retval = krb5_rc_io_sync(context, d);
197
198 cleanup:
199     if (retval) {
200         if (d->fn) {
201             if (!do_not_unlink)
202                 (void) unlink(d->fn);
203             free(d->fn);
204             d->fn = NULL;
205         }
206         if (d->fd != -1) {
207             (void) close(d->fd);
208         }
209     }
210     return retval;
211 }
212
213 static krb5_error_code
214 krb5_rc_io_open_internal(krb5_context context, krb5_rc_iostuff *d, char *fn,
215                          char* full_pathname)
216 {
217     krb5_int16 rc_vno;
218     krb5_error_code retval = 0;
219     int do_not_unlink = 1;
220 #ifndef NO_USERID
221     struct stat sb1, sb2;
222 #endif
223     char *dir;
224
225     dir = getdir();
226     if (full_pathname) {
227         if (!(d->fn = strdup(full_pathname)))
228             return KRB5_RC_IO_MALLOC;
229     } else {
230         if (asprintf(&d->fn, "%s%s%s", dir, PATH_SEPARATOR, fn) < 0)
231             return KRB5_RC_IO_MALLOC;
232     }
233
234 #ifdef NO_USERID
235     d->fd = THREEPARAMOPEN(d->fn, O_RDWR | O_BINARY, 0600);
236     if (d->fd == -1) {
237         retval = rc_map_errno(context, errno, d->fn, "open");
238         goto cleanup;
239     }
240 #else
241     d->fd = -1;
242     retval = lstat(d->fn, &sb1);
243     if (retval != 0) {
244         retval = rc_map_errno(context, errno, d->fn, "lstat");
245         goto cleanup;
246     }
247     d->fd = THREEPARAMOPEN(d->fn, O_RDWR | O_BINARY, 0600);
248     if (d->fd < 0) {
249         retval = rc_map_errno(context, errno, d->fn, "open");
250         goto cleanup;
251     }
252     retval = fstat(d->fd, &sb2);
253     if (retval < 0) {
254         retval = rc_map_errno(context, errno, d->fn, "fstat");
255         goto cleanup;
256     }
257     /* check if someone was playing with symlinks */
258     if ((sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino)
259         || (sb1.st_mode & S_IFMT) != S_IFREG)
260     {
261         retval = KRB5_RC_IO_PERM;
262         k5_setmsg(context, retval, "rcache not a file %s", d->fn);
263         goto cleanup;
264     }
265     /* check that non other can read/write/execute the file */
266     if (sb1.st_mode & 077) {
267         k5_setmsg(context, retval,
268                   _("Insecure file mode for replay cache file %s"), d->fn);
269         return KRB5_RC_IO_UNKNOWN;
270     }
271     /* owned by me */
272     if (sb1.st_uid != geteuid()) {
273         retval = KRB5_RC_IO_PERM;
274         k5_setmsg(context, retval, _("rcache not owned by %d"),
275                   (int)geteuid());
276         goto cleanup;
277     }
278 #endif
279     set_cloexec_fd(d->fd);
280
281     do_not_unlink = 0;
282     retval = krb5_rc_io_read(context, d, (krb5_pointer) &rc_vno,
283                              sizeof(rc_vno));
284     if (retval)
285         goto cleanup;
286
287     if (ntohs(rc_vno) != KRB5_RC_VNO)
288         retval = KRB5_RCACHE_BADVNO;
289
290 cleanup:
291     if (retval) {
292         if (!do_not_unlink)
293             (void) unlink(d->fn);
294         free(d->fn);
295         d->fn = NULL;
296         if (d->fd >= 0)
297             (void) close(d->fd);
298     }
299     return retval;
300 }
301
302 krb5_error_code
303 krb5_rc_io_open(krb5_context context, krb5_rc_iostuff *d, char *fn)
304 {
305     return krb5_rc_io_open_internal(context, d, fn, NULL);
306 }
307
308 krb5_error_code
309 krb5_rc_io_move(krb5_context context, krb5_rc_iostuff *new1,
310                 krb5_rc_iostuff *old)
311 {
312 #if defined(_WIN32) || defined(__CYGWIN__)
313     char *new_fn = NULL;
314     char *old_fn = NULL;
315     off_t offset = 0;
316     krb5_error_code retval = 0;
317     /*
318      * Initial work around provided by Tom Sanfilippo to work around
319      * poor Windows emulation of POSIX functions.  Rename and dup has
320      * different semantics!
321      *
322      * Additional fixes and explanation provided by dalmeida@mit.edu:
323      *
324      * First, we save the offset of "old".  Then, we close and remove
325      * the "new" file so we can do the rename.  We also close "old" to
326      * make sure the rename succeeds (though that might not be
327      * necessary on some systems).
328      *
329      * Next, we do the rename.  If all goes well, we seek the "new"
330      * file to the position "old" was at.
331      *
332      * --- WARNING!!! ---
333      *
334      * Since "old" is now gone, we mourn its disappearance, but we
335      * cannot emulate that Unix behavior...  THIS BEHAVIOR IS
336      * DIFFERENT FROM UNIX.  However, it is ok because this function
337      * gets called such that "old" gets closed right afterwards.
338      */
339     offset = lseek(old->fd, 0, SEEK_CUR);
340
341     new_fn = new1->fn;
342     new1->fn = NULL;
343     close(new1->fd);
344     new1->fd = -1;
345
346     unlink(new_fn);
347
348     old_fn = old->fn;
349     old->fn = NULL;
350     close(old->fd);
351     old->fd = -1;
352
353     if (rename(old_fn, new_fn) == -1) { /* MUST be atomic! */
354         retval = KRB5_RC_IO_UNKNOWN;
355         goto cleanup;
356     }
357
358     retval = krb5_rc_io_open_internal(context, new1, 0, new_fn);
359     if (retval)
360         goto cleanup;
361
362     if (lseek(new1->fd, offset, SEEK_SET) == -1) {
363         retval = KRB5_RC_IO_UNKNOWN;
364         goto cleanup;
365     }
366
367 cleanup:
368     free(new_fn);
369     free(old_fn);
370     return retval;
371 #else
372     char *fn = NULL;
373     if (rename(old->fn, new1->fn) == -1) /* MUST be atomic! */
374         return KRB5_RC_IO_UNKNOWN;
375     fn = new1->fn;
376     new1->fn = NULL;            /* avoid clobbering */
377     (void) krb5_rc_io_close(context, new1);
378     new1->fn = fn;
379     new1->fd = dup(old->fd);
380     set_cloexec_fd(new1->fd);
381     return 0;
382 #endif
383 }
384
385 krb5_error_code
386 krb5_rc_io_write(krb5_context context, krb5_rc_iostuff *d, krb5_pointer buf,
387                  unsigned int num)
388 {
389     if (write(d->fd, (char *) buf, num) == -1)
390         switch(errno)
391         {
392 #ifdef EDQUOT
393         case EDQUOT:
394 #endif
395         case EFBIG:
396         case ENOSPC:
397             k5_setmsg(context, KRB5_RC_IO_SPACE,
398                       _("Can't write to replay cache: %s"), strerror(errno));
399             return KRB5_RC_IO_SPACE;
400         case EIO:
401             k5_setmsg(context, KRB5_RC_IO_IO,
402                       _("Can't write to replay cache: %s"), strerror(errno));
403             return KRB5_RC_IO_IO;
404         case EBADF:
405         default:
406             k5_setmsg(context, KRB5_RC_IO_UNKNOWN,
407                       _("Can't write to replay cache: %s"), strerror(errno));
408             return KRB5_RC_IO_UNKNOWN;
409         }
410     return 0;
411 }
412
413 krb5_error_code
414 krb5_rc_io_sync(krb5_context context, krb5_rc_iostuff *d)
415 {
416 #if defined(_WIN32)
417 #ifndef fsync
418 #define fsync _commit
419 #endif
420 #endif
421     if (fsync(d->fd) == -1) {
422         switch(errno)
423         {
424         case EBADF: return KRB5_RC_IO_UNKNOWN;
425         case EIO: return KRB5_RC_IO_IO;
426         default:
427             k5_setmsg(context, KRB5_RC_IO_UNKNOWN,
428                       _("Cannot sync replay cache file: %s"), strerror(errno));
429             return KRB5_RC_IO_UNKNOWN;
430         }
431     }
432     return 0;
433 }
434
435 krb5_error_code
436 krb5_rc_io_read(krb5_context context, krb5_rc_iostuff *d, krb5_pointer buf,
437                 unsigned int num)
438 {
439     int count;
440     if ((count = read(d->fd, (char *) buf, num)) == -1)
441         switch(errno)
442         {
443         case EIO: return KRB5_RC_IO_IO;
444         case EBADF:
445         default:
446             k5_setmsg(context, KRB5_RC_IO_UNKNOWN,
447                       _("Can't read from replay cache: %s"), strerror(errno));
448             return KRB5_RC_IO_UNKNOWN;
449         }
450     if (count < 0 || (unsigned int)count != num)
451         return KRB5_RC_IO_EOF;
452     return 0;
453 }
454
455 krb5_error_code
456 krb5_rc_io_close(krb5_context context, krb5_rc_iostuff *d)
457 {
458     if (d->fn != NULL) {
459         free(d->fn);
460         d->fn = NULL;
461     }
462     if (d->fd != -1) {
463         if (close(d->fd) == -1) /* can't happen */
464             return KRB5_RC_IO_UNKNOWN;
465         d->fd = -1;
466     }
467     return 0;
468 }
469
470 krb5_error_code
471 krb5_rc_io_destroy(krb5_context context, krb5_rc_iostuff *d)
472 {
473     if (unlink(d->fn) == -1)
474         switch(errno)
475         {
476         case EIO:
477             k5_setmsg(context, KRB5_RC_IO_IO,
478                       _("Can't destroy replay cache: %s"), strerror(errno));
479             return KRB5_RC_IO_IO;
480         case EPERM:
481         case EBUSY:
482         case EROFS:
483             k5_setmsg(context, KRB5_RC_IO_PERM,
484                       _("Can't destroy replay cache: %s"), strerror(errno));
485             return KRB5_RC_IO_PERM;
486         case EBADF:
487         default:
488             k5_setmsg(context, KRB5_RC_IO_UNKNOWN,
489                       _("Can't destroy replay cache: %s"), strerror(errno));
490             return KRB5_RC_IO_UNKNOWN;
491         }
492     return 0;
493 }
494
495 krb5_error_code
496 krb5_rc_io_mark(krb5_context context, krb5_rc_iostuff *d)
497 {
498     d->mark = lseek(d->fd, (off_t) 0, SEEK_CUR); /* can't fail */
499     return 0;
500 }
501
502 krb5_error_code
503 krb5_rc_io_unmark(krb5_context context, krb5_rc_iostuff *d)
504 {
505     (void) lseek(d->fd, d->mark, SEEK_SET); /* if it fails, tough luck */
506     return 0;
507 }
508
509 long
510 krb5_rc_io_size(krb5_context context, krb5_rc_iostuff *d)
511 {
512     struct stat statb;
513
514     if (fstat(d->fd, &statb) == 0)
515         return statb.st_size;
516     else
517         return 0;
518 }