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/ccache/cc_memory.c
10  *
11  * Copyright 1990,1991,2000,2004 by the Massachusetts Institute of Technology.
12  * All Rights Reserved.
13  *
14  * Export of this software from the United States of America may
15  *   require a specific license from the United States Government.
16  *   It is the responsibility of any person or organization contemplating
17  *   export to obtain such a license before exporting.
18  *
19  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
20  * distribute this software and its documentation for any purpose and
21  * without fee is hereby granted, provided that the above copyright
22  * notice appear in all copies and that both that copyright notice and
23  * this permission notice appear in supporting documentation, and that
24  * the name of M.I.T. not be used in advertising or publicity pertaining
25  * to distribution of the software without specific, written prior
26  * permission.  Furthermore if you modify this software you must label
27  * your software as modified software and not distribute it in such a
28  * fashion that it might be confused with the original M.I.T. software.
29  * M.I.T. makes no representations about the suitability of
30  * this software for any purpose.  It is provided "as is" without express
31  * or implied warranty.
32  *
33  *
34  * implementation of memory-based credentials cache
35  */
36 #include "k5-int.h"
37 #include <errno.h>
38 
39 static krb5_error_code KRB5_CALLCONV krb5_mcc_close
40 	(krb5_context, krb5_ccache id );
41 
42 static krb5_error_code KRB5_CALLCONV krb5_mcc_destroy
43 	(krb5_context, krb5_ccache id );
44 
45 static krb5_error_code KRB5_CALLCONV krb5_mcc_end_seq_get
46 	(krb5_context, krb5_ccache id , krb5_cc_cursor *cursor );
47 
48 static krb5_error_code KRB5_CALLCONV krb5_mcc_generate_new
49 	(krb5_context, krb5_ccache *id );
50 
51 static const char * KRB5_CALLCONV krb5_mcc_get_name
52 	(krb5_context, krb5_ccache id );
53 
54 static krb5_error_code KRB5_CALLCONV krb5_mcc_get_principal
55 	(krb5_context, krb5_ccache id , krb5_principal *princ );
56 
57 static krb5_error_code KRB5_CALLCONV krb5_mcc_initialize
58 	(krb5_context, krb5_ccache id , krb5_principal princ );
59 
60 static krb5_error_code KRB5_CALLCONV krb5_mcc_next_cred
61 	(krb5_context,
62 		   krb5_ccache id ,
63 		   krb5_cc_cursor *cursor ,
64 		   krb5_creds *creds );
65 
66 static krb5_error_code KRB5_CALLCONV krb5_mcc_resolve
67 	(krb5_context, krb5_ccache *id , const char *residual );
68 
69 static krb5_error_code KRB5_CALLCONV krb5_mcc_retrieve
70 	(krb5_context,
71 		   krb5_ccache id ,
72 		   krb5_flags whichfields ,
73 		   krb5_creds *mcreds ,
74 		   krb5_creds *creds );
75 
76 static krb5_error_code KRB5_CALLCONV krb5_mcc_start_seq_get
77 	(krb5_context, krb5_ccache id , krb5_cc_cursor *cursor );
78 
79 static krb5_error_code KRB5_CALLCONV krb5_mcc_store
80 	(krb5_context, krb5_ccache id , krb5_creds *creds );
81 
82 static krb5_error_code KRB5_CALLCONV krb5_mcc_set_flags
83 	(krb5_context, krb5_ccache id , krb5_flags flags );
84 
85 extern const krb5_cc_ops krb5_mcc_ops;
86 extern krb5_error_code krb5_change_cache (void);
87 
88 #define KRB5_OK 0
89 
90 typedef struct _krb5_mcc_link {
91     struct _krb5_mcc_link *next;
92     krb5_creds *creds;
93 } krb5_mcc_link, *krb5_mcc_cursor;
94 
95 typedef struct _krb5_mcc_data {
96     char *name;
97     k5_mutex_t lock;
98     krb5_principal prin;
99     krb5_mcc_cursor link;
100 } krb5_mcc_data;
101 
102 typedef struct krb5_mcc_list_node {
103     struct krb5_mcc_list_node *next;
104     krb5_mcc_data *cache;
105 } krb5_mcc_list_node;
106 
107 k5_mutex_t krb5int_mcc_mutex = K5_MUTEX_PARTIAL_INITIALIZER;
108 static krb5_mcc_list_node *mcc_head = 0;
109 
110 /*
111  * Modifies:
112  * id
113  *
114  * Effects:
115  * Creates/refreshes the file cred cache id.  If the cache exists, its
116  * contents are destroyed.
117  *
118  * Errors:
119  * system errors
120  * permission errors
121  */
122 static void krb5_mcc_free (krb5_context context, krb5_ccache id);
123 
124 krb5_error_code KRB5_CALLCONV
125 krb5_mcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ)
126 {
127     krb5_error_code ret;
128 
129     krb5_mcc_free(context, id);
130     ret = krb5_copy_principal(context, princ,
131 			      &((krb5_mcc_data *)id->data)->prin);
132     if (ret == KRB5_OK)
133         krb5_change_cache();
134     return ret;
135 }
136 
137 /*
138  * Modifies:
139  * id
140  *
141  * Effects:
142  * Closes the file cache, invalidates the id, and frees any resources
143  * associated with the cache.
144  */
145 krb5_error_code KRB5_CALLCONV
146 krb5_mcc_close(krb5_context context, krb5_ccache id)
147 {
148      krb5_xfree(id);
149      return KRB5_OK;
150 }
151 
152 void
153 krb5_mcc_free(krb5_context context, krb5_ccache id)
154 {
155     krb5_mcc_cursor curr,next;
156     krb5_mcc_data *d;
157 
158     d = (krb5_mcc_data *) id->data;
159     for (curr = d->link; curr;) {
160 	krb5_free_creds(context, curr->creds);
161 	next = curr->next;
162 	krb5_xfree(curr);
163 	curr = next;
164     }
165     d->link = NULL;
166     krb5_free_principal(context, d->prin);
167 }
168 
169 /*
170  * Effects:
171  * Destroys the contents of id.
172  *
173  * Errors:
174  * none
175  */
176 krb5_error_code KRB5_CALLCONV
177 krb5_mcc_destroy(krb5_context context, krb5_ccache id)
178 {
179     krb5_mcc_list_node **curr, *node;
180     krb5_mcc_data *d;
181     krb5_error_code err;
182 
183     err = k5_mutex_lock(&krb5int_mcc_mutex);
184     if (err)
185 	return err;
186 
187     d = (krb5_mcc_data *)id->data;
188     for (curr = &mcc_head; *curr; curr = &(*curr)->next) {
189 	if ((*curr)->cache == d) {
190 	    node = *curr;
191 	    *curr = node->next;
192 	    free(node);
193 	    break;
194 	}
195     }
196     k5_mutex_unlock(&krb5int_mcc_mutex);
197 
198     krb5_mcc_free(context, id);
199     krb5_xfree(d->name);
200     k5_mutex_destroy(&d->lock);
201     krb5_xfree(d);
202     krb5_xfree(id);
203 
204     krb5_change_cache ();
205     return KRB5_OK;
206 }
207 
208 /*
209  * Requires:
210  * residual is a legal path name, and a null-terminated string
211  *
212  * Modifies:
213  * id
214  *
215  * Effects:
216  * creates a file-based cred cache that will reside in the file
217  * residual.  The cache is not opened, but the filename is reserved.
218  *
219  * Returns:
220  * A filled in krb5_ccache structure "id".
221  *
222  * Errors:
223  * KRB5_CC_NOMEM - there was insufficient memory to allocate the
224  *              krb5_ccache.  id is undefined.
225  * permission errors
226  */
227 static krb5_error_code new_mcc_data (const char *, krb5_mcc_data **);
228 
229 krb5_error_code KRB5_CALLCONV
230 krb5_mcc_resolve (krb5_context context, krb5_ccache *id, const char *residual)
231 {
232     krb5_ccache lid;
233     krb5_mcc_list_node *ptr;
234     krb5_error_code err;
235     krb5_mcc_data *d;
236 
237     lid = (krb5_ccache) malloc(sizeof(struct _krb5_ccache));
238     if (lid == NULL)
239 	return KRB5_CC_NOMEM;
240 
241     lid->ops = &krb5_mcc_ops;
242 
243     err = k5_mutex_lock(&krb5int_mcc_mutex);
244     if (err) {
245         /* SUNW14resync - fix mem leak */
246         krb5_xfree(lid);
247 	return err;
248     }
249     for (ptr = mcc_head; ptr; ptr=ptr->next)
250 	if (!strcmp(ptr->cache->name, residual))
251 	    break;
252     if (ptr)
253 	d = ptr->cache;
254     else {
255 	err = new_mcc_data(residual, &d);
256 	if (err) {
257 	    k5_mutex_unlock(&krb5int_mcc_mutex);
258 	    krb5_xfree(lid);
259 	    return err;
260 	}
261     }
262     k5_mutex_unlock(&krb5int_mcc_mutex);
263     lid->data = d;
264     *id = lid;
265     return KRB5_OK;
266 }
267 
268 /*
269  * Effects:
270  * Prepares for a sequential search of the credentials cache.
271  * Returns a krb5_cc_cursor to be used with krb5_mcc_next_cred and
272  * krb5_mcc_end_seq_get.
273  *
274  * If the cache is modified between the time of this call and the time
275  * of the final krb5_mcc_end_seq_get, the results are undefined.
276  *
277  * Errors:
278  * KRB5_CC_NOMEM
279  * system errors
280  */
281 krb5_error_code KRB5_CALLCONV
282 krb5_mcc_start_seq_get(krb5_context context, krb5_ccache id,
283 		       krb5_cc_cursor *cursor)
284 {
285      krb5_mcc_cursor mcursor;
286      krb5_error_code err;
287      krb5_mcc_data *d;
288 
289      d = id->data;
290      err = k5_mutex_lock(&d->lock);
291      if (err)
292 	 return err;
293      mcursor = d->link;
294      k5_mutex_unlock(&d->lock);
295      *cursor = (krb5_cc_cursor) mcursor;
296      return KRB5_OK;
297 }
298 
299 /*
300  * Requires:
301  * cursor is a krb5_cc_cursor originally obtained from
302  * krb5_mcc_start_seq_get.
303  *
304  * Modifes:
305  * cursor, creds
306  *
307  * Effects:
308  * Fills in creds with the "next" credentals structure from the cache
309  * id.  The actual order the creds are returned in is arbitrary.
310  * Space is allocated for the variable length fields in the
311  * credentials structure, so the object returned must be passed to
312  * krb5_destroy_credential.
313  *
314  * The cursor is updated for the next call to krb5_mcc_next_cred.
315  *
316  * Errors:
317  * system errors
318  */
319 krb5_error_code KRB5_CALLCONV
320 krb5_mcc_next_cred(krb5_context context, krb5_ccache id,
321 		   krb5_cc_cursor *cursor, krb5_creds *creds)
322 {
323      krb5_mcc_cursor mcursor;
324      krb5_error_code retval;
325      krb5_data *scratch;
326 
327      /* Once the node in the linked list is created, it's never
328 	modified, so we don't need to worry about locking here.  (Note
329 	that we don't support _remove_cred.)  */
330      mcursor = (krb5_mcc_cursor) *cursor;
331      if (mcursor == NULL)
332 	return KRB5_CC_END;
333      memset(creds, 0, sizeof(krb5_creds));
334      if (mcursor->creds) {
335 	*creds = *mcursor->creds;
336 	retval = krb5_copy_principal(context, mcursor->creds->client, &creds->client);
337 	if (retval)
338 		return retval;
339 	retval = krb5_copy_principal(context, mcursor->creds->server,
340 		&creds->server);
341 	if (retval)
342 		goto cleanclient;
343 	retval = krb5_copy_keyblock_contents(context, &mcursor->creds->keyblock,
344 		&creds->keyblock);
345 	if (retval)
346 		goto cleanserver;
347 	retval = krb5_copy_addresses(context, mcursor->creds->addresses,
348 		&creds->addresses);
349 	if (retval)
350 		goto cleanblock;
351 	retval = krb5_copy_data(context, &mcursor->creds->ticket, &scratch);
352 	if (retval)
353 		goto cleanaddrs;
354 	creds->ticket = *scratch;
355 	krb5_xfree(scratch);
356 	retval = krb5_copy_data(context, &mcursor->creds->second_ticket, &scratch);
357 	if (retval)
358 		goto cleanticket;
359 	creds->second_ticket = *scratch;
360 	krb5_xfree(scratch);
361 	retval = krb5_copy_authdata(context, mcursor->creds->authdata,
362 		&creds->authdata);
363 	if (retval)
364 		goto clearticket;
365      }
366      *cursor = (krb5_cc_cursor)mcursor->next;
367      return KRB5_OK;
368 
369 clearticket:
370 	memset(creds->ticket.data,0, (unsigned) creds->ticket.length);
371 cleanticket:
372 	krb5_xfree(creds->ticket.data);
373 cleanaddrs:
374 	krb5_free_addresses(context, creds->addresses);
375 cleanblock:
376 	krb5_xfree(creds->keyblock.contents);
377 cleanserver:
378 	krb5_free_principal(context, creds->server);
379 cleanclient:
380 	krb5_free_principal(context, creds->client);
381 	return retval;
382 }
383 
384 /*
385  * Requires:
386  * cursor is a krb5_cc_cursor originally obtained from
387  * krb5_mcc_start_seq_get.
388  *
389  * Modifies:
390  * id, cursor
391  *
392  * Effects:
393  * Finishes sequential processing of the file credentials ccache id,
394  * and invalidates the cursor (it must never be used after this call).
395  */
396 /* ARGSUSED */
397 krb5_error_code KRB5_CALLCONV
398 krb5_mcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
399 {
400      *cursor = 0L;
401      return KRB5_OK;
402 }
403 
404 /* Utility routine: Creates the back-end data for a memory cache, and
405    threads it into the global linked list.
406 
407    Call with the global list lock held.  */
408 static krb5_error_code
409 new_mcc_data (const char *name, krb5_mcc_data **dataptr)
410 {
411     krb5_error_code err;
412     krb5_mcc_data *d;
413     krb5_mcc_list_node *n;
414 
415     d = malloc(sizeof(krb5_mcc_data));
416     if (d == NULL)
417 	return KRB5_CC_NOMEM;
418 
419     err = k5_mutex_init(&d->lock);
420     if (err) {
421 	krb5_xfree(d);
422 	return err;
423     }
424 
425     d->name = malloc(strlen(name) + 1);
426     if (d->name == NULL) {
427 	k5_mutex_destroy(&d->lock);
428 	krb5_xfree(d);
429 	return KRB5_CC_NOMEM;
430     }
431     d->link = NULL;
432     d->prin = NULL;
433 
434     /* Set up the filename */
435     strcpy(d->name, name);
436 
437     n = malloc(sizeof(krb5_mcc_list_node));
438     if (n == NULL) {
439 	free(d->name);
440 	k5_mutex_destroy(&d->lock);
441 	free(d);
442 	return KRB5_CC_NOMEM;
443     }
444 
445     n->cache = d;
446     n->next = mcc_head;
447     mcc_head = n;
448 
449     *dataptr = d;
450     return 0;
451 }
452 
453 /*
454  * Effects:
455  * Creates a new file cred cache whose name is guaranteed to be
456  * unique.  The name begins with the string TKT_ROOT (from mcc.h).
457  * The cache is not opened, but the new filename is reserved.
458  *
459  * Returns:
460  * The filled in krb5_ccache id.
461  *
462  * Errors:
463  * KRB5_CC_NOMEM - there was insufficient memory to allocate the
464  *              krb5_ccache.  id is undefined.
465  * system errors (from open)
466  */
467 krb5_error_code KRB5_CALLCONV
468 krb5_mcc_generate_new (krb5_context context, krb5_ccache *id)
469 {
470     krb5_ccache lid;
471     char scratch[6+1]; /* 6 for the scratch part, +1 for NUL */
472     krb5_error_code err;
473     krb5_mcc_data *d;
474 
475     /* Allocate memory */
476     lid = (krb5_ccache) malloc(sizeof(struct _krb5_ccache));
477     if (lid == NULL)
478 	return KRB5_CC_NOMEM;
479 
480     lid->ops = &krb5_mcc_ops;
481 
482     (void) strcpy(scratch, "XXXXXX");
483     mktemp(scratch);
484 
485     err = k5_mutex_lock(&krb5int_mcc_mutex);
486     if (err) {
487 	free(lid);
488 	return err;
489     }
490     err = new_mcc_data(scratch, &d);
491     k5_mutex_unlock(&krb5int_mcc_mutex);
492     if (err) {
493 	krb5_xfree(lid);
494 	return err;
495     }
496     lid->data = d;
497     *id = lid; /* SUNW14resync - fix to 1.4.2 */
498     krb5_change_cache ();
499     return KRB5_OK;
500 }
501 
502 /*
503  * Requires:
504  * id is a file credential cache
505  *
506  * Returns:
507  * The name of the file cred cache id.
508  */
509 const char * KRB5_CALLCONV
510 krb5_mcc_get_name (krb5_context context, krb5_ccache id)
511 {
512      return (char *) ((krb5_mcc_data *) id->data)->name;
513 }
514 
515 /*
516  * Modifies:
517  * id, princ
518  *
519  * Effects:
520  * Retrieves the primary principal from id, as set with
521  * krb5_mcc_initialize.  The principal is returned is allocated
522  * storage that must be freed by the caller via krb5_free_principal.
523  *
524  * Errors:
525  * system errors
526  * KRB5_CC_NOMEM
527  */
528 krb5_error_code KRB5_CALLCONV
529 krb5_mcc_get_principal(krb5_context context, krb5_ccache id, krb5_principal *princ)
530 {
531      krb5_mcc_data *ptr = (krb5_mcc_data *)id->data;
532      if (!ptr->prin) {
533         *princ = 0L;
534         return KRB5_FCC_NOFILE;
535      }
536      return krb5_copy_principal(context, ptr->prin, princ);
537 }
538 
539 krb5_error_code KRB5_CALLCONV
540 krb5_mcc_retrieve(krb5_context context, krb5_ccache id, krb5_flags whichfields,
541 		  krb5_creds *mcreds, krb5_creds *creds)
542 {
543     return krb5_cc_retrieve_cred_default (context, id, whichfields,
544 					  mcreds, creds);
545 }
546 
547 /*
548  * Non-functional stub implementation for krb5_mcc_remove
549  *
550  * Errors:
551  *    KRB5_CC_NOSUPP - not implemented
552  */
553 static krb5_error_code KRB5_CALLCONV
554 krb5_mcc_remove_cred(krb5_context context, krb5_ccache cache, krb5_flags flags,
555                      krb5_creds *creds)
556 {
557     return KRB5_CC_NOSUPP;
558 }
559 
560 
561 /*
562  * Requires:
563  * id is a cred cache returned by krb5_mcc_resolve or
564  * krb5_mcc_generate_new, but has not been opened by krb5_mcc_initialize.
565  *
566  * Modifies:
567  * id
568  *
569  * Effects:
570  * Sets the operational flags of id to flags.
571  */
572 krb5_error_code KRB5_CALLCONV
573 krb5_mcc_set_flags(krb5_context context, krb5_ccache id, krb5_flags flags)
574 {
575     return KRB5_OK;
576 }
577 
578 /* store: Save away creds in the ccache.  */
579 krb5_error_code KRB5_CALLCONV
580 krb5_mcc_store(krb5_context ctx, krb5_ccache id, krb5_creds *creds)
581 {
582     krb5_error_code err;
583     krb5_mcc_link *new_node;
584     krb5_mcc_data *mptr = (krb5_mcc_data *)id->data;
585 
586     new_node = malloc(sizeof(krb5_mcc_link));
587     if (new_node == NULL)
588 	return errno;
589     err = krb5_copy_creds(ctx, creds, &new_node->creds);
590     if (err) {
591 	free(new_node);
592 	return err;
593     }
594     err = k5_mutex_lock(&mptr->lock);
595     if (err) {
596         /* SUNW14resync - fix mem leak */
597 	free(new_node);
598 	return err;
599     }
600     new_node->next = mptr->link;
601     mptr->link = new_node;
602     k5_mutex_unlock(&mptr->lock);
603     return 0;
604 }
605 
606 const krb5_cc_ops krb5_mcc_ops = {
607      0,
608      "MEMORY",
609      krb5_mcc_get_name,
610      krb5_mcc_resolve,
611      krb5_mcc_generate_new,
612      krb5_mcc_initialize,
613      krb5_mcc_destroy,
614      krb5_mcc_close,
615      krb5_mcc_store,
616      krb5_mcc_retrieve,
617      krb5_mcc_get_principal,
618      krb5_mcc_start_seq_get,
619      krb5_mcc_next_cred,
620      krb5_mcc_end_seq_get,
621      krb5_mcc_remove_cred,
622      krb5_mcc_set_flags,
623 };
624