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