#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
static void cli_credentials_invalidate_client_gss_creds(
struct cli_credentials *cred,
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)
return NT_STATUS_OK;
}
+/**
+ * @brief Initialize a Kerberos credential cache for later use.
+ *
+ * This functions initializes a Kerberos credential cache. If ccache_name is not
+ * specified it will either use the default credential cache or create a memory
+ * credential cache. This depends on the authentication credentials specified,
+ * username and password.
+ *
+ * @param[in] cred The credentials structure to init the cache on.
+ *
+ * @param[in] lp_ctx The loadparm context to use.
+ *
+ * @param[in] ccache_name The name of the credential cache to open or
+ * NULL.
+ *
+ * @return true on success, false if an error occcured.
+ */
+_PUBLIC_ bool cli_credentials_ccache_init(struct cli_credentials *cred,
+ struct loadparm_context *lp_ctx,
+ const char *ccache_name)
+{
+ TALLOC_CTX *tmp_ctx;
+ krb5_error_code code;
+ enum credentials_obtained principal_obtained = CRED_UNINITIALISED;
+ struct ccache_container *ccc = NULL;
+ const char *principal;
+ const char *password = NULL;
+ bool use_memory_ccache = false;
+ bool ok = false;
+
+ if (cred->krb5_ccache_obtained != CRED_UNINITIALISED) {
+ return false;
+ }
+
+ tmp_ctx = talloc_new(cred);
+ if (tmp_ctx == NULL) {
+ return false;
+ }
+
+ principal = cli_credentials_get_principal_and_obtained(cred,
+ tmp_ctx,
+ &principal_obtained);
+ if (principal == NULL) {
+ goto done;
+ }
+
+ ccc = talloc_zero(tmp_ctx, struct ccache_container);
+ if (ccc == NULL) {
+ goto done;
+ }
+
+ code = cli_credentials_get_krb5_context(cred,
+ lp_ctx,
+ &ccc->smb_krb5_context);
+ if (principal_obtained == CRED_SPECIFIED) {
+ password = cli_credentials_get_password(cred);
+ if (password != NULL) {
+ use_memory_ccache = true;
+ }
+ }
+
+ if (ccache_name == NULL && use_memory_ccache) {
+ ccache_name = talloc_asprintf(tmp_ctx, "MEMORY:cli_creds/%p", ccc);
+ if (ccache_name == NULL) {
+ goto done;
+ }
+ }
+
+ if (ccache_name != NULL) {
+ code = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context,
+ ccache_name,
+ &ccc->ccache);
+ } else {
+ code = krb5_cc_default(ccc->smb_krb5_context->krb5_context,
+ &ccc->ccache);
+ }
+ if (code != 0) {
+ goto done;
+ }
+
+ if (use_memory_ccache) {
+ talloc_set_destructor(ccc, free_mccache);
+ } else {
+ talloc_set_destructor(ccc, free_dccache);
+ }
+
+ cred->krb5_ccache = talloc_steal(cred, ccc);
+ cred->krb5_ccache_obtained = CRED_SPECIFIED;
+
+ ok = true;
+done:
+ talloc_free(tmp_ctx);
+ return ok;
+}
+
+/**
+ * @brief Destroy a Kerberos credential cache.
+ *
+ * This function destroys any existing contents of a cache and closes it.
+ *
+ * @param[in] cred The cli_credentials structure.
+ *
+ * @return true on success, false otherwise.
+ */
+_PUBLIC_ bool cli_credentials_ccache_destroy(struct cli_credentials *cred)
+{
+ struct ccache_container *ccc = cred->krb5_ccache;
+ krb5_error_code code;
+
+ code = krb5_cc_destroy(ccc->smb_krb5_context->krb5_context,
+ ccc->ccache);
+ if (code != 0) {
+ return false;
+ }
+ ccc->ccache = NULL;
+
+ TALLOC_FREE(cred->krb5_ccache);
+ cred->krb5_ccache_obtained = CRED_UNINITIALISED;
+
+ return true;
+}
+
+/**
+ * @brief Reinitialize the Kerberos credential cache
+ *
+ * If the credential cache is a memory credential cache it will be destroyed
+ * and a new clean cache will be allocated. Existing caches will just be
+ * reopened.
+ *
+ * @param[in] cred The credential structure
+ *
+ * @param[in] lp_ctx The loadparm context.
+ *
+ * @return true on success, false otherwise.
+ */
+_PUBLIC_ bool cli_credentials_ccache_reinit(struct cli_credentials *cred,
+ struct loadparm_context *lp_ctx)
+{
+ krb5_context context;
+ krb5_error_code code;
+ char *tmp_name = NULL;
+ const char *ccache_name;
+ bool ok;
+ int cmp;
+
+ if (cred->krb5_ccache_obtained == CRED_UNINITIALISED) {
+ return false;
+ }
+ context = cred->krb5_ccache->smb_krb5_context->krb5_context;
+
+ code = krb5_cc_get_full_name(context,
+ cred->krb5_ccache->ccache,
+ &tmp_name);
+ if (code != 0) {
+ return false;
+ }
+
+ ccache_name = tmp_name;
+ cmp = strncasecmp_m(ccache_name, "MEMORY:", 7);
+ if (cmp == 0) {
+ ccache_name = NULL;
+ }
+
+ TALLOC_FREE(cred->krb5_ccache);
+ cred->krb5_ccache_obtained = CRED_UNINITIALISED;
+
+ ok = cli_credentials_ccache_init(cred, lp_ctx, ccache_name);
+ krb5_free_string(context, tmp_name);
+
+ return ok;
+}
+
+/**
+ * @brief Get the credential cache containter
+ *
+ * @param[in] cred The cli_credentials to get the ccache from.
+ *
+ * @return A pointer to the credential cache containter or NULL on error.
+ */
+_PUBLIC_ struct ccache_container *cli_credentials_ccache_get(struct cli_credentials *cred)
+{
+ return cred->krb5_ccache;
+}
+
+_PUBLIC_ bool cli_credentials_ccache_update_principal(struct cli_credentials *creds)
+{
+ krb5_context context;
+ struct ccache_container *ccc = cli_credentials_ccache_get(creds);
+ krb5_principal cc_principal = NULL;
+ krb5_error_code code;
+ char *principal;
+ char *realm;
+ bool ok;
+
+ if (ccc == NULL) {
+ return false;
+ }
+ context = ccc->smb_krb5_context->krb5_context;
+
+ code = krb5_cc_get_principal(context,
+ ccc->ccache,
+ &cc_principal);
+ if (code != 0) {
+ switch (code) {
+ /* Empty cache */
+ case KRB5_CC_NOTFOUND:
+ case KRB5_FCC_NOFILE:
+ return true;
+ }
+ return false;
+ }
+
+ code = smb_krb5_unparse_name(creds,
+ context,
+ cc_principal,
+ &principal);
+ if (code != 0) {
+ return false;
+ }
+
+ ok = cli_credentials_set_principal(creds,
+ principal,
+ CRED_SPECIFIED);
+ TALLOC_FREE(principal);
+ if (!ok) {
+ krb5_free_principal(context, cc_principal);
+ return ok;
+ }
+
+ realm = smb_krb5_principal_get_realm(context, cc_principal);
+ krb5_free_principal(context, cc_principal);
+ if (realm == NULL) {
+ return false;
+ }
+ ok = cli_credentials_set_realm(creds,
+ realm,
+ CRED_SPECIFIED);
+ SAFE_FREE(realm);
+
+ return 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;
return ret;
}
- cli_credentials_set_principal(cred, name, obtained);
-
- free(name);
+ 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;
if (ret) {
(*error_string) = error_message(ret);
+ TALLOC_FREE(ccc);
return ret;
}
+ }
+
+ cred->ccache = ccc;
+ cred->ccache_obtained = obtained;
- cred->ccache = ccc;
- cred->ccache_obtained = obtained;
- talloc_steal(cred, ccc);
+ cli_credentials_invalidate_client_gss_creds(
+ cred, cred->ccache_obtained);
- cli_credentials_invalidate_client_gss_creds(cred, cred->ccache_obtained);
- return 0;
- }
return 0;
}
return ENOMEM;
}
- maj_stat = gss_krb5_import_cred(&min_stat, 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)) {
+ 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);
return ret;
}
- maj_stat = gss_krb5_import_cred(&min_stat, ccache->ccache, NULL, NULL,
- &gcc->creds);
+ maj_stat = smb_gss_krb5_import_cred(&min_stat, ccache->smb_krb5_context->krb5_context,
+ ccache->ccache, NULL, NULL,
+ &gcc->creds);
}
} else {
ret = EINVAL;
}
- (*error_string) = talloc_asprintf(cred, "gss_krb5_import_cred failed: %s", error_message(ret));
+ (*error_string) = talloc_asprintf(cred, "smb_gss_krb5_import_cred failed: %s", error_message(ret));
return ret;
}
{
int ret;
OM_uint32 maj_stat, min_stat;
- struct ccache_container *ccc;
- struct gssapi_creds_container *gcc;
+ struct ccache_container *ccc = NULL;
+ struct gssapi_creds_container *gcc = NULL;
if (cred->client_gss_creds_obtained > obtained) {
return 0;
}
return ret;
}
- maj_stat = gss_krb5_copy_ccache(&min_stat,
- gssapi_cred, ccc->ccache);
+ maj_stat = smb_gss_krb5_copy_ccache(&min_stat,
+ gssapi_cred,
+ ccc);
if (maj_stat) {
if (min_stat) {
ret = min_stat;
const struct ccache_container *old_ccc = NULL;
struct ccache_container *ccc = NULL;
char *ccache_name = NULL;
+ krb5_principal princ;
old_ccc = cred->ccache;
if (old_ccc == NULL) {
return 0;
}
+ 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.
+ */
+ cred->ccache = NULL;
+ return 0;
+ }
+ krb5_free_principal(old_ccc->smb_krb5_context->krb5_context, princ);
+
ccc = talloc(cred, struct ccache_container);
if (ccc == NULL) {
return ENOMEM;
return dst;
}
-static int smb_krb5_create_salt_principal(TALLOC_CTX *mem_ctx,
- const char *samAccountName,
- const char *realm,
- const char **salt_principal,
- const char **error_string)
-{
- char *machine_username;
- bool is_machine_account = false;
- char *upper_realm;
- TALLOC_CTX *tmp_ctx;
- int rc = -1;
-
- if (samAccountName == NULL) {
- *error_string = "Cannot determine salt principal, no "
- "saltPrincipal or samAccountName specified";
- return rc;
- }
-
- if (realm == NULL) {
- *error_string = "Cannot make principal without a realm";
- return rc;
- }
-
- tmp_ctx = talloc_new(mem_ctx);
- if (tmp_ctx == NULL) {
- *error_string = "Cannot allocate talloc context";
- return rc;
- }
-
- upper_realm = strupper_talloc(tmp_ctx, realm);
- if (upper_realm == NULL) {
- *error_string = "Cannot allocate to upper case realm";
- goto out;
- }
-
- machine_username = strlower_talloc(tmp_ctx, samAccountName);
- if (!machine_username) {
- *error_string = "Cannot duplicate samAccountName";
- goto out;
- }
-
- if (machine_username[strlen(machine_username) - 1] == '$') {
- machine_username[strlen(machine_username) - 1] = '\0';
- is_machine_account = true;
- }
-
- if (is_machine_account) {
- char *lower_realm;
-
- lower_realm = strlower_talloc(tmp_ctx, realm);
- if (lower_realm == NULL) {
- *error_string = "Cannot allocate to lower case realm";
- goto out;
- }
-
- *salt_principal = talloc_asprintf(mem_ctx,
- "host/%s.%s@%s",
- machine_username,
- lower_realm,
- upper_realm);
- } else {
- *salt_principal = talloc_asprintf(mem_ctx,
- "%s@%s",
- machine_username,
- upper_realm);
- }
- if (*salt_principal == NULL) {
- *error_string = "Cannot create salt principal";
- goto out;
- }
-
- rc = 0;
-out:
- talloc_free(tmp_ctx);
- return rc;
-}
-
/* 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.
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);
- const char *error_string;
- const char *salt_principal;
+ char *salt_principal = NULL;
+ uint32_t uac_flags = 0;
if (cred->keytab_obtained >= (MAX(cred->principal_obtained,
cred->username_obtained))) {
return ENOMEM;
}
- /*
- * FIXME: Currently there is no better way than to create the correct
- * salt principal by checking if the username ends with a '$'. It would
- * be better if it is part of the credentials.
- */
- ret = smb_krb5_create_salt_principal(mem_ctx,
- username,
- realm,
- &salt_principal,
- &error_string);
+ 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(realm,
+ username, /* sAMAccountName */
+ upn, /* userPrincipalName */
+ uac_flags,
+ mem_ctx,
+ &salt_principal);
if (ret) {
talloc_free(mem_ctx);
return ret;
}
if (ktc->password_based || obtained < CRED_SPECIFIED) {
- /* This creates a GSSAPI cred_id_t for match-by-key with only the keytab set */
- maj_stat = gss_krb5_import_cred(&min_stat, NULL, NULL, ktc->keytab,
- &gcc->creds);
- } else {
- /* This creates a GSSAPI cred_id_t with the principal and keytab set, matching by name */
- maj_stat = gss_krb5_import_cred(&min_stat, NULL, princ, ktc->keytab,
- &gcc->creds);
+ /*
+ * 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;