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_file.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 * An implementation for the default replay cache type. 18 */ 19 /* Solaris Kerberos */ 20 #define FREE_RC(x) ((void) free((char *) (x))) 21 #include "rc_common.h" 22 #include "rc_file.h" 23 24 /* 25 * Solaris: The NOIOSTUFF macro has been taken out for the Solaris version 26 * of this module, because this has been split into a separate mem rcache. 27 */ 28 29 /* of course, list is backwards from file */ 30 /* hash could be forwards since we have to search on match, but naaaah */ 31 32 static int 33 rc_store(krb5_context context, krb5_rcache id, krb5_donot_replay *rep) 34 { 35 struct file_data *t = (struct file_data *)id->data; 36 int rephash; 37 struct authlist *ta; 38 krb5_int32 time; 39 40 rephash = hash(rep, t->hsize); 41 42 /* Solaris: calling krb_timeofday() here, once for better perf. */ 43 krb5_timeofday(context, &time); 44 45 /* Solaris: calling alive() on rep since it doesn't make sense to store an 46 * expired replay. 47 */ 48 if (alive(context, rep, t->lifespan, time) == CMP_EXPIRED){ 49 return CMP_EXPIRED; 50 } 51 52 for (ta = t->h[rephash]; ta; ta = ta->nh) { 53 switch(cmp(&ta->rep, rep)) 54 { 55 case CMP_REPLAY: 56 return CMP_REPLAY; 57 case CMP_HOHUM: 58 if (alive(context, &ta->rep, t->lifespan, time) == CMP_EXPIRED) 59 t->nummisses++; 60 else 61 t->numhits++; 62 break; 63 default: 64 ; /* wtf? */ 65 } 66 } 67 68 if (!(ta = (struct authlist *) malloc(sizeof(struct authlist)))) 69 return CMP_MALLOC; 70 ta->rep = *rep; 71 if (!(ta->rep.client = strdup(rep->client))) { 72 FREE_RC(ta); 73 return CMP_MALLOC; 74 } 75 if (!(ta->rep.server = strdup(rep->server))) { 76 FREE_RC(ta->rep.client); 77 FREE_RC(ta); 78 return CMP_MALLOC; 79 } 80 ta->na = t->a; t->a = ta; 81 ta->nh = t->h[rephash]; t->h[rephash] = ta; 82 83 return CMP_HOHUM; 84 } 85 86 /*ARGSUSED*/ 87 char * KRB5_CALLCONV 88 krb5_rc_file_get_name(krb5_context context, krb5_rcache id) 89 { 90 return ((struct file_data *) (id->data))->name; 91 } 92 93 /*ARGSUSED*/ 94 krb5_error_code KRB5_CALLCONV 95 krb5_rc_file_get_span(krb5_context context, krb5_rcache id, 96 krb5_deltat *lifespan) 97 { 98 krb5_error_code err; 99 struct file_data *t; 100 101 err = k5_mutex_lock(&id->lock); 102 if (err) 103 return err; 104 t = (struct file_data *) id->data; 105 *lifespan = t->lifespan; 106 k5_mutex_unlock(&id->lock); 107 return 0; 108 } 109 110 static krb5_error_code KRB5_CALLCONV 111 krb5_rc_file_init_locked(krb5_context context, krb5_rcache id, krb5_deltat lifespan) 112 { 113 struct file_data *t = (struct file_data *)id->data; 114 krb5_error_code retval; 115 116 t->lifespan = lifespan ? lifespan : context->clockskew; 117 /* default to clockskew from the context */ 118 if ((retval = krb5_rc_io_creat(context, &t->d, &t->name))) { 119 return retval; 120 } 121 if ((krb5_rc_io_write(context, &t->d, 122 (krb5_pointer) &t->lifespan, sizeof(t->lifespan)) 123 || krb5_rc_io_sync(context, &t->d))) { 124 return KRB5_RC_IO; 125 } 126 return 0; 127 } 128 129 krb5_error_code KRB5_CALLCONV 130 krb5_rc_file_init(krb5_context context, krb5_rcache id, krb5_deltat lifespan) 131 { 132 krb5_error_code retval; 133 134 retval = k5_mutex_lock(&id->lock); 135 if (retval) 136 return retval; 137 retval = krb5_rc_file_init_locked(context, id, lifespan); 138 k5_mutex_unlock(&id->lock); 139 return retval; 140 } 141 142 /* Called with the mutex already locked. */ 143 krb5_error_code 144 krb5_rc_file_close_no_free(krb5_context context, krb5_rcache id) 145 { 146 struct file_data *t = (struct file_data *)id->data; 147 struct authlist *q; 148 149 if (t->h) 150 FREE_RC(t->h); 151 if (t->name) 152 FREE_RC(t->name); 153 while ((q = t->a)) 154 { 155 t->a = q->na; 156 FREE_RC(q->rep.client); 157 FREE_RC(q->rep.server); 158 FREE_RC(q); 159 } 160 if (t->d.fd >= 0) 161 (void) krb5_rc_io_close(context, &t->d); 162 FREE_RC(t); 163 id->data = NULL; 164 return 0; 165 } 166 167 krb5_error_code KRB5_CALLCONV 168 krb5_rc_file_close(krb5_context context, krb5_rcache id) 169 { 170 krb5_error_code retval; 171 retval = k5_mutex_lock(&id->lock); 172 if (retval) 173 return retval; 174 krb5_rc_file_close_no_free(context, id); 175 k5_mutex_unlock(&id->lock); 176 k5_mutex_destroy(&id->lock); 177 free(id); 178 return 0; 179 } 180 181 krb5_error_code KRB5_CALLCONV 182 krb5_rc_file_destroy(krb5_context context, krb5_rcache id) 183 { 184 if (krb5_rc_io_destroy(context, &((struct file_data *) (id->data))->d)) 185 return KRB5_RC_IO; 186 return krb5_rc_file_close(context, id); 187 } 188 189 /*ARGSUSED*/ 190 krb5_error_code KRB5_CALLCONV 191 krb5_rc_file_resolve(krb5_context context, krb5_rcache id, char *name) 192 { 193 struct file_data *t = 0; 194 krb5_error_code retval; 195 196 /* allocate id? no */ 197 if (!(t = (struct file_data *) malloc(sizeof(struct file_data)))) 198 return KRB5_RC_MALLOC; 199 id->data = (krb5_pointer) t; 200 memset(t, 0, sizeof(struct file_data)); 201 if (name) { 202 t->name = malloc(strlen(name)+1); 203 if (!t->name) { 204 retval = KRB5_RC_MALLOC; 205 goto cleanup; 206 } 207 strcpy(t->name, name); 208 } else 209 t->name = 0; 210 t->numhits = t->nummisses = 0; 211 t->hsize = HASHSIZE; /* no need to store---it's memory-only */ 212 t->h = (struct authlist **) malloc(t->hsize*sizeof(struct authlist *)); 213 if (!t->h) { 214 retval = KRB5_RC_MALLOC; 215 goto cleanup; 216 } 217 memset(t->h, 0, t->hsize*sizeof(struct authlist *)); 218 t->a = (struct authlist *) 0; 219 t->d.fd = -1; 220 t->recovering = 0; 221 return 0; 222 223 cleanup: 224 if (t) { 225 if (t->name) 226 krb5_xfree(t->name); 227 if (t->h) 228 krb5_xfree(t->h); 229 krb5_xfree(t); 230 id->data = NULL; 231 } 232 return retval; 233 } 234 235 /*ARGSUSED*/ 236 void 237 krb5_rc_free_entry(krb5_context context, krb5_donot_replay **rep) 238 { 239 krb5_donot_replay *rp = *rep; 240 241 *rep = NULL; 242 if (rp) 243 { 244 if (rp->client) 245 free(rp->client); 246 247 if (rp->server) 248 free(rp->server); 249 rp->client = NULL; 250 rp->server = NULL; 251 free(rp); 252 } 253 } 254 255 static krb5_error_code 256 krb5_rc_io_fetch(krb5_context context, struct file_data *t, 257 krb5_donot_replay *rep, int maxlen) 258 { 259 unsigned int len; 260 krb5_error_code retval; 261 262 rep->client = rep->server = 0; 263 264 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &len, 265 sizeof(len)); 266 if (retval) 267 return retval; 268 269 if ((len <= 0) || (len >= maxlen)) 270 return KRB5_RC_IO_EOF; 271 272 rep->client = malloc (len); 273 if (!rep->client) 274 return KRB5_RC_MALLOC; 275 276 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) rep->client, len); 277 if (retval) 278 goto errout; 279 280 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &len, 281 sizeof(len)); 282 if (retval) 283 goto errout; 284 285 if ((len <= 0) || (len >= maxlen)) { 286 retval = KRB5_RC_IO_EOF; 287 goto errout; 288 } 289 290 rep->server = malloc (len); 291 if (!rep->server) { 292 retval = KRB5_RC_MALLOC; 293 goto errout; 294 } 295 296 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) rep->server, len); 297 if (retval) 298 goto errout; 299 300 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &rep->cusec, 301 sizeof(rep->cusec)); 302 if (retval) 303 goto errout; 304 305 retval = krb5_rc_io_read(context, &t->d, (krb5_pointer) &rep->ctime, 306 sizeof(rep->ctime)); 307 if (retval) 308 goto errout; 309 310 return 0; 311 312 errout: 313 if (rep->client) 314 krb5_xfree(rep->client); 315 if (rep->server) 316 krb5_xfree(rep->server); 317 rep->client = rep->server = 0; 318 return retval; 319 } 320 321 322 static krb5_error_code 323 krb5_rc_file_expunge_locked(krb5_context context, krb5_rcache id); 324 325 static krb5_error_code 326 krb5_rc_file_recover_locked(krb5_context context, krb5_rcache id) 327 { 328 struct file_data *t = (struct file_data *)id->data; 329 krb5_donot_replay *rep = 0; 330 krb5_error_code retval; 331 long max_size; 332 int expired_entries = 0; 333 334 if ((retval = krb5_rc_io_open(context, &t->d, t->name))) { 335 return retval; 336 } 337 338 t->recovering = 1; 339 340 max_size = krb5_rc_io_size(context, &t->d); 341 342 rep = NULL; 343 if (krb5_rc_io_read(context, &t->d, (krb5_pointer) &t->lifespan, 344 sizeof(t->lifespan))) { 345 retval = KRB5_RC_IO; 346 goto io_fail; 347 } 348 349 if (!(rep = (krb5_donot_replay *) malloc(sizeof(krb5_donot_replay)))) { 350 retval = KRB5_RC_MALLOC; 351 goto io_fail; 352 } 353 rep->client = NULL; 354 rep->server = NULL; 355 356 /* now read in each auth_replay and insert into table */ 357 for (;;) { 358 if (krb5_rc_io_mark(context, &t->d)) { 359 retval = KRB5_RC_IO; 360 goto io_fail; 361 } 362 363 retval = krb5_rc_io_fetch (context, t, rep, (int) max_size); 364 365 if (retval == KRB5_RC_IO_EOF) 366 break; 367 else if (retval != 0) 368 goto io_fail; 369 370 /* Solaris: made the change below for better perf. */ 371 switch (rc_store(context, id, rep)) { 372 case CMP_EXPIRED: 373 expired_entries++; 374 break; 375 case CMP_MALLOC: 376 retval = KRB5_RC_MALLOC; 377 goto io_fail; 378 break; 379 } 380 /* 381 * free fields allocated by rc_io_fetch 382 */ 383 FREE_RC(rep->server); 384 FREE_RC(rep->client); 385 rep->server = 0; 386 rep->client = 0; 387 } 388 retval = 0; 389 krb5_rc_io_unmark(context, &t->d); 390 /* 391 * An automatic expunge here could remove the need for 392 * mark/unmark but that would be inefficient. 393 */ 394 io_fail: 395 krb5_rc_free_entry(context, &rep); 396 if (retval) 397 krb5_rc_io_close(context, &t->d); 398 else if (expired_entries > EXCESSREPS) 399 retval = krb5_rc_file_expunge_locked(context, id); 400 t->recovering = 0; 401 return retval; 402 } 403 404 krb5_error_code KRB5_CALLCONV 405 krb5_rc_file_recover(krb5_context context, krb5_rcache id) 406 { 407 krb5_error_code ret; 408 ret = k5_mutex_lock(&id->lock); 409 if (ret) 410 return ret; 411 ret = krb5_rc_file_recover_locked(context, id); 412 k5_mutex_unlock(&id->lock); 413 return ret; 414 } 415 416 krb5_error_code KRB5_CALLCONV 417 krb5_rc_file_recover_or_init(krb5_context context, krb5_rcache id, 418 krb5_deltat lifespan) 419 { 420 krb5_error_code retval; 421 422 retval = k5_mutex_lock(&id->lock); 423 if (retval) 424 return retval; 425 retval = krb5_rc_file_recover_locked(context, id); 426 if (retval) 427 retval = krb5_rc_file_init_locked(context, id, lifespan); 428 k5_mutex_unlock(&id->lock); 429 return retval; 430 } 431 432 static krb5_error_code 433 krb5_rc_io_store(krb5_context context, struct file_data *t, 434 krb5_donot_replay *rep) 435 { 436 int clientlen, serverlen, len; 437 char *buf, *ptr; 438 krb5_error_code ret; 439 440 clientlen = strlen(rep->client) + 1; 441 serverlen = strlen(rep->server) + 1; 442 len = sizeof(clientlen) + clientlen + sizeof(serverlen) + serverlen + 443 sizeof(rep->cusec) + sizeof(rep->ctime); 444 buf = malloc(len); 445 if (buf == 0) 446 return KRB5_RC_MALLOC; 447 ptr = buf; 448 memcpy(ptr, &clientlen, sizeof(clientlen)); ptr += sizeof(clientlen); 449 memcpy(ptr, rep->client, clientlen); ptr += clientlen; 450 memcpy(ptr, &serverlen, sizeof(serverlen)); ptr += sizeof(serverlen); 451 memcpy(ptr, rep->server, serverlen); ptr += serverlen; 452 memcpy(ptr, &rep->cusec, sizeof(rep->cusec)); ptr += sizeof(rep->cusec); 453 memcpy(ptr, &rep->ctime, sizeof(rep->ctime)); ptr += sizeof(rep->ctime); 454 455 ret = krb5_rc_io_write(context, &t->d, buf, len); 456 free(buf); 457 return ret; 458 } 459 460 static krb5_error_code krb5_rc_file_expunge_locked(krb5_context, krb5_rcache); 461 462 krb5_error_code KRB5_CALLCONV 463 krb5_rc_file_store(krb5_context context, krb5_rcache id, krb5_donot_replay *rep) 464 { 465 krb5_error_code ret; 466 struct file_data *t; 467 468 ret = k5_mutex_lock(&id->lock); 469 if (ret) 470 return ret; 471 472 t = (struct file_data *)id->data; 473 474 switch(rc_store(context, id,rep)) { 475 case CMP_MALLOC: 476 k5_mutex_unlock(&id->lock); 477 return KRB5_RC_MALLOC; 478 case CMP_REPLAY: 479 k5_mutex_unlock(&id->lock); 480 return KRB5KRB_AP_ERR_REPEAT; 481 case CMP_EXPIRED: 482 k5_mutex_unlock(&id->lock); 483 return KRB5KRB_AP_ERR_SKEW; 484 case CMP_HOHUM: break; 485 default: /* wtf? */ ; 486 } 487 ret = krb5_rc_io_store (context, t, rep); 488 if (ret) { 489 k5_mutex_unlock(&id->lock); 490 return ret; 491 } 492 /* Shall we automatically expunge? */ 493 if (t->nummisses > t->numhits + EXCESSREPS) 494 { 495 ret = krb5_rc_file_expunge_locked(context, id); 496 k5_mutex_unlock(&id->lock); 497 return ret; 498 } 499 else 500 { 501 if (krb5_rc_io_sync(context, &t->d)) { 502 k5_mutex_unlock(&id->lock); 503 return KRB5_RC_IO; 504 } 505 } 506 k5_mutex_unlock(&id->lock); 507 return 0; 508 } 509 510 static krb5_error_code 511 krb5_rc_file_expunge_locked(krb5_context context, krb5_rcache id) 512 { 513 struct file_data *t = (struct file_data *)id->data; 514 struct authlist *q; 515 char *name; 516 krb5_error_code retval = 0; 517 krb5_rcache tmp; 518 krb5_deltat lifespan = t->lifespan; /* save original lifespan */ 519 520 if (! t->recovering) { 521 name = t->name; 522 t->name = 0; /* Clear name so it isn't freed */ 523 (void) krb5_rc_file_close_no_free(context, id); 524 retval = krb5_rc_file_resolve(context, id, name); 525 free(name); 526 if (retval) 527 return retval; 528 retval = krb5_rc_file_recover_locked(context, id); 529 if (retval) 530 return retval; 531 t = (struct file_data *)id->data; /* point to recovered cache */ 532 } 533 534 tmp = (krb5_rcache) malloc(sizeof(*tmp)); 535 if (!tmp) 536 return ENOMEM; 537 538 retval = k5_mutex_init(&tmp->lock); 539 if (retval) { 540 free(tmp); 541 return retval; 542 } 543 544 tmp->ops = &krb5_rc_file_ops; 545 if ((retval = krb5_rc_file_resolve(context, tmp, 0)) != 0) 546 goto out; 547 if ((retval = krb5_rc_initialize(context, tmp, lifespan)) != 0) 548 goto out; 549 for (q = t->a;q;q = q->na) { 550 if (krb5_rc_io_store (context, (struct file_data *)tmp->data, &q->rep)) { 551 retval = KRB5_RC_IO; 552 goto out; 553 } 554 } 555 if (krb5_rc_io_sync(context, &t->d)) { 556 retval = KRB5_RC_IO; 557 goto out; 558 } 559 if (krb5_rc_io_move(context, &t->d, &((struct file_data *)tmp->data)->d)) 560 retval = KRB5_RC_IO; 561 562 out: 563 /* 564 * krb5_rc_file_close() will free the tmp struct and it's members that the 565 * previous functions had allocated. 566 */ 567 (void) krb5_rc_file_close(context, tmp); 568 569 return (retval); 570 } 571 572 krb5_error_code KRB5_CALLCONV 573 krb5_rc_file_expunge(krb5_context context, krb5_rcache id) 574 { 575 krb5_error_code ret; 576 ret = k5_mutex_lock(&id->lock); 577 if (ret) 578 return ret; 579 ret = krb5_rc_file_expunge_locked(context, id); 580 k5_mutex_unlock(&id->lock); 581 return ret; 582 } 583