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