/ lib / krb5 / xcache.c
xcache.c
   1  /*
   2   * Copyright (c) 2013 Kungliga Tekniska Högskolan
   3   * (Royal Institute of Technology, Stockholm, Sweden).
   4   * All rights reserved.
   5   *
   6   * Portions Copyright (c) 2013 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  #include "heimcred.h"
  38  
  39  #ifdef HAVE_XCC
  40  
  41  #define CFRELEASE_NULL(x) do { if (x) { CFRelease(x); x = NULL; } } while(0)
  42  
  43  typedef struct krb5_xcc {
  44      CFUUIDRef uuid;
  45      HeimCredRef cred;
  46      CFStringRef clientName;
  47      krb5_principal primary_principal;
  48      char *cache_name;
  49  } krb5_xcc;
  50  
  51  struct xcc_cursor {
  52      CFArrayRef array;
  53      CFIndex offset;
  54  };
  55  
  56  #define XCACHE(X) ((krb5_xcc *)(X)->data.data)
  57  
  58  static void
  59  free_cursor(struct xcc_cursor *c)
  60  {
  61      if (c->array)
  62  	CFRelease(c->array);
  63      free(c);
  64  }
  65  
  66  static CFStringRef
  67  CFStringCreateFromPrincipal(krb5_context context, krb5_principal principal)
  68  {
  69      CFStringRef str;
  70      char *p;
  71      
  72      if (krb5_unparse_name(context, principal, &p) != 0)
  73  	return NULL;
  74      
  75      str = CFStringCreateWithCString(NULL, p, kCFStringEncodingUTF8);
  76      krb5_xfree(p);
  77      
  78      return str;
  79  }
  80  
  81  static krb5_principal
  82  PrincipalFromCFString(krb5_context context, CFStringRef string)
  83  {
  84      krb5_principal principal = NULL;
  85      char *p = rk_cfstring2cstring(string);
  86      if (p == NULL)
  87  	return NULL;
  88      
  89      (void)krb5_parse_name(context, p, &principal);
  90      free(p);
  91      return principal;
  92  }
  93  
  94  
  95  static const char* KRB5_CALLCONV
  96  xcc_get_name(krb5_context context,
  97  	     krb5_ccache id)
  98  {
  99      krb5_xcc *a = XCACHE(id);
 100      return a->cache_name;
 101  }
 102  
 103  static krb5_error_code KRB5_CALLCONV
 104  xcc_alloc(krb5_context context, krb5_ccache *id)
 105  {
 106      (*id)->data.data = calloc(1, sizeof(krb5_xcc));
 107      if ((*id)->data.data == NULL)
 108  	return krb5_enomem(context);
 109      (*id)->data.length = sizeof(krb5_xcc);
 110      
 111      return 0;
 112  }
 113  
 114  static void
 115  genName(krb5_xcc *x)
 116  {
 117      if (x->cache_name)
 118  	return;
 119      CFUUIDBytes bytes = CFUUIDGetUUIDBytes(x->uuid);
 120      x->cache_name = malloc(37);
 121      uuid_unparse((void *)&bytes, x->cache_name);
 122  }
 123  
 124  static krb5_error_code KRB5_CALLCONV
 125  update_cache_info(krb5_context context,
 126  		  krb5_xcc *x)
 127  {
 128      if (x->cred == NULL) {
 129  	//only update x->cred if it exists in GSSCred
 130  
 131  	HeimCredRef cred = HeimCredCopyFromUUID(x->uuid);
 132  	CFDictionaryRef attrs = HeimCredCopyAttributes(cred, NULL, NULL);
 133  	if (attrs) {
 134  	    // if there are attrs, then the cache exists on the server.  this means we can save the cred entry for the cache.
 135  	    x->cred = cred;
 136  	}
 137  	CFRELEASE_NULL(attrs);
 138  	if (x->cred == NULL) {
 139  	    //if the cred is null, then return not found so that xcc_get_principal can return the error
 140  	    krb5_set_error_message(context, KRB5_CC_NOTFOUND, "no credential for %s", x->cache_name);
 141  	    return KRB5_CC_NOTFOUND;
 142  	}
 143      }
 144      if (x->clientName == NULL && x->cred) {
 145  	x->clientName = HeimCredCopyAttribute(x->cred, kHEIMAttrClientName);
 146  	if (x->clientName == NULL) {
 147  	    krb5_set_error_message(context, KRB5_CC_NOTFOUND, "no cache for %s", x->cache_name);
 148  	    return KRB5_CC_NOTFOUND;
 149  	}
 150      }
 151      if (x->primary_principal == NULL) {
 152  	x->primary_principal = PrincipalFromCFString(context, x->clientName);
 153  	if (x->primary_principal == NULL) {
 154  	    krb5_set_error_message(context, KRB5_CC_NOTFOUND, "no principal for %s", x->cache_name);
 155  	    return KRB5_CC_NOTFOUND;
 156  	}
 157      }
 158      return 0;
 159  }
 160  
 161  static krb5_error_code KRB5_CALLCONV
 162  xcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
 163  {
 164      krb5_error_code ret;
 165      CFUUIDBytes bytes;
 166      krb5_xcc *x;
 167  
 168      if (uuid_parse(res, (void *)&bytes) != 0) {
 169  	krb5_set_error_message(context, KRB5_CC_END, "failed to parse uuid: %s", res);
 170  	return KRB5_CC_END;
 171      }
 172      
 173      CFUUIDRef uuidref = CFUUIDCreateFromUUIDBytes(NULL, bytes);
 174      if (uuidref == NULL) {
 175  	krb5_set_error_message(context, KRB5_CC_END, "failed to create uuid from: %s", res);
 176  	return KRB5_CC_END;
 177      }
 178  
 179      ret = xcc_alloc(context, id);
 180      if (ret) {
 181  	CFRELEASE_NULL(uuidref);
 182  	return ret;
 183      }
 184      
 185      x = XCACHE((*id));
 186      
 187      x->uuid = uuidref;
 188      genName(x);
 189  
 190      //if the cache already exists, then fetch the values from gsscred to stay in sync
 191      ret = update_cache_info(context, x);
 192      if (ret) {
 193  	//ignore errors here, it's expected that the resolved cache may not exist.
 194      }
 195  
 196      return 0;
 197  }
 198  
 199  static krb5_error_code
 200  xcc_create(krb5_context context, krb5_xcc *x, CFUUIDRef uuid, bool isTemporaryCache)
 201  {
 202      const void *keys[] = {
 203      	(void *)kHEIMObjectType,
 204  	(void *)kHEIMAttrType,
 205  	(void *)kHEIMAttrTemporaryCache,
 206  	(void *)kHEIMAttrUUID
 207      };
 208      const void *values[] = {
 209  	(void *)kHEIMObjectKerberos,
 210  	(void *)kHEIMTypeKerberos,
 211  	(isTemporaryCache ? kCFBooleanTrue : kCFBooleanFalse),
 212  	(void *)uuid
 213      };
 214      CFDictionaryRef attrs;
 215      krb5_error_code ret;
 216      CFErrorRef error = NULL;
 217      
 218      CFIndex num_keys = sizeof(keys)/sizeof(keys[0]);
 219      if (uuid == NULL)
 220  	num_keys -= 1;
 221     
 222      attrs = CFDictionaryCreate(NULL, keys, values, num_keys, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
 223      heim_assert(attrs != NULL, "Failed to create dictionary");
 224      
 225      /*
 226       * Contract with HeimCredCreate is creates a uuid's when they are
 227       * not set on the attributes passed in.
 228       */
 229      x->cred = HeimCredCreate(attrs, &error);
 230      CFRelease(attrs);
 231      if (x->cred) {
 232  	// xcc_gen_new will not send a uuid, however, xcc_initialize after receiving an empty default cache can send a uuid.  This case has a x->uuid but no x-cred.
 233  	if (!uuid) {
 234  	    heim_assert(x->uuid == NULL, "credential should not already have a UUID");
 235  	    x->uuid = HeimCredGetUUID(x->cred);
 236  	    heim_assert(x->uuid != NULL, "no uuid for credential?");
 237  	    CFRetain(x->uuid);
 238  	}
 239  
 240  	ret = 0;
 241  	genName(x);
 242      } else {
 243  	ret = _krb5_set_cf_error_message(context, ENOMEM, error, N_("no reply from GSSCred", ""));
 244      }
 245  
 246      CFRELEASE_NULL(error);
 247  
 248      return ret;
 249  }
 250  
 251  static krb5_error_code KRB5_CALLCONV
 252  xcc_gen_new(krb5_context context, krb5_ccache *id)
 253  {
 254      krb5_error_code ret;
 255      krb5_xcc *x;
 256      
 257      ret = xcc_alloc(context, id);
 258      if (ret)
 259  	return ret;
 260      
 261      x = XCACHE(*id);
 262  
 263      ret = xcc_create(context, x, NULL, ((*id)->ops == &krb5_xcc_temp_api_ops));
 264  	
 265      return ret;
 266  }
 267  
 268  static krb5_error_code KRB5_CALLCONV
 269  xcc_initialize(krb5_context context,
 270  	       krb5_ccache id,
 271  	       krb5_principal primary_principal)
 272  {
 273      krb5_xcc *x = XCACHE(id);
 274      krb5_error_code ret;
 275  
 276      if (x->primary_principal)
 277  	krb5_free_principal(context, x->primary_principal);
 278      
 279      ret = krb5_copy_principal(context, primary_principal, &x->primary_principal);
 280      if (ret)
 281  	return ret;
 282  
 283      CFRELEASE_NULL(x->clientName);
 284  
 285      x->clientName = CFStringCreateFromPrincipal(context, primary_principal);
 286      if (x->clientName == NULL)
 287  	return krb5_enomem(context);
 288  
 289      if (x->cred == NULL) {
 290  	ret = xcc_create(context, x, x->uuid, (id->ops == &krb5_xcc_temp_api_ops));
 291  	if (ret)
 292  	    return ret;
 293      } else {
 294  	const void *remove_keys[] = {
 295  	    kHEIMAttrType,
 296  	    kHEIMAttrParentCredential
 297  	};
 298  	const void *remove_values[] = {
 299  	    (void *)kHEIMTypeKerberos,
 300  	    x->uuid,
 301  	};
 302  	CFDictionaryRef query;
 303  
 304  	query = CFDictionaryCreate(NULL, remove_keys, remove_values, sizeof(remove_keys) / sizeof(remove_keys[0]), NULL, NULL);
 305  	heim_assert(query != NULL, "Failed to create dictionary");
 306  
 307  	HeimCredDeleteQuery(query, NULL);
 308  	CFRelease(query);
 309  
 310      }
 311      if (!HeimCredSetAttribute(x->cred, kHEIMAttrClientName, x->clientName, NULL)) {\
 312  	ret = EINVAL;
 313  	krb5_set_error_message(context, ret, "failed to store credential to %s", x->cache_name);
 314      }
 315  
 316  
 317      return ret;
 318  }
 319  
 320  static krb5_error_code KRB5_CALLCONV
 321  xcc_close(krb5_context context,
 322  	  krb5_ccache id)
 323  {
 324      krb5_xcc *x = XCACHE(id);
 325      CFRELEASE_NULL(x->uuid);
 326      CFRELEASE_NULL(x->cred);
 327      CFRELEASE_NULL(x->clientName);
 328      krb5_free_principal(context, x->primary_principal);
 329      free(x->cache_name);
 330  
 331      krb5_data_free(&id->data);
 332  
 333      return 0;
 334  }
 335  
 336  static krb5_error_code KRB5_CALLCONV
 337  xcc_destroy(krb5_context context,
 338  	    krb5_ccache id)
 339  {
 340      krb5_xcc *x = XCACHE(id);
 341      if (x->uuid)
 342  	HeimCredDeleteByUUID(x->uuid);
 343      CFRELEASE_NULL(x->cred);
 344  
 345      return 0;
 346  }
 347  
 348  static krb5_error_code KRB5_CALLCONV
 349  xcc_store_cred(krb5_context context,
 350  	       krb5_ccache id,
 351  	       krb5_creds *creds)
 352  {
 353      krb5_xcc *x = XCACHE(id);
 354      krb5_storage *sp = NULL;
 355      CFDataRef dref = NULL;
 356      krb5_data data;
 357      CFStringRef principal = NULL;
 358      CFDictionaryRef query = NULL;
 359      krb5_error_code ret;
 360      CFBooleanRef is_tgt = kCFBooleanFalse;
 361      CFDateRef authtime = NULL;
 362      CFDateRef expiretime = NULL;
 363      CFDateRef renew_till = NULL;
 364  
 365      krb5_data_zero(&data);
 366      
 367      if (creds->times.starttime) {
 368  	authtime = CFDateCreate(NULL, (CFTimeInterval)creds->times.starttime - kCFAbsoluteTimeIntervalSince1970);
 369      } else if (creds->times.authtime) {
 370  	authtime = CFDateCreate(NULL, (CFTimeInterval)creds->times.authtime - kCFAbsoluteTimeIntervalSince1970);
 371      } else {
 372  	authtime = CFDateCreate(NULL, CFAbsoluteTimeGetCurrent());
 373      }
 374      if (authtime == NULL) {
 375  	ret = krb5_enomem(context);
 376  	goto out;
 377      }
 378  
 379      if (creds->times.endtime) {
 380  	expiretime = CFDateCreate(NULL, (CFTimeInterval)creds->times.endtime - kCFAbsoluteTimeIntervalSince1970);
 381      }
 382  
 383      if (creds->times.renew_till) {
 384  	renew_till = CFDateCreate(NULL, (CFTimeInterval)creds->times.renew_till - kCFAbsoluteTimeIntervalSince1970);
 385      }
 386  
 387      sp = krb5_storage_emem();
 388      if (sp == NULL) {
 389  	ret = krb5_enomem(context);
 390  	goto out;
 391      }
 392      
 393      ret = krb5_store_creds(sp, creds);
 394      if (ret)
 395  	goto out;
 396      
 397      krb5_storage_to_data(sp, &data);
 398      
 399      dref = CFDataCreateWithBytesNoCopy(NULL, data.data, data.length, kCFAllocatorNull);
 400      if (dref == NULL) {
 401  	ret = krb5_enomem(context);
 402  	goto out;
 403      }
 404      
 405      if (krb5_principal_is_root_krbtgt(context, creds->server))
 406  	is_tgt = kCFBooleanTrue;
 407  
 408      principal = CFStringCreateFromPrincipal(context, creds->server);
 409      if (principal == NULL) {
 410  	ret = krb5_enomem(context);
 411  	goto out;
 412      }
 413      
 414      const void *add_keys[] = {
 415  	(void *)kHEIMObjectType,
 416  	kHEIMAttrType,
 417  	kHEIMAttrClientName,
 418  	kHEIMAttrServerName,
 419  	kHEIMAttrData,
 420  	kHEIMAttrParentCredential,
 421  	kHEIMAttrLeadCredential,
 422  	kHEIMAttrAuthTime,
 423  	kHEIMAttrRenewTill
 424      };
 425      const void *add_values[] = {
 426  	(void *)kHEIMObjectKerberos,
 427  	kHEIMTypeKerberos,
 428  	x->clientName,
 429  	principal,
 430  	dref,
 431  	x->uuid,
 432  	is_tgt,
 433  	authtime,
 434  	renew_till
 435      };
 436      CFIndex num_keys = sizeof(add_keys)/sizeof(add_keys[0]);
 437      if (renew_till == NULL)
 438  	num_keys -= 1;
 439      
 440      query = CFDictionaryCreate(NULL, add_keys, add_values, num_keys, NULL, NULL);
 441      heim_assert(query != NULL, "Failed to create dictionary");
 442  
 443      if (expiretime) {
 444  	CFMutableDictionaryRef newQuery = CFDictionaryCreateMutableCopy(NULL, 0, query);
 445  	CFDictionarySetValue(newQuery, kHEIMAttrExpire, expiretime);
 446  	CFRELEASE_NULL(query);
 447  	query = newQuery;
 448      }
 449      
 450      HeimCredRef ccred = HeimCredCreate(query, NULL);
 451      if (ccred) {
 452  	CFRelease(ccred);
 453      } else {
 454  	_krb5_debugx(context, 5, "failed to add credential to %s\n", x->cache_name);
 455  	ret = EINVAL;
 456  	krb5_set_error_message(context, ret, "failed to store credential to %s", x->cache_name);
 457  	goto out;
 458      }
 459      
 460  out:
 461      if (sp)
 462  	krb5_storage_free(sp);
 463      CFRELEASE_NULL(query);
 464      CFRELEASE_NULL(dref);
 465      CFRELEASE_NULL(principal);
 466      CFRELEASE_NULL(authtime);
 467      CFRELEASE_NULL(expiretime);
 468      CFRELEASE_NULL(renew_till);
 469      krb5_data_free(&data);
 470      
 471      return ret;
 472  }
 473  
 474  static krb5_error_code KRB5_CALLCONV
 475  xcc_get_principal(krb5_context context,
 476  		  krb5_ccache id,
 477  		  krb5_principal *principal)
 478  {
 479      krb5_xcc *x = XCACHE(id);
 480      krb5_error_code ret;
 481      ret = update_cache_info(context, x);
 482      if (ret) {
 483  	return ret;
 484      }
 485  	    
 486      return krb5_copy_principal(context, x->primary_principal, principal);
 487  }
 488  
 489  static krb5_error_code KRB5_CALLCONV
 490  xcc_get_first (krb5_context context,
 491  	       krb5_ccache id,
 492  	       krb5_cc_cursor *cursor)
 493  {
 494      CFDictionaryRef query;
 495      krb5_xcc *x = XCACHE(id);
 496  
 497      CFUUIDRef uuid = x->uuid;
 498      if (uuid == NULL) {
 499  	return KRB5_CC_END;
 500      }
 501  
 502      struct xcc_cursor *c;
 503      
 504      c = calloc(1, sizeof(*c));
 505      if (c == NULL)
 506  	return krb5_enomem(context);
 507      
 508      const void *keys[] = { (void *)kHEIMAttrParentCredential, kHEIMAttrType };
 509      const void *values[] = { (void *)uuid, kHEIMTypeKerberos };
 510      
 511      query = CFDictionaryCreate(NULL, keys, values, sizeof(keys)/sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
 512      heim_assert(query != NULL, "out of memory");
 513      
 514      c->array = HeimCredCopyQuery(query);
 515      CFRELEASE_NULL(query);
 516      if (c->array == NULL) {
 517  	free_cursor(c);
 518  	return KRB5_CC_END;
 519      }
 520      
 521      *cursor = c;
 522      
 523      return 0;
 524  }
 525  
 526  
 527  static krb5_error_code KRB5_CALLCONV
 528  xcc_get_next (krb5_context context,
 529  	      krb5_ccache id,
 530  	      krb5_cc_cursor *cursor,
 531  	      krb5_creds *creds)
 532  {
 533      struct xcc_cursor *c = *cursor;
 534      krb5_error_code ret;
 535      krb5_storage *sp;
 536      HeimCredRef cred;
 537      CFDataRef data;
 538      
 539      if (c->array == NULL)
 540  	return KRB5_CC_END;
 541  
 542   next:
 543      if (c->offset >= CFArrayGetCount(c->array))
 544  	return KRB5_CC_END;
 545      
 546      cred = (HeimCredRef)CFArrayGetValueAtIndex(c->array, c->offset++);
 547      if (cred == NULL)
 548  	return KRB5_CC_END;
 549      
 550      data = HeimCredCopyAttribute(cred, kHEIMAttrData);
 551      if (data == NULL) {
 552  	goto next;
 553      }
 554      
 555      sp = krb5_storage_from_readonly_mem(CFDataGetBytePtr(data), CFDataGetLength(data));
 556      if (sp == NULL) {
 557  	CFRELEASE_NULL(data);
 558  	return KRB5_CC_END;
 559      }
 560      
 561      ret = krb5_ret_creds(sp, creds);
 562      krb5_storage_free(sp);
 563      CFRELEASE_NULL(data);
 564      
 565      return ret;
 566  }
 567  
 568  static krb5_error_code KRB5_CALLCONV
 569  xcc_end_get (krb5_context context,
 570  	     krb5_ccache id,
 571  	     krb5_cc_cursor *cursor)
 572  {
 573      free_cursor((struct xcc_cursor *)*cursor);
 574      *cursor = NULL;
 575      
 576      return 0;
 577  }
 578  
 579  static krb5_error_code KRB5_CALLCONV
 580  xcc_remove_cred(krb5_context context,
 581  		krb5_ccache id,
 582  		krb5_flags which,
 583  		krb5_creds *cred)
 584  {
 585      CFDictionaryRef query;
 586      krb5_xcc *x = XCACHE(id);
 587      
 588      CFStringRef servername = CFStringCreateFromPrincipal(context, cred->server);
 589      if (servername == NULL)
 590  	return KRB5_CC_END;
 591      
 592      const void *keys[] = { (const void *)kHEIMAttrParentCredential, kHEIMAttrType, kHEIMAttrServerName };
 593      const void *values[] = { (const void *)x->uuid, kHEIMTypeKerberos, servername };
 594      
 595      /* XXX match enctype */
 596      
 597      query = CFDictionaryCreate(NULL, keys, values, sizeof(keys)/sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
 598      heim_assert(query != NULL, "Failed to create dictionary");
 599      
 600      CFRELEASE_NULL(servername);
 601  
 602      bool res = HeimCredDeleteQuery(query, NULL);
 603      CFRELEASE_NULL(query);
 604  
 605      if (!res) {
 606  	krb5_set_error_message(context, KRB5_CC_NOTFOUND, N_("Deleted credential not found", ""));
 607  	return KRB5_CC_NOTFOUND;
 608      }
 609      return 0;
 610  }
 611  
 612  static krb5_error_code KRB5_CALLCONV
 613  xcc_set_flags(krb5_context context,
 614  	      krb5_ccache id,
 615  	      krb5_flags flags)
 616  {
 617      return 0;
 618  }
 619  
 620  static int KRB5_CALLCONV
 621  xcc_get_version(krb5_context context,
 622  		krb5_ccache id)
 623  {
 624      return 0;
 625  }
 626  
 627  static krb5_error_code KRB5_CALLCONV
 628  xcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
 629  {
 630      CFDictionaryRef query;
 631      struct xcc_cursor *c;
 632  
 633      const void *keys[] = {
 634  	(const void *)kHEIMAttrType,
 635  	(const void *)kHEIMAttrServerName,
 636      };
 637      const void *values[] = {
 638  	(const void *)kHEIMTypeKerberos,
 639  	(const void *)kCFNull,
 640      };
 641      
 642      c = calloc(1, sizeof(*c));
 643      if (c == NULL)
 644  	return krb5_enomem(context);
 645  
 646      query = CFDictionaryCreate(NULL, keys, values, sizeof(keys)/sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
 647      heim_assert(query != NULL, "Failed to create dictionary");
 648      
 649      c->array = HeimCredCopyQuery(query);
 650      CFRELEASE_NULL(query);
 651      if (c->array == NULL) {
 652  	free_cursor(c);
 653  	return KRB5_CC_END;
 654      }
 655      *cursor = c;
 656      return 0;
 657  }
 658  
 659  static krb5_error_code KRB5_CALLCONV
 660  xcc_temp_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
 661  {
 662      return KRB5_CC_END;
 663  }
 664  
 665  static krb5_error_code KRB5_CALLCONV
 666  get_cache_next(krb5_context context, krb5_cc_cursor cursor, const krb5_cc_ops *ops, krb5_ccache *id)
 667  {
 668      struct xcc_cursor *c = cursor;
 669      krb5_error_code ret;
 670      HeimCredRef cred;
 671      krb5_xcc *x;
 672      
 673      if (c->array == NULL)
 674  	return KRB5_CC_END;
 675      
 676      if (c->offset >= CFArrayGetCount(c->array))
 677  	return KRB5_CC_END;
 678      
 679      cred = (HeimCredRef)CFArrayGetValueAtIndex(c->array, c->offset++);
 680      if (cred == NULL)
 681  	return KRB5_CC_END;
 682      
 683      ret = _krb5_cc_allocate(context, ops, id);
 684      if (ret)
 685  	return ret;
 686      
 687      xcc_alloc(context, id);
 688      x = XCACHE((*id));
 689      
 690      x->uuid = HeimCredGetUUID(cred);
 691      CFRetain(x->uuid);
 692      x->cred = cred;
 693      CFRetain(cred);
 694      genName(x);
 695      
 696      return ret;
 697  }
 698  
 699  static krb5_error_code KRB5_CALLCONV
 700  xcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
 701  {
 702  #ifdef XCACHE_IS_API_CACHE
 703      return KRB5_CC_END;
 704  #else
 705      return get_cache_next(context, cursor, &krb5_xcc_ops, id);
 706  #endif
 707  }
 708  
 709  static krb5_error_code KRB5_CALLCONV
 710  xcc_api_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
 711  {
 712  #ifdef XCACHE_IS_API_CACHE
 713      return get_cache_next(context, cursor, &krb5_xcc_api_ops, id);
 714  #else
 715      return KRB5_CC_END;
 716  #endif
 717  }
 718  
 719  static krb5_error_code KRB5_CALLCONV
 720  xcc_temp_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
 721  {
 722      //temp caches do not need to be used in cache cursors
 723      return KRB5_CC_END;
 724  }
 725  
 726  static krb5_error_code KRB5_CALLCONV
 727  xcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
 728  {
 729      free_cursor((struct xcc_cursor *)cursor);
 730      return 0;
 731  }
 732  
 733  static krb5_error_code KRB5_CALLCONV
 734  xcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
 735  {
 736      krb5_xcc *xfrom = XCACHE(from);
 737      krb5_xcc *xto = XCACHE(to);
 738      
 739      /* uuid */
 740      if (!HeimCredMove(xfrom->uuid, xto->uuid))
 741  	return KRB5_CC_END;
 742  
 743      if (xfrom->uuid)
 744  	HeimCredDeleteByUUID(xfrom->uuid);
 745      
 746      CFRELEASE_NULL(xfrom->uuid);
 747  
 748      /* cred */
 749      CFRELEASE_NULL(xto->cred);
 750      CFRELEASE_NULL(xfrom->cred);
 751  
 752      /* clientName */
 753      CFRELEASE_NULL(xto->clientName);
 754      xto->clientName = xfrom->clientName;
 755      xfrom->clientName = NULL;
 756  
 757      /* primary_principal */
 758      if (xto->primary_principal)
 759  	krb5_free_principal(context, xto->primary_principal);
 760      xto->primary_principal = xfrom->primary_principal;
 761      xfrom->primary_principal = NULL;
 762      
 763      /* cache_name */
 764      free(xto->cache_name);
 765      xto->cache_name = NULL;
 766      genName(xto);
 767  
 768      free(xfrom->cache_name);
 769      
 770      /* outer foo */
 771      krb5_data_free(&from->data);
 772      
 773      return 0;
 774  }
 775  
 776  static krb5_error_code KRB5_CALLCONV
 777  get_default_name(krb5_context context,
 778  		 const krb5_cc_ops *ops,
 779  		 const char *cachename,
 780  		 char **str)
 781  {
 782      CFUUIDRef uuid = NULL;
 783      CFUUIDBytes bytes;
 784  
 785      uuid = HeimCredCopyDefaultCredential(kHEIMTypeKerberos, NULL);
 786      if (uuid == NULL) {
 787  	return _krb5_expand_default_cc_name(context, cachename, str);
 788      }
 789      bytes = CFUUIDGetUUIDBytes(uuid);
 790  
 791      char uuidstr[37];
 792      uuid_unparse((void *)&bytes, uuidstr);
 793  
 794      CFRELEASE_NULL(uuid);
 795      
 796      asprintf(str, "%s:%s", ops->prefix, uuidstr);
 797  
 798      return 0;
 799  }
 800  
 801  static krb5_error_code KRB5_CALLCONV
 802  xcc_get_default_name(krb5_context context, char **str)
 803  {
 804      return get_default_name(context, &krb5_xcc_ops, "XCACHE:11111111-71F2-48EB-94C4-7D7392E900E5", str);
 805  }
 806  
 807  static krb5_error_code KRB5_CALLCONV
 808  xcc_api_get_default_name(krb5_context context, char **str)
 809  {
 810      return get_default_name(context, &krb5_xcc_api_ops, "API:11111111-71F2-48EB-94C4-7D7392E900E5", str);
 811  }
 812  
 813  static krb5_error_code KRB5_CALLCONV
 814  xcc_set_default(krb5_context context, krb5_ccache id)
 815  {
 816      krb5_xcc *x = XCACHE(id);
 817      krb5_error_code ret = 0;
 818  
 819      if (x->cred == NULL) {
 820  	x->cred = HeimCredCopyFromUUID(x->uuid);
 821  	if (x->cred == NULL)
 822  	    return KRB5_CC_END;
 823      }
 824  
 825      if (!HeimCredSetAttribute(x->cred, kHEIMAttrDefaultCredential, kCFBooleanTrue, NULL)) {
 826  	ret = EINVAL;
 827  	krb5_set_error_message(context, ret, "XCACHE couldn't set default credential");
 828      }
 829      return ret;
 830  }
 831  
 832  static krb5_error_code KRB5_CALLCONV
 833  xcc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime)
 834  {
 835      *mtime = 0;
 836      return 0;
 837  }
 838  
 839  static krb5_error_code KRB5_CALLCONV
 840  xcc_hold(krb5_context context,
 841  	    krb5_ccache id)
 842  {
 843      krb5_xcc *x = XCACHE(id);
 844      if (!x->cred) {
 845  	x->cred = HeimCredCopyFromUUID(x->uuid);
 846      }
 847      if (x->cred) {
 848  	HeimCredRetainTransient(x->cred);
 849      } else {
 850  	return KRB5_CC_NOTFOUND;
 851      }
 852  
 853      return 0;
 854  }
 855  
 856  static krb5_error_code KRB5_CALLCONV
 857  xcc_unhold(krb5_context context,
 858  	    krb5_ccache id)
 859  {
 860      krb5_xcc *x = XCACHE(id);
 861      if (!x->cred) {
 862  	x->cred = HeimCredCopyFromUUID(x->uuid);
 863      }
 864      if (x->cred) {
 865  	HeimCredReleaseTransient(x->cred);
 866      } else {
 867  	return KRB5_CC_NOTFOUND;
 868      }
 869  
 870      return 0;
 871  }
 872  
 873  
 874  
 875  static krb5_error_code
 876  xcc_get_uuid(krb5_context context, krb5_ccache id, krb5_uuid uuid)
 877  {
 878      krb5_xcc *x = XCACHE(id);
 879      CFUUIDBytes bytes = CFUUIDGetUUIDBytes(x->uuid);
 880      memcpy(uuid, &bytes, sizeof(krb5_uuid));
 881      return 0;
 882  }
 883  
 884  static krb5_error_code
 885  resolve_by_uuid(krb5_context context,
 886  		krb5_ccache id, 
 887  		const krb5_cc_ops *ops,
 888  		krb5_uuid uuid)
 889  {
 890      krb5_error_code ret;
 891      CFUUIDBytes bytes;
 892      krb5_xcc *x;
 893  
 894      memcpy(&bytes, uuid, sizeof(bytes));
 895      
 896      CFUUIDRef uuidref = CFUUIDCreateFromUUIDBytes(NULL, bytes);
 897      if (uuidref == NULL) {
 898  	krb5_set_error_message(context, KRB5_CC_END, "failed to create uuid");
 899  	return KRB5_CC_END;
 900      }
 901      
 902      ret = xcc_alloc(context, &id);
 903      if (ret) {
 904  	CFRELEASE_NULL(uuidref);
 905  	return ret;
 906      }
 907      
 908      x = XCACHE(id);
 909      
 910      x->uuid = uuidref;
 911      genName(x);
 912      
 913      return 0;
 914  }
 915  
 916  static krb5_error_code
 917  xcc_resolve_by_uuid(krb5_context context, krb5_ccache id, krb5_uuid uuid)
 918  {
 919      return resolve_by_uuid(context, id, &krb5_xcc_ops, uuid);
 920  }
 921  
 922  static krb5_error_code
 923  xcc_api_resolve_by_uuid(krb5_context context, krb5_ccache id, krb5_uuid uuid)
 924  {
 925      return resolve_by_uuid(context, id, &krb5_xcc_api_ops, uuid);
 926  }
 927  
 928  static krb5_error_code
 929  xcc_set_acl(krb5_context context, krb5_ccache id, const char *type, /* heim_object_t */ void *obj)
 930  {
 931      krb5_xcc *x = XCACHE(id);
 932      bool res;
 933  
 934      if (x->cred == NULL) {
 935  	x->cred = HeimCredCopyFromUUID(x->uuid);
 936  	if (x->cred == NULL)
 937  	    return KRB5_CC_END;
 938      }
 939  
 940      CFStringRef t = CFStringCreateWithCString(NULL, type, kCFStringEncodingUTF8);
 941      if (t == NULL)
 942  	return krb5_enomem(context);
 943  
 944      res = HeimCredSetAttribute(x->cred, t, obj, NULL);
 945      CFRELEASE_NULL(t);
 946  
 947      if (!res)
 948  	return KRB5_CC_END;
 949  
 950      return 0;
 951  }
 952  
 953  static krb5_error_code
 954  xcc_copy_data(krb5_context context, krb5_ccache id, /* heim_dict_t */ void *keys, /* heim_dict_t */ void **data)
 955  {
 956      krb5_xcc *x = XCACHE(id);
 957  
 958      *data = NULL;
 959  
 960      if (x->cred == NULL) {
 961  	x->cred = HeimCredCopyFromUUID(x->uuid);
 962  	if (x->cred == NULL)
 963  	    return KRB5_CC_END;
 964      }
 965  
 966      *data = (heim_dict_t)HeimCredCopyAttributes(x->cred, NULL, NULL);
 967      if (*data == NULL) {
 968  	krb5_set_error_message(context, KRB5_CC_END,
 969  			       N_("Credential have no attributes", ""));
 970  	return KRB5_CC_END;
 971      }
 972      return 0;
 973  }
 974  
 975  static int KRB5_CALLCONV
 976  xcc_can_move_from(krb5_context context, krb5_ccache fromid)
 977  {
 978      //moves between other xcaches are allowed
 979      if (fromid->ops == &krb5_xcc_api_ops
 980  	|| fromid->ops == &krb5_xcc_ops
 981  	|| fromid->ops == &krb5_xcc_temp_api_ops) {
 982  	return true;
 983      }
 984      return false;
 985  }
 986  
 987  KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
 988  _krb5_xcc_get_initial_ticket(krb5_context context,
 989  			     krb5_ccache id,
 990  			     krb5_principal client,
 991  			     krb5_principal server,
 992  			     const char *password)
 993  {
 994      
 995      krb5_xcc *x = XCACHE(id);
 996      krb5_error_code ret = 0;
 997      CFDataRef dref = NULL;
 998      CFDictionaryRef query = NULL;
 999      CFStringRef serverStr = NULL;
1000      
1001      dref = CFDataCreateWithBytesNoCopy(NULL, (const UInt8 *)password, strlen(password), kCFAllocatorNull);
1002      if (dref == NULL) {
1003  	ret = krb5_enomem(context);
1004  	goto out;
1005      }
1006      
1007      if (server) {
1008  	serverStr = CFStringCreateFromPrincipal(context, server);
1009      }
1010      
1011      const void *add_keys[] = {
1012  	(void *)kHEIMObjectType,
1013  	kHEIMAttrType,
1014  	kHEIMAttrParentCredential,
1015  	kHEIMAttrClientName,
1016  	kHEIMAttrData,
1017  	kHEIMAttrCredential,
1018  	kHEIMAttrServerName
1019      };
1020      const void *add_values[] = {
1021  	(void *)kHEIMObjectKerberosAcquireCred,
1022  	kHEIMTypeKerberosAcquireCred,
1023  	x->uuid,
1024  	x->clientName, 
1025  	dref,
1026  	kCFBooleanTrue,
1027  	serverStr
1028      };
1029      
1030      CFIndex num_keys = sizeof(add_keys)/sizeof(add_keys[0]);
1031      if (serverStr == NULL)
1032  	num_keys -= 1;
1033      
1034      query = CFDictionaryCreate(NULL, add_keys, add_values, num_keys, NULL, NULL);
1035      heim_assert(query != NULL, "Failed to create dictionary");
1036      
1037      HeimCredRef ccred = HeimCredCreate(query, NULL);
1038      if (ccred) {
1039  	CFRelease(ccred);
1040      } else {
1041  	_krb5_debugx(context, 5, "failed to add initial ticket request to %s\n", x->cache_name);
1042  	ret = EINVAL;
1043  	krb5_set_error_message(context, ret, "failed to store initial ticket request to %s", x->cache_name);
1044  	goto out;
1045      }
1046  
1047  out:
1048      CFRELEASE_NULL(serverStr);
1049      CFRELEASE_NULL(query);
1050      CFRELEASE_NULL(dref);
1051      
1052      return ret;
1053  }
1054  
1055  /**
1056   * Variable containing the XCACHE based credential cache implemention.
1057   *
1058   * @ingroup krb5_ccache
1059   */
1060  
1061  KRB5_LIB_VARIABLE const krb5_cc_ops krb5_xcc_ops = {
1062      KRB5_CC_OPS_VERSION,
1063      "XCACHE",
1064      xcc_get_name,
1065      xcc_resolve,
1066      xcc_gen_new,
1067      xcc_initialize,
1068      xcc_destroy,
1069      xcc_close,
1070      xcc_store_cred,
1071      NULL, /* acc_retrieve */
1072      xcc_get_principal,
1073      xcc_get_first,
1074      xcc_get_next,
1075      xcc_end_get,
1076      xcc_remove_cred,
1077      xcc_set_flags,
1078      xcc_get_version,
1079      xcc_get_cache_first,
1080      xcc_get_cache_next,
1081      xcc_end_cache_get,
1082      xcc_move,
1083      xcc_get_default_name,
1084      xcc_set_default,
1085      xcc_lastchange,
1086      NULL, /* set_kdc_offset */
1087      NULL, /* get_kdc_offset */
1088      xcc_hold,
1089      xcc_unhold,
1090      xcc_get_uuid,
1091      xcc_resolve_by_uuid,
1092      NULL,
1093      NULL,
1094      xcc_set_acl,
1095      xcc_copy_data,
1096      NULL, /* copy_query */
1097      NULL, /* store_data */
1098      xcc_can_move_from,
1099  };
1100  
1101  /**
1102   * Variable containing the API (XCACHE version) based credential cache implemention.
1103   *
1104   * @ingroup krb5_ccache
1105   */
1106  
1107  KRB5_LIB_VARIABLE const krb5_cc_ops krb5_xcc_api_ops = {
1108      KRB5_CC_OPS_VERSION,
1109      "API",
1110      xcc_get_name,
1111      xcc_resolve,
1112      xcc_gen_new,
1113      xcc_initialize,
1114      xcc_destroy,
1115      xcc_close,
1116      xcc_store_cred,
1117      NULL, /* acc_retrieve */
1118      xcc_get_principal,
1119      xcc_get_first,
1120      xcc_get_next,
1121      xcc_end_get,
1122      xcc_remove_cred,
1123      xcc_set_flags,
1124      xcc_get_version,
1125      xcc_get_cache_first,
1126      xcc_api_get_cache_next,
1127      xcc_end_cache_get,
1128      xcc_move,
1129      xcc_api_get_default_name,
1130      xcc_set_default,
1131      xcc_lastchange,
1132      NULL, /* set_kdc_offset */
1133      NULL, /* get_kdc_offset */
1134      xcc_hold,
1135      xcc_unhold,
1136      xcc_get_uuid,
1137      xcc_api_resolve_by_uuid,
1138      NULL,
1139      NULL,
1140      xcc_set_acl,
1141      xcc_copy_data,
1142      NULL, /* copy_query */
1143      NULL, /* store_data */
1144      xcc_can_move_from,
1145  };
1146  
1147  /**
1148   * Variable containing the TEMP XCACHE  based credential cache implemention.
1149   *
1150   * @ingroup krb5_ccache
1151   */
1152  
1153  KRB5_LIB_VARIABLE const krb5_cc_ops krb5_xcc_temp_api_ops = {
1154      KRB5_CC_OPS_VERSION,
1155      "XCTEMP",
1156      xcc_get_name,
1157      xcc_resolve,
1158      xcc_gen_new,
1159      xcc_initialize,
1160      xcc_destroy,
1161      xcc_close,
1162      xcc_store_cred,
1163      NULL, /* acc_retrieve */
1164      xcc_get_principal,
1165      xcc_get_first,
1166      xcc_get_next,
1167      xcc_end_get,
1168      xcc_remove_cred,
1169      NULL, //xcc_set_flags,
1170      xcc_get_version,
1171      xcc_temp_get_cache_first,
1172      xcc_temp_get_cache_next,
1173      NULL, //xcc_end_cache_get,
1174      xcc_move,
1175      NULL, //xcc_api_get_default_name,
1176      NULL, //xcc_set_default,
1177      xcc_lastchange,
1178      NULL, /* set_kdc_offset */
1179      NULL, /* get_kdc_offset */
1180      NULL, //xcc_hold,
1181      NULL, //xcc_unhold,
1182      xcc_get_uuid,
1183      xcc_api_resolve_by_uuid,
1184      NULL,
1185      NULL,
1186      xcc_set_acl, //xcc_set_acl,
1187      xcc_copy_data,
1188      NULL, /* copy_query */
1189      NULL, /* store_data */
1190      xcc_can_move_from,
1191  };
1192  
1193  #endif /* HAVE_XCC */