Get rid of casts of free() argument to char*, except where it's
[krb5.git] / src / lib / krb5 / rcache / rc_io.c
1 /* -*- mode: c; indent-tabs-mode: nil -*- */
2 /*
3  * lib/krb5/rcache/rc_io.c
4  *
5  * This file of the Kerberos V5 software is derived from public-domain code
6  * contributed by Daniel J. Bernstein, <brnstnd@acf10.nyu.edu>.
7  *
8  */
9
10 /*
11  * I/O functions for the replay cache default implementation.
12  */
13
14 #if defined(_WIN32)
15 #  define PATH_SEPARATOR "\\"
16 #else
17 #  define PATH_SEPARATOR "/"
18 #endif
19
20 #define KRB5_RC_VNO     0x0501          /* krb5, rcache v 1 */
21
22 #if HAVE_SYS_STAT_H
23 #include <sys/stat.h>
24 #endif
25 #include "k5-int.h"
26 #include <stdio.h> /* for P_tmpdir */
27 #include "rc_base.h"
28 #include "rc_dfl.h"
29 #include "rc_io.h"
30
31 #ifndef O_BINARY
32 #define O_BINARY    0
33 #endif
34
35 #ifdef HAVE_NETINET_IN_H
36 #if !defined(_WINSOCKAPI_)
37 #include <netinet/in.h>
38 #endif
39 #else
40 #error find some way to use net-byte-order file version numbers.
41 #endif
42
43 #define UNIQUE getpid() /* hopefully unique number */
44
45 #define GETDIR (dir = getdir(), dirlen = strlen(dir) + sizeof(PATH_SEPARATOR) - 1)
46
47 static char *
48 getdir(void)
49 {
50     char *dir;
51
52     if (!(dir = getenv("KRB5RCACHEDIR"))) {
53 #if defined(_WIN32)
54         if (!(dir = getenv("TEMP")))
55             if (!(dir = getenv("TMP")))
56                 dir = "C:";
57 #else
58         if (!(dir = getenv("TMPDIR"))) {
59 #ifdef RCTMPDIR
60             dir = RCTMPDIR;
61 #else
62             dir = "/tmp";
63 #endif
64         }
65 #endif
66     }
67     return dir;
68 }
69
70 /*
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
75  * incorrect modes.
76  */
77 static krb5_error_code
78 krb5_rc_io_mkstemp(krb5_context context, krb5_rc_iostuff *d, char *dir)
79 {
80     krb5_error_code retval = 0;
81 #if HAVE_SYS_STAT_H
82     struct stat stbuf;
83 #endif
84
85     memset(&stbuf, 0, sizeof(stbuf));
86     if (asprintf(&d->fn, "%s%skrb5_RCXXXXXX",
87                  dir, PATH_SEPARATOR) < 0) {
88         d->fn = NULL;
89         return KRB5_RC_IO_MALLOC;
90     }
91     d->fd = mkstemp(d->fn);
92     if (d->fd == -1) {
93         /*
94          * This return value is deliberate because d->fd == -1 causes
95          * caller to go into errno interpretation code.
96          */
97         return 0;
98     }
99 #if HAVE_SYS_STAT_H
100     /*
101      * Be paranoid and check that mkstemp made the file accessible
102      * only to the user.
103      */
104     retval = fstat(d->fd, &stbuf);
105     if (retval) {
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;
110     }
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;
118     }
119 #endif
120     return 0;
121 }
122
123 #if 0
124 static krb5_error_code rc_map_errno (int) __attribute__((cold));
125 #endif
126
127 static krb5_error_code
128 rc_map_errno (krb5_context context, int e, const char *fn,
129               const char *operation)
130 {
131     switch (e) {
132     case EFBIG:
133 #ifdef EDQUOT
134     case EDQUOT:
135 #endif
136     case ENOSPC:
137         return KRB5_RC_IO_SPACE;
138
139     case EIO:
140         return KRB5_RC_IO_IO;
141
142     case EPERM:
143     case EACCES:
144     case EROFS:
145     case EEXIST:
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;
150
151     default:
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;
156     }
157 }
158
159
160 krb5_error_code
161 krb5_rc_io_creat(krb5_context context, krb5_rc_iostuff *d, char **fn)
162 {
163     krb5_int16 rc_vno = htons(KRB5_RC_VNO);
164     krb5_error_code retval = 0;
165     int do_not_unlink = 0;
166     char *dir;
167     size_t dirlen;
168
169     GETDIR;
170     if (fn && *fn) {
171         if (asprintf(&d->fn, "%s%s%s", dir, PATH_SEPARATOR, *fn) < 0)
172             return KRB5_RC_IO_MALLOC;
173         unlink(d->fn);
174         d->fd = THREEPARAMOPEN(d->fn, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL |
175                                O_BINARY, 0600);
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     size_t dirlen;
229
230     GETDIR;
231     if (full_pathname) {
232         if (!(d->fn = strdup(full_pathname)))
233             return KRB5_RC_IO_MALLOC;
234     } else {
235         if (asprintf(&d->fn, "%s%s%s", dir, PATH_SEPARATOR, fn) < 0)
236             return KRB5_RC_IO_MALLOC;
237     }
238
239 #ifdef NO_USERID
240     d->fd = THREEPARAMOPEN(d->fn, O_RDWR | O_BINARY, 0600);
241     if (d->fd == -1) {
242         retval = rc_map_errno(context, errno, d->fn, "open");
243         goto cleanup;
244     }
245 #else
246     d->fd = -1;
247     retval = lstat(d->fn, &sb1);
248     if (retval != 0) {
249         retval = rc_map_errno(context, errno, d->fn, "lstat");
250         goto cleanup;
251     }
252     d->fd = THREEPARAMOPEN(d->fn, O_RDWR | O_BINARY, 0600);
253     if (d->fd < 0) {
254         retval = rc_map_errno(context, errno, d->fn, "open");
255         goto cleanup;
256     }
257     retval = fstat(d->fd, &sb2);
258     if (retval < 0) {
259         retval = rc_map_errno(context, errno, d->fn, "fstat");
260         goto cleanup;
261     }
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)
265         {
266             retval = KRB5_RC_IO_PERM;
267             krb5_set_error_message(context, retval,
268                                    "rcache not a file %s", d->fn);
269             goto cleanup;
270         }
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;
276     }
277     /* owned by me */
278     if (sb1.st_uid != geteuid()) {
279         retval = KRB5_RC_IO_PERM;
280         krb5_set_error_message(context, retval, "rcache not owned by %d",
281                                (int)geteuid());
282         goto cleanup;
283     }
284 #endif
285     set_cloexec_fd(d->fd);
286
287     do_not_unlink = 0;
288     retval = krb5_rc_io_read(context, d, (krb5_pointer) &rc_vno,
289                              sizeof(rc_vno));
290     if (retval)
291         goto cleanup;
292
293     if (ntohs(rc_vno) != KRB5_RC_VNO)
294         retval = KRB5_RCACHE_BADVNO;
295
296 cleanup:
297     if (retval) {
298         if (d->fn) {
299             if (!do_not_unlink)
300                 (void) unlink(d->fn);
301             free(d->fn);
302             d->fn = NULL;
303         }
304         if (d->fd >= 0)
305             (void) close(d->fd);
306     }
307     return retval;
308 }
309
310 krb5_error_code
311 krb5_rc_io_open(krb5_context context, krb5_rc_iostuff *d, char *fn)
312 {
313     return krb5_rc_io_open_internal(context, d, fn, NULL);
314 }
315
316 krb5_error_code
317 krb5_rc_io_move(krb5_context context, krb5_rc_iostuff *new1,
318                 krb5_rc_iostuff *old)
319 {
320 #if defined(_WIN32) || defined(__CYGWIN__)
321     char *new_fn = NULL;
322     char *old_fn = NULL;
323     off_t offset = 0;
324     krb5_error_code retval = 0;
325     /*
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!
329      *
330      * Additional fixes and explanation provided by dalmeida@mit.edu:
331      *
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).
336      *
337      * Next, we do the rename.  If all goes well, we seek the "new"
338      * file to the position "old" was at.
339      *
340      * --- WARNING!!! ---
341      *
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.
346      */
347     offset = lseek(old->fd, 0, SEEK_CUR);
348
349     new_fn = new1->fn;
350     new1->fn = NULL;
351     close(new1->fd);
352     new1->fd = -1;
353
354     unlink(new_fn);
355
356     old_fn = old->fn;
357     old->fn = NULL;
358     close(old->fd);
359     old->fd = -1;
360
361     if (rename(old_fn, new_fn) == -1) { /* MUST be atomic! */
362         retval = KRB5_RC_IO_UNKNOWN;
363         goto cleanup;
364     }
365
366     retval = krb5_rc_io_open_internal(context, new1, 0, new_fn);
367     if (retval)
368         goto cleanup;
369
370     if (lseek(new1->fd, offset, SEEK_SET) == -1) {
371         retval = KRB5_RC_IO_UNKNOWN;
372         goto cleanup;
373     }
374
375 cleanup:
376     free(new_fn);
377     free(old_fn);
378     return retval;
379 #else
380     char *fn = NULL;
381     if (rename(old->fn, new1->fn) == -1) /* MUST be atomic! */
382         return KRB5_RC_IO_UNKNOWN;
383     fn = new1->fn;
384     new1->fn = NULL;            /* avoid clobbering */
385     (void) krb5_rc_io_close(context, new1);
386     new1->fn = fn;
387     new1->fd = dup(old->fd);
388     set_cloexec_fd(new1->fd);
389     return 0;
390 #endif
391 }
392
393 krb5_error_code
394 krb5_rc_io_write(krb5_context context, krb5_rc_iostuff *d, krb5_pointer buf,
395                  unsigned int num)
396 {
397     if (write(d->fd, (char *) buf, num) == -1)
398         switch(errno)
399         {
400 #ifdef EDQUOT
401         case EDQUOT:
402 #endif
403         case EFBIG:
404         case ENOSPC:
405             krb5_set_error_message (context, KRB5_RC_IO_SPACE,
406                                     "Can't write to replay cache: %s",
407                                     strerror(errno));
408             return KRB5_RC_IO_SPACE;
409         case EIO:
410             krb5_set_error_message (context, KRB5_RC_IO_IO,
411                                     "Can't write to replay cache: %s",
412                                     strerror(errno));
413             return KRB5_RC_IO_IO;
414         case EBADF:
415         default:
416             krb5_set_error_message (context, KRB5_RC_IO_UNKNOWN,
417                                     "Can't write to replay cache: %s",
418                                     strerror(errno));
419             return KRB5_RC_IO_UNKNOWN;
420         }
421     return 0;
422 }
423
424 krb5_error_code
425 krb5_rc_io_sync(krb5_context context, krb5_rc_iostuff *d)
426 {
427 #if defined(_WIN32)
428 #ifndef fsync
429 #define fsync _commit
430 #endif
431 #endif
432     if (fsync(d->fd) == -1) {
433         switch(errno)
434         {
435         case EBADF: return KRB5_RC_IO_UNKNOWN;
436         case EIO: return KRB5_RC_IO_IO;
437         default:
438             krb5_set_error_message(context, KRB5_RC_IO_UNKNOWN,
439                                    "Cannot sync replay cache file: %s",
440                                    strerror(errno));
441             return KRB5_RC_IO_UNKNOWN;
442         }
443     }
444     return 0;
445 }
446
447 krb5_error_code
448 krb5_rc_io_read(krb5_context context, krb5_rc_iostuff *d, krb5_pointer buf,
449                 unsigned int num)
450 {
451     int count;
452     if ((count = read(d->fd, (char *) buf, num)) == -1)
453         switch(errno)
454         {
455         case EIO: return KRB5_RC_IO_IO;
456         case EBADF:
457         default:
458             krb5_set_error_message(context, KRB5_RC_IO_UNKNOWN,
459                                    "Can't read from replay cache: %s",
460                                    strerror(errno));
461             return KRB5_RC_IO_UNKNOWN;
462         }
463     if (count < 0 || (unsigned int)count != num)
464         return KRB5_RC_IO_EOF;
465     return 0;
466 }
467
468 krb5_error_code
469 krb5_rc_io_close(krb5_context context, krb5_rc_iostuff *d)
470 {
471     if (d->fn != NULL) {
472         free(d->fn);
473         d->fn = NULL;
474     }
475     if (d->fd != -1) {
476         if (close(d->fd) == -1) /* can't happen */
477             return KRB5_RC_IO_UNKNOWN;
478         d->fd = -1;
479     }
480     return 0;
481 }
482
483 krb5_error_code
484 krb5_rc_io_destroy(krb5_context context, krb5_rc_iostuff *d)
485 {
486     if (unlink(d->fn) == -1)
487         switch(errno)
488         {
489         case EIO:
490             krb5_set_error_message(context, KRB5_RC_IO_IO,
491                                    "Can't destroy replay cache: %s",
492                                    strerror(errno));
493             return KRB5_RC_IO_IO;
494         case EPERM:
495         case EBUSY:
496         case EROFS:
497             krb5_set_error_message(context, KRB5_RC_IO_PERM,
498                                    "Can't destroy replay cache: %s",
499                                    strerror(errno));
500             return KRB5_RC_IO_PERM;
501         case EBADF:
502         default:
503             krb5_set_error_message(context, KRB5_RC_IO_UNKNOWN,
504                                    "Can't destroy replay cache: %s",
505                                    strerror(errno));
506             return KRB5_RC_IO_UNKNOWN;
507         }
508     return 0;
509 }
510
511 krb5_error_code
512 krb5_rc_io_mark(krb5_context context, krb5_rc_iostuff *d)
513 {
514     d->mark = lseek(d->fd, (off_t) 0, SEEK_CUR); /* can't fail */
515     return 0;
516 }
517
518 krb5_error_code
519 krb5_rc_io_unmark(krb5_context context, krb5_rc_iostuff *d)
520 {
521     (void) lseek(d->fd, d->mark, SEEK_SET); /* if it fails, tough luck */
522     return 0;
523 }
524
525 long
526 krb5_rc_io_size(krb5_context context, krb5_rc_iostuff *d)
527 {
528     struct stat statb;
529
530     if (fstat(d->fd, &statb) == 0)
531         return statb.st_size;
532     else
533         return 0;
534 }