xref: /illumos-gate/usr/src/lib/gss_mechs/mech_krb5/krb5/rcache/rc_io.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
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 
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      /* 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
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 				   "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 
191 static krb5_error_code
192 krb5_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 
324 krb5_error_code
325 krb5_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 
330 krb5_error_code
331 krb5_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 
406 krb5_error_code
407 krb5_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 
437 krb5_error_code
438 krb5_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*/
461 krb5_error_code
462 krb5_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*/
483 krb5_error_code
484 krb5_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*/
499 krb5_error_code
500 krb5_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*/
528 krb5_error_code
529 krb5_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*/
536 krb5_error_code
537 krb5_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*/
544 long
545 krb5_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