xref: /illumos-gate/usr/src/lib/gss_mechs/mech_krb5/krb5/rcache/rc_io.c (revision 7c478bd95313f5f23a4c958a745db2134aa03244)
1 /*
2  * Copyright 2004 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(_MSDOS) || 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_) && !defined(HAVE_MACSOCK_H)
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 #ifndef HAVE_ERRNO
51 extern int errno; /* this should be in errno.h, but isn't on some systems */
52 #endif
53 
54 #define free(x) ((void) free((char *) (x)))
55 #define UNIQUE getpid() /* hopefully unique number */
56 
57 static int dirlen = 0;
58 static char *dir;
59 
60 /* The do ... while(0) is required to insure that GETDIR looks like a
61    single statement in all situations (just {}'s may cause troubles in
62    certain situations, such as nested if/else clauses. */
63 
64 static int false = 0;
65 #define GETDIR do { if (!dirlen) getdir(); } while(false)
66 
67 static void
68 getdir(void)
69 {
70 #if defined(_MSDOS) || defined(_WIN32)
71      if (!(dir = getenv("TEMP")))
72 	 if (!(dir = getenv("TMP")))
73 	     dir = "C:\\";
74 #else
75      if (geteuid() == 0)
76 	 dir = "/var/krb5/rcache/root";
77      else
78 	 dir = "/var/krb5/rcache";
79 #endif
80    dirlen = strlen(dir) + sizeof(PATH_SEPARATOR);
81 }
82 
83 krb5_error_code krb5_rc_io_creat (context, d, fn)
84     krb5_context context;
85     krb5_rc_iostuff *d;
86     char **fn;
87 {
88  char *c;
89  krb5_int16 rc_vno = htons(KRB5_RC_VNO);
90  krb5_error_code retval;
91 
92  GETDIR;
93  if (fn && *fn)
94   {
95    if (*fn[0] == '/') {
96 	d->fn = strdup(*fn);
97 	if (d->fn == NULL)
98 		return (KRB5_RC_IO_MALLOC);
99    } else {
100 	if (!(d->fn = malloc(strlen(*fn) + dirlen + 1)))
101 		return KRB5_RC_IO_MALLOC;
102 	(void) strcpy(d->fn, dir);
103 	(void) strcat(d->fn, PATH_SEPARATOR);
104 	(void) strcat(d->fn,*fn);
105    }
106    d->fd = THREEPARAMOPEN(d->fn,O_WRONLY|O_CREAT|O_TRUNC|O_EXCL|O_BINARY, 0600);
107   }
108  else
109   {
110       /* %d is max 11 digits (-, 10 digits of 32-bit number)
111 	 * 11 + /krb5_RC + aaa = 24, +6 for slop */
112    if (!(d->fn = malloc(30 + dirlen)))
113      return KRB5_RC_IO_MALLOC;
114    if (fn)
115      if (!(*fn = malloc(35)))
116       { free(d->fn); return KRB5_RC_IO_MALLOC; }
117    (void) sprintf(d->fn,"%s%skrb5_RC%d",dir,PATH_SEPARATOR,UNIQUE);
118    c = d->fn + strlen(d->fn);
119    (void) strcpy(c,"aaa");
120    while ((d->fd = THREEPARAMOPEN(d->fn,O_WRONLY|O_CREAT|O_TRUNC|O_EXCL|O_BINARY,0600)) == -1)
121     {
122      if ((c[2]++) == 'z')
123       {
124        c[2] = 'a';
125        if ((c[1]++) == 'z')
126 	{
127          c[1] = 'a';
128          if ((c[0]++) == 'z')
129            break; /* sigh */
130         }
131       }
132     }
133    if (fn)
134      (void) strcpy(*fn,d->fn + dirlen);
135   }
136  if (d->fd == -1)
137     {
138    switch(errno)
139     {
140 	case EFBIG:
141 #ifdef EDQUOT
142 	case EDQUOT:
143 #endif
144 	case ENOSPC:
145 	    retval = KRB5_RC_IO_SPACE;
146 	    goto fail;
147 	case EIO:
148 	    retval = KRB5_RC_IO_IO; goto fail;
149 
150 	case EPERM:
151 	case EACCES:
152 	case EROFS:
153 	case EEXIST:
154 	    retval = KRB5_RC_IO_PERM; goto no_unlink;
155 
156 	default:
157 	    retval = KRB5_RC_IO_UNKNOWN; goto fail;
158     }
159     }
160     if (((retval = krb5_rc_io_write(context, d, (krb5_pointer)&rc_vno, sizeof(rc_vno))) != 0) ||
161 	(retval = krb5_rc_io_sync(context, d) != 0))
162     {
163     fail:
164      (void) unlink(d->fn);
165     no_unlink:
166      syslog(LOG_ERR, "Could not create replay cache %s\n", d->fn); /* SUNW */
167      free(d->fn);
168 	d->fn = NULL;
169      (void) close(d->fd);
170      return retval;
171  }
172  return 0;
173 }
174 
175 krb5_error_code krb5_rc_io_open (context, d, fn)
176     krb5_context context;
177     krb5_rc_iostuff *d;
178     char *fn;
179 {
180     krb5_int16 rc_vno;
181     krb5_error_code retval = 0;
182     int do_not_unlink = 1;
183     struct stat lstatb, fstatb;
184     int use_errno = 0;
185 
186     GETDIR;
187     if (fn[0] == '/') {
188 	d->fn = strdup(fn);
189 	if (d->fn == NULL)
190 		return (KRB5_RC_IO_MALLOC);
191     } else {
192 	if (!(d->fn = malloc(strlen(fn) + dirlen + 1)))
193 		return KRB5_RC_IO_MALLOC;
194 	(void) strcpy(d->fn,dir);
195 	(void) strcat(d->fn,PATH_SEPARATOR);
196 	(void) strcat(d->fn,fn);
197     }
198 
199     /* Solaris: BEGIN made changes to be safer and better code structure */
200     if ((d->fd = THREEPARAMOPEN(d->fn, O_RDWR|O_BINARY, 0600)) == -1) {
201 	use_errno = 1;
202 	goto cleanup;
203     }
204 
205     do_not_unlink = 0;
206     if (fstat(d->fd, &fstatb) == 0) {
207 #ifndef NO_USERID
208 	uid_t me;
209 
210 	me = geteuid();
211 	/* must be owned by this user, to prevent some security problems with
212 	 * other users modifying replay cache stuff and must be a regular file
213 	 */
214 	if ((fstatb.st_uid != me) || ((fstatb.st_mode & S_IFMT) != S_IFREG)) {
215 	    retval = KRB5_RC_IO_PERM;
216 	    goto cleanup;
217 	}
218 #else
219 	/* make sure the rcache is a regular file */
220 	if (((fstatb.st_mode & S_IFMT) != S_IFREG)) {
221 	    retval = KRB5_RC_IO_PERM;
222 	    goto cleanup;
223 	}
224 #endif
225 	if (lstat(d->fn, &lstatb) == 0) {
226 	    /* Make sure fstat() and lstat() have accessed the same file */
227 	    if ((lstatb.st_ino != fstatb.st_ino) ||
228 		    (lstatb.st_dev != fstatb.st_dev)) {
229 		retval = KRB5_RC_IO_PERM;
230 		goto cleanup;
231 	    }
232 
233 	    if ((lstatb.st_mode & S_IFMT) == S_IFLNK) {
234 		/* if we accessed the rcache via a symlink, bail out */
235 		syslog(LOG_ERR, "Error, krb replay cache %s is a symlink "
236 			   "and should be removed.\n", d->fn);
237 		retval = KRB5_RC_IO_PERM;
238 		goto cleanup;
239 	    }
240 	}
241 	else {
242 	    use_errno = 1;
243 	    goto cleanup;
244 	}
245     }
246     else {
247 	use_errno = 1;
248 	goto cleanup;
249     }
250 
251     retval = krb5_rc_io_read(context, d, (krb5_pointer) &rc_vno,
252 	    sizeof(rc_vno));
253     if (retval)
254 	goto cleanup;
255 
256     if (ntohs(rc_vno) != KRB5_RC_VNO)
257 	retval = KRB5_RCACHE_BADVNO;
258 
259 cleanup:
260     if (use_errno) {
261 	switch(errno)
262 	{
263 	    case EFBIG:
264 #ifdef EDQUOT
265 	    case EDQUOT:
266 #endif
267 	    case ENOSPC:
268 		retval = KRB5_RC_IO_SPACE;
269 		break;
270 
271 	    case EIO:
272 		retval = KRB5_RC_IO_IO;
273 		break;
274 
275 	    case EPERM:
276 	    case EACCES:
277 	    case EROFS:
278 		retval = KRB5_RC_IO_PERM;
279 		break;
280 
281 	    default:
282 		retval = KRB5_RC_IO_UNKNOWN;
283 	}
284     }
285     /* Solaris: END made changes to be safer and better code structure */
286     if (retval) {
287 	if (d->fn) {
288 	    if (!do_not_unlink) {
289 		/* unlink in case there is a bogus RC. */
290 		(void) unlink(d->fn);
291 	    }
292 	    free(d->fn);
293 	    d->fn = NULL;
294 	}
295 	(void) close(d->fd);
296     }
297     return retval;
298 }
299 
300 krb5_error_code
301 krb5_rc_io_move(krb5_context context, krb5_rc_iostuff *new1,
302 		krb5_rc_iostuff *old)
303 {
304     char *fn = NULL;
305 
306 #if defined(_MSDOS) || defined(_WIN32)
307     /*
308      * Work around provided by Tom Sanfilippo to work around poor
309      * Windows emulation of POSIX functions.  Rename and dup has
310      * different semantics!
311      */
312     char *fn = NULL;
313     GETDIR;
314     close(new->fd);
315     unlink(new->fn);
316     close(old->fd);
317     if (rename(old->fn,new->fn) == -1) /* MUST be atomic! */
318 	return KRB5_RC_IO_UNKNOWN;
319     if (!(fn = malloc(strlen(new->fn) - dirlen + 1)))
320 	return KRB5_RC_IO_MALLOC;
321     strcpy(fn, new->fn + dirlen);
322     krb5_rc_io_close(context, new);
323     krb5_rc_io_open(context, new, fn);
324     free(fn);
325 #else
326     if (rename(old->fn, new1->fn) == -1) /* MUST be atomic! */
327 	return KRB5_RC_IO_UNKNOWN;
328     fn = new1->fn;
329     new1->fn = NULL;		/* avoid clobbering */
330     (void) krb5_rc_io_close(context, new1);
331     new1->fn = fn;
332 #ifdef macintosh
333     new1->fd = fcntl(old->fd, F_DUPFD);
334 #else
335     new1->fd = dup(old->fd);
336 #endif
337 #endif
338     return 0;
339 }
340 
341 /*ARGSUSED*/
342 krb5_error_code krb5_rc_io_write (context, d, buf, num)
343     krb5_context context;
344     krb5_rc_iostuff *d;
345     krb5_pointer buf;
346     int num;
347 {
348  if (write(d->fd,(char *) buf,num) == -1)
349    switch(errno)
350     {
351      case EBADF: return KRB5_RC_IO_UNKNOWN;
352      case EFBIG: return KRB5_RC_IO_SPACE;
353 #ifdef EDQUOT
354      case EDQUOT: return KRB5_RC_IO_SPACE;
355 #endif
356      case ENOSPC: return KRB5_RC_IO_SPACE;
357      case EIO: return KRB5_RC_IO_IO;
358      default: return KRB5_RC_IO_UNKNOWN;
359     }
360  return 0;
361 }
362 
363 /*ARGSUSED*/
364 krb5_error_code krb5_rc_io_sync (context, d)
365     krb5_context context;
366     krb5_rc_iostuff *d;
367 {
368 #if !defined(MSDOS_FILESYSTEM) && !defined(macintosh)
369     if (fsync(d->fd) == -1) {
370       switch(errno)
371       {
372       case EBADF: return KRB5_RC_IO_UNKNOWN;
373       case EIO: return KRB5_RC_IO_IO;
374       default: return KRB5_RC_IO_UNKNOWN;
375       }
376     }
377 #endif
378     return 0;
379 }
380 
381 /*ARGSUSED*/
382 krb5_error_code krb5_rc_io_read (context, d, buf, num)
383     krb5_context context;
384     krb5_rc_iostuff *d;
385     krb5_pointer buf;
386     int num;
387 {
388  int count;
389  if ((count = read(d->fd,(char *) buf,num)) == -1)
390    switch(errno)
391     {
392      case EBADF: return KRB5_RC_IO_UNKNOWN;
393      case EIO: return KRB5_RC_IO_IO;
394      default: return KRB5_RC_IO_UNKNOWN;
395     }
396  if (count == 0)
397      return KRB5_RC_IO_EOF;
398  return 0;
399 }
400 
401 /*ARGSUSED*/
402 krb5_error_code krb5_rc_io_close (context, d)
403     krb5_context context;
404     krb5_rc_iostuff *d;
405 {
406  free(d->fn);
407  d->fn = NULL;
408  if (close(d->fd) == -1) /* can't happen */
409    return KRB5_RC_IO_UNKNOWN;
410  return 0;
411 }
412 
413 /*ARGSUSED*/
414 krb5_error_code krb5_rc_io_destroy (context, d)
415     krb5_context context;
416     krb5_rc_iostuff *d;
417 {
418  if (unlink(d->fn) == -1)
419    switch(errno)
420     {
421      case EBADF: return KRB5_RC_IO_UNKNOWN;
422      case EIO: return KRB5_RC_IO_IO;
423      case EPERM: return KRB5_RC_IO_PERM;
424      case EBUSY: return KRB5_RC_IO_PERM;
425      case EROFS: return KRB5_RC_IO_PERM;
426      default: return KRB5_RC_IO_UNKNOWN;
427     }
428  return 0;
429 }
430 
431 /*ARGSUSED*/
432 krb5_error_code krb5_rc_io_mark (context, d)
433     krb5_context context;
434     krb5_rc_iostuff *d;
435 {
436  d->mark = lseek(d->fd,0,SEEK_CUR); /* can't fail */
437  return 0;
438 }
439 
440 /*ARGSUSED*/
441 krb5_error_code krb5_rc_io_unmark (context, d)
442     krb5_context context;
443     krb5_rc_iostuff *d;
444 {
445  (void) lseek(d->fd,d->mark,SEEK_SET); /* if it fails, tough luck */
446  return 0;
447 }
448 
449 /*ARGSUSED*/
450 long
451 krb5_rc_io_size (context, d)
452     krb5_context context;
453     krb5_rc_iostuff *d;
454 {
455     struct stat statb;
456 
457     if (fstat (d->fd, &statb) == 0)
458 	return statb.st_size;
459     else
460 	return 0;
461 }
462