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