xref: /freebsd/crypto/heimdal/lib/krb5/mcache.c (revision 7fdf597e96a02165cfe22ff357b857d5fa15ed8a)
1 /*
2  * Copyright (c) 1997-2004 Kungliga Tekniska Högskolan
3  * (Royal Institute of Technology, Stockholm, Sweden).
4  * All rights reserved.
5  *
6  * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  *
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * 3. Neither the name of the Institute nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 #include "krb5_locl.h"
37 
38 typedef struct krb5_mcache {
39     char *name;
40     unsigned int refcnt;
41     int dead;
42     krb5_principal primary_principal;
43     struct link {
44 	krb5_creds cred;
45 	struct link *next;
46     } *creds;
47     struct krb5_mcache *next;
48     time_t mtime;
49     krb5_deltat kdc_offset;
50 } krb5_mcache;
51 
52 static HEIMDAL_MUTEX mcc_mutex = HEIMDAL_MUTEX_INITIALIZER;
53 static struct krb5_mcache *mcc_head;
54 
55 #define	MCACHE(X)	((krb5_mcache *)(X)->data.data)
56 
57 #define MISDEAD(X)	((X)->dead)
58 
59 static const char* KRB5_CALLCONV
60 mcc_get_name(krb5_context context,
61 	     krb5_ccache id)
62 {
63     return MCACHE(id)->name;
64 }
65 
66 static krb5_mcache * KRB5_CALLCONV
67 mcc_alloc(const char *name)
68 {
69     krb5_mcache *m, *m_c;
70     int ret = 0;
71 
72     ALLOC(m, 1);
73     if(m == NULL)
74 	return NULL;
75     if(name == NULL)
76 	ret = asprintf(&m->name, "%p", m);
77     else
78 	m->name = strdup(name);
79     if(ret < 0 || m->name == NULL) {
80 	free(m);
81 	return NULL;
82     }
83     /* check for dups first */
84     HEIMDAL_MUTEX_lock(&mcc_mutex);
85     for (m_c = mcc_head; m_c != NULL; m_c = m_c->next)
86 	if (strcmp(m->name, m_c->name) == 0)
87 	    break;
88     if (m_c) {
89 	free(m->name);
90 	free(m);
91 	HEIMDAL_MUTEX_unlock(&mcc_mutex);
92 	return NULL;
93     }
94 
95     m->dead = 0;
96     m->refcnt = 1;
97     m->primary_principal = NULL;
98     m->creds = NULL;
99     m->mtime = time(NULL);
100     m->kdc_offset = 0;
101     m->next = mcc_head;
102     mcc_head = m;
103     HEIMDAL_MUTEX_unlock(&mcc_mutex);
104     return m;
105 }
106 
107 static krb5_error_code KRB5_CALLCONV
108 mcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
109 {
110     krb5_mcache *m;
111 
112     HEIMDAL_MUTEX_lock(&mcc_mutex);
113     for (m = mcc_head; m != NULL; m = m->next)
114 	if (strcmp(m->name, res) == 0)
115 	    break;
116     HEIMDAL_MUTEX_unlock(&mcc_mutex);
117 
118     if (m != NULL) {
119 	m->refcnt++;
120 	(*id)->data.data = m;
121 	(*id)->data.length = sizeof(*m);
122 	return 0;
123     }
124 
125     m = mcc_alloc(res);
126     if (m == NULL) {
127 	krb5_set_error_message(context, KRB5_CC_NOMEM,
128 			       N_("malloc: out of memory", ""));
129 	return KRB5_CC_NOMEM;
130     }
131 
132     (*id)->data.data = m;
133     (*id)->data.length = sizeof(*m);
134 
135     return 0;
136 }
137 
138 
139 static krb5_error_code KRB5_CALLCONV
140 mcc_gen_new(krb5_context context, krb5_ccache *id)
141 {
142     krb5_mcache *m;
143 
144     m = mcc_alloc(NULL);
145 
146     if (m == NULL) {
147 	krb5_set_error_message(context, KRB5_CC_NOMEM,
148 			       N_("malloc: out of memory", ""));
149 	return KRB5_CC_NOMEM;
150     }
151 
152     (*id)->data.data = m;
153     (*id)->data.length = sizeof(*m);
154 
155     return 0;
156 }
157 
158 static krb5_error_code KRB5_CALLCONV
159 mcc_initialize(krb5_context context,
160 	       krb5_ccache id,
161 	       krb5_principal primary_principal)
162 {
163     krb5_mcache *m = MCACHE(id);
164     m->dead = 0;
165     m->mtime = time(NULL);
166     return krb5_copy_principal (context,
167 				primary_principal,
168 				&m->primary_principal);
169 }
170 
171 static int
172 mcc_close_internal(krb5_mcache *m)
173 {
174     if (--m->refcnt != 0)
175 	return 0;
176 
177     if (MISDEAD(m)) {
178 	free (m->name);
179 	return 1;
180     }
181     return 0;
182 }
183 
184 static krb5_error_code KRB5_CALLCONV
185 mcc_close(krb5_context context,
186 	  krb5_ccache id)
187 {
188     if (mcc_close_internal(MCACHE(id)))
189 	krb5_data_free(&id->data);
190     return 0;
191 }
192 
193 static krb5_error_code KRB5_CALLCONV
194 mcc_destroy(krb5_context context,
195 	    krb5_ccache id)
196 {
197     krb5_mcache **n, *m = MCACHE(id);
198     struct link *l;
199 
200     if (m->refcnt == 0)
201 	krb5_abortx(context, "mcc_destroy: refcnt already 0");
202 
203     if (!MISDEAD(m)) {
204 	/* if this is an active mcache, remove it from the linked
205            list, and free all data */
206 	HEIMDAL_MUTEX_lock(&mcc_mutex);
207 	for(n = &mcc_head; n && *n; n = &(*n)->next) {
208 	    if(m == *n) {
209 		*n = m->next;
210 		break;
211 	    }
212 	}
213 	HEIMDAL_MUTEX_unlock(&mcc_mutex);
214 	if (m->primary_principal != NULL) {
215 	    krb5_free_principal (context, m->primary_principal);
216 	    m->primary_principal = NULL;
217 	}
218 	m->dead = 1;
219 
220 	l = m->creds;
221 	while (l != NULL) {
222 	    struct link *old;
223 
224 	    krb5_free_cred_contents (context, &l->cred);
225 	    old = l;
226 	    l = l->next;
227 	    free (old);
228 	}
229 	m->creds = NULL;
230     }
231     return 0;
232 }
233 
234 static krb5_error_code KRB5_CALLCONV
235 mcc_store_cred(krb5_context context,
236 	       krb5_ccache id,
237 	       krb5_creds *creds)
238 {
239     krb5_mcache *m = MCACHE(id);
240     krb5_error_code ret;
241     struct link *l;
242 
243     if (MISDEAD(m))
244 	return ENOENT;
245 
246     l = malloc (sizeof(*l));
247     if (l == NULL) {
248 	krb5_set_error_message(context, KRB5_CC_NOMEM,
249 			       N_("malloc: out of memory", ""));
250 	return KRB5_CC_NOMEM;
251     }
252     l->next = m->creds;
253     m->creds = l;
254     memset (&l->cred, 0, sizeof(l->cred));
255     ret = krb5_copy_creds_contents (context, creds, &l->cred);
256     if (ret) {
257 	m->creds = l->next;
258 	free (l);
259 	return ret;
260     }
261     m->mtime = time(NULL);
262     return 0;
263 }
264 
265 static krb5_error_code KRB5_CALLCONV
266 mcc_get_principal(krb5_context context,
267 		  krb5_ccache id,
268 		  krb5_principal *principal)
269 {
270     krb5_mcache *m = MCACHE(id);
271 
272     if (MISDEAD(m) || m->primary_principal == NULL)
273 	return ENOENT;
274     return krb5_copy_principal (context,
275 				m->primary_principal,
276 				principal);
277 }
278 
279 static krb5_error_code KRB5_CALLCONV
280 mcc_get_first (krb5_context context,
281 	       krb5_ccache id,
282 	       krb5_cc_cursor *cursor)
283 {
284     krb5_mcache *m = MCACHE(id);
285 
286     if (MISDEAD(m))
287 	return ENOENT;
288 
289     *cursor = m->creds;
290     return 0;
291 }
292 
293 static krb5_error_code KRB5_CALLCONV
294 mcc_get_next (krb5_context context,
295 	      krb5_ccache id,
296 	      krb5_cc_cursor *cursor,
297 	      krb5_creds *creds)
298 {
299     krb5_mcache *m = MCACHE(id);
300     struct link *l;
301 
302     if (MISDEAD(m))
303 	return ENOENT;
304 
305     l = *cursor;
306     if (l != NULL) {
307 	*cursor = l->next;
308 	return krb5_copy_creds_contents (context,
309 					 &l->cred,
310 					 creds);
311     } else
312 	return KRB5_CC_END;
313 }
314 
315 static krb5_error_code KRB5_CALLCONV
316 mcc_end_get (krb5_context context,
317 	     krb5_ccache id,
318 	     krb5_cc_cursor *cursor)
319 {
320     return 0;
321 }
322 
323 static krb5_error_code KRB5_CALLCONV
324 mcc_remove_cred(krb5_context context,
325 		 krb5_ccache id,
326 		 krb5_flags which,
327 		 krb5_creds *mcreds)
328 {
329     krb5_mcache *m = MCACHE(id);
330     struct link **q, *p;
331     for(q = &m->creds, p = *q; p; p = *q) {
332 	if(krb5_compare_creds(context, which, mcreds, &p->cred)) {
333 	    *q = p->next;
334 	    krb5_free_cred_contents(context, &p->cred);
335 	    free(p);
336 	    m->mtime = time(NULL);
337 	} else
338 	    q = &p->next;
339     }
340     return 0;
341 }
342 
343 static krb5_error_code KRB5_CALLCONV
344 mcc_set_flags(krb5_context context,
345 	      krb5_ccache id,
346 	      krb5_flags flags)
347 {
348     return 0; /* XXX */
349 }
350 
351 struct mcache_iter {
352     krb5_mcache *cache;
353 };
354 
355 static krb5_error_code KRB5_CALLCONV
356 mcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
357 {
358     struct mcache_iter *iter;
359 
360     iter = calloc(1, sizeof(*iter));
361     if (iter == NULL) {
362 	krb5_set_error_message(context, ENOMEM,
363 			       N_("malloc: out of memory", ""));
364 	return ENOMEM;
365     }
366 
367     HEIMDAL_MUTEX_lock(&mcc_mutex);
368     iter->cache = mcc_head;
369     if (iter->cache)
370 	iter->cache->refcnt++;
371     HEIMDAL_MUTEX_unlock(&mcc_mutex);
372 
373     *cursor = iter;
374     return 0;
375 }
376 
377 static krb5_error_code KRB5_CALLCONV
378 mcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
379 {
380     struct mcache_iter *iter = cursor;
381     krb5_error_code ret;
382     krb5_mcache *m;
383 
384     if (iter->cache == NULL)
385 	return KRB5_CC_END;
386 
387     HEIMDAL_MUTEX_lock(&mcc_mutex);
388     m = iter->cache;
389     if (m->next)
390 	m->next->refcnt++;
391     iter->cache = m->next;
392     HEIMDAL_MUTEX_unlock(&mcc_mutex);
393 
394     ret = _krb5_cc_allocate(context, &krb5_mcc_ops, id);
395     if (ret)
396 	return ret;
397 
398     (*id)->data.data = m;
399     (*id)->data.length = sizeof(*m);
400 
401     return 0;
402 }
403 
404 static krb5_error_code KRB5_CALLCONV
405 mcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
406 {
407     struct mcache_iter *iter = cursor;
408 
409     if (iter->cache)
410 	mcc_close_internal(iter->cache);
411     iter->cache = NULL;
412     free(iter);
413     return 0;
414 }
415 
416 static krb5_error_code KRB5_CALLCONV
417 mcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
418 {
419     krb5_mcache *mfrom = MCACHE(from), *mto = MCACHE(to);
420     struct link *creds;
421     krb5_principal principal;
422     krb5_mcache **n;
423 
424     HEIMDAL_MUTEX_lock(&mcc_mutex);
425 
426     /* drop the from cache from the linked list to avoid lookups */
427     for(n = &mcc_head; n && *n; n = &(*n)->next) {
428 	if(mfrom == *n) {
429 	    *n = mfrom->next;
430 	    break;
431 	}
432     }
433 
434     /* swap creds */
435     creds = mto->creds;
436     mto->creds = mfrom->creds;
437     mfrom->creds = creds;
438     /* swap principal */
439     principal = mto->primary_principal;
440     mto->primary_principal = mfrom->primary_principal;
441     mfrom->primary_principal = principal;
442 
443     mto->mtime = mfrom->mtime = time(NULL);
444 
445     HEIMDAL_MUTEX_unlock(&mcc_mutex);
446     mcc_destroy(context, from);
447 
448     return 0;
449 }
450 
451 static krb5_error_code KRB5_CALLCONV
452 mcc_default_name(krb5_context context, char **str)
453 {
454     *str = strdup("MEMORY:");
455     if (*str == NULL) {
456 	krb5_set_error_message(context, ENOMEM,
457 			       N_("malloc: out of memory", ""));
458 	return ENOMEM;
459     }
460     return 0;
461 }
462 
463 static krb5_error_code KRB5_CALLCONV
464 mcc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime)
465 {
466     *mtime = MCACHE(id)->mtime;
467     return 0;
468 }
469 
470 static krb5_error_code KRB5_CALLCONV
471 mcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat kdc_offset)
472 {
473     krb5_mcache *m = MCACHE(id);
474     m->kdc_offset = kdc_offset;
475     return 0;
476 }
477 
478 static krb5_error_code KRB5_CALLCONV
479 mcc_get_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat *kdc_offset)
480 {
481     krb5_mcache *m = MCACHE(id);
482     *kdc_offset = m->kdc_offset;
483     return 0;
484 }
485 
486 
487 /**
488  * Variable containing the MEMORY based credential cache implemention.
489  *
490  * @ingroup krb5_ccache
491  */
492 
493 KRB5_LIB_VARIABLE const krb5_cc_ops krb5_mcc_ops = {
494     KRB5_CC_OPS_VERSION,
495     "MEMORY",
496     mcc_get_name,
497     mcc_resolve,
498     mcc_gen_new,
499     mcc_initialize,
500     mcc_destroy,
501     mcc_close,
502     mcc_store_cred,
503     NULL, /* mcc_retrieve */
504     mcc_get_principal,
505     mcc_get_first,
506     mcc_get_next,
507     mcc_end_get,
508     mcc_remove_cred,
509     mcc_set_flags,
510     NULL,
511     mcc_get_cache_first,
512     mcc_get_cache_next,
513     mcc_end_cache_get,
514     mcc_move,
515     mcc_default_name,
516     NULL,
517     mcc_lastchange,
518     mcc_set_kdc_offset,
519     mcc_get_kdc_offset
520 };
521