mirror of
https://github.com/samba-team/samba.git
synced 2024-12-22 13:34:15 +03:00
c49fd98ed7
Signed-off-by: Andrew Bartlett <abartlet@samba.org> Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
1582 lines
42 KiB
C
1582 lines
42 KiB
C
/*
|
|
Unix SMB/CIFS implementation.
|
|
|
|
Handle user credentials (as regards krb5)
|
|
|
|
Copyright (C) Jelmer Vernooij 2005
|
|
Copyright (C) Tim Potter 2001
|
|
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
#include "system/kerberos.h"
|
|
#include "system/gssapi.h"
|
|
#include "auth/kerberos/kerberos.h"
|
|
#include "auth/credentials/credentials.h"
|
|
#include "auth/credentials/credentials_internal.h"
|
|
#include "auth/credentials/credentials_krb5.h"
|
|
#include "auth/kerberos/kerberos_credentials.h"
|
|
#include "auth/kerberos/kerberos_srv_keytab.h"
|
|
#include "auth/kerberos/kerberos_util.h"
|
|
#include "auth/kerberos/pac_utils.h"
|
|
#include "param/param.h"
|
|
#include "../libds/common/flags.h"
|
|
|
|
#undef DBGC_CLASS
|
|
#define DBGC_CLASS DBGC_AUTH
|
|
|
|
#undef strncasecmp
|
|
|
|
static void cli_credentials_invalidate_client_gss_creds(
|
|
struct cli_credentials *cred,
|
|
enum credentials_obtained obtained);
|
|
|
|
/* Free a memory ccache */
|
|
static int free_mccache(struct ccache_container *ccc)
|
|
{
|
|
if (ccc->ccache != NULL) {
|
|
krb5_cc_destroy(ccc->smb_krb5_context->krb5_context,
|
|
ccc->ccache);
|
|
ccc->ccache = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Free a disk-based ccache */
|
|
static int free_dccache(struct ccache_container *ccc)
|
|
{
|
|
if (ccc->ccache != NULL) {
|
|
krb5_cc_close(ccc->smb_krb5_context->krb5_context,
|
|
ccc->ccache);
|
|
ccc->ccache = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint32_t smb_gss_krb5_copy_ccache(uint32_t *min_stat,
|
|
gss_cred_id_t cred,
|
|
struct ccache_container *ccc)
|
|
{
|
|
#ifndef SAMBA4_USES_HEIMDAL /* MIT 1.10 */
|
|
krb5_context context = ccc->smb_krb5_context->krb5_context;
|
|
krb5_ccache dummy_ccache = NULL;
|
|
krb5_creds creds = {0};
|
|
krb5_cc_cursor cursor = NULL;
|
|
krb5_principal princ = NULL;
|
|
krb5_error_code code;
|
|
char *dummy_name;
|
|
uint32_t maj_stat = GSS_S_FAILURE;
|
|
|
|
dummy_name = talloc_asprintf(ccc,
|
|
"MEMORY:gss_krb5_copy_ccache-%p",
|
|
&ccc->ccache);
|
|
if (dummy_name == NULL) {
|
|
*min_stat = ENOMEM;
|
|
return GSS_S_FAILURE;
|
|
}
|
|
|
|
/*
|
|
* Create a dummy ccache, so we can iterate over the credentials
|
|
* and find the default principal for the ccache we want to
|
|
* copy. The new ccache needs to be initialized with this
|
|
* principal.
|
|
*/
|
|
code = krb5_cc_resolve(context, dummy_name, &dummy_ccache);
|
|
TALLOC_FREE(dummy_name);
|
|
if (code != 0) {
|
|
*min_stat = code;
|
|
return GSS_S_FAILURE;
|
|
}
|
|
|
|
/*
|
|
* We do not need set a default principal on the temporary dummy
|
|
* ccache, as we do consume it at all in this function.
|
|
*/
|
|
maj_stat = gss_krb5_copy_ccache(min_stat, cred, dummy_ccache);
|
|
if (maj_stat != 0) {
|
|
krb5_cc_close(context, dummy_ccache);
|
|
return maj_stat;
|
|
}
|
|
|
|
code = krb5_cc_start_seq_get(context, dummy_ccache, &cursor);
|
|
if (code != 0) {
|
|
krb5_cc_close(context, dummy_ccache);
|
|
*min_stat = EINVAL;
|
|
return GSS_S_FAILURE;
|
|
}
|
|
|
|
code = krb5_cc_next_cred(context,
|
|
dummy_ccache,
|
|
&cursor,
|
|
&creds);
|
|
if (code != 0) {
|
|
krb5_cc_close(context, dummy_ccache);
|
|
*min_stat = EINVAL;
|
|
return GSS_S_FAILURE;
|
|
}
|
|
|
|
do {
|
|
if (creds.ticket_flags & TKT_FLG_PRE_AUTH) {
|
|
krb5_data *tgs;
|
|
|
|
tgs = krb5_princ_component(context,
|
|
creds.server,
|
|
0);
|
|
if (tgs != NULL && tgs->length >= 1) {
|
|
int cmp;
|
|
|
|
cmp = memcmp(tgs->data,
|
|
KRB5_TGS_NAME,
|
|
tgs->length);
|
|
if (cmp == 0 && creds.client != NULL) {
|
|
princ = creds.client;
|
|
code = KRB5_CC_END;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
krb5_free_cred_contents(context, &creds);
|
|
|
|
code = krb5_cc_next_cred(context,
|
|
dummy_ccache,
|
|
&cursor,
|
|
&creds);
|
|
} while (code == 0);
|
|
|
|
if (code == KRB5_CC_END) {
|
|
krb5_cc_end_seq_get(context, dummy_ccache, &cursor);
|
|
code = 0;
|
|
}
|
|
krb5_cc_close(context, dummy_ccache);
|
|
|
|
if (code != 0 || princ == NULL) {
|
|
krb5_free_cred_contents(context, &creds);
|
|
*min_stat = EINVAL;
|
|
return GSS_S_FAILURE;
|
|
}
|
|
|
|
/*
|
|
* Set the default principal for the cache we copy
|
|
* into. This is needed to be able that other calls
|
|
* can read it with e.g. gss_acquire_cred() or
|
|
* krb5_cc_get_principal().
|
|
*/
|
|
code = krb5_cc_initialize(context, ccc->ccache, princ);
|
|
if (code != 0) {
|
|
krb5_free_cred_contents(context, &creds);
|
|
*min_stat = EINVAL;
|
|
return GSS_S_FAILURE;
|
|
}
|
|
krb5_free_cred_contents(context, &creds);
|
|
|
|
#endif /* SAMBA4_USES_HEIMDAL */
|
|
|
|
return gss_krb5_copy_ccache(min_stat,
|
|
cred,
|
|
ccc->ccache);
|
|
}
|
|
|
|
_PUBLIC_ int cli_credentials_get_krb5_context(struct cli_credentials *cred,
|
|
struct loadparm_context *lp_ctx,
|
|
struct smb_krb5_context **smb_krb5_context)
|
|
{
|
|
int ret;
|
|
if (cred->smb_krb5_context) {
|
|
*smb_krb5_context = cred->smb_krb5_context;
|
|
return 0;
|
|
}
|
|
|
|
ret = smb_krb5_init_context(cred, lp_ctx,
|
|
&cred->smb_krb5_context);
|
|
if (ret) {
|
|
cred->smb_krb5_context = NULL;
|
|
return ret;
|
|
}
|
|
*smb_krb5_context = cred->smb_krb5_context;
|
|
return 0;
|
|
}
|
|
|
|
/* For most predictable behaviour, this needs to be called directly after the cli_credentials_init(),
|
|
* otherwise we may still have references to the old smb_krb5_context in a credential cache etc
|
|
*/
|
|
_PUBLIC_ NTSTATUS cli_credentials_set_krb5_context(struct cli_credentials *cred,
|
|
struct smb_krb5_context *smb_krb5_context)
|
|
{
|
|
if (smb_krb5_context == NULL) {
|
|
talloc_unlink(cred, cred->smb_krb5_context);
|
|
cred->smb_krb5_context = NULL;
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
if (!talloc_reference(cred, smb_krb5_context)) {
|
|
return NT_STATUS_NO_MEMORY;
|
|
}
|
|
cred->smb_krb5_context = smb_krb5_context;
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
static int cli_credentials_set_from_ccache(struct cli_credentials *cred,
|
|
struct ccache_container *ccache,
|
|
enum credentials_obtained obtained,
|
|
const char **error_string)
|
|
{
|
|
bool ok;
|
|
char *realm;
|
|
krb5_principal princ;
|
|
krb5_error_code ret;
|
|
char *name;
|
|
|
|
if (cred->ccache_obtained > obtained) {
|
|
return 0;
|
|
}
|
|
|
|
ret = krb5_cc_get_principal(ccache->smb_krb5_context->krb5_context,
|
|
ccache->ccache, &princ);
|
|
|
|
if (ret) {
|
|
(*error_string) = talloc_asprintf(cred, "failed to get principal from ccache: %s\n",
|
|
smb_get_krb5_error_message(ccache->smb_krb5_context->krb5_context,
|
|
ret, cred));
|
|
return ret;
|
|
}
|
|
|
|
ret = krb5_unparse_name(ccache->smb_krb5_context->krb5_context, princ, &name);
|
|
if (ret) {
|
|
(*error_string) = talloc_asprintf(cred, "failed to unparse principal from ccache: %s\n",
|
|
smb_get_krb5_error_message(ccache->smb_krb5_context->krb5_context,
|
|
ret, cred));
|
|
krb5_free_principal(ccache->smb_krb5_context->krb5_context, princ);
|
|
return ret;
|
|
}
|
|
|
|
ok = cli_credentials_set_principal(cred, name, obtained);
|
|
krb5_free_unparsed_name(ccache->smb_krb5_context->krb5_context, name);
|
|
if (!ok) {
|
|
krb5_free_principal(ccache->smb_krb5_context->krb5_context, princ);
|
|
return ENOMEM;
|
|
}
|
|
|
|
realm = smb_krb5_principal_get_realm(
|
|
cred, ccache->smb_krb5_context->krb5_context, princ);
|
|
krb5_free_principal(ccache->smb_krb5_context->krb5_context, princ);
|
|
if (realm == NULL) {
|
|
return ENOMEM;
|
|
}
|
|
ok = cli_credentials_set_realm(cred, realm, obtained);
|
|
TALLOC_FREE(realm);
|
|
if (!ok) {
|
|
return ENOMEM;
|
|
}
|
|
|
|
/* set the ccache_obtained here, as it just got set to UNINITIALISED by the calls above */
|
|
cred->ccache_obtained = obtained;
|
|
|
|
return 0;
|
|
}
|
|
|
|
_PUBLIC_ int cli_credentials_set_ccache(struct cli_credentials *cred,
|
|
struct loadparm_context *lp_ctx,
|
|
const char *name,
|
|
enum credentials_obtained obtained,
|
|
const char **error_string)
|
|
{
|
|
krb5_error_code ret;
|
|
krb5_principal princ;
|
|
struct ccache_container *ccc;
|
|
if (cred->ccache_obtained > obtained) {
|
|
return 0;
|
|
}
|
|
|
|
ccc = talloc(cred, struct ccache_container);
|
|
if (!ccc) {
|
|
(*error_string) = error_message(ENOMEM);
|
|
return ENOMEM;
|
|
}
|
|
|
|
ret = cli_credentials_get_krb5_context(cred, lp_ctx,
|
|
&ccc->smb_krb5_context);
|
|
if (ret) {
|
|
(*error_string) = error_message(ret);
|
|
talloc_free(ccc);
|
|
return ret;
|
|
}
|
|
if (!talloc_reference(ccc, ccc->smb_krb5_context)) {
|
|
talloc_free(ccc);
|
|
(*error_string) = error_message(ENOMEM);
|
|
return ENOMEM;
|
|
}
|
|
|
|
if (name) {
|
|
ret = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context, name, &ccc->ccache);
|
|
if (ret) {
|
|
(*error_string) = talloc_asprintf(cred, "failed to read krb5 ccache: %s: %s\n",
|
|
name,
|
|
smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context,
|
|
ret, ccc));
|
|
talloc_free(ccc);
|
|
return ret;
|
|
}
|
|
} else {
|
|
ret = krb5_cc_default(ccc->smb_krb5_context->krb5_context, &ccc->ccache);
|
|
if (ret) {
|
|
(*error_string) = talloc_asprintf(cred, "failed to read default krb5 ccache: %s\n",
|
|
smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context,
|
|
ret, ccc));
|
|
talloc_free(ccc);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
talloc_set_destructor(ccc, free_dccache);
|
|
|
|
ret = krb5_cc_get_principal(ccc->smb_krb5_context->krb5_context, ccc->ccache, &princ);
|
|
|
|
if (ret == 0) {
|
|
krb5_free_principal(ccc->smb_krb5_context->krb5_context, princ);
|
|
ret = cli_credentials_set_from_ccache(cred, ccc, obtained, error_string);
|
|
|
|
if (ret) {
|
|
(*error_string) = error_message(ret);
|
|
TALLOC_FREE(ccc);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
cred->ccache = ccc;
|
|
cred->ccache_obtained = obtained;
|
|
|
|
cli_credentials_invalidate_client_gss_creds(
|
|
cred, cred->ccache_obtained);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifndef SAMBA4_USES_HEIMDAL
|
|
/*
|
|
* This function is a workaround for old MIT Kerberos versions which did not
|
|
* implement the krb5_cc_remove_cred function. It creates a temporary
|
|
* credentials cache to copy the credentials in the current cache
|
|
* except the one we want to remove and then overwrites the contents of the
|
|
* current cache with the temporary copy.
|
|
*/
|
|
static krb5_error_code krb5_cc_remove_cred_wrap(struct ccache_container *ccc,
|
|
krb5_creds *creds)
|
|
{
|
|
krb5_ccache dummy_ccache = NULL;
|
|
krb5_creds cached_creds = {0};
|
|
krb5_cc_cursor cursor = NULL;
|
|
krb5_error_code code;
|
|
char *dummy_name;
|
|
|
|
dummy_name = talloc_asprintf(ccc,
|
|
"MEMORY:copy_ccache-%p",
|
|
&ccc->ccache);
|
|
if (dummy_name == NULL) {
|
|
return KRB5_CC_NOMEM;
|
|
}
|
|
|
|
code = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context,
|
|
dummy_name,
|
|
&dummy_ccache);
|
|
if (code != 0) {
|
|
DBG_ERR("krb5_cc_resolve failed: %s\n",
|
|
smb_get_krb5_error_message(
|
|
ccc->smb_krb5_context->krb5_context,
|
|
code, ccc));
|
|
TALLOC_FREE(dummy_name);
|
|
return code;
|
|
}
|
|
|
|
TALLOC_FREE(dummy_name);
|
|
|
|
code = krb5_cc_start_seq_get(ccc->smb_krb5_context->krb5_context,
|
|
ccc->ccache,
|
|
&cursor);
|
|
if (code != 0) {
|
|
krb5_cc_destroy(ccc->smb_krb5_context->krb5_context,
|
|
dummy_ccache);
|
|
|
|
DBG_ERR("krb5_cc_start_seq_get failed: %s\n",
|
|
smb_get_krb5_error_message(
|
|
ccc->smb_krb5_context->krb5_context,
|
|
code, ccc));
|
|
return code;
|
|
}
|
|
|
|
while ((code = krb5_cc_next_cred(ccc->smb_krb5_context->krb5_context,
|
|
ccc->ccache,
|
|
&cursor,
|
|
&cached_creds)) == 0) {
|
|
/* If the principal matches skip it and do not copy to the
|
|
* temporary cache as this is the one we want to remove */
|
|
if (krb5_principal_compare_flags(
|
|
ccc->smb_krb5_context->krb5_context,
|
|
creds->server,
|
|
cached_creds.server,
|
|
0)) {
|
|
continue;
|
|
}
|
|
|
|
code = krb5_cc_store_cred(
|
|
ccc->smb_krb5_context->krb5_context,
|
|
dummy_ccache,
|
|
&cached_creds);
|
|
if (code != 0) {
|
|
krb5_cc_destroy(ccc->smb_krb5_context->krb5_context,
|
|
dummy_ccache);
|
|
DBG_ERR("krb5_cc_store_cred failed: %s\n",
|
|
smb_get_krb5_error_message(
|
|
ccc->smb_krb5_context->krb5_context,
|
|
code, ccc));
|
|
return code;
|
|
}
|
|
}
|
|
|
|
if (code == KRB5_CC_END) {
|
|
krb5_cc_end_seq_get(ccc->smb_krb5_context->krb5_context,
|
|
dummy_ccache,
|
|
&cursor);
|
|
code = 0;
|
|
}
|
|
|
|
if (code != 0) {
|
|
krb5_cc_destroy(ccc->smb_krb5_context->krb5_context,
|
|
dummy_ccache);
|
|
DBG_ERR("krb5_cc_next_cred failed: %s\n",
|
|
smb_get_krb5_error_message(
|
|
ccc->smb_krb5_context->krb5_context,
|
|
code, ccc));
|
|
return code;
|
|
}
|
|
|
|
code = krb5_cc_initialize(ccc->smb_krb5_context->krb5_context,
|
|
ccc->ccache,
|
|
creds->client);
|
|
if (code != 0) {
|
|
krb5_cc_destroy(ccc->smb_krb5_context->krb5_context,
|
|
dummy_ccache);
|
|
DBG_ERR("krb5_cc_initialize failed: %s\n",
|
|
smb_get_krb5_error_message(
|
|
ccc->smb_krb5_context->krb5_context,
|
|
code, ccc));
|
|
return code;
|
|
}
|
|
|
|
code = krb5_cc_copy_creds(ccc->smb_krb5_context->krb5_context,
|
|
dummy_ccache,
|
|
ccc->ccache);
|
|
if (code != 0) {
|
|
krb5_cc_destroy(ccc->smb_krb5_context->krb5_context,
|
|
dummy_ccache);
|
|
DBG_ERR("krb5_cc_copy_creds failed: %s\n",
|
|
smb_get_krb5_error_message(
|
|
ccc->smb_krb5_context->krb5_context,
|
|
code, ccc));
|
|
return code;
|
|
}
|
|
|
|
code = krb5_cc_destroy(ccc->smb_krb5_context->krb5_context,
|
|
dummy_ccache);
|
|
if (code != 0) {
|
|
DBG_ERR("krb5_cc_destroy failed: %s\n",
|
|
smb_get_krb5_error_message(
|
|
ccc->smb_krb5_context->krb5_context,
|
|
code, ccc));
|
|
return code;
|
|
}
|
|
|
|
return code;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Indicate that we failed to log in to this service/host with these
|
|
* credentials. The caller passes an unsigned int which they
|
|
* initialise to the number of times they would like to retry.
|
|
*
|
|
* This method is used to support re-trying with freshly fetched
|
|
* credentials in case a server is rebuilt while clients have
|
|
* non-expired tickets. When the client code gets a logon failure they
|
|
* throw away the existing credentials for the server and retry.
|
|
*/
|
|
_PUBLIC_ bool cli_credentials_failed_kerberos_login(struct cli_credentials *cred,
|
|
const char *principal,
|
|
unsigned int *count)
|
|
{
|
|
struct ccache_container *ccc;
|
|
krb5_creds creds, creds2;
|
|
int ret;
|
|
|
|
if (principal == NULL) {
|
|
/* no way to delete if we don't know the principal */
|
|
return false;
|
|
}
|
|
|
|
ccc = cred->ccache;
|
|
if (ccc == NULL) {
|
|
/* not a kerberos connection */
|
|
return false;
|
|
}
|
|
|
|
if (*count > 0) {
|
|
/* We have already tried discarding the credentials */
|
|
return false;
|
|
}
|
|
(*count)++;
|
|
|
|
ZERO_STRUCT(creds);
|
|
ret = krb5_parse_name(ccc->smb_krb5_context->krb5_context, principal, &creds.server);
|
|
if (ret != 0) {
|
|
return false;
|
|
}
|
|
|
|
/* MIT kerberos requires creds.client to match against cached
|
|
* credentials */
|
|
ret = krb5_cc_get_principal(ccc->smb_krb5_context->krb5_context,
|
|
ccc->ccache,
|
|
&creds.client);
|
|
if (ret != 0) {
|
|
krb5_free_cred_contents(ccc->smb_krb5_context->krb5_context,
|
|
&creds);
|
|
DBG_ERR("krb5_cc_get_principal failed: %s\n",
|
|
smb_get_krb5_error_message(
|
|
ccc->smb_krb5_context->krb5_context,
|
|
ret, ccc));
|
|
return false;
|
|
}
|
|
|
|
ret = krb5_cc_retrieve_cred(ccc->smb_krb5_context->krb5_context, ccc->ccache, KRB5_TC_MATCH_SRV_NAMEONLY, &creds, &creds2);
|
|
if (ret != 0) {
|
|
/* don't retry - we didn't find these credentials to remove */
|
|
krb5_free_cred_contents(ccc->smb_krb5_context->krb5_context, &creds);
|
|
return false;
|
|
}
|
|
|
|
ret = krb5_cc_remove_cred(ccc->smb_krb5_context->krb5_context, ccc->ccache, KRB5_TC_MATCH_SRV_NAMEONLY, &creds);
|
|
#ifndef SAMBA4_USES_HEIMDAL
|
|
if (ret == KRB5_CC_NOSUPP) {
|
|
/* Old MIT kerberos versions did not implement
|
|
* krb5_cc_remove_cred */
|
|
ret = krb5_cc_remove_cred_wrap(ccc, &creds);
|
|
}
|
|
#endif
|
|
krb5_free_cred_contents(ccc->smb_krb5_context->krb5_context, &creds);
|
|
krb5_free_cred_contents(ccc->smb_krb5_context->krb5_context, &creds2);
|
|
if (ret != 0) {
|
|
/* don't retry - we didn't find these credentials to
|
|
* remove. Note that with the current backend this
|
|
* never happens, as it always returns 0 even if the
|
|
* creds don't exist, which is why we do a separate
|
|
* krb5_cc_retrieve_cred() above.
|
|
*/
|
|
DBG_ERR("krb5_cc_remove_cred failed: %s\n",
|
|
smb_get_krb5_error_message(
|
|
ccc->smb_krb5_context->krb5_context,
|
|
ret, ccc));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
static int cli_credentials_new_ccache(struct cli_credentials *cred,
|
|
struct loadparm_context *lp_ctx,
|
|
char *ccache_name,
|
|
struct ccache_container **_ccc,
|
|
const char **error_string)
|
|
{
|
|
bool must_free_cc_name = false;
|
|
krb5_error_code ret;
|
|
struct ccache_container *ccc = talloc(cred, struct ccache_container);
|
|
if (!ccc) {
|
|
return ENOMEM;
|
|
}
|
|
|
|
ret = cli_credentials_get_krb5_context(cred, lp_ctx,
|
|
&ccc->smb_krb5_context);
|
|
if (ret) {
|
|
talloc_free(ccc);
|
|
(*error_string) = talloc_asprintf(cred, "Failed to get krb5_context: %s",
|
|
error_message(ret));
|
|
return ret;
|
|
}
|
|
if (!talloc_reference(ccc, ccc->smb_krb5_context)) {
|
|
talloc_free(ccc);
|
|
(*error_string) = strerror(ENOMEM);
|
|
return ENOMEM;
|
|
}
|
|
|
|
if (!ccache_name) {
|
|
must_free_cc_name = true;
|
|
|
|
if (lpcfg_parm_bool(lp_ctx, NULL, "credentials", "krb5_cc_file", false)) {
|
|
ccache_name = talloc_asprintf(ccc, "FILE:/tmp/krb5_cc_samba_%u_%p",
|
|
(unsigned int)getpid(), ccc);
|
|
} else {
|
|
ccache_name = talloc_asprintf(ccc, "MEMORY:%p",
|
|
ccc);
|
|
}
|
|
|
|
if (!ccache_name) {
|
|
talloc_free(ccc);
|
|
(*error_string) = strerror(ENOMEM);
|
|
return ENOMEM;
|
|
}
|
|
}
|
|
|
|
ret = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context, ccache_name,
|
|
&ccc->ccache);
|
|
if (ret) {
|
|
(*error_string) = talloc_asprintf(cred, "failed to resolve a krb5 ccache (%s): %s\n",
|
|
ccache_name,
|
|
smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context,
|
|
ret, ccc));
|
|
talloc_free(ccache_name);
|
|
talloc_free(ccc);
|
|
return ret;
|
|
}
|
|
|
|
if (strncasecmp(ccache_name, "MEMORY:", 7) == 0) {
|
|
talloc_set_destructor(ccc, free_mccache);
|
|
} else {
|
|
talloc_set_destructor(ccc, free_dccache);
|
|
}
|
|
|
|
if (must_free_cc_name) {
|
|
talloc_free(ccache_name);
|
|
}
|
|
|
|
*_ccc = ccc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
_PUBLIC_ int cli_credentials_get_named_ccache(struct cli_credentials *cred,
|
|
struct tevent_context *event_ctx,
|
|
struct loadparm_context *lp_ctx,
|
|
char *ccache_name,
|
|
struct ccache_container **ccc,
|
|
const char **error_string)
|
|
{
|
|
krb5_error_code ret;
|
|
enum credentials_obtained obtained;
|
|
|
|
if (cred->machine_account_pending) {
|
|
cli_credentials_set_machine_account(cred, lp_ctx);
|
|
}
|
|
|
|
if (cred->ccache_obtained >= cred->ccache_threshold &&
|
|
cred->ccache_obtained > CRED_UNINITIALISED) {
|
|
time_t lifetime;
|
|
bool expired = false;
|
|
ret = smb_krb5_cc_get_lifetime(cred->ccache->smb_krb5_context->krb5_context,
|
|
cred->ccache->ccache, &lifetime);
|
|
if (ret == KRB5_CC_END || ret == ENOENT) {
|
|
/* If we have a particular ccache set, without
|
|
* an initial ticket, then assume there is a
|
|
* good reason */
|
|
} else if (ret == 0) {
|
|
if (lifetime == 0) {
|
|
DEBUG(3, ("Ticket in credentials cache for %s expired, will refresh\n",
|
|
cli_credentials_get_principal(cred, cred)));
|
|
expired = true;
|
|
} else if (lifetime < 300) {
|
|
DEBUG(3, ("Ticket in credentials cache for %s will shortly expire (%u secs), will refresh\n",
|
|
cli_credentials_get_principal(cred, cred), (unsigned int)lifetime));
|
|
expired = true;
|
|
}
|
|
} else {
|
|
(*error_string) = talloc_asprintf(cred, "failed to get ccache lifetime: %s\n",
|
|
smb_get_krb5_error_message(cred->ccache->smb_krb5_context->krb5_context,
|
|
ret, cred));
|
|
return ret;
|
|
}
|
|
|
|
DEBUG(5, ("Ticket in credentials cache for %s will expire in %u secs\n",
|
|
cli_credentials_get_principal(cred, cred), (unsigned int)lifetime));
|
|
|
|
if (!expired) {
|
|
*ccc = cred->ccache;
|
|
return 0;
|
|
}
|
|
}
|
|
if (cli_credentials_is_anonymous(cred)) {
|
|
(*error_string) = "Cannot get anonymous kerberos credentials";
|
|
return EINVAL;
|
|
}
|
|
|
|
ret = cli_credentials_new_ccache(cred, lp_ctx, ccache_name, ccc, error_string);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
ret = kinit_to_ccache(cred,
|
|
cred,
|
|
(*ccc)->smb_krb5_context,
|
|
lp_ctx,
|
|
event_ctx,
|
|
(*ccc)->ccache,
|
|
&obtained,
|
|
error_string);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
ret = cli_credentials_set_from_ccache(cred, *ccc,
|
|
obtained, error_string);
|
|
|
|
cred->ccache = *ccc;
|
|
cred->ccache_obtained = cred->principal_obtained;
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
cli_credentials_invalidate_client_gss_creds(cred, cred->ccache_obtained);
|
|
return 0;
|
|
}
|
|
|
|
_PUBLIC_ int cli_credentials_get_ccache(struct cli_credentials *cred,
|
|
struct tevent_context *event_ctx,
|
|
struct loadparm_context *lp_ctx,
|
|
struct ccache_container **ccc,
|
|
const char **error_string)
|
|
{
|
|
return cli_credentials_get_named_ccache(cred, event_ctx, lp_ctx, NULL, ccc, error_string);
|
|
}
|
|
|
|
/* We have good reason to think the ccache in these credentials is invalid - blow it away */
|
|
static void cli_credentials_unconditionally_invalidate_client_gss_creds(struct cli_credentials *cred)
|
|
{
|
|
if (cred->client_gss_creds_obtained > CRED_UNINITIALISED) {
|
|
talloc_unlink(cred, cred->client_gss_creds);
|
|
cred->client_gss_creds = NULL;
|
|
}
|
|
cred->client_gss_creds_obtained = CRED_UNINITIALISED;
|
|
}
|
|
|
|
void cli_credentials_invalidate_client_gss_creds(struct cli_credentials *cred,
|
|
enum credentials_obtained obtained)
|
|
{
|
|
/* If the caller just changed the username/password etc, then
|
|
* any cached credentials are now invalid */
|
|
if (obtained >= cred->client_gss_creds_obtained) {
|
|
if (cred->client_gss_creds_obtained > CRED_UNINITIALISED) {
|
|
talloc_unlink(cred, cred->client_gss_creds);
|
|
cred->client_gss_creds = NULL;
|
|
}
|
|
cred->client_gss_creds_obtained = CRED_UNINITIALISED;
|
|
}
|
|
/* Now that we know that the data is 'this specified', then
|
|
* don't allow something less 'known' to be returned as a
|
|
* ccache. Ie, if the username is on the command line, we
|
|
* don't want to later guess to use a file-based ccache */
|
|
if (obtained > cred->client_gss_creds_threshold) {
|
|
cred->client_gss_creds_threshold = obtained;
|
|
}
|
|
}
|
|
|
|
/* We have good reason to think this CCACHE is invalid. Blow it away */
|
|
static void cli_credentials_unconditionally_invalidate_ccache(struct cli_credentials *cred)
|
|
{
|
|
if (cred->ccache_obtained > CRED_UNINITIALISED) {
|
|
talloc_unlink(cred, cred->ccache);
|
|
cred->ccache = NULL;
|
|
}
|
|
cred->ccache_obtained = CRED_UNINITIALISED;
|
|
|
|
cli_credentials_unconditionally_invalidate_client_gss_creds(cred);
|
|
}
|
|
|
|
_PUBLIC_ void cli_credentials_invalidate_ccache(struct cli_credentials *cred,
|
|
enum credentials_obtained obtained)
|
|
{
|
|
/* If the caller just changed the username/password etc, then
|
|
* any cached credentials are now invalid */
|
|
if (obtained >= cred->ccache_obtained) {
|
|
if (cred->ccache_obtained > CRED_UNINITIALISED) {
|
|
talloc_unlink(cred, cred->ccache);
|
|
cred->ccache = NULL;
|
|
}
|
|
cred->ccache_obtained = CRED_UNINITIALISED;
|
|
}
|
|
/* Now that we know that the data is 'this specified', then
|
|
* don't allow something less 'known' to be returned as a
|
|
* ccache. i.e, if the username is on the command line, we
|
|
* don't want to later guess to use a file-based ccache */
|
|
if (obtained > cred->ccache_threshold) {
|
|
cred->ccache_threshold = obtained;
|
|
}
|
|
|
|
cli_credentials_invalidate_client_gss_creds(cred,
|
|
obtained);
|
|
}
|
|
|
|
static int free_gssapi_creds(struct gssapi_creds_container *gcc)
|
|
{
|
|
OM_uint32 min_stat;
|
|
(void)gss_release_cred(&min_stat, &gcc->creds);
|
|
return 0;
|
|
}
|
|
|
|
_PUBLIC_ int cli_credentials_get_client_gss_creds(struct cli_credentials *cred,
|
|
struct tevent_context *event_ctx,
|
|
struct loadparm_context *lp_ctx,
|
|
struct gssapi_creds_container **_gcc,
|
|
const char **error_string)
|
|
{
|
|
int ret = 0;
|
|
OM_uint32 maj_stat, min_stat;
|
|
struct gssapi_creds_container *gcc;
|
|
struct ccache_container *ccache;
|
|
#ifdef HAVE_GSS_KRB5_CRED_NO_CI_FLAGS_X
|
|
gss_buffer_desc empty_buffer = GSS_C_EMPTY_BUFFER;
|
|
gss_OID oid = discard_const(GSS_KRB5_CRED_NO_CI_FLAGS_X);
|
|
#endif
|
|
krb5_enctype *etypes = NULL;
|
|
|
|
if (cred->client_gss_creds_obtained >= cred->client_gss_creds_threshold &&
|
|
cred->client_gss_creds_obtained > CRED_UNINITIALISED) {
|
|
bool expired = false;
|
|
OM_uint32 lifetime = 0;
|
|
gss_cred_usage_t usage = 0;
|
|
maj_stat = gss_inquire_cred(&min_stat, cred->client_gss_creds->creds,
|
|
NULL, &lifetime, &usage, NULL);
|
|
if (maj_stat == GSS_S_CREDENTIALS_EXPIRED) {
|
|
DEBUG(3, ("Credentials for %s expired, must refresh credentials cache\n", cli_credentials_get_principal(cred, cred)));
|
|
expired = true;
|
|
} else if (maj_stat == GSS_S_COMPLETE && lifetime < 300) {
|
|
DEBUG(3, ("Credentials for %s will expire shortly (%u sec), must refresh credentials cache\n", cli_credentials_get_principal(cred, cred), lifetime));
|
|
expired = true;
|
|
} else if (maj_stat != GSS_S_COMPLETE) {
|
|
*error_string = talloc_asprintf(cred, "inquiry of credential lifetime via GSSAPI gss_inquire_cred failed: %s\n",
|
|
gssapi_error_string(cred, maj_stat, min_stat, NULL));
|
|
return EINVAL;
|
|
}
|
|
if (expired) {
|
|
cli_credentials_unconditionally_invalidate_client_gss_creds(cred);
|
|
} else {
|
|
DEBUG(5, ("GSSAPI credentials for %s will expire in %u secs\n",
|
|
cli_credentials_get_principal(cred, cred), (unsigned int)lifetime));
|
|
|
|
*_gcc = cred->client_gss_creds;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
ret = cli_credentials_get_ccache(cred, event_ctx, lp_ctx,
|
|
&ccache, error_string);
|
|
if (ret) {
|
|
if (cli_credentials_get_kerberos_state(cred) == CRED_USE_KERBEROS_REQUIRED) {
|
|
DEBUG(1, ("Failed to get kerberos credentials (kerberos required): %s\n", *error_string));
|
|
} else {
|
|
DEBUG(4, ("Failed to get kerberos credentials: %s\n", *error_string));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
gcc = talloc(cred, struct gssapi_creds_container);
|
|
if (!gcc) {
|
|
(*error_string) = error_message(ENOMEM);
|
|
return ENOMEM;
|
|
}
|
|
|
|
maj_stat = smb_gss_krb5_import_cred(&min_stat, ccache->smb_krb5_context->krb5_context,
|
|
ccache->ccache, NULL, NULL,
|
|
&gcc->creds);
|
|
if ((maj_stat == GSS_S_FAILURE) &&
|
|
(min_stat == (OM_uint32)KRB5_CC_END ||
|
|
min_stat == (OM_uint32)KRB5_CC_NOTFOUND ||
|
|
min_stat == (OM_uint32)KRB5_FCC_NOFILE))
|
|
{
|
|
/* This CCACHE is no good. Ensure we don't use it again */
|
|
cli_credentials_unconditionally_invalidate_ccache(cred);
|
|
|
|
/* Now try again to get a ccache */
|
|
ret = cli_credentials_get_ccache(cred, event_ctx, lp_ctx,
|
|
&ccache, error_string);
|
|
if (ret) {
|
|
DEBUG(1, ("Failed to re-get CCACHE for GSSAPI client: %s\n", error_message(ret)));
|
|
return ret;
|
|
}
|
|
|
|
maj_stat = smb_gss_krb5_import_cred(&min_stat, ccache->smb_krb5_context->krb5_context,
|
|
ccache->ccache, NULL, NULL,
|
|
&gcc->creds);
|
|
|
|
}
|
|
|
|
if (maj_stat) {
|
|
talloc_free(gcc);
|
|
if (min_stat) {
|
|
ret = min_stat;
|
|
} else {
|
|
ret = EINVAL;
|
|
}
|
|
(*error_string) = talloc_asprintf(cred, "smb_gss_krb5_import_cred failed: %s", error_message(ret));
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
* transfer the enctypes from the smb_krb5_context to the gssapi layer
|
|
*
|
|
* We use 'our' smb_krb5_context to do the AS-REQ and it is possible
|
|
* to configure the enctypes via the krb5.conf.
|
|
*
|
|
* And the gss_init_sec_context() creates it's own krb5_context and
|
|
* the TGS-REQ had all enctypes in it and only the ones configured
|
|
* and used for the AS-REQ, so it wasn't possible to disable the usage
|
|
* of AES keys.
|
|
*/
|
|
min_stat = smb_krb5_get_allowed_etypes(ccache->smb_krb5_context->krb5_context,
|
|
&etypes);
|
|
if (min_stat == 0) {
|
|
OM_uint32 num_ktypes;
|
|
|
|
for (num_ktypes = 0; etypes[num_ktypes]; num_ktypes++);
|
|
|
|
maj_stat = gss_krb5_set_allowable_enctypes(&min_stat, gcc->creds,
|
|
num_ktypes,
|
|
(int32_t *) etypes);
|
|
krb5_free_enctypes(ccache->smb_krb5_context->krb5_context,
|
|
etypes);
|
|
if (maj_stat) {
|
|
talloc_free(gcc);
|
|
if (min_stat) {
|
|
ret = min_stat;
|
|
} else {
|
|
ret = EINVAL;
|
|
}
|
|
(*error_string) = talloc_asprintf(cred, "gss_krb5_set_allowable_enctypes failed: %s", error_message(ret));
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_GSS_KRB5_CRED_NO_CI_FLAGS_X
|
|
/*
|
|
* Don't force GSS_C_CONF_FLAG and GSS_C_INTEG_FLAG.
|
|
*
|
|
* This allows us to disable SIGN and SEAL on a TLS connection with
|
|
* GSS-SPNENO. For example ldaps:// connections.
|
|
*
|
|
* https://groups.yahoo.com/neo/groups/cat-ietf/conversations/topics/575
|
|
* http://krbdev.mit.edu/rt/Ticket/Display.html?id=6938
|
|
*/
|
|
maj_stat = gss_set_cred_option(&min_stat, &gcc->creds,
|
|
oid,
|
|
&empty_buffer);
|
|
if (maj_stat) {
|
|
talloc_free(gcc);
|
|
if (min_stat) {
|
|
ret = min_stat;
|
|
} else {
|
|
ret = EINVAL;
|
|
}
|
|
(*error_string) = talloc_asprintf(cred, "gss_set_cred_option failed: %s", error_message(ret));
|
|
return ret;
|
|
}
|
|
#endif
|
|
cred->client_gss_creds_obtained = cred->ccache_obtained;
|
|
talloc_set_destructor(gcc, free_gssapi_creds);
|
|
cred->client_gss_creds = gcc;
|
|
*_gcc = gcc;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
Set a gssapi cred_id_t into the credentials system. (Client case)
|
|
|
|
This grabs the credentials both 'intact' and getting the krb5
|
|
ccache out of it. This routine can be generalised in future for
|
|
the case where we deal with GSSAPI mechs other than krb5.
|
|
|
|
On success, the caller must not free gssapi_cred, as it now belongs
|
|
to the credentials system.
|
|
*/
|
|
|
|
int cli_credentials_set_client_gss_creds(struct cli_credentials *cred,
|
|
struct loadparm_context *lp_ctx,
|
|
gss_cred_id_t gssapi_cred,
|
|
enum credentials_obtained obtained,
|
|
const char **error_string)
|
|
{
|
|
int ret;
|
|
OM_uint32 maj_stat, min_stat;
|
|
struct ccache_container *ccc = NULL;
|
|
struct gssapi_creds_container *gcc = NULL;
|
|
if (cred->client_gss_creds_obtained > obtained) {
|
|
return 0;
|
|
}
|
|
|
|
gcc = talloc(cred, struct gssapi_creds_container);
|
|
if (!gcc) {
|
|
(*error_string) = error_message(ENOMEM);
|
|
return ENOMEM;
|
|
}
|
|
|
|
ret = cli_credentials_new_ccache(cred, lp_ctx, NULL, &ccc, error_string);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
maj_stat = smb_gss_krb5_copy_ccache(&min_stat,
|
|
gssapi_cred,
|
|
ccc);
|
|
if (maj_stat) {
|
|
if (min_stat) {
|
|
ret = min_stat;
|
|
} else {
|
|
ret = EINVAL;
|
|
}
|
|
if (ret) {
|
|
(*error_string) = error_message(ENOMEM);
|
|
}
|
|
}
|
|
|
|
if (ret == 0) {
|
|
ret = cli_credentials_set_from_ccache(cred, ccc, obtained, error_string);
|
|
}
|
|
cred->ccache = ccc;
|
|
cred->ccache_obtained = obtained;
|
|
if (ret == 0) {
|
|
gcc->creds = gssapi_cred;
|
|
talloc_set_destructor(gcc, free_gssapi_creds);
|
|
|
|
/* set the client_gss_creds_obtained here, as it just
|
|
got set to UNINITIALISED by the calls above */
|
|
cred->client_gss_creds_obtained = obtained;
|
|
cred->client_gss_creds = gcc;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int cli_credentials_shallow_ccache(struct cli_credentials *cred)
|
|
{
|
|
krb5_error_code ret;
|
|
const struct ccache_container *old_ccc = NULL;
|
|
enum credentials_obtained old_obtained;
|
|
struct ccache_container *ccc = NULL;
|
|
char *ccache_name = NULL;
|
|
krb5_principal princ;
|
|
|
|
old_obtained = cred->ccache_obtained;
|
|
old_ccc = cred->ccache;
|
|
if (old_ccc == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
cred->ccache = NULL;
|
|
cred->ccache_obtained = CRED_UNINITIALISED;
|
|
cred->client_gss_creds = NULL;
|
|
cred->client_gss_creds_obtained = CRED_UNINITIALISED;
|
|
|
|
ret = krb5_cc_get_principal(
|
|
old_ccc->smb_krb5_context->krb5_context,
|
|
old_ccc->ccache,
|
|
&princ);
|
|
if (ret != 0) {
|
|
/*
|
|
* This is an empty ccache. No point in copying anything.
|
|
*/
|
|
return 0;
|
|
}
|
|
krb5_free_principal(old_ccc->smb_krb5_context->krb5_context, princ);
|
|
|
|
ccc = talloc(cred, struct ccache_container);
|
|
if (ccc == NULL) {
|
|
return ENOMEM;
|
|
}
|
|
*ccc = *old_ccc;
|
|
ccc->ccache = NULL;
|
|
|
|
ccache_name = talloc_asprintf(ccc, "MEMORY:%p", ccc);
|
|
|
|
ret = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context,
|
|
ccache_name, &ccc->ccache);
|
|
if (ret != 0) {
|
|
TALLOC_FREE(ccc);
|
|
return ret;
|
|
}
|
|
|
|
talloc_set_destructor(ccc, free_mccache);
|
|
|
|
TALLOC_FREE(ccache_name);
|
|
|
|
ret = smb_krb5_cc_copy_creds(ccc->smb_krb5_context->krb5_context,
|
|
old_ccc->ccache, ccc->ccache);
|
|
if (ret != 0) {
|
|
TALLOC_FREE(ccc);
|
|
return ret;
|
|
}
|
|
|
|
cred->ccache = ccc;
|
|
cred->ccache_obtained = old_obtained;
|
|
return ret;
|
|
}
|
|
|
|
_PUBLIC_ struct cli_credentials *cli_credentials_shallow_copy(TALLOC_CTX *mem_ctx,
|
|
struct cli_credentials *src)
|
|
{
|
|
struct cli_credentials *dst, *armor_credentials;
|
|
int ret;
|
|
|
|
dst = talloc(mem_ctx, struct cli_credentials);
|
|
if (dst == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
*dst = *src;
|
|
|
|
if (dst->krb5_fast_armor_credentials != NULL) {
|
|
armor_credentials = talloc_reference(dst, dst->krb5_fast_armor_credentials);
|
|
if (armor_credentials == NULL) {
|
|
TALLOC_FREE(dst);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
ret = cli_credentials_shallow_ccache(dst);
|
|
if (ret != 0) {
|
|
TALLOC_FREE(dst);
|
|
return NULL;
|
|
}
|
|
|
|
return dst;
|
|
}
|
|
|
|
/* Get the keytab (actually, a container containing the krb5_keytab)
|
|
* attached to this context. If this hasn't been done or set before,
|
|
* it will be generated from the password.
|
|
*/
|
|
_PUBLIC_ int cli_credentials_get_keytab(struct cli_credentials *cred,
|
|
struct loadparm_context *lp_ctx,
|
|
struct keytab_container **_ktc)
|
|
{
|
|
krb5_error_code ret;
|
|
struct keytab_container *ktc;
|
|
struct smb_krb5_context *smb_krb5_context;
|
|
const char *keytab_name;
|
|
krb5_keytab keytab;
|
|
TALLOC_CTX *mem_ctx;
|
|
const char *username = cli_credentials_get_username(cred);
|
|
const char *upn = NULL;
|
|
const char *realm = cli_credentials_get_realm(cred);
|
|
char *salt_principal = NULL;
|
|
uint32_t uac_flags = 0;
|
|
|
|
if (cred->keytab_obtained >= (MAX(cred->principal_obtained,
|
|
cred->username_obtained))) {
|
|
*_ktc = cred->keytab;
|
|
return 0;
|
|
}
|
|
|
|
if (cli_credentials_is_anonymous(cred)) {
|
|
return EINVAL;
|
|
}
|
|
|
|
ret = cli_credentials_get_krb5_context(cred, lp_ctx,
|
|
&smb_krb5_context);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
mem_ctx = talloc_new(cred);
|
|
if (!mem_ctx) {
|
|
return ENOMEM;
|
|
}
|
|
|
|
switch (cred->secure_channel_type) {
|
|
case SEC_CHAN_WKSTA:
|
|
case SEC_CHAN_RODC:
|
|
uac_flags = UF_WORKSTATION_TRUST_ACCOUNT;
|
|
break;
|
|
case SEC_CHAN_BDC:
|
|
uac_flags = UF_SERVER_TRUST_ACCOUNT;
|
|
break;
|
|
case SEC_CHAN_DOMAIN:
|
|
case SEC_CHAN_DNS_DOMAIN:
|
|
uac_flags = UF_INTERDOMAIN_TRUST_ACCOUNT;
|
|
break;
|
|
default:
|
|
upn = cli_credentials_get_principal(cred, mem_ctx);
|
|
if (upn == NULL) {
|
|
TALLOC_FREE(mem_ctx);
|
|
return ENOMEM;
|
|
}
|
|
uac_flags = UF_NORMAL_ACCOUNT;
|
|
break;
|
|
}
|
|
|
|
ret = smb_krb5_salt_principal_str(realm,
|
|
username, /* sAMAccountName */
|
|
upn, /* userPrincipalName */
|
|
uac_flags,
|
|
mem_ctx,
|
|
&salt_principal);
|
|
if (ret) {
|
|
talloc_free(mem_ctx);
|
|
return ret;
|
|
}
|
|
|
|
ret = smb_krb5_create_memory_keytab(mem_ctx,
|
|
smb_krb5_context->krb5_context,
|
|
cli_credentials_get_password(cred),
|
|
username,
|
|
realm,
|
|
salt_principal,
|
|
cli_credentials_get_kvno(cred),
|
|
&keytab,
|
|
&keytab_name);
|
|
if (ret) {
|
|
talloc_free(mem_ctx);
|
|
return ret;
|
|
}
|
|
|
|
ret = smb_krb5_get_keytab_container(mem_ctx, smb_krb5_context,
|
|
keytab, keytab_name, &ktc);
|
|
if (ret) {
|
|
talloc_free(mem_ctx);
|
|
return ret;
|
|
}
|
|
|
|
cred->keytab_obtained = (MAX(cred->principal_obtained,
|
|
cred->username_obtained));
|
|
|
|
/* We make this keytab up based on a password. Therefore
|
|
* match-by-key is acceptable, we can't match on the wrong
|
|
* principal */
|
|
ktc->password_based = true;
|
|
|
|
talloc_steal(cred, ktc);
|
|
cred->keytab = ktc;
|
|
*_ktc = cred->keytab;
|
|
talloc_free(mem_ctx);
|
|
return ret;
|
|
}
|
|
|
|
/* Given the name of a keytab (presumably in the format
|
|
* FILE:/etc/krb5.keytab), open it and attach it */
|
|
|
|
_PUBLIC_ int cli_credentials_set_keytab_name(struct cli_credentials *cred,
|
|
struct loadparm_context *lp_ctx,
|
|
const char *keytab_name,
|
|
enum credentials_obtained obtained)
|
|
{
|
|
krb5_error_code ret;
|
|
struct keytab_container *ktc;
|
|
struct smb_krb5_context *smb_krb5_context;
|
|
TALLOC_CTX *mem_ctx;
|
|
|
|
if (cred->keytab_obtained >= obtained) {
|
|
return 0;
|
|
}
|
|
|
|
ret = cli_credentials_get_krb5_context(cred, lp_ctx, &smb_krb5_context);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
mem_ctx = talloc_new(cred);
|
|
if (!mem_ctx) {
|
|
return ENOMEM;
|
|
}
|
|
|
|
ret = smb_krb5_get_keytab_container(mem_ctx, smb_krb5_context,
|
|
NULL, keytab_name, &ktc);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
cred->keytab_obtained = obtained;
|
|
|
|
talloc_steal(cred, ktc);
|
|
cred->keytab = ktc;
|
|
talloc_free(mem_ctx);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Get server gss credentials (in gsskrb5, this means the keytab) */
|
|
|
|
_PUBLIC_ int cli_credentials_get_server_gss_creds(struct cli_credentials *cred,
|
|
struct loadparm_context *lp_ctx,
|
|
struct gssapi_creds_container **_gcc)
|
|
{
|
|
int ret = 0;
|
|
OM_uint32 maj_stat, min_stat;
|
|
struct gssapi_creds_container *gcc;
|
|
struct keytab_container *ktc;
|
|
struct smb_krb5_context *smb_krb5_context;
|
|
TALLOC_CTX *mem_ctx;
|
|
krb5_principal princ;
|
|
const char *error_string;
|
|
enum credentials_obtained obtained;
|
|
|
|
mem_ctx = talloc_new(cred);
|
|
if (!mem_ctx) {
|
|
return ENOMEM;
|
|
}
|
|
|
|
ret = cli_credentials_get_krb5_context(cred, lp_ctx, &smb_krb5_context);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
ret = principal_from_credentials(mem_ctx, cred, smb_krb5_context, &princ, &obtained, &error_string);
|
|
if (ret) {
|
|
DEBUG(1,("cli_credentials_get_server_gss_creds: making krb5 principal failed (%s)\n",
|
|
error_string));
|
|
talloc_free(mem_ctx);
|
|
return ret;
|
|
}
|
|
|
|
if (cred->server_gss_creds_obtained >= (MAX(cred->keytab_obtained, obtained))) {
|
|
talloc_free(mem_ctx);
|
|
*_gcc = cred->server_gss_creds;
|
|
return 0;
|
|
}
|
|
|
|
ret = cli_credentials_get_keytab(cred, lp_ctx, &ktc);
|
|
if (ret) {
|
|
DEBUG(1, ("Failed to get keytab for GSSAPI server: %s\n", error_message(ret)));
|
|
return ret;
|
|
}
|
|
|
|
gcc = talloc(cred, struct gssapi_creds_container);
|
|
if (!gcc) {
|
|
talloc_free(mem_ctx);
|
|
return ENOMEM;
|
|
}
|
|
|
|
if (ktc->password_based || obtained < CRED_SPECIFIED) {
|
|
/*
|
|
* This creates a GSSAPI cred_id_t for match-by-key with only
|
|
* the keytab set
|
|
*/
|
|
princ = NULL;
|
|
}
|
|
maj_stat = smb_gss_krb5_import_cred(&min_stat,
|
|
smb_krb5_context->krb5_context,
|
|
NULL, princ,
|
|
ktc->keytab,
|
|
&gcc->creds);
|
|
if (maj_stat) {
|
|
if (min_stat) {
|
|
ret = min_stat;
|
|
} else {
|
|
ret = EINVAL;
|
|
}
|
|
}
|
|
if (ret == 0) {
|
|
cred->server_gss_creds_obtained = cred->keytab_obtained;
|
|
talloc_set_destructor(gcc, free_gssapi_creds);
|
|
cred->server_gss_creds = gcc;
|
|
*_gcc = gcc;
|
|
}
|
|
talloc_free(mem_ctx);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Set Kerberos KVNO
|
|
*/
|
|
|
|
_PUBLIC_ void cli_credentials_set_kvno(struct cli_credentials *cred,
|
|
int kvno)
|
|
{
|
|
cred->kvno = kvno;
|
|
}
|
|
|
|
/**
|
|
* Return Kerberos KVNO
|
|
*/
|
|
|
|
_PUBLIC_ int cli_credentials_get_kvno(struct cli_credentials *cred)
|
|
{
|
|
return cred->kvno;
|
|
}
|
|
|
|
|
|
const char *cli_credentials_get_salt_principal(struct cli_credentials *cred)
|
|
{
|
|
return cred->salt_principal;
|
|
}
|
|
|
|
_PUBLIC_ void cli_credentials_set_salt_principal(struct cli_credentials *cred, const char *principal)
|
|
{
|
|
talloc_free(cred->salt_principal);
|
|
cred->salt_principal = talloc_strdup(cred, principal);
|
|
}
|
|
|
|
/* The 'impersonate_principal' is used to allow one Kerberos principal
|
|
* (and it's associated keytab etc) to impersonate another. The
|
|
* ability to do this is controlled by the KDC, but it is generally
|
|
* permitted to impersonate anyone to yourself. This allows any
|
|
* member of the domain to get the groups of a user. This is also
|
|
* known as S4U2Self */
|
|
|
|
_PUBLIC_ const char *cli_credentials_get_impersonate_principal(struct cli_credentials *cred)
|
|
{
|
|
return cred->impersonate_principal;
|
|
}
|
|
|
|
/*
|
|
* The 'self_service' is the service principal that
|
|
* represents the same object (by its objectSid)
|
|
* as the client principal (typically our machine account).
|
|
* When trying to impersonate 'impersonate_principal' with
|
|
* S4U2Self.
|
|
*/
|
|
_PUBLIC_ const char *cli_credentials_get_self_service(struct cli_credentials *cred)
|
|
{
|
|
return cred->self_service;
|
|
}
|
|
|
|
_PUBLIC_ void cli_credentials_set_impersonate_principal(struct cli_credentials *cred,
|
|
const char *principal,
|
|
const char *self_service)
|
|
{
|
|
talloc_free(cred->impersonate_principal);
|
|
cred->impersonate_principal = talloc_strdup(cred, principal);
|
|
talloc_free(cred->self_service);
|
|
cred->self_service = talloc_strdup(cred, self_service);
|
|
cli_credentials_set_kerberos_state(cred,
|
|
CRED_USE_KERBEROS_REQUIRED,
|
|
CRED_SPECIFIED);
|
|
}
|
|
|
|
/*
|
|
* when impersonating for S4U2proxy we need to set the target principal.
|
|
* Similarly, we may only be authorized to do general impersonation to
|
|
* some particular services.
|
|
*
|
|
* Likewise, password changes typically require a ticket to kpasswd/realm directly, not via a TGT
|
|
*
|
|
* NULL means that tickets will be obtained for the krbtgt service.
|
|
*/
|
|
|
|
const char *cli_credentials_get_target_service(struct cli_credentials *cred)
|
|
{
|
|
return cred->target_service;
|
|
}
|
|
|
|
_PUBLIC_ void cli_credentials_set_target_service(struct cli_credentials *cred, const char *target_service)
|
|
{
|
|
talloc_free(cred->target_service);
|
|
cred->target_service = talloc_strdup(cred, target_service);
|
|
}
|
|
|
|
_PUBLIC_ int cli_credentials_get_aes256_key(struct cli_credentials *cred,
|
|
TALLOC_CTX *mem_ctx,
|
|
struct loadparm_context *lp_ctx,
|
|
const char *salt,
|
|
DATA_BLOB *aes_256)
|
|
{
|
|
struct smb_krb5_context *smb_krb5_context = NULL;
|
|
krb5_error_code krb5_ret;
|
|
int ret;
|
|
const char *password = NULL;
|
|
krb5_data cleartext_data;
|
|
krb5_data salt_data = {
|
|
.length = 0,
|
|
};
|
|
krb5_keyblock key;
|
|
|
|
if (cred->password_will_be_nt_hash) {
|
|
DEBUG(1,("cli_credentials_get_aes256_key: cannot generate AES256 key using NT hash\n"));
|
|
return EINVAL;
|
|
}
|
|
|
|
password = cli_credentials_get_password(cred);
|
|
if (password == NULL) {
|
|
return EINVAL;
|
|
}
|
|
|
|
cleartext_data.data = discard_const_p(char, password);
|
|
cleartext_data.length = strlen(password);
|
|
|
|
ret = cli_credentials_get_krb5_context(cred, lp_ctx,
|
|
&smb_krb5_context);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
salt_data.data = discard_const_p(char, salt);
|
|
salt_data.length = strlen(salt);
|
|
|
|
/*
|
|
* create ENCTYPE_AES256_CTS_HMAC_SHA1_96 key out of
|
|
* the salt and the cleartext password
|
|
*/
|
|
krb5_ret = smb_krb5_create_key_from_string(smb_krb5_context->krb5_context,
|
|
NULL,
|
|
&salt_data,
|
|
&cleartext_data,
|
|
ENCTYPE_AES256_CTS_HMAC_SHA1_96,
|
|
&key);
|
|
if (krb5_ret != 0) {
|
|
DEBUG(1,("cli_credentials_get_aes256_key: "
|
|
"generation of a aes256-cts-hmac-sha1-96 key failed: %s\n",
|
|
smb_get_krb5_error_message(smb_krb5_context->krb5_context,
|
|
krb5_ret, mem_ctx)));
|
|
return EINVAL;
|
|
}
|
|
*aes_256 = data_blob_talloc(mem_ctx,
|
|
KRB5_KEY_DATA(&key),
|
|
KRB5_KEY_LENGTH(&key));
|
|
krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &key);
|
|
if (aes_256->data == NULL) {
|
|
return ENOMEM;
|
|
}
|
|
talloc_keep_secret(aes_256->data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* This take a reference to the armor credentials to ensure the lifetime is appropriate */
|
|
|
|
NTSTATUS cli_credentials_set_krb5_fast_armor_credentials(struct cli_credentials *creds,
|
|
struct cli_credentials *armor_creds,
|
|
bool require_fast_armor)
|
|
{
|
|
talloc_unlink(creds, creds->krb5_fast_armor_credentials);
|
|
if (armor_creds == NULL) {
|
|
creds->krb5_fast_armor_credentials = NULL;
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
creds->krb5_fast_armor_credentials = talloc_reference(creds, armor_creds);
|
|
if (creds->krb5_fast_armor_credentials == NULL) {
|
|
return NT_STATUS_NO_MEMORY;
|
|
}
|
|
|
|
creds->krb5_require_fast_armor = require_fast_armor;
|
|
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
struct cli_credentials *cli_credentials_get_krb5_fast_armor_credentials(struct cli_credentials *creds)
|
|
{
|
|
return creds->krb5_fast_armor_credentials;
|
|
}
|
|
|
|
bool cli_credentials_get_krb5_require_fast_armor(struct cli_credentials *creds)
|
|
{
|
|
return creds->krb5_require_fast_armor;
|
|
}
|