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