rc_io.c revision 7934:6aeeafc994de
1/*
2 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
3 * Use is subject to license terms.
4 */
5
6
7/*
8 * lib/krb5/rcache/rc_io.c
9 *
10 * This file of the Kerberos V5 software is derived from public-domain code
11 * contributed by Daniel J. Bernstein, <brnstnd@acf10.nyu.edu>.
12 *
13 */
14
15
16/*
17 * I/O functions for the replay cache default implementation.
18 */
19
20#if defined(_WIN32)
21#  define PATH_SEPARATOR "\\"
22#else
23#  define PATH_SEPARATOR "/"
24#endif
25
26#define KRB5_RC_VNO	0x0501		/* krb5, rcache v 1 */
27
28#include "k5-int.h"
29#include <stdio.h> /* for P_tmpdir */
30#include <sys/types.h>
31#include <unistd.h>
32#include <syslog.h> /* SUNW */
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
55static char *
56getdir(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
74krb5_error_code
75krb5_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				   "Cannot create replay cache: %s",
157				   strerror(errno));
158	    do_not_unlink = 1;
159	    goto cleanup;
160
161	default:
162	    retval = KRB5_RC_IO_UNKNOWN;
163	    krb5_set_error_message(context, retval,
164				   "Cannot create replay cache: %s",
165				   strerror(errno));
166	    goto cleanup;
167	}
168    }
169    retval = krb5_rc_io_write(context, d, (krb5_pointer)&rc_vno,
170			      sizeof(rc_vno));
171    if (retval)
172	goto cleanup;
173
174    retval = krb5_rc_io_sync(context, d);
175
176 cleanup:
177    if (retval) {
178	if (d->fn) {
179	    if (!do_not_unlink)
180		(void) unlink(d->fn);
181	    FREE_RC(d->fn);
182	    d->fn = NULL;
183	}
184	if (d->fd != -1) {
185	  (void) close(d->fd);
186	}
187    }
188    return retval;
189}
190
191static krb5_error_code
192krb5_rc_io_open_internal(krb5_context context, krb5_rc_iostuff *d, char *fn,
193			 char* full_pathname)
194{
195    krb5_int16 rc_vno;
196    krb5_error_code retval = 0;
197    int do_not_unlink = 1;
198    struct stat lstatb, fstatb;
199    int use_errno = 0;
200    char *dir;
201    size_t dirlen;
202
203    GETDIR;
204    if (fn[0] == '/') {
205	d->fn = strdup(fn);
206	if (d->fn == NULL)
207		return (KRB5_RC_IO_MALLOC);
208    } else {
209	if (!(d->fn = malloc(strlen(fn) + dirlen + 1)))
210	    return KRB5_RC_IO_MALLOC;
211	(void) strcpy(d->fn, dir);
212	(void) strcat(d->fn, PATH_SEPARATOR);
213	(void) strcat(d->fn, fn);
214    }
215
216    /* Solaris: BEGIN made changes to be safer and better code structure */
217    if ((d->fd = THREEPARAMOPEN(d->fn, O_RDWR|O_BINARY, 0600)) == -1) {
218	use_errno = 1;
219	goto cleanup;
220    }
221
222    do_not_unlink = 0;
223    if (fstat(d->fd, &fstatb) == 0) {
224#ifndef NO_USERID
225	uid_t me;
226
227	me = geteuid();
228	/* must be owned by this user, to prevent some security problems with
229	 * other users modifying replay cache stuff and must be a regular file
230	 */
231	if ((fstatb.st_uid != me) || ((fstatb.st_mode & S_IFMT) != S_IFREG)) {
232	    retval = KRB5_RC_IO_PERM;
233	    goto cleanup;
234	}
235#else
236	/* make sure the rcache is a regular file */
237	if (((fstatb.st_mode & S_IFMT) != S_IFREG)) {
238	    retval = KRB5_RC_IO_PERM;
239
240	    goto cleanup;
241	}
242#endif
243	if (lstat(d->fn, &lstatb) == 0) {
244	    /* Make sure fstat() and lstat() have accessed the same file */
245	    if ((lstatb.st_ino != fstatb.st_ino) ||
246		    (lstatb.st_dev != fstatb.st_dev)) {
247		retval = KRB5_RC_IO_PERM;
248		goto cleanup;
249	    }
250
251	    if ((lstatb.st_mode & S_IFMT) == S_IFLNK) {
252		/* if we accessed the rcache via a symlink, bail out */
253		syslog(LOG_ERR, "Error, krb replay cache %s is a symlink "
254			   "and should be removed.\n", d->fn);
255		retval = KRB5_RC_IO_PERM;
256		goto cleanup;
257	    }
258	}
259	else {
260	    use_errno = 1;
261	    goto cleanup;
262	}
263    }
264    else {
265	use_errno = 1;
266	goto cleanup;
267    }
268
269    do_not_unlink = 0;
270    retval = krb5_rc_io_read(context, d, (krb5_pointer) &rc_vno,
271			     sizeof(rc_vno));
272    if (retval)
273	goto cleanup;
274
275    if (ntohs(rc_vno) != KRB5_RC_VNO)
276	retval = KRB5_RCACHE_BADVNO;
277
278 cleanup:
279    if (use_errno) {
280	switch(errno)
281	{
282	    case EFBIG:
283#ifdef EDQUOT
284	    case EDQUOT:
285#endif
286	    case ENOSPC:
287		retval = KRB5_RC_IO_SPACE;
288		break;
289
290	    case EIO:
291		retval = KRB5_RC_IO_IO;
292		break;
293
294	    case EPERM:
295	    case EACCES:
296	    case EROFS:
297		retval = KRB5_RC_IO_PERM;
298	    	krb5_set_error_message (context, retval,
299			    "Cannot open replay cache %s: %s",
300			    d->fn, strerror(errno));
301		break;
302
303	    default:
304		retval = KRB5_RC_IO_UNKNOWN;
305		krb5_set_error_message (context, retval,
306			    "Cannot open replay cache %s: %s",
307			    d->fn, strerror(errno));
308	}
309    }
310    /* Solaris: END made changes to be safer and better code structure */
311    if (retval) {
312	if (d->fn) {
313	    if (!do_not_unlink)
314		(void) unlink(d->fn);
315	    FREE_RC(d->fn);
316	    d->fn = NULL;
317	}
318	if (d->fd >= 0)
319	     (void) close(d->fd);
320    }
321    return retval;
322}
323
324krb5_error_code
325krb5_rc_io_open(krb5_context context, krb5_rc_iostuff *d, char *fn)
326{
327    return krb5_rc_io_open_internal(context, d, fn, NULL);
328}
329
330krb5_error_code
331krb5_rc_io_move(krb5_context context, krb5_rc_iostuff *new1,
332		krb5_rc_iostuff *old)
333{
334#if defined(_WIN32) || defined(__CYGWIN__)
335    char *new_fn = NULL;
336    char *old_fn = NULL;
337    off_t offset = 0;
338    krb5_error_code retval = 0;
339    /*
340     * Initial work around provided by Tom Sanfilippo to work around
341     * poor Windows emulation of POSIX functions.  Rename and dup has
342     * different semantics!
343     *
344     * Additional fixes and explanation provided by dalmeida@mit.edu:
345     *
346     * First, we save the offset of "old".  Then, we close and remove
347     * the "new" file so we can do the rename.  We also close "old" to
348     * make sure the rename succeeds (though that might not be
349     * necessary on some systems).
350     *
351     * Next, we do the rename.  If all goes well, we seek the "new"
352     * file to the position "old" was at.
353     *
354     * --- WARNING!!! ---
355     *
356     * Since "old" is now gone, we mourn its disappearance, but we
357     * cannot emulate that Unix behavior...  THIS BEHAVIOR IS
358     * DIFFERENT FROM UNIX.  However, it is ok because this function
359     * gets called such that "old" gets closed right afterwards.
360     */
361    offset = lseek(old->fd, 0, SEEK_CUR);
362
363    new_fn = new1->fn;
364    new1->fn = NULL;
365    close(new1->fd);
366    new1->fd = -1;
367
368    unlink(new_fn);
369
370    old_fn = old->fn;
371    old->fn = NULL;
372    close(old->fd);
373    old->fd = -1;
374
375    if (rename(old_fn, new_fn) == -1) { /* MUST be atomic! */
376	retval = KRB5_RC_IO_UNKNOWN;
377	goto cleanup;
378    }
379
380    retval = krb5_rc_io_open_internal(context, new1, 0, new_fn);
381    if (retval)
382	goto cleanup;
383
384    if (lseek(new1->fd, offset, SEEK_SET) == -1) {
385	retval = KRB5_RC_IO_UNKNOWN;
386	goto cleanup;
387    }
388
389 cleanup:
390    free(new_fn);
391    free(old_fn);
392    return retval;
393#else
394    char *fn = NULL;
395    if (rename(old->fn, new1->fn) == -1) /* MUST be atomic! */
396	return KRB5_RC_IO_UNKNOWN;
397    fn = new1->fn;
398    new1->fn = NULL;		/* avoid clobbering */
399    (void) krb5_rc_io_close(context, new1);
400    new1->fn = fn;
401    new1->fd = dup(old->fd);
402    return 0;
403#endif
404}
405
406krb5_error_code
407krb5_rc_io_write(krb5_context context, krb5_rc_iostuff *d, krb5_pointer buf,
408		 unsigned int num)
409{
410    if (write(d->fd, (char *) buf, num) == -1)
411	switch(errno)
412	{
413#ifdef EDQUOT
414	case EDQUOT:
415#endif
416	case EFBIG:
417	case ENOSPC:
418	    krb5_set_error_message (context, KRB5_RC_IO_SPACE,
419				    "Can't write to replay cache: %s",
420				    strerror(errno));
421	    return KRB5_RC_IO_SPACE;
422	case EIO:
423	    krb5_set_error_message (context, KRB5_RC_IO_IO,
424				    "Can't write to replay cache: %s",
425				    strerror(errno));
426	    return KRB5_RC_IO_IO;
427	case EBADF:
428	default:
429	    krb5_set_error_message (context, KRB5_RC_IO_UNKNOWN,
430				    "Can't write to replay cache: %s",
431				    strerror(errno));
432	    return KRB5_RC_IO_UNKNOWN;
433	}
434    return 0;
435}
436
437krb5_error_code
438krb5_rc_io_sync(krb5_context context, krb5_rc_iostuff *d)
439{
440#if defined(_WIN32)
441#ifndef fsync
442#define fsync _commit
443#endif
444#endif
445    if (fsync(d->fd) == -1) {
446	switch(errno)
447	{
448	case EBADF: return KRB5_RC_IO_UNKNOWN;
449	case EIO: return KRB5_RC_IO_IO;
450	default:
451	    krb5_set_error_message(context, KRB5_RC_IO_UNKNOWN,
452				   "Cannot sync replay cache file: %s",
453				   strerror(errno));
454	    return KRB5_RC_IO_UNKNOWN;
455	}
456    }
457    return 0;
458}
459
460/*ARGSUSED*/
461krb5_error_code
462krb5_rc_io_read(krb5_context context, krb5_rc_iostuff *d, krb5_pointer buf,
463		unsigned int num)
464{
465    int count;
466    if ((count = read(d->fd, (char *) buf, num)) == -1)
467	switch(errno)
468	{
469	case EIO: return KRB5_RC_IO_IO;
470	case EBADF:
471	default:
472	    krb5_set_error_message(context, KRB5_RC_IO_UNKNOWN,
473				   "Can't read from replay cache: %s",
474				   strerror(errno));
475	    return KRB5_RC_IO_UNKNOWN;
476	}
477    if (count == 0)
478	return KRB5_RC_IO_EOF;
479    return 0;
480}
481
482/*ARGSUSED*/
483krb5_error_code
484krb5_rc_io_close(krb5_context context, krb5_rc_iostuff *d)
485{
486    if (d->fn != NULL) {
487	FREE_RC(d->fn);
488	d->fn = NULL;
489    }
490    if (d->fd != -1) {
491	if (close(d->fd) == -1) /* can't happen */
492	    return KRB5_RC_IO_UNKNOWN;
493	d->fd = -1;
494    }
495    return 0;
496}
497
498/*ARGSUSED*/
499krb5_error_code
500krb5_rc_io_destroy(krb5_context context, krb5_rc_iostuff *d)
501{
502    if (unlink(d->fn) == -1)
503	switch(errno)
504	{
505	case EIO:
506	    krb5_set_error_message(context, KRB5_RC_IO_IO,
507				   "Can't destroy replay cache: %s",
508				   strerror(errno));
509	    return KRB5_RC_IO_IO;
510	case EPERM:
511	case EBUSY:
512	case EROFS:
513	    krb5_set_error_message(context, KRB5_RC_IO_PERM,
514				   "Can't destroy replay cache: %s",
515				   strerror(errno));
516	    return KRB5_RC_IO_PERM;
517	case EBADF:
518	default:
519	    krb5_set_error_message(context, KRB5_RC_IO_UNKNOWN,
520				   "Can't destroy replay cache: %s",
521				   strerror(errno));
522	    return KRB5_RC_IO_UNKNOWN;
523	}
524    return 0;
525}
526
527/*ARGSUSED*/
528krb5_error_code
529krb5_rc_io_mark(krb5_context context, krb5_rc_iostuff *d)
530{
531    d->mark = lseek(d->fd, (off_t) 0, SEEK_CUR); /* can't fail */
532    return 0;
533}
534
535/*ARGSUSED*/
536krb5_error_code
537krb5_rc_io_unmark(krb5_context context, krb5_rc_iostuff *d)
538{
539    (void) lseek(d->fd, d->mark, SEEK_SET); /* if it fails, tough luck */
540    return 0;
541}
542
543/*ARGSUSED*/
544long
545krb5_rc_io_size(krb5_context context, krb5_rc_iostuff *d)
546{
547    struct stat statb;
548
549    if (fstat(d->fd, &statb) == 0)
550	return statb.st_size;
551    else
552	return 0;
553}
554