1 /*
   2  * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. */
   3 
   4 #include "k5-int.h"
   5 #include "com_err.h"
   6 #include <admin.h>
   7 #include <locale.h>
   8 #include <syslog.h>
   9 
  10 /* Solaris Kerberos:
  11  *
  12  * Change Password functionality is handled by the libkadm5clnt.so.1 library in
  13  * Solaris Kerberos. In order to avoid a circular dependency between that lib
  14  * and the kerberos mech lib, we use the #pragma weak compiler directive.
  15  * This way, when applications link with the libkadm5clnt.so.1 lib the circular
  16  * dependancy between the two libs will be resolved.
  17  */
  18 
  19 #pragma weak kadm5_get_cpw_host_srv_name
  20 #pragma weak kadm5_init_with_password
  21 #pragma weak kadm5_chpass_principal_util
  22 
  23 extern kadm5_ret_t kadm5_get_cpw_host_srv_name(krb5_context, const char *,
  24                         char **);
  25 extern kadm5_ret_t kadm5_init_with_password(char *, char *, char *,
  26                         kadm5_config_params *, krb5_ui_4, krb5_ui_4, char **,
  27                         void **);
  28 extern kadm5_ret_t kadm5_chpass_principal_util(void *, krb5_principal,
  29                         char *, char **, char *, unsigned int);
  30 
  31 /*
  32  * Solaris Kerberos:
  33  * See the function's definition for the description of this interface.
  34  */
  35 krb5_error_code __krb5_get_init_creds_password(krb5_context,
  36         krb5_creds *, krb5_principal, char *, krb5_prompter_fct, void *,
  37         krb5_deltat, char *, krb5_get_init_creds_opt *, krb5_kdc_rep **);
  38 
  39 static krb5_error_code
  40 krb5_get_as_key_password(
  41     krb5_context context,
  42     krb5_principal client,
  43     krb5_enctype etype,
  44     krb5_prompter_fct prompter,
  45     void *prompter_data,
  46     krb5_data *salt,
  47     krb5_data *params,
  48     krb5_keyblock *as_key,
  49     void *gak_data)
  50 {
  51     krb5_data *password;
  52     krb5_error_code ret;
  53     krb5_data defsalt;
  54     char *clientstr;
  55     char promptstr[1024];
  56     krb5_prompt prompt;
  57     krb5_prompt_type prompt_type;
  58 
  59     password = (krb5_data *) gak_data;
  60 
  61     /* If there's already a key of the correct etype, we're done.
  62        If the etype is wrong, free the existing key, and make
  63        a new one.
  64 
  65        XXX This was the old behavior, and was wrong in hw preauth
  66        cases.  Is this new behavior -- always asking -- correct in all
  67        cases?  */
  68 
  69     if (as_key->length) {
  70         if (as_key->enctype != etype) {
  71             krb5_free_keyblock_contents (context, as_key);
  72             as_key->length = 0;
  73         }
  74     }
  75 
  76     if (password->data[0] == '\0') {
  77         if (prompter == NULL)
  78                 prompter = krb5_prompter_posix; /* Solaris Kerberos */
  79 
  80         if ((ret = krb5_unparse_name(context, client, &clientstr)))
  81           return(ret);
  82 
  83         strcpy(promptstr, "Password for ");
  84         strncat(promptstr, clientstr, sizeof(promptstr)-strlen(promptstr)-1);
  85         promptstr[sizeof(promptstr)-1] = '\0';
  86 
  87         free(clientstr);
  88 
  89         prompt.prompt = promptstr;
  90         prompt.hidden = 1;
  91         prompt.reply = password;
  92         prompt_type = KRB5_PROMPT_TYPE_PASSWORD;
  93 
  94         /* PROMPTER_INVOCATION */
  95         krb5int_set_prompt_types(context, &prompt_type);
  96         if ((ret = (((*prompter)(context, prompter_data, NULL, NULL,
  97                                 1, &prompt))))) {
  98             krb5int_set_prompt_types(context, 0);
  99             return(ret);
 100         }
 101         krb5int_set_prompt_types(context, 0);
 102     }
 103 
 104     if ((salt->length == -1 || salt->length == SALT_TYPE_AFS_LENGTH) && (salt->data == NULL)) {
 105         if ((ret = krb5_principal2salt(context, client, &defsalt)))
 106             return(ret);
 107 
 108         salt = &defsalt;
 109     } else {
 110         defsalt.length = 0;
 111     }
 112 
 113     ret = krb5_c_string_to_key_with_params(context, etype, password, salt,
 114                                            params->data?params:NULL, as_key);
 115 
 116     if (defsalt.length)
 117         krb5_xfree(defsalt.data);
 118 
 119     return(ret);
 120 }
 121 
 122 krb5_error_code KRB5_CALLCONV
 123 krb5_get_init_creds_password(krb5_context context,
 124                              krb5_creds *creds,
 125                              krb5_principal client,
 126                              char *password,
 127                              krb5_prompter_fct prompter,
 128                              void *data,
 129                              krb5_deltat start_time,
 130                              char *in_tkt_service,
 131                              krb5_get_init_creds_opt *options)
 132 {
 133         /*
 134          * Solaris Kerberos:
 135          * We call our own private function that returns the as_reply back to
 136          * the caller.  This structure contains information, such as
 137          * key-expiration and last-req fields.  Entities such as pam_krb5 can
 138          * use this information to provide account/password expiration warnings.
 139          * The original "prompter" interface is not granular enough for PAM,
 140          * as it will perform all passes w/o coordination with other modules.
 141          */
 142         return (__krb5_get_init_creds_password(context, creds, client, password,
 143                 prompter, data, start_time, in_tkt_service, options, NULL));
 144 }
 145 
 146 /*
 147  * Solaris Kerberos:
 148  * See krb5_get_init_creds_password()'s comments for the justification of this
 149  * private function.  Caller must free ptr_as_reply if non-NULL.
 150  */
 151 krb5_error_code KRB5_CALLCONV
 152 __krb5_get_init_creds_password(
 153      krb5_context context,
 154      krb5_creds *creds,
 155      krb5_principal client,
 156      char *password,
 157      krb5_prompter_fct prompter,
 158      void *data,
 159      krb5_deltat start_time,
 160      char *in_tkt_service,
 161      krb5_get_init_creds_opt *options,
 162      krb5_kdc_rep **ptr_as_reply)
 163 {
 164    krb5_error_code ret, ret2;
 165    int use_master;
 166    krb5_kdc_rep *as_reply;
 167    int tries;
 168    krb5_creds chpw_creds;
 169    krb5_data pw0, pw1;
 170    char banner[1024], pw0array[1024], pw1array[1024];
 171    krb5_prompt prompt[2];
 172    krb5_prompt_type prompt_types[sizeof(prompt)/sizeof(prompt[0])];
 173    krb5_gic_opt_ext *opte = NULL;
 174    krb5_gic_opt_ext *chpw_opte = NULL;
 175 
 176    char admin_realm[1024], *cpw_service=NULL, *princ_str=NULL;
 177    kadm5_config_params  params;
 178    void *server_handle;
 179    const char *err_msg_1 = NULL;
 180 
 181    use_master = 0;
 182    as_reply = NULL;
 183    memset(&chpw_creds, 0, sizeof(chpw_creds));
 184 
 185    pw0.data = pw0array;
 186 
 187    if (password && password[0]) {
 188       if ((pw0.length = strlen(password)) > sizeof(pw0array)) {
 189          ret = EINVAL;
 190          goto cleanup;
 191       }
 192       strcpy(pw0.data, password);
 193    } else {
 194       pw0.data[0] = '\0';
 195       pw0.length = sizeof(pw0array);
 196    }
 197 
 198    pw1.data = pw1array;
 199    pw1.data[0] = '\0';
 200    pw1.length = sizeof(pw1array);
 201 
 202    ret = krb5int_gic_opt_to_opte(context, options, &opte, 1,
 203                                  "krb5_get_init_creds_password");
 204    if (ret)
 205       goto cleanup;
 206 
 207    /* first try: get the requested tkt from any kdc */
 208 
 209    ret = krb5_get_init_creds(context, creds, client, prompter, data,
 210                              start_time, in_tkt_service, opte,
 211                              krb5_get_as_key_password, (void *) &pw0,
 212                              &use_master, &as_reply);
 213    /* check for success */
 214 
 215    if (ret == 0)
 216       goto cleanup;
 217 
 218    /* If all the kdc's are unavailable, or if the error was due to a
 219       user interrupt, or preauth errored out, fail */
 220 
 221    if ((ret == KRB5_KDC_UNREACH) ||
 222        (ret == KRB5_PREAUTH_FAILED) ||
 223        (ret == KRB5_LIBOS_PWDINTR) ||
 224        (ret == KRB5_REALM_CANT_RESOLVE))
 225       goto cleanup;
 226 
 227    /* if the reply did not come from the master kdc, try again with
 228       the master kdc */
 229 
 230    if (!use_master) {
 231       use_master = 1;
 232 
 233       if (as_reply) {
 234           krb5_free_kdc_rep( context, as_reply);
 235           as_reply = NULL;
 236       }
 237       
 238       err_msg_1 = krb5_get_error_message(context, ret);
 239 
 240       ret2 = krb5_get_init_creds(context, creds, client, prompter, data,
 241                                  start_time, in_tkt_service, opte,
 242                                  krb5_get_as_key_password, (void *) &pw0,
 243                                  &use_master, &as_reply);
 244       
 245       if (ret2 == 0) {
 246          ret = 0;
 247          goto cleanup;
 248       }
 249 
 250       /* if the master is unreachable, return the error from the
 251          slave we were able to contact or reset the use_master flag */
 252 
 253       if ((ret2 != KRB5_KDC_UNREACH) &&
 254         (ret2 != KRB5_REALM_CANT_RESOLVE) &&
 255         (ret2 != KRB5_REALM_UNKNOWN)) {
 256         ret = ret2;
 257       } else {
 258         use_master = 0;
 259         /* Solaris - if 2nd try failed, reset 1st err msg */
 260         if (ret2 && err_msg_1) {
 261           krb5_set_error_message(context, ret, err_msg_1);
 262         }
 263       }
 264    }
 265 
 266 /* Solaris Kerberos: 163 resync */
 267 /* #ifdef USE_LOGIN_LIBRARY */
 268         if (ret == KRB5KDC_ERR_KEY_EXP)
 269                 goto cleanup;   /* Login library will deal appropriately with this error */
 270 /* #endif */
 271 
 272    /* at this point, we have an error from the master.  if the error
 273       is not password expired, or if it is but there's no prompter,
 274       return this error */
 275 
 276    if ((ret != KRB5KDC_ERR_KEY_EXP) ||
 277        (prompter == NULL))
 278       goto cleanup;
 279 
 280     /* historically the default has been to prompt for password change.
 281      * if the change password prompt option has not been set, we continue
 282      * to prompt.  Prompting is only disabled if the option has been set
 283      * and the value has been set to false.
 284      */
 285     if (!(options->flags & KRB5_GET_INIT_CREDS_OPT_CHG_PWD_PRMPT))
 286         goto cleanup;
 287 
 288     /* ok, we have an expired password.  Give the user a few chances
 289       to change it */
 290 
 291 
 292    /*
 293     * Solaris Kerberos:
 294     * Get the correct change password service principal name to use.
 295     * This is necessary because SEAM based admin servers require
 296     * a slightly different service principal name than MIT/MS servers.
 297     */
 298 
 299    memset((char *) &params, 0, sizeof (params));
 300 
 301    snprintf(admin_realm, sizeof (admin_realm),
 302         krb5_princ_realm(context, client)->data);
 303    params.mask |= KADM5_CONFIG_REALM;
 304    params.realm = admin_realm;
 305 
 306    ret=kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service);
 307 
 308    if (ret != KADM5_OK) {
 309         syslog(LOG_ERR, dgettext(TEXT_DOMAIN,
 310             "Kerberos mechanism library: Unable to get change password "
 311             "service name for realm %s\n"), admin_realm);
 312         goto cleanup;
 313    } else {
 314         ret=0;
 315    }
 316 
 317    /* extract the string version of the principal */
 318    if ((ret = krb5_unparse_name(context, client, &princ_str)))
 319         goto cleanup;
 320 
 321    ret = kadm5_init_with_password(princ_str, pw0array, cpw_service,
 322         &params, KADM5_STRUCT_VERSION, KADM5_API_VERSION_2, NULL,
 323         &server_handle);
 324 
 325    if (ret != 0) {
 326         goto cleanup;
 327    }
 328 
 329    prompt[0].prompt = "Enter new password";
 330    prompt[0].hidden = 1;
 331    prompt[0].reply = &pw0;
 332    prompt_types[0] = KRB5_PROMPT_TYPE_NEW_PASSWORD;
 333 
 334    prompt[1].prompt = "Enter it again";
 335    prompt[1].hidden = 1;
 336    prompt[1].reply = &pw1;
 337    prompt_types[1] = KRB5_PROMPT_TYPE_NEW_PASSWORD_AGAIN;
 338 
 339    strcpy(banner, "Password expired.  You must change it now.");
 340 
 341    for (tries = 3; tries; tries--) {
 342       pw0.length = sizeof(pw0array);
 343       pw1.length = sizeof(pw1array);
 344 
 345       /* PROMPTER_INVOCATION */
 346       krb5int_set_prompt_types(context, prompt_types);
 347       if ((ret = ((*prompter)(context, data, 0, banner,
 348                               sizeof(prompt)/sizeof(prompt[0]), prompt))))
 349          goto cleanup;
 350       krb5int_set_prompt_types(context, 0);
 351 
 352 
 353       if (strcmp(pw0.data, pw1.data) != 0) {
 354          ret = KRB5_LIBOS_BADPWDMATCH;
 355          sprintf(banner, "%s.  Please try again.", error_message(ret));
 356       } else if (pw0.length == 0) {
 357          ret = KRB5_CHPW_PWDNULL;
 358          sprintf(banner, "%s.  Please try again.", error_message(ret));
 359       } else {
 360          int result_code;
 361          krb5_data code_string;
 362          krb5_data result_string;
 363 
 364          if ((ret = krb5_change_password(context, &chpw_creds, pw0array,
 365                                          &result_code, &code_string,
 366                                          &result_string)))
 367             goto cleanup;
 368 
 369          /* the change succeeded.  go on */
 370 
 371          if (result_code == 0) {
 372             krb5_xfree(result_string.data);
 373             break;
 374          }
 375 
 376          /* set this in case the retry loop falls through */
 377 
 378          ret = KRB5_CHPW_FAIL;
 379 
 380          if (result_code != KRB5_KPASSWD_SOFTERROR) {
 381             krb5_xfree(result_string.data);
 382             goto cleanup;
 383          }
 384 
 385          /* the error was soft, so try again */
 386 
 387          /* 100 is I happen to know that no code_string will be longer
 388             than 100 chars */
 389 
 390          if (result_string.length > (sizeof(banner)-100))
 391             result_string.length = sizeof(banner)-100;
 392 
 393          sprintf(banner, "%.*s%s%.*s.  Please try again.\n",
 394                  (int) code_string.length, code_string.data,
 395                  result_string.length ? ": " : "",
 396                  (int) result_string.length,
 397                  result_string.data ? result_string.data : "");
 398 
 399          krb5_xfree(code_string.data);
 400          krb5_xfree(result_string.data);
 401       }
 402    }
 403 
 404    if (ret)
 405       goto cleanup;
 406 
 407    /* the password change was successful.  Get an initial ticket
 408       from the master.  this is the last try.  the return from this
 409       is final.  */
 410 
 411    ret = krb5_get_init_creds(context, creds, client, prompter, data,
 412                              start_time, in_tkt_service, opte,
 413                              krb5_get_as_key_password, (void *) &pw0,
 414                              &use_master, &as_reply);
 415 
 416 cleanup:
 417    if (err_msg_1)
 418      free((void *)err_msg_1);
 419 
 420    krb5int_set_prompt_types(context, 0);
 421    /* if getting the password was successful, then check to see if the
 422       password is about to expire, and warn if so */
 423 
 424    if (ret == 0) {
 425       krb5_timestamp now;
 426       krb5_last_req_entry **last_req;
 427       int hours;
 428 
 429       /* XXX 7 days should be configurable.  This is all pretty ad hoc,
 430          and could probably be improved if I was willing to screw around
 431          with timezones, etc. */
 432 
 433       if (prompter &&
 434           (in_tkt_service && cpw_service &&
 435            (strcmp(in_tkt_service, cpw_service) != 0)) &&
 436           ((ret = krb5_timeofday(context, &now)) == 0) &&
 437           as_reply->enc_part2->key_exp &&
 438           ((hours = ((as_reply->enc_part2->key_exp-now)/(60*60))) <= 7*24) &&
 439           (hours >= 0)) {
 440          if (hours < 1)
 441             sprintf(banner,
 442                     "Warning: Your password will expire in less than one hour.");
 443          else if (hours <= 48)
 444             sprintf(banner, "Warning: Your password will expire in %d hour%s.",
 445                     hours, (hours == 1)?"":"s");
 446          else
 447             sprintf(banner, "Warning: Your password will expire in %d days.",
 448                     hours/24);
 449 
 450          /* ignore an error here */
 451          /* PROMPTER_INVOCATION */
 452          (*prompter)(context, data, 0, banner, 0, 0);
 453       } else if (prompter &&
 454                  (!in_tkt_service ||
 455                   (strcmp(in_tkt_service, "kadmin/changepw") != 0)) &&
 456                  as_reply->enc_part2 && as_reply->enc_part2->last_req) {
 457          /*
 458           * Check the last_req fields
 459           */
 460 
 461          for (last_req = as_reply->enc_part2->last_req; *last_req; last_req++)
 462             if ((*last_req)->lr_type == KRB5_LRQ_ALL_PW_EXPTIME ||
 463                 (*last_req)->lr_type == KRB5_LRQ_ONE_PW_EXPTIME) {
 464                krb5_deltat delta;
 465                char ts[256];
 466 
 467                if ((ret = krb5_timeofday(context, &now)))
 468                   break;
 469 
 470                if ((ret = krb5_timestamp_to_string((*last_req)->value,
 471                                                    ts, sizeof(ts))))
 472                   break;
 473 
 474                delta = (*last_req)->value - now;
 475 
 476                if (delta < 3600)
 477                   sprintf(banner,
 478                     "Warning: Your password will expire in less than one "
 479                      "hour on %s", ts);
 480                else if (delta < 86400*2)
 481                   sprintf(banner,
 482                      "Warning: Your password will expire in %d hour%s on %s",
 483                      delta / 3600, delta < 7200 ? "" : "s", ts);
 484                else
 485                   sprintf(banner,
 486                      "Warning: Your password will expire in %d days on %s",
 487                      delta / 86400, ts);
 488                /* ignore an error here */
 489                /* PROMPTER_INVOCATION */
 490                (*prompter)(context, data, 0, banner, 0, 0);
 491             }
 492       }
 493    }
 494 
 495    free(cpw_service);
 496    free(princ_str);
 497    if (opte && krb5_gic_opt_is_shadowed(opte))
 498       krb5_get_init_creds_opt_free(context, (krb5_get_init_creds_opt *)opte);
 499    memset(pw0array, 0, sizeof(pw0array));
 500    memset(pw1array, 0, sizeof(pw1array));
 501    krb5_free_cred_contents(context, &chpw_creds);
 502    /*
 503     * Solaris Kerberos:
 504     * Argument, ptr_as_reply, being returned to caller if success and non-NULL.
 505     */
 506    if (as_reply != NULL) {
 507         if (ptr_as_reply == NULL)
 508            krb5_free_kdc_rep(context, as_reply);
 509         else
 510            *ptr_as_reply = as_reply;
 511    }
 512 
 513    return(ret);
 514 }
 515 krb5_error_code krb5int_populate_gic_opt (
 516     krb5_context context, krb5_gic_opt_ext **opte,
 517     krb5_flags options, krb5_address * const *addrs, krb5_enctype *ktypes,
 518     krb5_preauthtype *pre_auth_types, krb5_creds *creds)
 519 {
 520   int i;
 521   krb5_int32 starttime;
 522   krb5_get_init_creds_opt *opt;
 523 
 524 
 525     krb5_get_init_creds_opt_init(opt);
 526     if (addrs)
 527       krb5_get_init_creds_opt_set_address_list(opt, (krb5_address **) addrs);
 528     if (ktypes) {
 529         for (i=0; ktypes[i]; i++);
 530         if (i)
 531             krb5_get_init_creds_opt_set_etype_list(opt, ktypes, i);
 532     }
 533     if (pre_auth_types) {
 534         for (i=0; pre_auth_types[i]; i++);
 535         if (i)
 536             krb5_get_init_creds_opt_set_preauth_list(opt, pre_auth_types, i);
 537     }
 538     if (options&KDC_OPT_FORWARDABLE)
 539         krb5_get_init_creds_opt_set_forwardable(opt, 1);
 540     else krb5_get_init_creds_opt_set_forwardable(opt, 0);
 541     if (options&KDC_OPT_PROXIABLE)
 542         krb5_get_init_creds_opt_set_proxiable(opt, 1);
 543     else krb5_get_init_creds_opt_set_proxiable(opt, 0);
 544     if (creds && creds->times.endtime) {
 545         krb5_timeofday(context, &starttime);
 546         if (creds->times.starttime) starttime = creds->times.starttime;
 547         krb5_get_init_creds_opt_set_tkt_life(opt, creds->times.endtime - starttime);
 548     }
 549     return krb5int_gic_opt_to_opte(context, opt, opte, 0,
 550                                    "krb5int_populate_gic_opt");
 551 }
 552 
 553 /*
 554   Rewrites get_in_tkt in terms of newer get_init_creds API.
 555  Attempts to get an initial ticket for creds->client to use server
 556  creds->server, (realm is taken from creds->client), with options
 557  options, and using creds->times.starttime, creds->times.endtime,
 558  creds->times.renew_till as from, till, and rtime.  
 559  creds->times.renew_till is ignored unless the RENEWABLE option is requested.
 560 
 561  If addrs is non-NULL, it is used for the addresses requested.  If it is
 562  null, the system standard addresses are used.
 563 
 564  If password is non-NULL, it is converted using the cryptosystem entry
 565  point for a string conversion routine, seeded with the client's name.
 566  If password is passed as NULL, the password is read from the terminal,
 567  and then converted into a key.
 568 
 569  A succesful call will place the ticket in the credentials cache ccache.
 570 
 571  returns system errors, encryption errors
 572  */
 573 krb5_error_code KRB5_CALLCONV
 574 krb5_get_in_tkt_with_password(krb5_context context, krb5_flags options,
 575                               krb5_address *const *addrs, krb5_enctype *ktypes,
 576                               krb5_preauthtype *pre_auth_types,
 577                               const char *password, krb5_ccache ccache,
 578                               krb5_creds *creds, krb5_kdc_rep **ret_as_reply)
 579 {
 580     krb5_error_code retval;
 581     krb5_data pw0;
 582     char pw0array[1024];
 583     char * server;
 584     krb5_principal server_princ, client_princ;
 585     int use_master = 0;
 586     krb5_gic_opt_ext *opte = NULL;
 587 
 588     pw0array[0] = '\0';
 589     pw0.data = pw0array;
 590     if (password) {
 591         pw0.length = strlen(password);
 592         if (pw0.length > sizeof(pw0array))
 593             return EINVAL;
 594         strncpy(pw0.data, password, sizeof(pw0array));
 595         if (pw0.length == 0)
 596             pw0.length = sizeof(pw0array);
 597     } else {
 598         pw0.length = sizeof(pw0array);
 599     }
 600     retval = krb5int_populate_gic_opt(context, &opte,
 601                                       options, addrs, ktypes,
 602                                       pre_auth_types, creds);
 603     if (retval)
 604       return (retval);
 605     retval = krb5_unparse_name( context, creds->server, &server);
 606     if (retval) {
 607       return (retval);
 608       krb5_get_init_creds_opt_free(context, (krb5_get_init_creds_opt *)opte);
 609     }
 610     server_princ = creds->server;
 611     client_princ = creds->client;
 612         retval = krb5_get_init_creds (context,
 613                                            creds, creds->client,  
 614                                            krb5_prompter_posix,  NULL,
 615                                            0, server, opte,
 616                                       krb5_get_as_key_password, &pw0,
 617                                       &use_master, ret_as_reply);
 618           krb5_free_unparsed_name( context, server);
 619           krb5_get_init_creds_opt_free(context, (krb5_get_init_creds_opt *)opte);
 620         if (retval) {
 621           return (retval);
 622         }
 623         if (creds->server)
 624             krb5_free_principal( context, creds->server);
 625         if (creds->client)
 626             krb5_free_principal( context, creds->client);
 627         creds->client = client_princ;
 628         creds->server = server_princ;
 629         /* store it in the ccache! */
 630         if (ccache)
 631           if ((retval = krb5_cc_store_cred(context, ccache, creds)))
 632             return (retval);
 633         return retval;
 634   }
 635