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