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