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