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