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