#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_proto.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"
static void cli_credentials_invalidate_client_gss_creds(
return 0;
}
- ret = smb_krb5_init_context(cred, NULL, lp_ctx,
+ ret = smb_krb5_init_context(cred, lp_ctx,
&cred->smb_krb5_context);
if (ret) {
cred->smb_krb5_context = NULL;
return 0;
}
+/*
+ * Indicate the 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;
+ }
+
+ 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);
+ 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.
+ */
+ return false;
+ }
+ return true;
+}
+
static int cli_credentials_new_ccache(struct cli_credentials *cred,
struct loadparm_context *lp_ctx,
cred->ccache_obtained > CRED_UNINITIALISED) {
time_t lifetime;
bool expired = false;
- ret = krb5_cc_get_lifetime(cred->ccache->smb_krb5_context->krb5_context,
- cred->ccache->ccache, &lifetime);
+ ret = smb_krb5_cc_get_lifetime(cred->ccache->smb_krb5_context->krb5_context,
+ cred->ccache->ccache, &lifetime);
if (ret == KRB5_CC_END) {
/* If we have a particular ccache set, without
* an initial ticket, then assume there is a
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 &&
return ret;
}
+
/*
* transfer the enctypes from the smb_krb5_context to the gssapi layer
*
* and used for the AS-REQ, so it wasn't possible to disable the usage
* of AES keys.
*/
- min_stat = krb5_get_default_in_tkt_etypes(ccache->smb_krb5_context->krb5_context,
- KRB5_PDU_NONE,
- &etypes);
+ min_stat = smb_krb5_get_allowed_etypes(ccache->smb_krb5_context->krb5_context,
+ &etypes);
if (min_stat == 0) {
OM_uint32 num_ktypes;
maj_stat = gss_krb5_set_allowable_enctypes(&min_stat, gcc->creds,
num_ktypes,
(int32_t *) etypes);
- krb5_xfree (etypes);
+ SAFE_FREE(etypes);
if (maj_stat) {
talloc_free(gcc);
if (min_stat) {
}
}
- /* don't force GSS_C_CONF_FLAG and GSS_C_INTEG_FLAG */
+#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,
- GSS_KRB5_CRED_NO_CI_FLAGS_X,
+ oid,
&empty_buffer);
if (maj_stat) {
talloc_free(gcc);
(*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;
return ret;
}
+static int cli_credentials_shallow_ccache(struct cli_credentials *cred)
+{
+ krb5_error_code ret;
+ const struct ccache_container *old_ccc = NULL;
+ struct ccache_container *ccc = NULL;
+ char *ccache_name = NULL;
+
+ old_ccc = cred->ccache;
+ if (old_ccc == NULL) {
+ return 0;
+ }
+
+ 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->client_gss_creds = NULL;
+ cred->client_gss_creds_obtained = CRED_UNINITIALISED;
+ return ret;
+}
+
+_PUBLIC_ struct cli_credentials *cli_credentials_shallow_copy(TALLOC_CTX *mem_ctx,
+ struct cli_credentials *src)
+{
+ struct cli_credentials *dst;
+ int ret;
+
+ dst = talloc(mem_ctx, struct cli_credentials);
+ if (dst == NULL) {
+ return NULL;
+ }
+
+ *dst = *src;
+
+ ret = cli_credentials_shallow_ccache(dst);
+ if (ret != 0) {
+ TALLOC_FREE(dst);
+ return NULL;
+ }
+
+ 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.
const char *keytab_name;
krb5_keytab keytab;
TALLOC_CTX *mem_ctx;
+ const char *username = cli_credentials_get_username(cred);
+ const char *realm = cli_credentials_get_realm(cred);
+ const char *error_string;
+ const char *salt_principal;
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);
+ 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),
- cli_credentials_get_username(cred),
- cli_credentials_get_realm(cred),
- cli_credentials_get_kvno(cred),
- &keytab, &keytab_name);
+ 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;
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;
return ENOMEM;
}
- if (obtained < CRED_SPECIFIED) {
- /* This creates a GSSAPI cred_id_t with the principal and keytab set */
+ 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 */
+ /* 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);
}