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