Imported Upstream version 1.15.1
[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 #if 0
121 static krb5_error_code rc_map_errno (int) __attribute__((cold));
122 #endif
123
124 static krb5_error_code
125 rc_map_errno (krb5_context context, int e, const char *fn,
126               const char *operation)
127 {
128     switch (e) {
129     case EFBIG:
130 #ifdef EDQUOT
131     case EDQUOT:
132 #endif
133     case ENOSPC:
134         return KRB5_RC_IO_SPACE;
135
136     case EIO:
137         return KRB5_RC_IO_IO;
138
139     case EPERM:
140     case EACCES:
141     case EROFS:
142     case EEXIST:
143         k5_setmsg(context, KRB5_RC_IO_PERM,
144                   _("Cannot %s replay cache file %s: %s"),
145                   operation, fn, strerror(e));
146         return KRB5_RC_IO_PERM;
147
148     default:
149         k5_setmsg(context, KRB5_RC_IO_UNKNOWN, _("Cannot %s replay cache: %s"),
150                   operation, strerror(e));
151         return KRB5_RC_IO_UNKNOWN;
152     }
153 }
154
155
156 krb5_error_code
157 krb5_rc_io_creat(krb5_context context, krb5_rc_iostuff *d, char **fn)
158 {
159     krb5_int16 rc_vno = htons(KRB5_RC_VNO);
160     krb5_error_code retval = 0;
161     int flags, do_not_unlink = 0;
162     char *dir;
163     size_t dirlen;
164
165     GETDIR;
166     if (fn && *fn) {
167         if (asprintf(&d->fn, "%s%s%s", dir, PATH_SEPARATOR, *fn) < 0)
168             return KRB5_RC_IO_MALLOC;
169         d->fd = -1;
170         do {
171             if (unlink(d->fn) == -1 && errno != ENOENT)
172                 break;
173             flags = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | O_BINARY;
174             d->fd = THREEPARAMOPEN(d->fn, flags, 0600);
175         } while (d->fd == -1 && errno == EEXIST);
176     } else {
177         retval = krb5_rc_io_mkstemp(context, d, dir);
178         if (retval)
179             goto cleanup;
180         if (d->fd != -1 && fn) {
181             *fn = strdup(d->fn + dirlen);
182             if (*fn == NULL) {
183                 free(d->fn);
184                 return KRB5_RC_IO_MALLOC;
185             }
186         }
187     }
188     if (d->fd == -1) {
189         retval = rc_map_errno(context, errno, d->fn, "create");
190         if (retval == KRB5_RC_IO_PERM)
191             do_not_unlink = 1;
192         goto cleanup;
193     }
194     set_cloexec_fd(d->fd);
195     retval = krb5_rc_io_write(context, d, (krb5_pointer)&rc_vno,
196                               sizeof(rc_vno));
197     if (retval)
198         goto cleanup;
199
200     retval = krb5_rc_io_sync(context, d);
201
202 cleanup:
203     if (retval) {
204         if (d->fn) {
205             if (!do_not_unlink)
206                 (void) unlink(d->fn);
207             free(d->fn);
208             d->fn = NULL;
209         }
210         if (d->fd != -1) {
211             (void) close(d->fd);
212         }
213     }
214     return retval;
215 }
216
217 static krb5_error_code
218 krb5_rc_io_open_internal(krb5_context context, krb5_rc_iostuff *d, char *fn,
219                          char* full_pathname)
220 {
221     krb5_int16 rc_vno;
222     krb5_error_code retval = 0;
223     int do_not_unlink = 1;
224 #ifndef NO_USERID
225     struct stat sb1, sb2;
226 #endif
227     char *dir;
228
229     dir = getdir();
230     if (full_pathname) {
231         if (!(d->fn = strdup(full_pathname)))
232             return KRB5_RC_IO_MALLOC;
233     } else {
234         if (asprintf(&d->fn, "%s%s%s", dir, PATH_SEPARATOR, fn) < 0)
235             return KRB5_RC_IO_MALLOC;
236     }
237
238 #ifdef NO_USERID
239     d->fd = THREEPARAMOPEN(d->fn, O_RDWR | O_BINARY, 0600);
240     if (d->fd == -1) {
241         retval = rc_map_errno(context, errno, d->fn, "open");
242         goto cleanup;
243     }
244 #else
245     d->fd = -1;
246     retval = lstat(d->fn, &sb1);
247     if (retval != 0) {
248         retval = rc_map_errno(context, errno, d->fn, "lstat");
249         goto cleanup;
250     }
251     d->fd = THREEPARAMOPEN(d->fn, O_RDWR | O_BINARY, 0600);
252     if (d->fd < 0) {
253         retval = rc_map_errno(context, errno, d->fn, "open");
254         goto cleanup;
255     }
256     retval = fstat(d->fd, &sb2);
257     if (retval < 0) {
258         retval = rc_map_errno(context, errno, d->fn, "fstat");
259         goto cleanup;
260     }
261     /* check if someone was playing with symlinks */
262     if ((sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino)
263         || (sb1.st_mode & S_IFMT) != S_IFREG)
264     {
265         retval = KRB5_RC_IO_PERM;
266         k5_setmsg(context, retval, "rcache not a file %s", d->fn);
267         goto cleanup;
268     }
269     /* check that non other can read/write/execute the file */
270     if (sb1.st_mode & 077) {
271         k5_setmsg(context, retval,
272                   _("Insecure file mode for replay cache file %s"), d->fn);
273         return KRB5_RC_IO_UNKNOWN;
274     }
275     /* owned by me */
276     if (sb1.st_uid != geteuid()) {
277         retval = KRB5_RC_IO_PERM;
278         k5_setmsg(context, retval, _("rcache not owned by %d"),
279                   (int)geteuid());
280         goto cleanup;
281     }
282 #endif
283     set_cloexec_fd(d->fd);
284
285     do_not_unlink = 0;
286     retval = krb5_rc_io_read(context, d, (krb5_pointer) &rc_vno,
287                              sizeof(rc_vno));
288     if (retval)
289         goto cleanup;
290
291     if (ntohs(rc_vno) != KRB5_RC_VNO)
292         retval = KRB5_RCACHE_BADVNO;
293
294 cleanup:
295     if (retval) {
296         if (!do_not_unlink)
297             (void) unlink(d->fn);
298         free(d->fn);
299         d->fn = NULL;
300         if (d->fd >= 0)
301             (void) close(d->fd);
302     }
303     return retval;
304 }
305
306 krb5_error_code
307 krb5_rc_io_open(krb5_context context, krb5_rc_iostuff *d, char *fn)
308 {
309     return krb5_rc_io_open_internal(context, d, fn, NULL);
310 }
311
312 krb5_error_code
313 krb5_rc_io_move(krb5_context context, krb5_rc_iostuff *new1,
314                 krb5_rc_iostuff *old)
315 {
316 #if defined(_WIN32) || defined(__CYGWIN__)
317     char *new_fn = NULL;
318     char *old_fn = NULL;
319     off_t offset = 0;
320     krb5_error_code retval = 0;
321     /*
322      * Initial work around provided by Tom Sanfilippo to work around
323      * poor Windows emulation of POSIX functions.  Rename and dup has
324      * different semantics!
325      *
326      * Additional fixes and explanation provided by dalmeida@mit.edu:
327      *
328      * First, we save the offset of "old".  Then, we close and remove
329      * the "new" file so we can do the rename.  We also close "old" to
330      * make sure the rename succeeds (though that might not be
331      * necessary on some systems).
332      *
333      * Next, we do the rename.  If all goes well, we seek the "new"
334      * file to the position "old" was at.
335      *
336      * --- WARNING!!! ---
337      *
338      * Since "old" is now gone, we mourn its disappearance, but we
339      * cannot emulate that Unix behavior...  THIS BEHAVIOR IS
340      * DIFFERENT FROM UNIX.  However, it is ok because this function
341      * gets called such that "old" gets closed right afterwards.
342      */
343     offset = lseek(old->fd, 0, SEEK_CUR);
344
345     new_fn = new1->fn;
346     new1->fn = NULL;
347     close(new1->fd);
348     new1->fd = -1;
349
350     unlink(new_fn);
351
352     old_fn = old->fn;
353     old->fn = NULL;
354     close(old->fd);
355     old->fd = -1;
356
357     if (rename(old_fn, new_fn) == -1) { /* MUST be atomic! */
358         retval = KRB5_RC_IO_UNKNOWN;
359         goto cleanup;
360     }
361
362     retval = krb5_rc_io_open_internal(context, new1, 0, new_fn);
363     if (retval)
364         goto cleanup;
365
366     if (lseek(new1->fd, offset, SEEK_SET) == -1) {
367         retval = KRB5_RC_IO_UNKNOWN;
368         goto cleanup;
369     }
370
371 cleanup:
372     free(new_fn);
373     free(old_fn);
374     return retval;
375 #else
376     char *fn = NULL;
377     if (rename(old->fn, new1->fn) == -1) /* MUST be atomic! */
378         return KRB5_RC_IO_UNKNOWN;
379     fn = new1->fn;
380     new1->fn = NULL;            /* avoid clobbering */
381     (void) krb5_rc_io_close(context, new1);
382     new1->fn = fn;
383     new1->fd = dup(old->fd);
384     set_cloexec_fd(new1->fd);
385     return 0;
386 #endif
387 }
388
389 krb5_error_code
390 krb5_rc_io_write(krb5_context context, krb5_rc_iostuff *d, krb5_pointer buf,
391                  unsigned int num)
392 {
393     if (write(d->fd, (char *) buf, num) == -1)
394         switch(errno)
395         {
396 #ifdef EDQUOT
397         case EDQUOT:
398 #endif
399         case EFBIG:
400         case ENOSPC:
401             k5_setmsg(context, KRB5_RC_IO_SPACE,
402                       _("Can't write to replay cache: %s"), strerror(errno));
403             return KRB5_RC_IO_SPACE;
404         case EIO:
405             k5_setmsg(context, KRB5_RC_IO_IO,
406                       _("Can't write to replay cache: %s"), strerror(errno));
407             return KRB5_RC_IO_IO;
408         case EBADF:
409         default:
410             k5_setmsg(context, KRB5_RC_IO_UNKNOWN,
411                       _("Can't write to replay cache: %s"), strerror(errno));
412             return KRB5_RC_IO_UNKNOWN;
413         }
414     return 0;
415 }
416
417 krb5_error_code
418 krb5_rc_io_sync(krb5_context context, krb5_rc_iostuff *d)
419 {
420 #if defined(_WIN32)
421 #ifndef fsync
422 #define fsync _commit
423 #endif
424 #endif
425     if (fsync(d->fd) == -1) {
426         switch(errno)
427         {
428         case EBADF: return KRB5_RC_IO_UNKNOWN;
429         case EIO: return KRB5_RC_IO_IO;
430         default:
431             k5_setmsg(context, KRB5_RC_IO_UNKNOWN,
432                       _("Cannot sync replay cache file: %s"), strerror(errno));
433             return KRB5_RC_IO_UNKNOWN;
434         }
435     }
436     return 0;
437 }
438
439 krb5_error_code
440 krb5_rc_io_read(krb5_context context, krb5_rc_iostuff *d, krb5_pointer buf,
441                 unsigned int num)
442 {
443     int count;
444     if ((count = read(d->fd, (char *) buf, num)) == -1)
445         switch(errno)
446         {
447         case EIO: return KRB5_RC_IO_IO;
448         case EBADF:
449         default:
450             k5_setmsg(context, KRB5_RC_IO_UNKNOWN,
451                       _("Can't read from replay cache: %s"), strerror(errno));
452             return KRB5_RC_IO_UNKNOWN;
453         }
454     if (count < 0 || (unsigned int)count != num)
455         return KRB5_RC_IO_EOF;
456     return 0;
457 }
458
459 krb5_error_code
460 krb5_rc_io_close(krb5_context context, krb5_rc_iostuff *d)
461 {
462     if (d->fn != NULL) {
463         free(d->fn);
464         d->fn = NULL;
465     }
466     if (d->fd != -1) {
467         if (close(d->fd) == -1) /* can't happen */
468             return KRB5_RC_IO_UNKNOWN;
469         d->fd = -1;
470     }
471     return 0;
472 }
473
474 krb5_error_code
475 krb5_rc_io_destroy(krb5_context context, krb5_rc_iostuff *d)
476 {
477     if (unlink(d->fn) == -1)
478         switch(errno)
479         {
480         case EIO:
481             k5_setmsg(context, KRB5_RC_IO_IO,
482                       _("Can't destroy replay cache: %s"), strerror(errno));
483             return KRB5_RC_IO_IO;
484         case EPERM:
485         case EBUSY:
486         case EROFS:
487             k5_setmsg(context, KRB5_RC_IO_PERM,
488                       _("Can't destroy replay cache: %s"), strerror(errno));
489             return KRB5_RC_IO_PERM;
490         case EBADF:
491         default:
492             k5_setmsg(context, KRB5_RC_IO_UNKNOWN,
493                       _("Can't destroy replay cache: %s"), strerror(errno));
494             return KRB5_RC_IO_UNKNOWN;
495         }
496     return 0;
497 }
498
499 krb5_error_code
500 krb5_rc_io_mark(krb5_context context, krb5_rc_iostuff *d)
501 {
502     d->mark = lseek(d->fd, (off_t) 0, SEEK_CUR); /* can't fail */
503     return 0;
504 }
505
506 krb5_error_code
507 krb5_rc_io_unmark(krb5_context context, krb5_rc_iostuff *d)
508 {
509     (void) lseek(d->fd, d->mark, SEEK_SET); /* if it fails, tough luck */
510     return 0;
511 }
512
513 long
514 krb5_rc_io_size(krb5_context context, krb5_rc_iostuff *d)
515 {
516     struct stat statb;
517
518     if (fstat(d->fd, &statb) == 0)
519         return statb.st_size;
520     else
521         return 0;
522 }