/*
* Copyright 2014 Nexenta Systems, Inc. All rights reserved.
*/
/*
*
* Copyright 1990,1991, 2003 by the Massachusetts Institute of Technology.
* All Rights Reserved.
*
* Export of this software from the United States of America may
* require a specific license from the United States Government.
* It is the responsibility of any person or organization contemplating
* export to obtain such a license before exporting.
*
* WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
* distribute this software and its documentation for any purpose and
* without fee is hereby granted, provided that the above copyright
* notice appear in all copies and that both that copyright notice and
* this permission notice appear in supporting documentation, and that
* the name of M.I.T. not be used in advertising or publicity pertaining
* to distribution of the software without specific, written prior
* permission. Furthermore if you modify this software you must label
* your software as modified software and not distribute it in such a
* fashion that it might be confused with the original M.I.T. software.
* M.I.T. makes no representations about the suitability of
* this software for any purpose. It is provided "as is" without express
* or implied warranty.
*
*
* krb5_get_in_tkt()
*/
#include <string.h>
#include <ctype.h>
#include "k5-int.h"
#include "int-proto.h"
#include "os-proto.h"
#include <locale.h>
#include <syslog.h>
/*
All-purpose initial ticket routine, usually called via
krb5_get_in_tkt_with_password or krb5_get_in_tkt_with_skey.
Attempts to get an initial ticket for creds->client to use server
creds->server, (realm is taken from creds->client), with options
options, and using creds->times.starttime, creds->times.endtime,
creds->times.renew_till as from, till, and rtime.
creds->times.renew_till is ignored unless the RENEWABLE option is requested.
key_proc is called to fill in the key to be used for decryption.
keyseed is passed on to key_proc.
decrypt_proc is called to perform the decryption of the response (the
encrypted part is in dec_rep->enc_part; the decrypted part should be
allocated and filled into dec_rep->enc_part2
arg is passed on to decrypt_proc.
If addrs is non-NULL, it is used for the addresses requested. If it is
null, the system standard addresses are used.
A succesful call will place the ticket in the credentials cache ccache
returns system errors, encryption errors
*/
/* Solaris Kerberos */
#define max(a, b) ((a) > (b) ? (a) : (b))
/* some typedef's for the function args to make things look a bit cleaner */
const krb5_enctype,
krb5_data *,
krb5_keyblock **);
const krb5_keyblock *,
krb5_kdc_rep * );
int, krb5_pa_data ***);
krb5_pa_data **padata);
/*
* This function performs 32 bit bounded addition so we can generate
* lifetimes without overflowing krb5_int32
*/
{
if ((x > 0) && (y > (KRB5_INT32_MAX - x))) {
/* sum will be be greater than KRB5_INT32_MAX */
return KRB5_INT32_MAX;
} else if ((x < 0) && (y < (KRB5_INT32_MIN - x))) {
/* sum will be less than KRB5_INT32_MIN */
return KRB5_INT32_MIN;
}
return x + y;
}
/*
* This function sends a request to the KDC, and gets back a response;
* the response is parsed into ret_err_reply or ret_as_reply if the
* reponse is a KRB_ERROR or a KRB_AS_REP packet. If it is some other
* unexpected response, an error is returned.
*/
static krb5_error_code
int *use_master,
char **hostname_used)
{
int tcp_only = 0;
/* Solaris Kerberos (illumos) */
if (krb5_getenv("MS_INTEROP")) {
/* Don't bother with UDP. */
tcp_only = 1;
}
/* set the nonce if the caller expects us to do it */
goto cleanup;
}
/* encode & send to KDC */
goto cleanup;
if (retval)
goto cleanup;
/* now decode the reply...could be error or as_rep */
if (krb5_is_krb_error(&reply)) {
/* some other error code--??? */
goto cleanup;
if (ret_err_reply) {
&& tcp_only == 0) {
tcp_only = 1;
goto send_again;
}
} else {
}
goto cleanup;
}
/*
* Check to make sure it isn't a V4 reply.
*/
if (!krb5_is_as_rep(&reply)) {
/* these are in <kerberosIV/prot.h> as well but it isn't worth including. */
/* check here for V4 reply */
unsigned int t_switch;
/* From v4 g_in_tkt.c: This used to be
switch (pkt_msg_type(rpkt) & ~1) {
but SCO 3.2v4 cc compiled that incorrectly. */
t_switch &= ~1;
if (t_switch == V4_AUTH_MSG_ERR_REPLY
} else {
}
goto cleanup;
}
/* It must be a KRB_AS_REP message, or an bad returned packet */
/* some other error code ??? */
goto cleanup;
goto cleanup;
}
if (ret_as_reply)
*ret_as_reply = as_reply;
else
if (packet)
return retval;
}
static krb5_error_code
int *use_master)
{
return send_as_request2(context,
NULL);
}
static krb5_error_code
krb5_keyblock * key,
{
return 0;
if (key)
decrypt_key = key;
/* Solaris Kerberos */
return(retval);
if (retval)
goto cleanup;
} else {
"error key == NULL and request == NULL");
return (EINVAL);
}
/*
* Solaris kerberos: Overwriting the decrypt_key->enctype because the
* decrypt key's enctype may not be an exact match with the enctype that the
* KDC used to encrypt this part of the AS reply. This assumes the
* as_reply->enc_part.enctype has been validated which is done by checking
* to see if the enctype that the KDC sent back in the as_reply is one of
* the enctypes originally requested. Note, if request is NULL then the
* as_reply->enc_part.enctype could not be validated.
*/
} else {
"error is_in_keytype() returned false");
goto cleanup;
}
}
"= %d", retval);
goto cleanup;
}
if (!key && decrypt_key)
return (retval);
}
static krb5_error_code
{
/* check the contents for sanity: */
/* XXX check for extraneous flags */
/* XXX || (!krb5_addresses_compare(context, addrs, as_reply->enc_part2->caddrs)) */
/*
* Solaris Kerberos: Here we error only if renewable_ok was not set.
*/
/*
* Solaris Kerberos: renew_till should never be greater than till or
* rtime.
*/
)
return KRB5_KDCREP_MODIFIED;
if (retval)
return retval;
} else {
return (KRB5_KDCREP_SKEW);
}
return 0;
}
/*ARGSUSED*/
static krb5_error_code
krb5_creds * creds,
{
goto cleanup;
&server)))
goto cleanup;
/* fill in the credentials */
goto cleanup;
be encrypted in skey */
goto cleanup;
goto cleanup;
/* store it in the ccache! */
if (ccache) /* Solaris Kerberos */
goto cleanup;
if (retval) {
if (client)
if (server)
}
}
}
}
return (retval);
}
/*ARGSUSED*/
static krb5_error_code
int nptypes,
krb5_pa_data *** ret_list)
{
int i;
if (nptypes < 0) {
;
}
/* allocate space for a NULL to terminate the list */
if ((preauthp =
return(ENOMEM);
for (i=0; i<nptypes; i++) {
if ((preauthp[i] =
for (; i>=0; i++)
return (ENOMEM);
}
}
/* fill in the terminating NULL */
return 0;
}
0
};
const krb5_flags options,
krb5_address * const * addrs,
krb5_creds * creds,
{
int loopcount = 0;
int use_master = 0;
/* Solaris Kerberos */
if (s_name)
if (c_name)
return KRB5_IN_TKT_REALM_MISMATCH;
}
if (ret_as_reply)
*ret_as_reply = 0;
/*
* Set up the basic request structure
*/
if (addrs)
else
goto cleanup;
goto cleanup;
}
if (ktypes) {
next++;
continue;
}
/* Found the enctype we want, but not in the
position we want. Move it, but keep the old
one from the desired slot around in case it's
later in our requested-ktypes list. */
krb5_enctype t;
next++;
break;
}
/* If we didn't find it, don't do anything special, just
drop it. */
}
}
request.unenc_authdata = 0;
request.second_ticket = 0;
/*
* If a list of preauth types are passed in, convert it to a
* preauth_to_use list.
*/
if (ptypes) {
if (retval)
goto cleanup;
}
while (1) {
if (loopcount++ > MAX_IN_TKT_LOOPS) {
/* Solaris Kerberos */
{
"Looping detected getting ticket: '%s' requesting ticket '%s'. Max loops is %d. Make sure a KDC is available"),
if (s_name)
if (c_name)
}
goto cleanup;
}
goto cleanup;
if (preauth_to_use)
preauth_to_use = 0;
err_reply = 0;
as_reply = 0;
goto cleanup;
/*
* XXX we know they are the same size... and we should do
* something better than just the current time
*/
&as_reply, &use_master,
&hostname_used)))
goto cleanup;
if (err_reply) {
if (retval)
goto cleanup;
padata);
if (retval)
goto cleanup;
continue;
} else {
goto cleanup;
}
} else if (!as_reply) {
goto cleanup;
}
&decrypt_key, creds,
&do_more)) != 0)
goto cleanup;
if (!do_more)
break;
}
decryptarg)))
goto cleanup;
goto cleanup;
goto cleanup;
if (padata)
if (preauth_to_use)
if (decrypt_key)
if (as_reply) {
if (ret_as_reply)
*ret_as_reply = as_reply;
else
}
if (hostname_used)
return (retval);
}
/* begin libdefaults parsing code. This should almost certainly move
somewhere else, but I don't know where the correct somewhere else
is yet. */
/* XXX Duplicating this is annoying; try to work on a better way.*/
static const char *const conf_yes[] = {
"y", "yes", "true", "t", "1", "on",
0,
};
static const char *const conf_no[] = {
"n", "no", "false", "nil", "0", "off",
0,
};
int
_krb5_conf_boolean(const char *s)
{
const char *const *p;
for(p=conf_yes; *p; p++) {
if (!strcasecmp(*p,s))
return 1;
}
for(p=conf_no; *p; p++) {
if (!strcasecmp(*p,s))
return 0;
}
/* Default to "no" */
return 0;
}
static krb5_error_code
{
return(EINVAL);
return KV5M_CONTEXT;
/* Solaris Kerberos */
names[0] = "realms";
/*
* Try number one:
*
* [realms]
* REALM = {
* option = <boolean>
* }
*/
names[3] = 0;
goto goodbye;
/*
* Try number two:
*
* [libdefaults]
* option = <boolean>
*/
names[0] = "libdefaults";
names[2] = 0;
goto goodbye;
if (!nameval)
return(ENOENT);
if (!nameval[0]) {
} else {
if (!*ret_value)
else
}
return retval;
}
/* not static so verify_init_creds() can call it */
/* as well as the DNS code */
{
if (retval)
return(retval);
return(0);
}
/* Sort a pa_data sequence so that types named in the "preferred_preauth_types"
* libdefaults entry are listed before any others. */
static krb5_error_code
{
int i, j, base;
const char *p;
long l;
return 0;
}
/* Try to use PKINIT first. */
preauth_types = "17, 16, 15, 14";
need_free_string = 0;
}
#ifdef DEBUG
for (i = 0; padata[i]; i++) {
}
#endif
base = 0;
for (p = preauth_types; *p != '\0';) {
/* skip whitespace to find an entry */
p += strspn(p, ", ");
if (*p != '\0') {
/* see if we can extract a number */
l = strtol(p, &q, 10);
if ((q != NULL) && (q > p)) {
/* got a valid number; search for a matchin entry */
/* bubble the matching entry to the front of the list */
for (j = i; j > base; j--)
base++;
break;
}
}
p = q;
} else {
break;
}
}
}
if (need_free_string)
#ifdef DEBUG
for (i = 0; padata[i]; i++)
#endif
return 0;
}
/*
* Solaris Kerberos
* Return 1 if any char in string is lower-case.
*/
static int
is_lower_case(char *s)
{
if (!s)
return 0;
while (*s) {
if (islower((int)*s))
return 1;
s++;
}
return 0;
}
void *prompter_data,
char *in_tkt_service,
void *gak_data,
int *use_master,
{
int tempint;
int loopcount;
/* initialize everything which will be freed at cleanup */
kdc_padata = NULL;
local_as_reply = 0;
/*
* Set up the basic request structure
*/
/* request.nonce is filled in when we send a request to the kdc */
/* request.padata is filled in later */
/* forwardable */
"forwardable", &tempint)) == 0)
/*EMPTY*/
;
else
tempint = 0;
if (tempint)
/* proxiable */
"proxiable", &tempint)) == 0)
/*EMPTY*/
;
else
tempint = 0;
if (tempint)
/* allow_postdate */
if (start_time > 0)
/* ticket lifetime */
goto cleanup;
"ticket_lifetime", &tempstr))
== 0) {
if (ret) {
goto cleanup;
}
} else {
/* this used to be hardcoded in kinit.c */
}
/* renewable lifetime */
"renew_lifetime", &tempstr))
== 0) {
if (ret) {
goto cleanup;
}
} else {
renew_life = 0;
}
if (renew_life > 0)
if (renew_life > 0) {
/* don't ask for a smaller renewable time than the lifetime */
}
/* we are already asking for renewable tickets so strip this option */
} else {
}
/* client */
/* service */
if (in_tkt_service) {
/* this is ugly, because so are the data structures involved. I'm
in the library, so I'm going to manipulate the data structures
directly, otherwise, it will be worse. */
goto cleanup;
/* stuff the client realm into the server principal.
realloc if necessary */
goto cleanup;
}
} else {
0)))
goto cleanup;
}
/* nonce is filled in by send_as_request if we don't take care of it */
;
} else {
/* there isn't any useful default here. ret is set from above */
goto cleanup;
}
}
/* it would be nice if this parsed out an address list, but
that would be work. */
"no_addresses", &tempint)) == 0)
|| (tempint == 1)) {
/*EMPTY*/
;
"noaddresses", &tempint)) == 0)
|| (tempint == 1)) {
/*EMPTY*/
;
} else {
goto cleanup;
}
request.unenc_authdata = 0;
request.second_ticket = 0;
/* set up the other state. */
&preauth_to_use)))
goto cleanup;
}
/* the salt is allocated from somewhere, unless it is from the caller,
then it is a reference */
} else {
}
/* set the request nonce */
goto cleanup;
/*
* XXX we know they are the same size... and we should do
* something better than just the current time
*/
/* give the preauth plugins a chance to prep the request body */
if (ret)
goto cleanup;
/* now, loop processing preauth data and talking to the kdc */
}
if (!err_reply) {
/* either our first attempt, or retrying after PREAUTH_NEEDED */
&request,
&get_data_rock, options)))
goto cleanup;
} else {
if (preauth_to_use != NULL) {
/*
* Retry after an error other than PREAUTH_NEEDED,
* using e-data to figure out what to change.
*/
&request,
&as_key,
&get_data_rock, options);
} else {
/* No preauth supplied, so can't query the plug-ins. */
}
if (ret) {
/* couldn't come up with anything better */
}
if (ret)
goto cleanup;
}
if (encoded_previous_request != NULL) {
}
if (ret)
goto cleanup;
local_as_reply = 0;
&hostname_used)))
goto cleanup;
if (err_reply) {
/* reset the list of preauth types to try */
if (preauth_to_use) {
}
if (ret)
goto cleanup;
if (ret)
goto cleanup;
/* continue to next iteration */
} else {
/* continue to next iteration */
} else {
/* error + no hints = give up */
goto cleanup;
}
}
} else if (local_as_reply) {
break;
} else {
goto cleanup;
}
}
if (loopcount == MAX_IN_TKT_LOOPS) {
/* Solaris Kerberos */
{
"Looping detected getting initial creds: '%s' requesting ticket '%s'. Max loops is %d. Make sure a KDC is available"),
if (s_name)
if (c_name)
}
goto cleanup;
}
/* process any preauth data in the as_reply */
local_as_reply->padata)))
goto cleanup;
&request,
&get_data_rock, options)))
goto cleanup;
/* XXX For 1.1.1 and prior KDC's, when SAM is used w/ USE_SAD_AS_KEY,
the AS_REP comes back encrypted in the user's longterm key
instead of in the SAD. If there was a SAM preauth, there
will be an as_key here which will be the SAD. If that fails,
use the gak_fct to get the password, and try again. */
/* XXX because etypes are handled poorly (particularly wrt SAM,
where the etype is fixed by the kdc), we may want to try
decrypt_as_reply twice. If there's an as_key available, try
it. If decrypting the as_rep fails, or if there isn't an
as_key at all yet, then use the gak_fct to get one, and try
again. */
NULL);
else
ret = -1;
if (ret) {
/* if we haven't get gotten a key, get it now */
goto cleanup;
NULL)))
goto cleanup;
}
goto cleanup;
/* XXX this should be inside stash_as_reply, but as long as
do that */
/* Solaris Kerberos */
/* Solaris Kerberos */
goto cleanup;
/* success */
ret = 0;
if (ret != 0) {
/* See if we can produce a more detailed error message. */
switch (ret) {
"Client '%s' not found in Kerberos database"),
}
break;
/* Solaris Kerberos: spruce-up the err msg */
case KRB5_PREAUTH_FAILED:
"Client '%s' pre-authentication failed"),
}
break;
/* Solaris Kerberos: spruce-up the err msg */
case KRB5KRB_AP_ERR_SKEW: /* KRB_AP_ERR_SKEW + ERROR_TABLE_BASE_krb5 */
{
sizeof (stimestring),
&fill);
"Clock skew too great: '%s' requesting ticket '%s' from KDC '%s' (%s). Skew is %dm"),
(s_time != 0) ? 0 :
if (s_name)
if (c_name)
}
break;
case KRB5_KDCREP_MODIFIED:
/*
* Solaris Kerberos
* Extra err msg for common(?) case of
* 'kinit user@lower-case-def-realm'.
* DNS SRV recs will match (case insensitive) and trigger sendto
*/
int set = 0;
if (realm++) {
"KDC reply did not match expectations for client '%s': lower-case detected in realm '%s'"),
client_name, realm);
set = 1;
}
}
if (!set)
"KDC reply did not match expectations for client '%s'"),
}
break;
default:
break;
}
}
if (err_reply)
if (encoded_previous_request != NULL) {
}
if (encoded_request_body != NULL) {
}
(!(options &&
if (preauth_to_use)
if (kdc_padata)
if (as_reply)
else if (local_as_reply)
if (hostname_used)
return(ret);
}