Example #1
0
krb5_error_code KRB5_CALLCONV
krb5_cc_retrieve_cred(krb5_context context, krb5_ccache cache,
                      krb5_flags flags, krb5_creds *mcreds,
                      krb5_creds *creds)
{
    krb5_error_code ret;
    krb5_data tmprealm;

    ret = cache->ops->retrieve(context, cache, flags, mcreds, creds);
    TRACE_CC_RETRIEVE(context, cache, mcreds, ret);
    if (ret != KRB5_CC_NOTFOUND)
        return ret;
    if (!krb5_is_referral_realm(&mcreds->server->realm))
        return ret;

    /*
     * Retry using client's realm if service has referral realm.
     */
    tmprealm = mcreds->server->realm;
    mcreds->server->realm = mcreds->client->realm;
    ret = cache->ops->retrieve(context, cache, flags, mcreds, creds);
    TRACE_CC_RETRIEVE_REF(context, cache, mcreds, ret);
    mcreds->server->realm = tmprealm;
    return ret;
}
Example #2
0
/* Possibly try a non-referral request after a referral request failure.
 * Expects ctx->reply_code to be set to the error from a referral request. */
static krb5_error_code
try_fallback(krb5_context context, krb5_tkt_creds_context ctx)
{
    krb5_error_code code;
    char **hrealms;

    /* Only fall back if our error was from the first referral request. */
    if (ctx->referral_count > 1)
        return ctx->reply_code;

    /* If the request used a specified realm, make a non-referral request to
     * that realm (in case it's a KDC which rejects KDC_OPT_CANONICALIZE). */
    if (!krb5_is_referral_realm(&ctx->req_server->realm))
        return begin_non_referral(context, ctx);

    if (ctx->server->length < 2) {
        /* We need a type/host format principal to find a fallback realm. */
        return KRB5_ERR_HOST_REALM_UNKNOWN;
    }

    /* We expect this to give exactly one answer (XXX clean up interface). */
    code = krb5_get_fallback_host_realm(context, &ctx->server->data[1],
                                        &hrealms);
    if (code != 0)
        return code;

    /* If the fallback realm isn't any different, use the existing TGT. */
    if (data_eq_string(ctx->server->realm, hrealms[0])) {
        krb5_free_host_realm(context, hrealms);
        return begin_non_referral(context, ctx);
    }

    /* Rewrite server->realm to be the fallback realm. */
    krb5_free_data_contents(context, &ctx->server->realm);
    ctx->server->realm = string2data(hrealms[0]);
    free(hrealms);
    TRACE_TKT_CREDS_FALLBACK(context, &ctx->server->realm);

    /* Obtain a TGT for the new service realm. */
    ctx->getting_tgt_for = STATE_NON_REFERRAL;
    return begin_get_tgt(context, ctx);
}
Example #3
0
static krb5_error_code
realm_choose(krb5_context context, krb5_ccselect_moddata data,
             krb5_principal server, krb5_ccache *cache_out,
             krb5_principal *princ_out)
{
    krb5_error_code ret;
    krb5_cccol_cursor cursor;
    krb5_ccache cache;
    krb5_principal princ;

    *cache_out = NULL;
    *princ_out = NULL;

    if (krb5_is_referral_realm(&server->realm))
        return KRB5_PLUGIN_NO_HANDLE;

    /* Scan the collection for a cache with a client principal in the same
     * realm as the server principal. */
    ret = krb5_cccol_cursor_new(context, &cursor);
    if (ret)
        return ret;
    while ((ret = krb5_cccol_cursor_next(context, cursor, &cache)) == 0 &&
           cache != NULL) {
        ret = krb5_cc_get_principal(context, cache, &princ);
        if (ret == 0) {
            if (data_eq(princ->realm, server->realm))
                break;
            krb5_free_principal(context, princ);
        }
        krb5_cc_close(context, cache);
    }
    krb5_cccol_cursor_free(context, &cursor);
    if (ret)
        return ret;

    if (cache == NULL)
        return KRB5_PLUGIN_NO_HANDLE;
    *cache_out = cache;
    *princ_out = princ;
    return 0;
}
Example #4
0
/* Decide where to begin the acquisition process. */
static krb5_error_code
begin(krb5_context context, krb5_tkt_creds_context ctx)
{
    krb5_error_code code;

    code = check_cache(context, ctx);
    if (code != 0 || ctx->state == STATE_COMPLETE)
        return code;

    /* If the server realm is unspecified, start with the client realm. */
    if (krb5_is_referral_realm(&ctx->server->realm)) {
        krb5_free_data_contents(context, &ctx->server->realm);
        code = krb5int_copy_data_contents(context, &ctx->client->realm,
                                          &ctx->server->realm);
        TRACE_TKT_CREDS_REFERRAL_REALM(context, ctx->server);
        if (code != 0)
            return code;
    }

    /* Obtain a TGT for the service realm. */
    ctx->getting_tgt_for = STATE_REFERRALS;
    return begin_get_tgt(context, ctx);
}
Example #5
0
krb5_error_code KRB5_CALLCONV
krb5_kt_get_entry(krb5_context context, krb5_keytab keytab,
		  krb5_const_principal principal, krb5_kvno vno,
		  krb5_enctype enctype, krb5_keytab_entry *entry)
{
    krb5_error_code err;
    krb5_principal_data princ_data;

    if (krb5_is_referral_realm(&principal->realm)) {
	char *realm;
	princ_data = *principal;
	principal = &princ_data;
	err = krb5_get_default_realm(context, &realm);
	if (err)
	    return err;
	princ_data.realm.data = realm;
	princ_data.realm.length = strlen(realm);
    }
    err = krb5_x((keytab)->ops->get,(context, keytab, principal, vno, enctype,
				     entry));
    if (principal == &princ_data)
	krb5_free_default_realm(context, princ_data.realm.data);
    return err;
}
Example #6
0
static krb5_error_code
krb5_rd_req_decoded_opt(krb5_context context, krb5_auth_context *auth_context,
			const krb5_ap_req *req, krb5_const_principal server,
			krb5_keytab keytab, krb5_flags *ap_req_options,
			krb5_ticket **ticket, int check_valid_flag)
{
    krb5_error_code 	  retval = 0;
    krb5_timestamp 	  currenttime;
    krb5_principal_data princ_data;
    
    req->ticket->enc_part2 == NULL;
    if (server && krb5_is_referral_realm(&server->realm)) {
	char *realm;
	princ_data = *server;
	server = &princ_data;
	retval = krb5_get_default_realm(context, &realm);
	if (retval)
	    return retval;
	princ_data.realm.data = realm;
	princ_data.realm.length = strlen(realm);
    }
    if (server && !krb5_principal_compare(context, server, req->ticket->server)) {
	char *found_name = 0, *wanted_name = 0;
	if (krb5_unparse_name(context, server, &wanted_name) == 0
	    && krb5_unparse_name(context, req->ticket->server, &found_name) == 0)
	    krb5_set_error_message(context, KRB5KRB_AP_WRONG_PRINC,
				   "Wrong principal in request (found %s, wanted %s)",
				   found_name, wanted_name);
	krb5_free_unparsed_name(context, wanted_name);
	krb5_free_unparsed_name(context, found_name);
	retval =  KRB5KRB_AP_WRONG_PRINC;
	goto cleanup;
    }

    /* if (req->ap_options & AP_OPTS_USE_SESSION_KEY)
       do we need special processing here ?	*/

    /* decrypt the ticket */
    if ((*auth_context)->keyblock) { /* User to User authentication */
    	if ((retval = krb5_decrypt_tkt_part(context, (*auth_context)->keyblock,
					    req->ticket)))
goto cleanup;
	krb5_free_keyblock(context, (*auth_context)->keyblock);
	(*auth_context)->keyblock = NULL;
    } else {
    	if ((retval = krb5_rd_req_decrypt_tkt_part(context, req, keytab)))
	    goto cleanup;
    }

    /* XXX this is an evil hack.  check_valid_flag is set iff the call
       is not from inside the kdc.  we can use this to determine which
       key usage to use */
    if ((retval = decrypt_authenticator(context, req, 
					&((*auth_context)->authentp),
					check_valid_flag)))
	goto cleanup;

    if (!krb5_principal_compare(context, (*auth_context)->authentp->client,
				req->ticket->enc_part2->client)) {
	retval = KRB5KRB_AP_ERR_BADMATCH;
	goto cleanup;
    }

    if ((*auth_context)->remote_addr && 
      !krb5_address_search(context, (*auth_context)->remote_addr, 
			   req->ticket->enc_part2->caddrs)) {
	retval = KRB5KRB_AP_ERR_BADADDR;
	goto cleanup;
    }

    /* okay, now check cross-realm policy */

#if defined(_SINGLE_HOP_ONLY)

    /* Single hop cross-realm tickets only */

    { 
	krb5_transited *trans = &(req->ticket->enc_part2->transited);

      	/* If the transited list is empty, then we have at most one hop */
      	if (trans->tr_contents.data && trans->tr_contents.data[0])
            retval = KRB5KRB_AP_ERR_ILL_CR_TKT;
    }

#elif defined(_NO_CROSS_REALM)

    /* No cross-realm tickets */

    { 
	char		* lrealm;
      	krb5_data      	* realm;
      	krb5_transited 	* trans;
  
	realm = krb5_princ_realm(context, req->ticket->enc_part2->client);
	trans = &(req->ticket->enc_part2->transited);

	/*
      	 * If the transited list is empty, then we have at most one hop 
      	 * So we also have to check that the client's realm is the local one 
	 */
      	krb5_get_default_realm(context, &lrealm);
      	if ((trans->tr_contents.data && trans->tr_contents.data[0]) ||
          strlen(lrealm) != realm->length ||
          memcmp(lrealm, realm->data, strlen(lrealm))) {
            retval = KRB5KRB_AP_ERR_ILL_CR_TKT;
      	}
      	free(lrealm);
    }

#else

    /* Hierarchical Cross-Realm */
  
    {
      	krb5_data      * realm;
      	krb5_transited * trans;
  
	realm = krb5_princ_realm(context, req->ticket->enc_part2->client);
	trans = &(req->ticket->enc_part2->transited);

	/*
      	 * If the transited list is not empty, then check that all realms 
      	 * transited are within the hierarchy between the client's realm  
      	 * and the local realm.                                        
  	 */
	if (trans->tr_contents.data && trans->tr_contents.data[0]) {
	    retval = krb5_check_transited_list(context, &(trans->tr_contents), 
					       realm,
					       krb5_princ_realm (context,
								 server));
      	}
    }

#endif

    if (retval)  goto cleanup;

    /* only check rcache if sender has provided one---some services
       may not be able to use replay caches (such as datagram servers) */

    if ((*auth_context)->rcache) {
	krb5_donot_replay  rep;
        krb5_tkt_authent   tktauthent;

	tktauthent.ticket = req->ticket;	
	tktauthent.authenticator = (*auth_context)->authentp;
	if (!(retval = krb5_auth_to_rep(context, &tktauthent, &rep))) {
	    retval = krb5_rc_store(context, (*auth_context)->rcache, &rep);
	    krb5_xfree(rep.server);
	    krb5_xfree(rep.client);
	}

	if (retval)
	    goto cleanup;
    }

    retval = krb5_validate_times(context, &req->ticket->enc_part2->times);
    if (retval != 0)
	    goto cleanup;

    if ((retval = krb5_timeofday(context, &currenttime)))
	goto cleanup;

    if (!in_clock_skew((*auth_context)->authentp->ctime)) {
	retval = KRB5KRB_AP_ERR_SKEW;
	goto cleanup;
    }

    if (check_valid_flag) {
      if (req->ticket->enc_part2->flags & TKT_FLG_INVALID) {
	retval = KRB5KRB_AP_ERR_TKT_INVALID;
	goto cleanup;
      }
    }

    /* check if the various etypes are permitted */

    if ((*auth_context)->auth_context_flags & KRB5_AUTH_CONTEXT_PERMIT_ALL) {
	/* no etype check needed */;
    } else if ((*auth_context)->permitted_etypes == NULL) {
	int etype;
	/* check against the default set */
	if ((!krb5_is_permitted_enctype(context,
					etype = req->ticket->enc_part.enctype)) ||
	    (!krb5_is_permitted_enctype(context,
					etype = req->ticket->enc_part2->session->enctype)) ||
	    (((*auth_context)->authentp->subkey) &&
	     !krb5_is_permitted_enctype(context,
					etype = (*auth_context)->authentp->subkey->enctype))) {
	    char enctype_name[30];
	    retval = KRB5_NOPERM_ETYPE;
	    if (krb5_enctype_to_string(etype, enctype_name, sizeof(enctype_name)) == 0)
		krb5_set_error_message(context, retval,
				       "Encryption type %s not permitted",
				       enctype_name);
	    goto cleanup;
	}
    } else {
	/* check against the set in the auth_context */
	int i;

	for (i=0; (*auth_context)->permitted_etypes[i]; i++)
	    if ((*auth_context)->permitted_etypes[i] ==
		req->ticket->enc_part.enctype)
		break;
	if (!(*auth_context)->permitted_etypes[i]) {
	    char enctype_name[30];
	    retval = KRB5_NOPERM_ETYPE;
	    if (krb5_enctype_to_string(req->ticket->enc_part.enctype,
				       enctype_name, sizeof(enctype_name)) == 0)
		krb5_set_error_message(context, retval,
				       "Encryption type %s not permitted",
				       enctype_name);
	    goto cleanup;
	}
	
	for (i=0; (*auth_context)->permitted_etypes[i]; i++)
	    if ((*auth_context)->permitted_etypes[i] ==
		req->ticket->enc_part2->session->enctype)
		break;
	if (!(*auth_context)->permitted_etypes[i]) {
	    char enctype_name[30];
	    retval = KRB5_NOPERM_ETYPE;
	    if (krb5_enctype_to_string(req->ticket->enc_part2->session->enctype,
				       enctype_name, sizeof(enctype_name)) == 0)
		krb5_set_error_message(context, retval,
				       "Encryption type %s not permitted",
				       enctype_name);
	    goto cleanup;
	}
	
	if ((*auth_context)->authentp->subkey) {
	    for (i=0; (*auth_context)->permitted_etypes[i]; i++)
		if ((*auth_context)->permitted_etypes[i] ==
		    (*auth_context)->authentp->subkey->enctype)
		    break;
	    if (!(*auth_context)->permitted_etypes[i]) {
		char enctype_name[30];
		retval = KRB5_NOPERM_ETYPE;
		if (krb5_enctype_to_string((*auth_context)->authentp->subkey->enctype,
					   enctype_name,
					   sizeof(enctype_name)) == 0)
		    krb5_set_error_message(context, retval,
					   "Encryption type %s not permitted",
					   enctype_name);
		goto cleanup;
	    }
	}
    }

    (*auth_context)->remote_seq_number = (*auth_context)->authentp->seq_number;
    if ((*auth_context)->authentp->subkey) {
	if ((retval = krb5_copy_keyblock(context,
					 (*auth_context)->authentp->subkey,
					 &((*auth_context)->recv_subkey))))
	    goto cleanup;
	retval = krb5_copy_keyblock(context, (*auth_context)->authentp->subkey,
				    &((*auth_context)->send_subkey));
	if (retval) {
	    krb5_free_keyblock(context, (*auth_context)->recv_subkey);
	    (*auth_context)->recv_subkey = NULL;
	    goto cleanup;
	}
    } else {
	(*auth_context)->recv_subkey = 0;
	(*auth_context)->send_subkey = 0;
    }

    if ((retval = krb5_copy_keyblock(context, req->ticket->enc_part2->session,
				     &((*auth_context)->keyblock))))
	goto cleanup;

    /*
     * If not AP_OPTS_MUTUAL_REQUIRED then and sequence numbers are used 
     * then the default sequence number is the one's complement of the
     * sequence number sent ot us.
     */
    if ((!(req->ap_options & AP_OPTS_MUTUAL_REQUIRED)) && 
      (*auth_context)->remote_seq_number) {
	(*auth_context)->local_seq_number ^= 
	  (*auth_context)->remote_seq_number;
    }

    if (ticket)
   	if ((retval = krb5_copy_ticket(context, req->ticket, ticket)))
	    goto cleanup;
    if (ap_req_options)
    	*ap_req_options = req->ap_options;
    retval = 0;
    
cleanup:
    if (server == &princ_data)
	krb5_free_default_realm(context, princ_data.realm.data);
    if (retval) {
	/* only free if we're erroring out...otherwise some
	   applications will need the output. */
	if (req->ticket->enc_part2)
	    krb5_free_enc_tkt_part(context, req->ticket->enc_part2);
	req->ticket->enc_part2 = NULL;
    }
    return retval;
}
Example #7
0
static void
get_tickets(krb5_context context)
{
    char *def_realm, *server;
    krb5_error_code retval;
    krb5_keytab keytab = NULL;
    krb5_principal server_princ = NULL;

    /* Figure out what tickets we'll be using to send. */
    retval = krb5_sname_to_principal(context, NULL, NULL, KRB5_NT_SRV_HST,
                                     &my_principal);
    if (retval) {
        com_err(progname, errno, _("while setting client principal name"));
        exit(1);
    }
    if (realm != NULL) {
        retval = krb5_set_principal_realm(context, my_principal, realm);
        if (retval) {
            com_err(progname, errno,
                    _("while setting client principal realm"));
            exit(1);
        }
    } else if (krb5_is_referral_realm(krb5_princ_realm(context,
                                                       my_principal))) {
        /* We're going to use this as a client principal, so it can't have the
         * referral realm.  Use the default realm instead. */
        retval = krb5_get_default_realm(context, &def_realm);
        if (retval) {
            com_err(progname, errno, _("while getting default realm"));
            exit(1);
        }
        retval = krb5_set_principal_realm(context, my_principal, def_realm);
        if (retval) {
            com_err(progname, errno,
                    _("while setting client principal realm"));
            exit(1);
        }
    }

    /* Construct the principal name for the slave host. */
    memset(&creds, 0, sizeof(creds));
    retval = krb5_sname_to_principal(context, slave_host, KPROP_SERVICE_NAME,
                                     KRB5_NT_SRV_HST, &server_princ);
    if (retval) {
        com_err(progname, errno, _("while setting server principal name"));
        exit(1);
    }
    retval = krb5_unparse_name_flags(context, server_princ,
                                     KRB5_PRINCIPAL_UNPARSE_NO_REALM, &server);
    if (retval) {
        com_err(progname, retval, _("while unparsing server name"));
        exit(1);
    }

    /* Fill in the client. */
    retval = krb5_copy_principal(context, my_principal, &creds.client);
    if (retval) {
        com_err(progname, retval, _("while copying client principal"));
        exit(1);
    }

    if (srvtab != NULL) {
        retval = krb5_kt_resolve(context, srvtab, &keytab);
        if (retval) {
            com_err(progname, retval, _("while resolving keytab"));
            exit(1);
        }
    }

    retval = krb5_get_init_creds_keytab(context, &creds, my_principal, keytab,
                                        0, server, NULL);
    if (retval) {
        com_err(progname, retval, _("while getting initial credentials\n"));
        exit(1);
    }

    if (keytab != NULL)
        krb5_kt_close(context, keytab);
    krb5_free_unparsed_name(context, server);
    krb5_free_principal(context, server_princ);
}
Example #8
0
krb5_error_code
krb5int_locate_server (krb5_context context, const krb5_data *realm,
		       struct addrlist *addrlist,
		       enum locate_service_type svc,
		       int socktype, int family)
{
    krb5_error_code code;
    struct addrlist al = ADDRLIST_INIT;
    char **hostlist = NULL;
    struct srv_dns_entry *dns_list_head = NULL;

    *addrlist = al;

    /*
     * Solaris Kerberos (illumos)
     * Allow main programs to override _locate_server()
     */
    code = override_locate_server(context, realm, &al, svc, socktype, family);
    if (code != KRB5_PLUGIN_NO_HANDLE) {
    	if (code == 0) 
	    *addrlist = al;
	else if (al.space)
	    free_list (&al);
	return (code);
    }
    /* Solaris Kerberos (illumos) */

    code = module_locate_server(context, realm, &al, svc, socktype, family);
    Tprintf("module_locate_server returns %d\n", code);
    if (code == KRB5_PLUGIN_NO_HANDLE) {
	/*
	 * We always try the local file before DNS.  Note that there
	 * is no way to indicate "service not available" via the
	 * config file.
	 */
	code = prof_locate_server(context, realm, &hostlist, svc);

	/*
	 * Solaris Kerberos:
	 * If kpasswd_server has not been configured and dns_lookup_kdc -
	 * dns_fallback are not configured then admin_server should
	 * be inferred, per krb5.conf(4).
	 */
	if (code && svc == locate_service_kpasswd &&
	    !maybe_use_dns(context, "dns_lookup_kdc", 0)) {
		code = prof_locate_server(context, realm, &hostlist,
			locate_service_kadmin);
	}

#ifdef KRB5_DNS_LOOKUP
	/*
	 * Solaris Kerberos:
	 * There is no point in trying to locate the KDC in DNS if "realm"
	 * is empty.
	 */
	/* Try DNS for all profile errors?  */
	if (code && !krb5_is_referral_realm(realm)) {
	    krb5_error_code code2;
	    code2 = dns_locate_server(context, realm, &dns_list_head,
				    svc, socktype, family);

	    if (code2 != KRB5_PLUGIN_NO_HANDLE)
		code = code2;
	}
#endif /* KRB5_DNS_LOOKUP */

	/* We could put more heuristics here, like looking up a hostname
	   of "kerberos."+REALM, etc.  */
    }

    if (code != 0) {
	if (al.space)
	    free_list (&al);
	if (hostlist)
	    profile_free_list(hostlist);
	if (dns_list_head)
	    krb5int_free_srv_dns_data(dns_list_head);

	return code;
    }

    /*
     * At this point we have no errors, let's check to see if we have
     * any KDC entries from krb5.conf or DNS.
     */
    if (!hostlist && !dns_list_head) {
	switch(svc) {
	case locate_service_master_kdc:
	    krb5_set_error_message(context,
				KRB5_REALM_CANT_RESOLVE,
				dgettext(TEXT_DOMAIN,
					"Cannot find a master KDC entry in krb5.conf(4) or DNS Service Location records for realm '%.*s'"),
				realm->length, realm->data);
	    break;
	case locate_service_kadmin:
	    krb5_set_error_message(context,
				KRB5_REALM_CANT_RESOLVE,
				dgettext(TEXT_DOMAIN,
					"Cannot find a kadmin KDC entry in krb5.conf(4) or DNS Service Location records for realm '%.*s'"),
				realm->length, realm->data);
	    break;
	case locate_service_kpasswd:
	    krb5_set_error_message(context,
				KRB5_REALM_CANT_RESOLVE,
				dgettext(TEXT_DOMAIN,
					"Cannot find a kpasswd KDC entry in krb5.conf(4) or DNS Service Location records for realm '%.*s'"),
				realm->length, realm->data);
	    break;
	default: 	  /*  locate_service_kdc: */
		krb5_set_error_message(context,
				    KRB5_REALM_CANT_RESOLVE,
				    dgettext(TEXT_DOMAIN,
					    "Cannot find any KDC entries in krb5.conf(4) or DNS Service Location records for realm '%.*s'"),
				    realm->length, realm->data);
			       
	}
	return KRB5_REALM_CANT_RESOLVE;
    }

    /* We have KDC entries, let see if we can get their net addrs. */
    if (hostlist)
	code = prof_hostnames2netaddrs(hostlist, svc,
				    socktype, family, &al);
    else if (dns_list_head)
	code = dns_hostnames2netaddrs(dns_list_head, svc,
				    socktype, family, &al);
    if (code) {
	if (hostlist)
	    profile_free_list(hostlist);
	if (dns_list_head)
	    krb5int_free_srv_dns_data(dns_list_head);
	return code;
    }

    /*
     * Solaris Kerberos:
     * If an entry for _kerberos-master. does not exist (checked for
     * above) but _kpasswd. does then treat that as an entry for the
     * master KDC (but use port 88 not the kpasswd port). MS AD creates
     * kpasswd entries by default in DNS.
     */
    if (!dns_list_head && svc == locate_service_master_kdc &&
	al.naddrs == 0) {

	/* Look for _kpasswd._tcp|udp */
	code = dns_locate_server(context, realm, &dns_list_head,
				locate_service_kpasswd, socktype, family);

	if (code == 0 && dns_list_head) {
	    int i;
	    struct addrinfo *a;

	    code = dns_hostnames2netaddrs(dns_list_head, svc,
					socktype, family, &al);

	    /* Set the port to 88 instead of the kpasswd port */
	    if (code == 0 && al.naddrs > 0) {
		for (i = 0; i < al.naddrs; i++) {
		    if (al.addrs[i].ai->ai_family == AF_INET)
			for (a = al.addrs[i].ai; a != NULL; a = a->ai_next)
			    ((struct sockaddr_in *)a->ai_addr)->sin_port =
				htons(KRB5_DEFAULT_PORT);
				
		    if (al.addrs[i].ai->ai_family == AF_INET6)
			for (a = al.addrs[i].ai; a != NULL; a = a->ai_next)
			     ((struct sockaddr_in6 *)a->ai_addr)->sin6_port =
				    htons(KRB5_DEFAULT_PORT);
		}
	    }
	}
    }

    /* No errors so far, lets see if we have KDC net addrs */
    if (al.naddrs == 0) {
	char *hostlist_str = NULL, *dnslist_str  = NULL;
	if (al.space)
	    free_list (&al);

	if (hostlist) {
	    hostlist_str = hostlist2str(hostlist);
	    krb5_set_error_message(context, KRB5_REALM_CANT_RESOLVE,
				dgettext(TEXT_DOMAIN,
					"Cannot resolve network address for KDCs '%s' specified in krb5.conf(4) for realm %.*s"),
				hostlist_str ? hostlist_str : "unknown",
				realm->length, realm->data);
	    if (hostlist_str)
		free(hostlist_str);
	} else if (dns_list_head) {
	    dnslist_str = dnslist2str(dns_list_head);
	    krb5_set_error_message(context, KRB5_REALM_CANT_RESOLVE,
				dgettext(TEXT_DOMAIN,
					"Cannot resolve network address for KDCs '%s' discovered via DNS Service Location records for realm '%.*s'"),
				dnslist_str ? dnslist_str : "unknown",
				realm->length, realm->data);
	    if (dnslist_str)
		    free(dnslist_str);
	}

	if (hostlist)
	    profile_free_list(hostlist);
	if (dns_list_head)
	    krb5int_free_srv_dns_data(dns_list_head);

	return KRB5_REALM_CANT_RESOLVE;
    }

    if (hostlist)
	    profile_free_list(hostlist);
    if (dns_list_head)
	    krb5int_free_srv_dns_data(dns_list_head);

    *addrlist = al;
    return 0;
}
Example #9
0
static krb5_error_code
krb5_get_cred_from_kdc_opt(krb5_context context, krb5_ccache ccache,
			   krb5_creds *in_cred, krb5_creds **out_cred,
			   krb5_creds ***tgts, int kdcopt)
{
    krb5_error_code retval, subretval;
    krb5_principal client, server, supplied_server, out_supplied_server;
    krb5_creds tgtq, cc_tgt, *tgtptr, *referral_tgts[KRB5_REFERRAL_MAXHOPS];
    krb5_creds *otgtptr = NULL;
    int tgtptr_isoffpath = 0;
    krb5_boolean old_use_conf_ktypes;
    char **hrealms;
    unsigned int referral_count, i;

    /* 
     * Set up client and server pointers.  Make a fresh and modifyable
     * copy of the in_cred server and save the supplied version.
     */
    client = in_cred->client;
    if ((retval=krb5_copy_principal(context, in_cred->server, &server)))
        return retval;
    /* We need a second copy for the output creds. */
    if ((retval = krb5_copy_principal(context, server,
				      &out_supplied_server)) != 0 ) {
	krb5_free_principal(context, server);
	return retval;
    }
    supplied_server = in_cred->server;
    in_cred->server=server;

    DUMP_PRINC("gc_from_kdc initial client", client);
    DUMP_PRINC("gc_from_kdc initial server", server);
    memset(&cc_tgt, 0, sizeof(cc_tgt));
    memset(&tgtq, 0, sizeof(tgtq));
    memset(&referral_tgts, 0, sizeof(referral_tgts));

    tgtptr = NULL;
    *tgts = NULL;
    *out_cred=NULL;
    old_use_conf_ktypes = context->use_conf_ktypes;

    /* Copy client realm to server if no hint. */
    if (krb5_is_referral_realm(&server->realm)) {
        /* Use the client realm. */
        DPRINTF(("gc_from_kdc: no server realm supplied, "
		 "using client realm.\n"));
	krb5_free_data_contents(context, &server->realm);
	if (!( server->realm.data = (char *)malloc(client->realm.length+1)))
	    return ENOMEM;
	memcpy(server->realm.data, client->realm.data, client->realm.length);
	server->realm.length = client->realm.length;
	server->realm.data[server->realm.length] = 0;
    }
    /*
     * Retreive initial TGT to match the specified server, either for the
     * local realm in the default (referral) case or for the remote
     * realm if we're starting someplace non-local.
     */
    retval = tgt_mcred(context, client, server, client, &tgtq);
    if (retval)
	goto cleanup;

    /* Fast path: Is it in the ccache? */
    context->use_conf_ktypes = 1;
    retval = krb5_cc_retrieve_cred(context, ccache, RETR_FLAGS,
				   &tgtq, &cc_tgt);
    if (!retval) {
	tgtptr = &cc_tgt;
    } else if (!HARD_CC_ERR(retval)) {
        DPRINTF(("gc_from_kdc: starting do_traversal to find "
		 "initial TGT for referral\n"));
	tgtptr_isoffpath = 0;
	otgtptr = NULL;
	retval = do_traversal(context, ccache, client, server,
			      &cc_tgt, &tgtptr, tgts, &tgtptr_isoffpath);
    }
    if (retval) {
        DPRINTF(("gc_from_kdc: failed to find initial TGT for referral\n"));
        goto cleanup;
    }

    DUMP_PRINC("gc_from_kdc: server as requested", supplied_server);

    /*
     * Try requesting a service ticket from our local KDC with referrals
     * turned on.  If the first referral succeeds, follow a referral-only
     * path, otherwise fall back to old-style assumptions.
     */

    /*
     * Save TGTPTR because we rewrite it in the referral loop, and
     * we might need to explicitly free it later.
     */
    otgtptr = tgtptr;
    for (referral_count = 0;
	 referral_count < KRB5_REFERRAL_MAXHOPS;
	 referral_count++) {
#if 0
        DUMP_PRINC("gc_from_kdc: referral loop: tgt in use", tgtptr->server);
        DUMP_PRINC("gc_from_kdc: referral loop: request is for", server);
#endif
        retval = krb5_get_cred_via_tkt(context, tgtptr,
				       KDC_OPT_CANONICALIZE | 
				       FLAGS2OPTS(tgtptr->ticket_flags) |  
				       kdcopt |
				       (in_cred->second_ticket.length ?
					KDC_OPT_ENC_TKT_IN_SKEY : 0),
				       tgtptr->addresses, in_cred, out_cred);
	if (retval) {
	    DPRINTF(("gc_from_kdc: referral TGS-REQ request failed: <%s>\n",
		     error_message(retval)));
	    /* If we haven't gone anywhere yet, fail through to the
	       non-referral case. */
	    if (referral_count==0) {
	        DPRINTF(("gc_from_kdc: initial referral failed; "
			 "punting to fallback.\n"));
	        break;
	    }
	    /* Otherwise, try the same query without canonicalization
	       set, and fail hard if that doesn't work. */
	    DPRINTF(("gc_from_kdc: referral #%d failed; "
		     "retrying without option.\n", referral_count + 1));
	    retval = krb5_get_cred_via_tkt(context, tgtptr,
					   FLAGS2OPTS(tgtptr->ticket_flags) |  
					   kdcopt |
					   (in_cred->second_ticket.length ?
					    KDC_OPT_ENC_TKT_IN_SKEY : 0),
					   tgtptr->addresses,
					   in_cred, out_cred);
	    /* Whether or not that succeeded, we're done. */
	    goto cleanup;
	}
	/* Referral request succeeded; let's see what it is. */
	if (krb5_principal_compare(context, in_cred->server,
				   (*out_cred)->server)) {
	    DPRINTF(("gc_from_kdc: request generated ticket "
		     "for requested server principal\n"));
	    DUMP_PRINC("gc_from_kdc final referred reply",
		       in_cred->server);

	    /*
	     * Check if the return enctype is one that we requested if
	     * needed.
	     */
	    if (old_use_conf_ktypes || context->tgs_ktype_count == 0)
		goto cleanup;
	    for (i = 0; i < context->tgs_ktype_count; i++) {
		if ((*out_cred)->keyblock.enctype == context->tgs_ktypes[i]) {
		    /* Found an allowable etype, so we're done */
		    goto cleanup;
		}
	    }
	    /*
	     *  We need to try again, but this time use the
	     *  tgs_ktypes in the context. At this point we should
	     *  have all the tgts to succeed.
	     */

	    /* Free "wrong" credential */
	    krb5_free_creds(context, *out_cred);
	    *out_cred = NULL;
	    /* Re-establish tgs etypes */
	    context->use_conf_ktypes = old_use_conf_ktypes;
	    retval = krb5_get_cred_via_tkt(context, tgtptr,
					   KDC_OPT_CANONICALIZE | 
					   FLAGS2OPTS(tgtptr->ticket_flags) |  
					   kdcopt |
					   (in_cred->second_ticket.length ?
					    KDC_OPT_ENC_TKT_IN_SKEY : 0),
					   tgtptr->addresses,
					   in_cred, out_cred);
	    goto cleanup;
	}
	else if (IS_TGS_PRINC(context, (*out_cred)->server)) {
	    krb5_data *r1, *r2;

	    DPRINTF(("gc_from_kdc: request generated referral tgt\n"));
	    DUMP_PRINC("gc_from_kdc credential received",
		       (*out_cred)->server);

	    if (referral_count == 0)
		r1 = &tgtptr->server->data[1];
	    else
		r1 = &referral_tgts[referral_count-1]->server->data[1];

	    r2 = &(*out_cred)->server->data[1];
	    if (data_eq(*r1, *r2)) {
		DPRINTF(("gc_from_kdc: referred back to "
			 "previous realm; fall back\n"));
		krb5_free_creds(context, *out_cred);
		*out_cred = NULL;
		break;
	    }
	    /* Check for referral routing loop. */
	    for (i=0;i<referral_count;i++) {
#if 0
		DUMP_PRINC("gc_from_kdc: loop compare #1",
			   (*out_cred)->server);
		DUMP_PRINC("gc_from_kdc: loop compare #2",
			   referral_tgts[i]->server);
#endif
		if (krb5_principal_compare(context,
					   (*out_cred)->server,
					   referral_tgts[i]->server)) {
		    DFPRINTF((stderr,
			      "krb5_get_cred_from_kdc_opt: "
			      "referral routing loop - "
			      "got referral back to hop #%d\n", i));
		    retval=KRB5_KDC_UNREACH;
		    goto cleanup;
		}
	    }
	    /* Point current tgt pointer at newly-received TGT. */
	    if (tgtptr == &cc_tgt)
		krb5_free_cred_contents(context, tgtptr);
	    tgtptr=*out_cred;
	    /* Save pointer to tgt in referral_tgts. */
	    referral_tgts[referral_count]=*out_cred;
	    *out_cred = NULL;
	    /* Copy krbtgt realm to server principal. */
	    krb5_free_data_contents(context, &server->realm);
	    retval = krb5int_copy_data_contents(context,
						&tgtptr->server->data[1],
						&server->realm);
	    if (retval)
		return retval;
	    /*
	     * Future work: rewrite server principal per any
	     * supplied padata.
	     */
	} else {
	    /* Not a TGT; punt to fallback. */
	    krb5_free_creds(context, *out_cred);
	    *out_cred = NULL;
	    break;
	}
    }

    DUMP_PRINC("gc_from_kdc client at fallback", client);
    DUMP_PRINC("gc_from_kdc server at fallback", server);

    /*
     * At this point referrals have been tried and have failed.  Go
     * back to the server principal as originally issued and try the
     * conventional path.
     */

    /*
     * Referrals have failed.  Look up fallback realm if not
     * originally provided.
     */
    if (krb5_is_referral_realm(&supplied_server->realm)) {
        if (server->length >= 2) {
	    retval=krb5_get_fallback_host_realm(context, &server->data[1],
						&hrealms);
	    if (retval) goto cleanup;
#if 0
	    DPRINTF(("gc_from_kdc: using fallback realm of %s\n",
		     hrealms[0]));
#endif
	    krb5_free_data_contents(context,&in_cred->server->realm);
	    server->realm.data=hrealms[0];
	    server->realm.length=strlen(hrealms[0]);
	    free(hrealms);
	}
	else {
	    /*
	     * Problem case: Realm tagged for referral but apparently not
	     * in a <type>/<host> format that
	     * krb5_get_fallback_host_realm can deal with.
	     */
	    DPRINTF(("gc_from_kdc: referral specified "
		     "but no fallback realm avaiable!\n"));
	    return KRB5_ERR_HOST_REALM_UNKNOWN;
	}
    }

    DUMP_PRINC("gc_from_kdc server at fallback after fallback rewrite",
	       server);

    /*
     * Get a TGT for the target realm.
     */

    krb5_free_cred_contents(context, &tgtq);
    retval = tgt_mcred(context, client, server, client, &tgtq);
    if (retval)
	goto cleanup;

    /* Fast path: Is it in the ccache? */
    /* Free tgtptr data if reused from above. */
    if (tgtptr == &cc_tgt)
	krb5_free_cred_contents(context, tgtptr);
    tgtptr = NULL;
    /* Free saved TGT in OTGTPTR if it was off-path. */
    if (tgtptr_isoffpath)
	krb5_free_creds(context, otgtptr);
    otgtptr = NULL;
    /* Free TGTS if previously filled by do_traversal() */
    if (*tgts != NULL) {
	for (i = 0; (*tgts)[i] != NULL; i++) {
	    krb5_free_creds(context, (*tgts)[i]);
	}
	free(*tgts);
	*tgts = NULL;
    }
    context->use_conf_ktypes = 1;
    retval = krb5_cc_retrieve_cred(context, ccache, RETR_FLAGS,
				   &tgtq, &cc_tgt);
    if (!retval) {
	tgtptr = &cc_tgt;
    } else if (!HARD_CC_ERR(retval)) {
	tgtptr_isoffpath = 0;
	retval = do_traversal(context, ccache, client, server,
			      &cc_tgt, &tgtptr, tgts, &tgtptr_isoffpath);
    }
    if (retval)
	goto cleanup;
    otgtptr = tgtptr;

    /*
     * Finally have TGT for target realm!  Try using it to get creds.
     */

    if (!krb5_c_valid_enctype(tgtptr->keyblock.enctype)) {
	retval = KRB5_PROG_ETYPE_NOSUPP;
	goto cleanup;
    }

    context->use_conf_ktypes = old_use_conf_ktypes;
    retval = krb5_get_cred_via_tkt(context, tgtptr,
				   FLAGS2OPTS(tgtptr->ticket_flags) |
				   kdcopt |
				   (in_cred->second_ticket.length ?
				    KDC_OPT_ENC_TKT_IN_SKEY : 0),
				   tgtptr->addresses, in_cred, out_cred);

cleanup:
    krb5_free_cred_contents(context, &tgtq);
    if (tgtptr == &cc_tgt)
	krb5_free_cred_contents(context, tgtptr);
    if (tgtptr_isoffpath)
	krb5_free_creds(context, otgtptr);
    context->use_conf_ktypes = old_use_conf_ktypes;
    /* Drop the original principal back into in_cred so that it's cached
       in the expected format. */
    DUMP_PRINC("gc_from_kdc: final hacked server principal at cleanup",
	       server);
    krb5_free_principal(context, server);
    in_cred->server = supplied_server;
    if (*out_cred && !retval) {
        /* Success: free server, swap supplied server back in. */
        krb5_free_principal (context, (*out_cred)->server);
	(*out_cred)->server= out_supplied_server;
    }
    else {
        /* 
	 * Failure: free out_supplied_server.  Don't free out_cred here
	 * since it's either null or a referral TGT that we free below,
	 * and we may need it to return.
	 */
        krb5_free_principal (context, out_supplied_server);
    }
    DUMP_PRINC("gc_from_kdc: final server after reversion", in_cred->server);
    /*
     * Deal with ccache TGT management: If tgts has been set from
     * initial non-referral TGT discovery, leave it alone.  Otherwise, if
     * referral_tgts[0] exists return it as the only entry in tgts.
     * (Further referrals are never cached, only the referral from the
     * local KDC.)  This is part of cleanup because useful received TGTs
     * should be cached even if the main request resulted in failure.
     */

    if (*tgts == NULL) {
        if (referral_tgts[0]) {
#if 0
  	    /*
	     * This should possibly be a check on the candidate return
	     * credential against the cache, in the circumstance where we
	     * don't want to clutter the cache with near-duplicate
	     * credentials on subsequent iterations.  For now, it is
	     * disabled.
	     */
	    subretval=...?;
	    if (subretval) {
#endif
	        /* Allocate returnable TGT list. */
	        if (!(*tgts=calloc(sizeof (krb5_creds *), 2)))
		    return ENOMEM;
		subretval=krb5_copy_creds(context, referral_tgts[0], &((*tgts)[0]));
		if(subretval)
		    return subretval;
		(*tgts)[1]=NULL;
		DUMP_PRINC("gc_from_kdc: returning referral TGT for ccache",
			   (*tgts)[0]->server);
#if 0
	    }
#endif
	}
    }

    /* Free referral TGTs list. */
    for (i=0;i<KRB5_REFERRAL_MAXHOPS;i++) {
        if(referral_tgts[i]) {
	    krb5_free_creds(context, referral_tgts[i]);
	}
    }
    DPRINTF(("gc_from_kdc finishing with %s\n",
	     retval ? error_message(retval) : "no error"));
    return retval;
}
Example #10
0
krb5_error_code KRB5_CALLCONV
krb5_verify_init_creds(krb5_context context,
                       krb5_creds *creds,
                       krb5_principal server_arg,
                       krb5_keytab keytab_arg,
                       krb5_ccache *ccache_arg,
                       krb5_verify_init_creds_opt *options)
{
    krb5_error_code ret;
    krb5_principal server;
    krb5_keytab keytab;
    krb5_ccache ccache;
    krb5_keytab_entry kte;
    krb5_creds in_creds, *out_creds;
    krb5_auth_context authcon;
    krb5_data ap_req;

    /* KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN */

    server = NULL;
    keytab = NULL;
    ccache = NULL;
    out_creds = NULL;
    authcon = NULL;
    ap_req.data = NULL;

    if (keytab_arg) {
        keytab = keytab_arg;
    } else {
        if ((ret = krb5_kt_default(context, &keytab)))
            goto cleanup;
    }

    if (server_arg) {
        ret = krb5_copy_principal(context, server_arg, &server);
        if (ret)
            goto cleanup;
    } else {
        /* Use a principal name from the keytab. */
        ret = k5_kt_get_principal(context, keytab, &server);
        if (ret) {
            /* There's no keytab, or it's empty, or we can't read it.
             * Allow this unless configuration demands verification. */
            if (!nofail(context, options, creds))
                ret = 0;
            goto cleanup;
        }
    }

    /* first, check if the server is in the keytab.  If not, there's
       no reason to continue.  rd_req does all this, but there's
       no way to know that a given error is caused by a missing
       keytab or key, and not by some other problem. */

    if (krb5_is_referral_realm(&server->realm)) {
        krb5_free_data_contents(context, &server->realm);
        ret = krb5_get_default_realm(context, &server->realm.data);
        if (ret) goto cleanup;
        server->realm.length = strlen(server->realm.data);
    }

    if ((ret = krb5_kt_get_entry(context, keytab, server, 0, 0, &kte))) {
        /* this means there is no keying material.  This is ok, as long as
           it is not prohibited by the configuration */
        if (!nofail(context, options, creds))
            ret = 0;
        goto cleanup;
    }

    krb5_kt_free_entry(context, &kte);

    /* If the creds are for the server principal, we're set, just do a mk_req.
     * Otherwise, do a get_credentials first.
     */

    if (krb5_principal_compare(context, server, creds->server)) {
        /* make an ap_req */
        if ((ret = krb5_mk_req_extended(context, &authcon, 0, NULL, creds,
                                        &ap_req)))
            goto cleanup;
    } else {
        /* this is unclean, but it's the easiest way without ripping the
           library into very small pieces.  store the client's initial cred
           in a memory ccache, then call the library.  Later, we'll copy
           everything except the initial cred into the ccache we return to
           the user.  A clean implementation would involve library
           internals with a coherent idea of "in" and "out". */

        /* insert the initial cred into the ccache */

        if ((ret = krb5_cc_new_unique(context, "MEMORY", NULL, &ccache))) {
            ccache = NULL;
            goto cleanup;
        }

        if ((ret = krb5_cc_initialize(context, ccache, creds->client)))
            goto cleanup;

        if ((ret = krb5_cc_store_cred(context, ccache, creds)))
            goto cleanup;

        /* set up for get_creds */
        memset(&in_creds, 0, sizeof(in_creds));
        in_creds.client = creds->client;
        in_creds.server = server;
        if ((ret = krb5_timeofday(context, &in_creds.times.endtime)))
            goto cleanup;
        in_creds.times.endtime += 5*60;

        if ((ret = krb5_get_credentials(context, 0, ccache, &in_creds,
                                        &out_creds)))
            goto cleanup;

        /* make an ap_req */
        if ((ret = krb5_mk_req_extended(context, &authcon, 0, NULL, out_creds,
                                        &ap_req)))
            goto cleanup;
    }

    /* wipe the auth context for mk_req */
    if (authcon) {
        krb5_auth_con_free(context, authcon);
        authcon = NULL;
    }

    /* verify the ap_req */

    if ((ret = krb5_rd_req(context, &authcon, &ap_req, server, keytab,
                           NULL, NULL)))
        goto cleanup;

    /* if we get this far, then the verification succeeded.  We can
       still fail if the library stuff here fails, but that's it */

    if (ccache_arg && ccache) {
        if (*ccache_arg == NULL) {
            krb5_ccache retcc;

            retcc = NULL;

            if ((ret = krb5_cc_resolve(context, "MEMORY:rd_req2", &retcc)) ||
                (ret = krb5_cc_initialize(context, retcc, creds->client)) ||
                (ret = copy_creds_except(context, ccache, retcc,
                                         creds->server))) {
                if (retcc)
                    krb5_cc_destroy(context, retcc);
            } else {
                *ccache_arg = retcc;
            }
        } else {
            ret = copy_creds_except(context, ccache, *ccache_arg,
                                    server);
        }
    }

    /* if any of the above paths returned an errors, then ret is set accordingly.
     * Either that, or it's zero, which is fine, too
     */

cleanup:
    if ( server)
        krb5_free_principal(context, server);
    if (!keytab_arg && keytab)
        krb5_kt_close(context, keytab);
    if (ccache)
        krb5_cc_destroy(context, ccache);
    if (out_creds)
        krb5_free_creds(context, out_creds);
    if (authcon)
        krb5_auth_con_free(context, authcon);
    if (ap_req.data)
        free(ap_req.data);

    return(ret);
}
Example #11
0
krb5_error_code KRB5_CALLCONV
krb5_get_init_creds_keytab(krb5_context context,
			   krb5_creds *creds,
			   krb5_principal client,
			   krb5_keytab arg_keytab,
			   krb5_deltat start_time,
			   char *in_tkt_service,
			   krb5_get_init_creds_opt *options)
{
   krb5_error_code ret, ret2;
   int use_master;
   krb5_keytab keytab;
   krb5_gic_opt_ext *opte = NULL;

   if (arg_keytab == NULL) {
       if ((ret = krb5_kt_default(context, &keytab)))
	    return ret;
   } else {
       keytab = arg_keytab;
   }

   ret = krb5int_gic_opt_to_opte(context, options, &opte, 1,
				 "krb5_get_init_creds_keytab");
   if (ret)
      return ret;

   /*
    * Solaris Kerberos:
    * If "client" was constructed from krb5_sname_to_princ() it may
    * have a referral realm. This happens when there is no applicable 
    * domain-to-realm mapping in the Kerberos configuration file.
    * If that is the case then the realm of the first principal found
    * in the keytab which matches the client can be used for the client's
    * realm.
    */
   if (krb5_is_referral_realm(&client->realm)) {
	krb5_data realm;
	ret = krb5_kt_find_realm(context, keytab, client, &realm);
	if (ret == 0) {
		krb5_free_data_contents(context, &client->realm);
		client->realm.length = realm.length;
		client->realm.data = realm.data;
	} else {
		/* Try to set a useful error message */
		char *princ = NULL;
		krb5_unparse_name(context, client, &princ);

		krb5_set_error_message(context, ret,
		    gettext("Failed to find realm for %s in keytab"),
		    princ ? princ : "<unknown>");
		if (princ)
			krb5_free_unparsed_name(context, princ);
	}
   }

   if (ret != 0)
	goto cleanup;


   use_master = 0;

   /* first try: get the requested tkt from any kdc */

   ret = krb5_get_init_creds(context, creds, client, NULL, NULL,
			     start_time, in_tkt_service, opte,
			     krb5_get_as_key_keytab, (void *) keytab,
			     &use_master,NULL);

   /* check for success */

   if (ret == 0)
      goto cleanup;

   /* If all the kdc's are unavailable fail */

   if ((ret == KRB5_KDC_UNREACH) || (ret == KRB5_REALM_CANT_RESOLVE))
      goto cleanup;

   /* if the reply did not come from the master kdc, try again with
      the master kdc */

   if (!use_master) {
      use_master = 1;

      ret2 = krb5_get_init_creds(context, creds, client, NULL, NULL,
				 start_time, in_tkt_service, opte,
				 krb5_get_as_key_keytab, (void *) keytab,
				 &use_master, NULL);
      
      if (ret2 == 0) {
	 ret = 0;
	 goto cleanup;
      }

      /* if the master is unreachable, return the error from the
	 slave we were able to contact */

      if ((ret2 == KRB5_KDC_UNREACH) ||
	  (ret2 == KRB5_REALM_CANT_RESOLVE) ||
	  (ret2 == KRB5_REALM_UNKNOWN))
	 goto cleanup;

      ret = ret2;
   }

   /* at this point, we have a response from the master.  Since we don't
      do any prompting or changing for keytabs, that's it. */

cleanup:
   if (opte && krb5_gic_opt_is_shadowed(opte))
       krb5_get_init_creds_opt_free(context, (krb5_get_init_creds_opt *)opte);
   if (arg_keytab == NULL)
       (void) krb5_kt_close(context, keytab); /* Solaris Kerberos */

   return(ret);
}