xref: /illumos-gate/usr/src/lib/gss_mechs/mech_krb5/krb5/rcache/rc_file.c (revision 2b24ab6b3865caeede9eeb9db6b83e1d89dcd1ea)
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