diff options
Diffstat (limited to 'source4/auth/gensec')
-rw-r--r-- | source4/auth/gensec/gensec_gssapi.c | 1729 | ||||
-rw-r--r-- | source4/auth/gensec/gensec_gssapi.h | 69 | ||||
-rw-r--r-- | source4/auth/gensec/gensec_krb5.c | 1133 | ||||
-rw-r--r-- | source4/auth/gensec/gensec_krb5.h | 10 | ||||
-rw-r--r-- | source4/auth/gensec/gensec_krb5_heimdal.c | 102 | ||||
-rw-r--r-- | source4/auth/gensec/gensec_krb5_helpers.c | 72 | ||||
-rw-r--r-- | source4/auth/gensec/gensec_krb5_helpers.h | 32 | ||||
-rw-r--r-- | source4/auth/gensec/gensec_krb5_internal.h | 47 | ||||
-rw-r--r-- | source4/auth/gensec/gensec_krb5_mit.c | 102 | ||||
-rw-r--r-- | source4/auth/gensec/gensec_tstream.c | 616 | ||||
-rw-r--r-- | source4/auth/gensec/gensec_tstream.h | 40 | ||||
-rw-r--r-- | source4/auth/gensec/pygensec.c | 779 | ||||
-rw-r--r-- | source4/auth/gensec/wscript_build | 41 |
13 files changed, 4772 insertions, 0 deletions
diff --git a/source4/auth/gensec/gensec_gssapi.c b/source4/auth/gensec/gensec_gssapi.c new file mode 100644 index 0000000..e33c784 --- /dev/null +++ b/source4/auth/gensec/gensec_gssapi.c @@ -0,0 +1,1729 @@ +/* + Unix SMB/CIFS implementation. + + Kerberos backend for GENSEC + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + Copyright (C) Stefan Metzmacher <metze@samba.org> 2004-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 <tevent.h> +#include "lib/util/tevent_ntstatus.h" +#include "lib/events/events.h" +#include "system/kerberos.h" +#include "system/gssapi.h" +#include "auth/kerberos/kerberos.h" +#include "librpc/gen_ndr/krb5pac.h" +#include "auth/auth.h" +#include <ldb.h> +#include "auth/auth_sam.h" +#include "librpc/gen_ndr/dcerpc.h" +#include "auth/credentials/credentials.h" +#include "auth/credentials/credentials_krb5.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" +#include "auth/gensec/gensec_proto.h" +#include "auth/gensec/gensec_toplevel_proto.h" +#include "param/param.h" +#include "auth/session_proto.h" +#include "gensec_gssapi.h" +#include "lib/util/util_net.h" +#include "auth/kerberos/pac_utils.h" +#include "auth/kerberos/gssapi_helper.h" +#include "lib/util/smb_strtox.h" + +#ifndef gss_mech_spnego +gss_OID_desc spnego_mech_oid_desc = + { 6, discard_const_p(void, "\x2b\x06\x01\x05\x05\x02") }; +#define gss_mech_spnego (&spnego_mech_oid_desc) +#endif + +_PUBLIC_ NTSTATUS gensec_gssapi_init(TALLOC_CTX *); + +static size_t gensec_gssapi_max_input_size(struct gensec_security *gensec_security); +static size_t gensec_gssapi_max_wrapped_size(struct gensec_security *gensec_security); +static size_t gensec_gssapi_sig_size(struct gensec_security *gensec_security, size_t data_size); + +static int gensec_gssapi_destructor(struct gensec_gssapi_state *gensec_gssapi_state) +{ + OM_uint32 min_stat; + + if (gensec_gssapi_state->delegated_cred_handle != GSS_C_NO_CREDENTIAL) { + gss_release_cred(&min_stat, + &gensec_gssapi_state->delegated_cred_handle); + } + + if (gensec_gssapi_state->gssapi_context != GSS_C_NO_CONTEXT) { + gss_delete_sec_context(&min_stat, + &gensec_gssapi_state->gssapi_context, + GSS_C_NO_BUFFER); + } + + if (gensec_gssapi_state->server_name != GSS_C_NO_NAME) { + gss_release_name(&min_stat, + &gensec_gssapi_state->server_name); + } + if (gensec_gssapi_state->client_name != GSS_C_NO_NAME) { + gss_release_name(&min_stat, + &gensec_gssapi_state->client_name); + } + + return 0; +} + +static NTSTATUS gensec_gssapi_setup_server_principal(TALLOC_CTX *mem_ctx, + const char *target_principal, + const char *service, + const char *hostname, + const char *realm, + const gss_OID mech, + char **pserver_principal, + gss_name_t *pserver_name) +{ + char *server_principal = NULL; + gss_buffer_desc name_token; + gss_OID name_type; + OM_uint32 maj_stat, min_stat = 0; + + if (target_principal != NULL) { + server_principal = talloc_strdup(mem_ctx, target_principal); + name_type = GSS_C_NULL_OID; + } else { + server_principal = talloc_asprintf(mem_ctx, + "%s/%s@%s", + service, hostname, realm); + name_type = GSS_C_NT_USER_NAME; + } + if (server_principal == NULL) { + return NT_STATUS_NO_MEMORY; + } + + name_token.value = (uint8_t *)server_principal; + name_token.length = strlen(server_principal); + + maj_stat = gss_import_name(&min_stat, + &name_token, + name_type, + pserver_name); + if (maj_stat) { + DBG_WARNING("GSS Import name of %s failed: %s\n", + server_principal, + gssapi_error_string(mem_ctx, + maj_stat, + min_stat, + mech)); + TALLOC_FREE(server_principal); + return NT_STATUS_INVALID_PARAMETER; + } + + *pserver_principal = server_principal; + + return NT_STATUS_OK; +} + +static NTSTATUS gensec_gssapi_start(struct gensec_security *gensec_security) +{ + struct gensec_gssapi_state *gensec_gssapi_state; + krb5_error_code ret; +#ifdef SAMBA4_USES_HEIMDAL + const char *realm; +#endif + + gensec_gssapi_state = talloc_zero(gensec_security, struct gensec_gssapi_state); + if (!gensec_gssapi_state) { + return NT_STATUS_NO_MEMORY; + } + + gensec_security->private_data = gensec_gssapi_state; + + gensec_gssapi_state->gssapi_context = GSS_C_NO_CONTEXT; + + /* TODO: Fill in channel bindings */ + gensec_gssapi_state->input_chan_bindings = GSS_C_NO_CHANNEL_BINDINGS; + + gensec_gssapi_state->server_name = GSS_C_NO_NAME; + gensec_gssapi_state->client_name = GSS_C_NO_NAME; + + gensec_gssapi_state->gss_want_flags = 0; + gensec_gssapi_state->expire_time = GENSEC_EXPIRE_TIME_INFINITY; + + if (gensec_setting_bool(gensec_security->settings, "gensec_gssapi", "delegation_by_kdc_policy", true)) { + gensec_gssapi_state->gss_want_flags |= GSS_C_DELEG_POLICY_FLAG; + } + if (gensec_setting_bool(gensec_security->settings, "gensec_gssapi", "mutual", true)) { + gensec_gssapi_state->gss_want_flags |= GSS_C_MUTUAL_FLAG; + } + if (gensec_setting_bool(gensec_security->settings, "gensec_gssapi", "delegation", false)) { + gensec_gssapi_state->gss_want_flags |= GSS_C_DELEG_FLAG; + } + if (gensec_setting_bool(gensec_security->settings, "gensec_gssapi", "replay", true)) { + gensec_gssapi_state->gss_want_flags |= GSS_C_REPLAY_FLAG; + } + if (gensec_setting_bool(gensec_security->settings, "gensec_gssapi", "sequence", true)) { + gensec_gssapi_state->gss_want_flags |= GSS_C_SEQUENCE_FLAG; + } + + if (gensec_security->want_features & GENSEC_FEATURE_SESSION_KEY) { + gensec_gssapi_state->gss_want_flags |= GSS_C_INTEG_FLAG; + } + if (gensec_security->want_features & GENSEC_FEATURE_SIGN) { + gensec_gssapi_state->gss_want_flags |= GSS_C_INTEG_FLAG; + } + if (gensec_security->want_features & GENSEC_FEATURE_SEAL) { + gensec_gssapi_state->gss_want_flags |= GSS_C_INTEG_FLAG; + gensec_gssapi_state->gss_want_flags |= GSS_C_CONF_FLAG; + } + if (gensec_security->want_features & GENSEC_FEATURE_DCE_STYLE) { + gensec_gssapi_state->gss_want_flags |= GSS_C_DCE_STYLE; + } + + gensec_gssapi_state->gss_got_flags = 0; + + switch (gensec_security->ops->auth_type) { + case DCERPC_AUTH_TYPE_SPNEGO: + gensec_gssapi_state->gss_oid = gss_mech_spnego; + break; + case DCERPC_AUTH_TYPE_KRB5: + default: + gensec_gssapi_state->gss_oid = + discard_const_p(void, gss_mech_krb5); + break; + } + + ret = smb_krb5_init_context(gensec_gssapi_state, + gensec_security->settings->lp_ctx, + &gensec_gssapi_state->smb_krb5_context); + if (ret) { + DEBUG(1,("gensec_gssapi_start: smb_krb5_init_context failed (%s)\n", + error_message(ret))); + talloc_free(gensec_gssapi_state); + return NT_STATUS_INTERNAL_ERROR; + } + + gensec_gssapi_state->client_cred = NULL; + gensec_gssapi_state->server_cred = NULL; + + gensec_gssapi_state->delegated_cred_handle = GSS_C_NO_CREDENTIAL; + + gensec_gssapi_state->sasl = false; + gensec_gssapi_state->sasl_state = STAGE_GSS_NEG; + gensec_gssapi_state->sasl_protection = 0; + + gensec_gssapi_state->max_wrap_buf_size + = gensec_setting_int(gensec_security->settings, "gensec_gssapi", "max wrap buf size", 65536); + gensec_gssapi_state->gss_exchange_count = 0; + gensec_gssapi_state->sig_size = 0; + + talloc_set_destructor(gensec_gssapi_state, gensec_gssapi_destructor); + +#ifdef SAMBA4_USES_HEIMDAL + realm = lpcfg_realm(gensec_security->settings->lp_ctx); + if (realm != NULL) { + ret = gsskrb5_set_default_realm(realm); + if (ret) { + DEBUG(1,("gensec_gssapi_start: gsskrb5_set_default_realm failed\n")); + talloc_free(gensec_gssapi_state); + return NT_STATUS_INTERNAL_ERROR; + } + } + + /* don't do DNS lookups of any kind, it might/will fail for a netbios name */ + ret = gsskrb5_set_dns_canonicalize(false); + if (ret) { + DEBUG(1,("gensec_gssapi_start: gsskrb5_set_dns_canonicalize failed\n")); + talloc_free(gensec_gssapi_state); + return NT_STATUS_INTERNAL_ERROR; + } +#endif + return NT_STATUS_OK; +} + +static NTSTATUS gensec_gssapi_server_start(struct gensec_security *gensec_security) +{ + NTSTATUS nt_status; + int ret; + struct gensec_gssapi_state *gensec_gssapi_state; + struct cli_credentials *machine_account; + struct gssapi_creds_container *gcc; + + nt_status = gensec_gssapi_start(gensec_security); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + gensec_gssapi_state = talloc_get_type(gensec_security->private_data, struct gensec_gssapi_state); + + machine_account = gensec_get_credentials(gensec_security); + + if (!machine_account) { + DEBUG(3, ("No machine account credentials specified\n")); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } else { + ret = cli_credentials_get_server_gss_creds(machine_account, + gensec_security->settings->lp_ctx, &gcc); + if (ret) { + DEBUG(1, ("Acquiring acceptor credentials failed: %s\n", + error_message(ret))); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + } + + gensec_gssapi_state->server_cred = gcc; + return NT_STATUS_OK; + +} + +static NTSTATUS gensec_gssapi_sasl_server_start(struct gensec_security *gensec_security) +{ + NTSTATUS nt_status; + struct gensec_gssapi_state *gensec_gssapi_state; + nt_status = gensec_gssapi_server_start(gensec_security); + + if (NT_STATUS_IS_OK(nt_status)) { + gensec_gssapi_state = talloc_get_type(gensec_security->private_data, struct gensec_gssapi_state); + gensec_gssapi_state->sasl = true; + } + return nt_status; +} + +static NTSTATUS gensec_gssapi_client_creds(struct gensec_security *gensec_security, + struct tevent_context *ev) +{ + struct gensec_gssapi_state *gensec_gssapi_state; + struct gssapi_creds_container *gcc; + struct cli_credentials *creds = gensec_get_credentials(gensec_security); + const char *error_string; + int ret; + + gensec_gssapi_state = talloc_get_type(gensec_security->private_data, struct gensec_gssapi_state); + + /* Only run this the first time the update() call is made */ + if (gensec_gssapi_state->client_cred) { + return NT_STATUS_OK; + } + + ret = cli_credentials_get_client_gss_creds(creds, + ev, + gensec_security->settings->lp_ctx, &gcc, &error_string); + switch (ret) { + case 0: + break; + case EINVAL: + DEBUG(3, ("Cannot obtain client GSS credentials we need to contact %s : %s\n", gensec_gssapi_state->target_principal, error_string)); + return NT_STATUS_INVALID_PARAMETER; + case KRB5KDC_ERR_PREAUTH_FAILED: + case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN: + case KRB5KRB_AP_ERR_BAD_INTEGRITY: + DEBUG(1, ("Wrong username or password: %s\n", error_string)); + return NT_STATUS_LOGON_FAILURE; + case KRB5KDC_ERR_CLIENT_REVOKED: + DEBUG(1, ("Account locked out: %s\n", error_string)); + return NT_STATUS_ACCOUNT_LOCKED_OUT; + case KRB5_REALM_UNKNOWN: + case KRB5_KDC_UNREACH: + DEBUG(3, ("Cannot reach a KDC we require to contact %s : %s\n", gensec_gssapi_state->target_principal, error_string)); + return NT_STATUS_NO_LOGON_SERVERS; + case KRB5_CC_NOTFOUND: + case KRB5_CC_END: + DEBUG(2, ("Error obtaining ticket we require to contact %s: (possibly due to clock skew between us and the KDC) %s\n", gensec_gssapi_state->target_principal, error_string)); + return NT_STATUS_TIME_DIFFERENCE_AT_DC; + default: + DEBUG(1, ("Aquiring initiator credentials failed: %s\n", error_string)); + return NT_STATUS_UNSUCCESSFUL; + } + + gensec_gssapi_state->client_cred = gcc; + if (!talloc_reference(gensec_gssapi_state, gcc)) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +static NTSTATUS gensec_gssapi_client_start(struct gensec_security *gensec_security) +{ + struct gensec_gssapi_state *gensec_gssapi_state; + struct cli_credentials *creds = gensec_get_credentials(gensec_security); + NTSTATUS nt_status; + const char *target_principal = NULL; + const char *hostname = gensec_get_target_hostname(gensec_security); + const char *service = gensec_get_target_service(gensec_security); + const char *realm = cli_credentials_get_realm(creds); + + target_principal = gensec_get_target_principal(gensec_security); + if (target_principal != NULL) { + goto do_start; + } + + if (!hostname) { + DEBUG(3, ("No hostname for target computer passed in, cannot use kerberos for this connection\n")); + return NT_STATUS_INVALID_PARAMETER; + } + if (is_ipaddress(hostname)) { + DEBUG(2, ("Cannot do GSSAPI to an IP address\n")); + return NT_STATUS_INVALID_PARAMETER; + } + if (strcmp(hostname, "localhost") == 0) { + DEBUG(2, ("GSSAPI to 'localhost' does not make sense\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (realm == NULL) { + char *cred_name = cli_credentials_get_unparsed_name(creds, + gensec_security); + DEBUG(3, ("cli_credentials(%s) without realm, " + "cannot use kerberos for this connection %s/%s\n", + cred_name, service, hostname)); + TALLOC_FREE(cred_name); + return NT_STATUS_INVALID_PARAMETER; + } + +do_start: + + nt_status = gensec_gssapi_start(gensec_security); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + gensec_gssapi_state = talloc_get_type(gensec_security->private_data, struct gensec_gssapi_state); + + if (cli_credentials_get_impersonate_principal(creds)) { + gensec_gssapi_state->gss_want_flags &= ~(GSS_C_DELEG_FLAG|GSS_C_DELEG_POLICY_FLAG); + } + + return NT_STATUS_OK; +} + +static NTSTATUS gensec_gssapi_sasl_client_start(struct gensec_security *gensec_security) +{ + NTSTATUS nt_status; + struct gensec_gssapi_state *gensec_gssapi_state; + nt_status = gensec_gssapi_client_start(gensec_security); + + if (NT_STATUS_IS_OK(nt_status)) { + gensec_gssapi_state = talloc_get_type(gensec_security->private_data, struct gensec_gssapi_state); + gensec_gssapi_state->sasl = true; + } + return nt_status; +} + +static NTSTATUS gensec_gssapi_update_internal(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + struct tevent_context *ev, + const DATA_BLOB in, DATA_BLOB *out) +{ + struct gensec_gssapi_state *gensec_gssapi_state + = talloc_get_type(gensec_security->private_data, struct gensec_gssapi_state); + NTSTATUS nt_status; + OM_uint32 maj_stat, min_stat; + OM_uint32 min_stat2; + gss_buffer_desc input_token = { 0, NULL }; + gss_buffer_desc output_token = { 0, NULL }; + struct cli_credentials *cli_creds = gensec_get_credentials(gensec_security); + const char *target_principal = gensec_get_target_principal(gensec_security); + const char *hostname = gensec_get_target_hostname(gensec_security); + const char *service = gensec_get_target_service(gensec_security); + gss_OID gss_oid_p = NULL; + OM_uint32 time_req = 0; + OM_uint32 time_rec = 0; + struct timeval tv; + + time_req = gensec_setting_int(gensec_security->settings, + "gensec_gssapi", "requested_life_time", + time_req); + + input_token.length = in.length; + input_token.value = in.data; + + switch (gensec_gssapi_state->sasl_state) { + case STAGE_GSS_NEG: + { + switch (gensec_security->gensec_role) { + case GENSEC_CLIENT: + { + const char *client_realm = NULL; +#ifdef SAMBA4_USES_HEIMDAL + struct gsskrb5_send_to_kdc send_to_kdc; + krb5_error_code ret; +#else + bool fallback = false; +#endif + + nt_status = gensec_gssapi_client_creds(gensec_security, ev); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + +#ifdef SAMBA4_USES_HEIMDAL + send_to_kdc.func = smb_krb5_send_and_recv_func; + send_to_kdc.ptr = ev; + + min_stat = gsskrb5_set_send_to_kdc(&send_to_kdc); + if (min_stat) { + DEBUG(1,("gensec_gssapi_update: gsskrb5_set_send_to_kdc failed\n")); + return NT_STATUS_INTERNAL_ERROR; + } +#endif + + /* + * With credentials for + * administrator@FOREST1.EXAMPLE.COM this patch changes + * the target_principal for the ldap service of host + * dc2.forest2.example.com from + * + * ldap/dc2.forest2.example.com@FOREST1.EXAMPLE.COM + * + * to + * + * ldap/dc2.forest2.example.com@FOREST2.EXAMPLE.COM + * + * Typically + * ldap/dc2.forest2.example.com@FOREST1.EXAMPLE.COM + * should be used in order to allow the KDC of + * FOREST1.EXAMPLE.COM to generate a referral ticket + * for krbtgt/FOREST2.EXAMPLE.COM@FOREST1.EXAMPLE.COM. + * + * The problem is that KDCs only return such referral + * tickets if there's a forest trust between + * FOREST1.EXAMPLE.COM and FOREST2.EXAMPLE.COM. If + * there's only an external domain trust between + * FOREST1.EXAMPLE.COM and FOREST2.EXAMPLE.COM the KDC + * of FOREST1.EXAMPLE.COM will respond with + * S_PRINCIPAL_UNKNOWN when being asked for + * ldap/dc2.forest2.example.com@FOREST1.EXAMPLE.COM. + * + * In the case of an external trust the client can + * still ask explicitly for + * krbtgt/FOREST2.EXAMPLE.COM@FOREST1.EXAMPLE.COM and + * the KDC of FOREST1.EXAMPLE.COM will generate it. + * + * From there the client can use the + * krbtgt/FOREST2.EXAMPLE.COM@FOREST1.EXAMPLE.COM + * ticket and ask a KDC of FOREST2.EXAMPLE.COM for a + * service ticket for + * ldap/dc2.forest2.example.com@FOREST2.EXAMPLE.COM. + * + * With Heimdal we'll get the fallback on + * S_PRINCIPAL_UNKNOWN behavior when we pass + * ldap/dc2.forest2.example.com@FOREST2.EXAMPLE.COM as + * target principal. As _krb5_get_cred_kdc_any() first + * calls get_cred_kdc_referral() (which always starts + * with the client realm) and falls back to + * get_cred_kdc_capath() (which starts with the given + * realm). + * + * MIT krb5 only tries the given realm of the target + * principal, if we want to autodetect support for + * transitive forest trusts, would have to do the + * fallback ourself. + */ + client_realm = cli_credentials_get_realm(cli_creds); +#ifndef SAMBA4_USES_HEIMDAL + if (gensec_gssapi_state->server_name == NULL) { + nt_status = gensec_gssapi_setup_server_principal(gensec_gssapi_state, + target_principal, + service, + hostname, + client_realm, + gensec_gssapi_state->gss_oid, + &gensec_gssapi_state->target_principal, + &gensec_gssapi_state->server_name); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + maj_stat = gss_init_sec_context(&min_stat, + gensec_gssapi_state->client_cred->creds, + &gensec_gssapi_state->gssapi_context, + gensec_gssapi_state->server_name, + gensec_gssapi_state->gss_oid, + gensec_gssapi_state->gss_want_flags, + time_req, + gensec_gssapi_state->input_chan_bindings, + &input_token, + &gss_oid_p, + &output_token, + &gensec_gssapi_state->gss_got_flags, /* ret flags */ + &time_rec); + if (maj_stat != GSS_S_FAILURE) { + goto init_sec_context_done; + } + if (min_stat != (OM_uint32)KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN) { + goto init_sec_context_done; + } + if (target_principal != NULL) { + goto init_sec_context_done; + } + + fallback = true; + TALLOC_FREE(gensec_gssapi_state->target_principal); + gss_release_name(&min_stat2, &gensec_gssapi_state->server_name); + } +#endif /* !SAMBA4_USES_HEIMDAL */ + if (gensec_gssapi_state->server_name == NULL) { + const char *server_realm = NULL; + + server_realm = smb_krb5_get_realm_from_hostname(gensec_gssapi_state, + hostname, + client_realm); + if (server_realm == NULL) { + return NT_STATUS_NO_MEMORY; + } + +#ifndef SAMBA4_USES_HEIMDAL + if (fallback && + strequal(client_realm, server_realm)) { + goto init_sec_context_done; + } +#endif /* !SAMBA4_USES_HEIMDAL */ + + nt_status = gensec_gssapi_setup_server_principal(gensec_gssapi_state, + target_principal, + service, + hostname, + server_realm, + gensec_gssapi_state->gss_oid, + &gensec_gssapi_state->target_principal, + &gensec_gssapi_state->server_name); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + } + + maj_stat = gss_init_sec_context(&min_stat, + gensec_gssapi_state->client_cred->creds, + &gensec_gssapi_state->gssapi_context, + gensec_gssapi_state->server_name, + gensec_gssapi_state->gss_oid, + gensec_gssapi_state->gss_want_flags, + time_req, + gensec_gssapi_state->input_chan_bindings, + &input_token, + &gss_oid_p, + &output_token, + &gensec_gssapi_state->gss_got_flags, /* ret flags */ + &time_rec); + goto init_sec_context_done; + /* JUMP! */ +init_sec_context_done: + if (gss_oid_p) { + gensec_gssapi_state->gss_oid = gss_oid_p; + } + +#ifdef SAMBA4_USES_HEIMDAL + send_to_kdc.func = smb_krb5_send_and_recv_func; + send_to_kdc.ptr = NULL; + + ret = gsskrb5_set_send_to_kdc(&send_to_kdc); + if (ret) { + DEBUG(1,("gensec_gssapi_update: gsskrb5_set_send_to_kdc failed\n")); + return NT_STATUS_INTERNAL_ERROR; + } +#endif + break; + } + case GENSEC_SERVER: + { + maj_stat = gss_accept_sec_context(&min_stat, + &gensec_gssapi_state->gssapi_context, + gensec_gssapi_state->server_cred->creds, + &input_token, + gensec_gssapi_state->input_chan_bindings, + &gensec_gssapi_state->client_name, + &gss_oid_p, + &output_token, + &gensec_gssapi_state->gss_got_flags, + &time_rec, + &gensec_gssapi_state->delegated_cred_handle); + if (gss_oid_p) { + gensec_gssapi_state->gss_oid = gss_oid_p; + } + break; + } + default: + return NT_STATUS_INVALID_PARAMETER; + + } + + gensec_gssapi_state->gss_exchange_count++; + + if (maj_stat == GSS_S_COMPLETE) { + *out = data_blob_talloc(out_mem_ctx, output_token.value, output_token.length); + gss_release_buffer(&min_stat2, &output_token); + + if (gensec_gssapi_state->gss_got_flags & GSS_C_DELEG_FLAG && + gensec_gssapi_state->delegated_cred_handle != GSS_C_NO_CREDENTIAL) { + DEBUG(5, ("gensec_gssapi: credentials were delegated\n")); + } else { + DEBUG(5, ("gensec_gssapi: NO credentials were delegated\n")); + } + + tv = timeval_current_ofs(time_rec, 0); + gensec_gssapi_state->expire_time = timeval_to_nttime(&tv); + + /* We may have been invoked as SASL, so there + * is more work to do */ + if (gensec_gssapi_state->sasl) { + gensec_gssapi_state->sasl_state = STAGE_SASL_SSF_NEG; + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } else { + gensec_gssapi_state->sasl_state = STAGE_DONE; + + if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) { + DEBUG(5, ("GSSAPI Connection will be cryptographically sealed\n")); + } else if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + DEBUG(5, ("GSSAPI Connection will be cryptographically signed\n")); + } else { + DEBUG(5, ("GSSAPI Connection will have no cryptographic protection\n")); + } + + return NT_STATUS_OK; + } + } else if (maj_stat == GSS_S_CONTINUE_NEEDED) { + *out = data_blob_talloc(out_mem_ctx, output_token.value, output_token.length); + gss_release_buffer(&min_stat2, &output_token); + + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } else if (maj_stat == GSS_S_CONTEXT_EXPIRED) { + gss_cred_id_t creds = NULL; + gss_name_t name; + gss_buffer_desc buffer; + OM_uint32 lifetime = 0; + gss_cred_usage_t usage; + const char *role = NULL; + + switch (gensec_security->gensec_role) { + case GENSEC_CLIENT: + creds = gensec_gssapi_state->client_cred->creds; + role = "client"; + break; + case GENSEC_SERVER: + creds = gensec_gssapi_state->server_cred->creds; + role = "server"; + break; + } + + DBG_ERR("GSS %s Update(krb5)(%d) failed, credentials " + "expired during GSSAPI handshake!\n", + role, + gensec_gssapi_state->gss_exchange_count); + + maj_stat = gss_inquire_cred(&min_stat, + creds, + &name, &lifetime, &usage, NULL); + + if (maj_stat == GSS_S_COMPLETE) { + const char *usage_string = NULL; + switch (usage) { + case GSS_C_BOTH: + usage_string = "GSS_C_BOTH"; + break; + case GSS_C_ACCEPT: + usage_string = "GSS_C_ACCEPT"; + break; + case GSS_C_INITIATE: + usage_string = "GSS_C_INITIATE"; + break; + } + maj_stat = gss_display_name(&min_stat, name, &buffer, NULL); + if (maj_stat) { + buffer.value = NULL; + buffer.length = 0; + } + if (lifetime > 0) { + DEBUG(0, ("GSSAPI gss_inquire_cred indicates expiry of %*.*s in %u sec for %s\n", + (int)buffer.length, (int)buffer.length, (char *)buffer.value, + lifetime, usage_string)); + } else { + DEBUG(0, ("GSSAPI gss_inquire_cred indicates %*.*s has already expired for %s\n", + (int)buffer.length, (int)buffer.length, (char *)buffer.value, + usage_string)); + } + gss_release_buffer(&min_stat, &buffer); + gss_release_name(&min_stat, &name); + } else if (maj_stat != GSS_S_COMPLETE) { + DEBUG(0, ("inquiry of credential lifefime via GSSAPI gss_inquire_cred failed: %s\n", + gssapi_error_string(out_mem_ctx, maj_stat, min_stat, gensec_gssapi_state->gss_oid))); + } + return NT_STATUS_INVALID_PARAMETER; + } else if (smb_gss_oid_equal(gensec_gssapi_state->gss_oid, + gss_mech_krb5)) { + switch (min_stat) { + case (OM_uint32)KRB5KRB_AP_ERR_TKT_NYV: + DEBUG(1, ("Error with ticket to contact %s: possible clock skew between us and the KDC or target server: %s\n", + gensec_gssapi_state->target_principal, + gssapi_error_string(out_mem_ctx, maj_stat, min_stat, gensec_gssapi_state->gss_oid))); + return NT_STATUS_TIME_DIFFERENCE_AT_DC; /* Make SPNEGO ignore us, we can't go any further here */ + case (OM_uint32)KRB5KRB_AP_ERR_TKT_EXPIRED: + DEBUG(1, ("Error with ticket to contact %s: ticket is expired, possible clock skew between us and the KDC or target server: %s\n", + gensec_gssapi_state->target_principal, + gssapi_error_string(out_mem_ctx, maj_stat, min_stat, gensec_gssapi_state->gss_oid))); + return NT_STATUS_INVALID_PARAMETER; /* Make SPNEGO ignore us, we can't go any further here */ + case (OM_uint32)KRB5_KDC_UNREACH: + DEBUG(3, ("Cannot reach a KDC we require in order to obtain a ticket to %s: %s\n", + gensec_gssapi_state->target_principal, + gssapi_error_string(out_mem_ctx, maj_stat, min_stat, gensec_gssapi_state->gss_oid))); + return NT_STATUS_NO_LOGON_SERVERS; /* Make SPNEGO ignore us, we can't go any further here */ + case (OM_uint32)KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN: + DEBUG(3, ("Server %s is not registered with our KDC: %s\n", + gensec_gssapi_state->target_principal, + gssapi_error_string(out_mem_ctx, maj_stat, min_stat, gensec_gssapi_state->gss_oid))); + return NT_STATUS_INVALID_PARAMETER; /* Make SPNEGO ignore us, we can't go any further here */ + case (OM_uint32)KRB5KRB_AP_ERR_MSG_TYPE: + /* garbage input, possibly from the auto-mech detection */ + return NT_STATUS_INVALID_PARAMETER; + default: + DEBUG(1, ("GSS %s Update(krb5)(%d) Update failed: %s\n", + gensec_security->gensec_role == GENSEC_CLIENT ? "client" : "server", + gensec_gssapi_state->gss_exchange_count, + gssapi_error_string(out_mem_ctx, maj_stat, min_stat, gensec_gssapi_state->gss_oid))); + return NT_STATUS_LOGON_FAILURE; + } + } else { + DEBUG(1, ("GSS %s Update(%d) failed: %s\n", + gensec_security->gensec_role == GENSEC_CLIENT ? "client" : "server", + gensec_gssapi_state->gss_exchange_count, + gssapi_error_string(out_mem_ctx, maj_stat, min_stat, gensec_gssapi_state->gss_oid))); + return NT_STATUS_LOGON_FAILURE; + } + break; + } + + /* These last two stages are only done if we were invoked as SASL */ + case STAGE_SASL_SSF_NEG: + { + switch (gensec_security->gensec_role) { + case GENSEC_CLIENT: + { + uint8_t maxlength_proposed[4]; + uint8_t maxlength_accepted[4]; + uint8_t security_supported; + int conf_state; + gss_qop_t qop_state; + input_token.length = in.length; + input_token.value = in.data; + + /* As a client, we have just send a + * zero-length blob to the server (after the + * normal GSSAPI exchange), and it has replied + * with it's SASL negotiation */ + + maj_stat = gss_unwrap(&min_stat, + gensec_gssapi_state->gssapi_context, + &input_token, + &output_token, + &conf_state, + &qop_state); + if (GSS_ERROR(maj_stat)) { + DEBUG(1, ("gensec_gssapi_update: GSS UnWrap of SASL protection negotiation failed: %s\n", + gssapi_error_string(out_mem_ctx, maj_stat, min_stat, gensec_gssapi_state->gss_oid))); + return NT_STATUS_ACCESS_DENIED; + } + + if (output_token.length < 4) { + return NT_STATUS_INVALID_PARAMETER; + } + + memcpy(maxlength_proposed, output_token.value, 4); + gss_release_buffer(&min_stat, &output_token); + + /* first byte is the proposed security */ + security_supported = maxlength_proposed[0]; + maxlength_proposed[0] = '\0'; + + /* Rest is the proposed max wrap length */ + gensec_gssapi_state->max_wrap_buf_size = MIN(RIVAL(maxlength_proposed, 0), + gensec_gssapi_state->max_wrap_buf_size); + gensec_gssapi_state->sasl_protection = 0; + if (security_supported & NEG_SEAL) { + if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) { + gensec_gssapi_state->sasl_protection |= NEG_SEAL; + } + } + if (security_supported & NEG_SIGN) { + if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + gensec_gssapi_state->sasl_protection |= NEG_SIGN; + } + } + if (security_supported & NEG_NONE) { + gensec_gssapi_state->sasl_protection |= NEG_NONE; + } + if (gensec_gssapi_state->sasl_protection == 0) { + DEBUG(1, ("Remote server does not support unprotected connections\n")); + return NT_STATUS_ACCESS_DENIED; + } + + /* Send back the negotiated max length */ + + RSIVAL(maxlength_accepted, 0, gensec_gssapi_state->max_wrap_buf_size); + + maxlength_accepted[0] = gensec_gssapi_state->sasl_protection; + + input_token.value = maxlength_accepted; + input_token.length = sizeof(maxlength_accepted); + + maj_stat = gss_wrap(&min_stat, + gensec_gssapi_state->gssapi_context, + false, + GSS_C_QOP_DEFAULT, + &input_token, + &conf_state, + &output_token); + if (GSS_ERROR(maj_stat)) { + DEBUG(1, ("GSS Update(SSF_NEG): GSS Wrap failed: %s\n", + gssapi_error_string(out_mem_ctx, maj_stat, min_stat, gensec_gssapi_state->gss_oid))); + return NT_STATUS_ACCESS_DENIED; + } + + *out = data_blob_talloc(out_mem_ctx, output_token.value, output_token.length); + gss_release_buffer(&min_stat, &output_token); + + /* quirk: This changes the value that gensec_have_feature returns, to be that after SASL negotiation */ + gensec_gssapi_state->sasl_state = STAGE_DONE; + + if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) { + DEBUG(3, ("SASL/GSSAPI Connection to server will be cryptographically sealed\n")); + } else if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + DEBUG(3, ("SASL/GSSAPI Connection to server will be cryptographically signed\n")); + } else { + DEBUG(3, ("SASL/GSSAPI Connection to server will have no cryptographically protection\n")); + } + + return NT_STATUS_OK; + } + case GENSEC_SERVER: + { + uint8_t maxlength_proposed[4]; + uint8_t security_supported = 0x0; + int conf_state; + + /* As a server, we have just been sent a zero-length blob (note this, but it isn't fatal) */ + if (in.length != 0) { + DEBUG(1, ("SASL/GSSAPI: client sent non-zero length starting SASL negotiation!\n")); + } + + /* Give the client some idea what we will support */ + + RSIVAL(maxlength_proposed, 0, gensec_gssapi_state->max_wrap_buf_size); + /* first byte is the proposed security */ + maxlength_proposed[0] = '\0'; + + gensec_gssapi_state->sasl_protection = 0; + if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) { + security_supported |= NEG_SEAL; + } + if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + security_supported |= NEG_SIGN; + } + if (security_supported == 0) { + /* If we don't support anything, this must be 0 */ + RSIVAL(maxlength_proposed, 0, 0x0); + } + + /* TODO: We may not wish to support this */ + security_supported |= NEG_NONE; + maxlength_proposed[0] = security_supported; + + input_token.value = maxlength_proposed; + input_token.length = sizeof(maxlength_proposed); + + maj_stat = gss_wrap(&min_stat, + gensec_gssapi_state->gssapi_context, + false, + GSS_C_QOP_DEFAULT, + &input_token, + &conf_state, + &output_token); + if (GSS_ERROR(maj_stat)) { + DEBUG(1, ("GSS Update(SSF_NEG): GSS Wrap failed: %s\n", + gssapi_error_string(out_mem_ctx, maj_stat, min_stat, gensec_gssapi_state->gss_oid))); + return NT_STATUS_ACCESS_DENIED; + } + + *out = data_blob_talloc(out_mem_ctx, output_token.value, output_token.length); + gss_release_buffer(&min_stat, &output_token); + + gensec_gssapi_state->sasl_state = STAGE_SASL_SSF_ACCEPT; + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } + default: + return NT_STATUS_INVALID_PARAMETER; + + } + } + /* This is s server-only stage */ + case STAGE_SASL_SSF_ACCEPT: + { + uint8_t maxlength_accepted[4]; + uint8_t security_accepted; + int conf_state; + gss_qop_t qop_state; + input_token.length = in.length; + input_token.value = in.data; + + maj_stat = gss_unwrap(&min_stat, + gensec_gssapi_state->gssapi_context, + &input_token, + &output_token, + &conf_state, + &qop_state); + if (GSS_ERROR(maj_stat)) { + DEBUG(1, ("gensec_gssapi_update: GSS UnWrap of SASL protection negotiation failed: %s\n", + gssapi_error_string(out_mem_ctx, maj_stat, min_stat, gensec_gssapi_state->gss_oid))); + return NT_STATUS_ACCESS_DENIED; + } + + if (output_token.length < 4) { + return NT_STATUS_INVALID_PARAMETER; + } + + memcpy(maxlength_accepted, output_token.value, 4); + gss_release_buffer(&min_stat, &output_token); + + /* first byte is the proposed security */ + security_accepted = maxlength_accepted[0]; + maxlength_accepted[0] = '\0'; + + /* Rest is the proposed max wrap length */ + gensec_gssapi_state->max_wrap_buf_size = MIN(RIVAL(maxlength_accepted, 0), + gensec_gssapi_state->max_wrap_buf_size); + + gensec_gssapi_state->sasl_protection = 0; + if (security_accepted & NEG_SEAL) { + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) { + DEBUG(1, ("Remote client wanted seal, but gensec refused\n")); + return NT_STATUS_ACCESS_DENIED; + } + gensec_gssapi_state->sasl_protection |= NEG_SEAL; + } + if (security_accepted & NEG_SIGN) { + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + DEBUG(1, ("Remote client wanted sign, but gensec refused\n")); + return NT_STATUS_ACCESS_DENIED; + } + gensec_gssapi_state->sasl_protection |= NEG_SIGN; + } + if (security_accepted & NEG_NONE) { + gensec_gssapi_state->sasl_protection |= NEG_NONE; + } + + /* quirk: This changes the value that gensec_have_feature returns, to be that after SASL negotiation */ + gensec_gssapi_state->sasl_state = STAGE_DONE; + if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) { + DEBUG(5, ("SASL/GSSAPI Connection from client will be cryptographically sealed\n")); + } else if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + DEBUG(5, ("SASL/GSSAPI Connection from client will be cryptographically signed\n")); + } else { + DEBUG(5, ("SASL/GSSAPI Connection from client will have no cryptographic protection\n")); + } + + *out = data_blob(NULL, 0); + return NT_STATUS_OK; + } + default: + return NT_STATUS_INVALID_PARAMETER; + } +} + +struct gensec_gssapi_update_state { + NTSTATUS status; + DATA_BLOB out; +}; + +static struct tevent_req *gensec_gssapi_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_security, + const DATA_BLOB in) +{ + struct tevent_req *req = NULL; + struct gensec_gssapi_update_state *state = NULL; + NTSTATUS status; + + req = tevent_req_create(mem_ctx, &state, + struct gensec_gssapi_update_state); + if (req == NULL) { + return NULL; + } + + status = gensec_gssapi_update_internal(gensec_security, + state, ev, in, + &state->out); + state->status = status; + if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static NTSTATUS gensec_gssapi_update_recv(struct tevent_req *req, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + struct gensec_gssapi_update_state *state = + tevent_req_data(req, + struct gensec_gssapi_update_state); + NTSTATUS status; + + *out = data_blob_null; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *out = state->out; + talloc_steal(out_mem_ctx, state->out.data); + status = state->status; + tevent_req_received(req); + return status; +} + +static NTSTATUS gensec_gssapi_wrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + struct gensec_gssapi_state *gensec_gssapi_state + = talloc_get_type(gensec_security->private_data, struct gensec_gssapi_state); + OM_uint32 maj_stat, min_stat; + gss_buffer_desc input_token, output_token; + int conf_state; + input_token.length = in->length; + input_token.value = in->data; + + maj_stat = gss_wrap(&min_stat, + gensec_gssapi_state->gssapi_context, + gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL), + GSS_C_QOP_DEFAULT, + &input_token, + &conf_state, + &output_token); + if (GSS_ERROR(maj_stat)) { + DEBUG(1, ("gensec_gssapi_wrap: GSS Wrap failed: %s\n", + gssapi_error_string(mem_ctx, maj_stat, min_stat, gensec_gssapi_state->gss_oid))); + return NT_STATUS_ACCESS_DENIED; + } + + *out = data_blob_talloc(mem_ctx, output_token.value, output_token.length); + gss_release_buffer(&min_stat, &output_token); + + if (gensec_gssapi_state->sasl) { + size_t max_wrapped_size = gensec_gssapi_max_wrapped_size(gensec_security); + if (max_wrapped_size < out->length) { + DEBUG(1, ("gensec_gssapi_wrap: when wrapped, INPUT data (%u) is grew to be larger than SASL negotiated maximum output size (%u > %u)\n", + (unsigned)in->length, + (unsigned)out->length, + (unsigned int)max_wrapped_size)); + return NT_STATUS_INVALID_PARAMETER; + } + } + + if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL) + && !conf_state) { + return NT_STATUS_ACCESS_DENIED; + } + return NT_STATUS_OK; +} + +static NTSTATUS gensec_gssapi_unwrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + struct gensec_gssapi_state *gensec_gssapi_state + = talloc_get_type(gensec_security->private_data, struct gensec_gssapi_state); + OM_uint32 maj_stat, min_stat; + gss_buffer_desc input_token, output_token; + int conf_state; + gss_qop_t qop_state; + input_token.length = in->length; + input_token.value = in->data; + + if (gensec_gssapi_state->sasl) { + size_t max_wrapped_size = gensec_gssapi_max_wrapped_size(gensec_security); + if (max_wrapped_size < in->length) { + DEBUG(1, ("gensec_gssapi_unwrap: WRAPPED data is larger than SASL negotiated maximum size\n")); + return NT_STATUS_INVALID_PARAMETER; + } + } + + /* + * FIXME: input_message_buffer is marked const, but gss_unwrap() may + * modify it (see calls to rrc_rotate() in _gssapi_unwrap_cfx()). + */ + maj_stat = gss_unwrap(&min_stat, + gensec_gssapi_state->gssapi_context, + &input_token, + &output_token, + &conf_state, + &qop_state); + if (GSS_ERROR(maj_stat)) { + DEBUG(1, ("gensec_gssapi_unwrap: GSS UnWrap failed: %s\n", + gssapi_error_string(mem_ctx, maj_stat, min_stat, gensec_gssapi_state->gss_oid))); + return NT_STATUS_ACCESS_DENIED; + } + + *out = data_blob_talloc(mem_ctx, output_token.value, output_token.length); + gss_release_buffer(&min_stat, &output_token); + + if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL) + && !conf_state) { + return NT_STATUS_ACCESS_DENIED; + } + return NT_STATUS_OK; +} + +/* Find out the maximum input size negotiated on this connection */ + +static size_t gensec_gssapi_max_input_size(struct gensec_security *gensec_security) +{ + struct gensec_gssapi_state *gensec_gssapi_state + = talloc_get_type(gensec_security->private_data, struct gensec_gssapi_state); + OM_uint32 maj_stat, min_stat; + OM_uint32 max_input_size; + + maj_stat = gss_wrap_size_limit(&min_stat, + gensec_gssapi_state->gssapi_context, + gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL), + GSS_C_QOP_DEFAULT, + gensec_gssapi_state->max_wrap_buf_size, + &max_input_size); + if (GSS_ERROR(maj_stat)) { + TALLOC_CTX *mem_ctx = talloc_new(NULL); + DEBUG(1, ("gensec_gssapi_max_input_size: determining signature size with gss_wrap_size_limit failed: %s\n", + gssapi_error_string(mem_ctx, maj_stat, min_stat, gensec_gssapi_state->gss_oid))); + talloc_free(mem_ctx); + return 0; + } + + return max_input_size; +} + +/* Find out the maximum output size negotiated on this connection */ +static size_t gensec_gssapi_max_wrapped_size(struct gensec_security *gensec_security) +{ + struct gensec_gssapi_state *gensec_gssapi_state = talloc_get_type(gensec_security->private_data, struct gensec_gssapi_state);; + return gensec_gssapi_state->max_wrap_buf_size; +} + +static NTSTATUS gensec_gssapi_seal_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + struct gensec_gssapi_state *gensec_gssapi_state + = talloc_get_type(gensec_security->private_data, struct gensec_gssapi_state); + bool hdr_signing = false; + size_t sig_size = 0; + NTSTATUS status; + + if (gensec_security->want_features & GENSEC_FEATURE_SIGN_PKT_HEADER) { + hdr_signing = true; + } + + sig_size = gensec_gssapi_sig_size(gensec_security, length); + + status = gssapi_seal_packet(gensec_gssapi_state->gssapi_context, + gensec_gssapi_state->gss_oid, + hdr_signing, sig_size, + data, length, + whole_pdu, pdu_length, + mem_ctx, sig); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("gssapi_seal_packet(hdr_signing=%u,sig_size=%zu," + "data=%zu,pdu=%zu) failed: %s\n", + hdr_signing, sig_size, length, pdu_length, + nt_errstr(status))); + return status; + } + + return NT_STATUS_OK; +} + +static NTSTATUS gensec_gssapi_unseal_packet(struct gensec_security *gensec_security, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) +{ + struct gensec_gssapi_state *gensec_gssapi_state + = talloc_get_type(gensec_security->private_data, struct gensec_gssapi_state); + bool hdr_signing = false; + NTSTATUS status; + + if (gensec_security->want_features & GENSEC_FEATURE_SIGN_PKT_HEADER) { + hdr_signing = true; + } + + status = gssapi_unseal_packet(gensec_gssapi_state->gssapi_context, + gensec_gssapi_state->gss_oid, + hdr_signing, + data, length, + whole_pdu, pdu_length, + sig); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("gssapi_unseal_packet(hdr_signing=%u,sig_size=%zu," + "data=%zu,pdu=%zu) failed: %s\n", + hdr_signing, sig->length, length, pdu_length, + nt_errstr(status))); + return status; + } + + return NT_STATUS_OK; +} + +static NTSTATUS gensec_gssapi_sign_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + struct gensec_gssapi_state *gensec_gssapi_state + = talloc_get_type(gensec_security->private_data, struct gensec_gssapi_state); + bool hdr_signing = false; + NTSTATUS status; + + if (gensec_security->want_features & GENSEC_FEATURE_SIGN_PKT_HEADER) { + hdr_signing = true; + } + + status = gssapi_sign_packet(gensec_gssapi_state->gssapi_context, + gensec_gssapi_state->gss_oid, + hdr_signing, + data, length, + whole_pdu, pdu_length, + mem_ctx, sig); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("gssapi_sign_packet(hdr_signing=%u," + "data=%zu,pdu=%zu) failed: %s\n", + hdr_signing, length, pdu_length, + nt_errstr(status))); + return status; + } + + return NT_STATUS_OK; +} + +static NTSTATUS gensec_gssapi_check_packet(struct gensec_security *gensec_security, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) +{ + struct gensec_gssapi_state *gensec_gssapi_state + = talloc_get_type(gensec_security->private_data, struct gensec_gssapi_state); + bool hdr_signing = false; + NTSTATUS status; + + if (gensec_security->want_features & GENSEC_FEATURE_SIGN_PKT_HEADER) { + hdr_signing = true; + } + + status = gssapi_check_packet(gensec_gssapi_state->gssapi_context, + gensec_gssapi_state->gss_oid, + hdr_signing, + data, length, + whole_pdu, pdu_length, + sig); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("gssapi_check_packet(hdr_signing=%u,sig_size=%zu," + "data=%zu,pdu=%zu) failed: %s\n", + hdr_signing, sig->length, length, pdu_length, + nt_errstr(status))); + return status; + } + + return NT_STATUS_OK; +} + +/* Try to figure out what features we actually got on the connection */ +static bool gensec_gssapi_have_feature(struct gensec_security *gensec_security, + uint32_t feature) +{ + struct gensec_gssapi_state *gensec_gssapi_state + = talloc_get_type(gensec_security->private_data, struct gensec_gssapi_state); + if (feature & GENSEC_FEATURE_SIGN) { + /* If we are going GSSAPI SASL, then we honour the second negotiation */ + if (gensec_gssapi_state->sasl + && gensec_gssapi_state->sasl_state == STAGE_DONE) { + return ((gensec_gssapi_state->sasl_protection & NEG_SIGN) + && (gensec_gssapi_state->gss_got_flags & GSS_C_INTEG_FLAG)); + } + return gensec_gssapi_state->gss_got_flags & GSS_C_INTEG_FLAG; + } + if (feature & GENSEC_FEATURE_SEAL) { + /* If we are going GSSAPI SASL, then we honour the second negotiation */ + if (gensec_gssapi_state->sasl + && gensec_gssapi_state->sasl_state == STAGE_DONE) { + return ((gensec_gssapi_state->sasl_protection & NEG_SEAL) + && (gensec_gssapi_state->gss_got_flags & GSS_C_CONF_FLAG)); + } + return gensec_gssapi_state->gss_got_flags & GSS_C_CONF_FLAG; + } + if (feature & GENSEC_FEATURE_SESSION_KEY) { + /* Only for GSSAPI/Krb5 */ + if (smb_gss_oid_equal(gensec_gssapi_state->gss_oid, + gss_mech_krb5)) { + return true; + } + } + if (feature & GENSEC_FEATURE_DCE_STYLE) { + return gensec_gssapi_state->gss_got_flags & GSS_C_DCE_STYLE; + } + if (feature & GENSEC_FEATURE_NEW_SPNEGO) { + NTSTATUS status; + uint32_t keytype; + + if (!(gensec_gssapi_state->gss_got_flags & GSS_C_INTEG_FLAG)) { + return false; + } + + if (gensec_setting_bool(gensec_security->settings, "gensec_gssapi", "force_new_spnego", false)) { + return true; + } + if (gensec_setting_bool(gensec_security->settings, "gensec_gssapi", "disable_new_spnego", false)) { + return false; + } + + status = gssapi_get_session_key(gensec_gssapi_state, + gensec_gssapi_state->gssapi_context, NULL, &keytype); + /* + * We should do a proper sig on the mechListMic unless + * we know we have to be backwards compatible with + * earlier windows versions. + * + * Negotiating a non-krb5 + * mech for example should be regarded as having + * NEW_SPNEGO + */ + if (NT_STATUS_IS_OK(status)) { + switch (keytype) { + case ENCTYPE_DES_CBC_CRC: + case ENCTYPE_DES_CBC_MD5: + case ENCTYPE_ARCFOUR_HMAC: + case ENCTYPE_DES3_CBC_SHA1: + return false; + } + } + return true; + } + /* We can always do async (rather than strict request/reply) packets. */ + if (feature & GENSEC_FEATURE_ASYNC_REPLIES) { + return true; + } + if (feature & GENSEC_FEATURE_SIGN_PKT_HEADER) { + return true; + } + return false; +} + +static NTTIME gensec_gssapi_expire_time(struct gensec_security *gensec_security) +{ + struct gensec_gssapi_state *gensec_gssapi_state = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_gssapi_state); + + return gensec_gssapi_state->expire_time; +} + +/* + * Extract the 'sesssion key' needed by SMB signing and ncacn_np + * (for encrypting some passwords). + * + * This breaks all the abstractions, but what do you expect... + */ +static NTSTATUS gensec_gssapi_session_key(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + DATA_BLOB *session_key) +{ + struct gensec_gssapi_state *gensec_gssapi_state + = talloc_get_type(gensec_security->private_data, struct gensec_gssapi_state); + return gssapi_get_session_key(mem_ctx, gensec_gssapi_state->gssapi_context, session_key, NULL); +} + +/* Get some basic (and authorization) information about the user on + * this session. This uses either the PAC (if present) or a local + * database lookup */ +static NTSTATUS gensec_gssapi_session_info(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + struct auth_session_info **_session_info) +{ + NTSTATUS nt_status; + TALLOC_CTX *tmp_ctx; + struct gensec_gssapi_state *gensec_gssapi_state + = talloc_get_type(gensec_security->private_data, struct gensec_gssapi_state); + struct auth_session_info *session_info = NULL; + OM_uint32 maj_stat, min_stat; + DATA_BLOB pac_blob, *pac_blob_ptr = NULL; + + gss_buffer_desc name_token; + char *principal_string; + + tmp_ctx = talloc_named(mem_ctx, 0, "gensec_gssapi_session_info context"); + NT_STATUS_HAVE_NO_MEMORY(tmp_ctx); + + maj_stat = gss_display_name (&min_stat, + gensec_gssapi_state->client_name, + &name_token, + NULL); + if (GSS_ERROR(maj_stat)) { + DEBUG(1, ("GSS display_name failed: %s\n", + gssapi_error_string(tmp_ctx, maj_stat, min_stat, gensec_gssapi_state->gss_oid))); + talloc_free(tmp_ctx); + return NT_STATUS_FOOBAR; + } + + principal_string = talloc_strndup(tmp_ctx, + (const char *)name_token.value, + name_token.length); + + gss_release_buffer(&min_stat, &name_token); + + if (!principal_string) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + nt_status = gssapi_obtain_pac_blob(tmp_ctx, gensec_gssapi_state->gssapi_context, + gensec_gssapi_state->client_name, + &pac_blob); + + /* IF we have the PAC - otherwise we need to get this + * data from elsewere - local ldb, or (TODO) lookup of some + * kind... + */ + if (NT_STATUS_IS_OK(nt_status)) { + pac_blob_ptr = &pac_blob; + } + nt_status = gensec_generate_session_info_pac(tmp_ctx, + gensec_security, + gensec_gssapi_state->smb_krb5_context, + pac_blob_ptr, principal_string, + gensec_get_remote_address(gensec_security), + &session_info); + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(tmp_ctx); + return nt_status; + } + + nt_status = gensec_gssapi_session_key(gensec_security, session_info, &session_info->session_key); + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(tmp_ctx); + return nt_status; + } + + if (gensec_gssapi_state->gss_got_flags & GSS_C_DELEG_FLAG && + gensec_gssapi_state->delegated_cred_handle != GSS_C_NO_CREDENTIAL) { + krb5_error_code ret; + const char *error_string; + + DEBUG(10, ("gensec_gssapi: delegated credentials supplied by client\n")); + + /* + * Create anonymous credentials for now. + * + * We will update them with the provided client gss creds. + */ + session_info->credentials = cli_credentials_init_anon(session_info); + if (session_info->credentials == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + ret = cli_credentials_set_client_gss_creds(session_info->credentials, + gensec_security->settings->lp_ctx, + gensec_gssapi_state->delegated_cred_handle, + CRED_SPECIFIED, &error_string); + if (ret) { + talloc_free(tmp_ctx); + DEBUG(2,("Failed to get gss creds: %s\n", error_string)); + return NT_STATUS_NO_MEMORY; + } + + /* This credential handle isn't useful for password authentication, so ensure nobody tries to do that */ + cli_credentials_set_kerberos_state(session_info->credentials, + CRED_USE_KERBEROS_REQUIRED, + CRED_SPECIFIED); + + /* It has been taken from this place... */ + gensec_gssapi_state->delegated_cred_handle = GSS_C_NO_CREDENTIAL; + } else { + DEBUG(10, ("gensec_gssapi: NO delegated credentials supplied by client\n")); + } + + *_session_info = talloc_steal(mem_ctx, session_info); + talloc_free(tmp_ctx); + + return NT_STATUS_OK; +} + +static size_t gensec_gssapi_sig_size(struct gensec_security *gensec_security, size_t data_size) +{ + struct gensec_gssapi_state *gensec_gssapi_state + = talloc_get_type(gensec_security->private_data, struct gensec_gssapi_state); + size_t sig_size; + + if (gensec_gssapi_state->sig_size > 0) { + return gensec_gssapi_state->sig_size; + } + + sig_size = gssapi_get_sig_size(gensec_gssapi_state->gssapi_context, + gensec_gssapi_state->gss_oid, + gensec_gssapi_state->gss_got_flags, + data_size); + + gensec_gssapi_state->sig_size = sig_size; + return gensec_gssapi_state->sig_size; +} + +static const char *gensec_gssapi_final_auth_type(struct gensec_security *gensec_security) +{ + struct gensec_gssapi_state *gensec_gssapi_state + = talloc_get_type(gensec_security->private_data, struct gensec_gssapi_state); + /* Only return the string for GSSAPI/Krb5 */ + if (smb_gss_oid_equal(gensec_gssapi_state->gss_oid, + gss_mech_krb5)) { + return GENSEC_FINAL_AUTH_TYPE_KRB5; + } else { + return "gensec_gssapi: UNKNOWN MECH"; + } +} + +static const char *gensec_gssapi_krb5_oids[] = { + GENSEC_OID_KERBEROS5_OLD, + GENSEC_OID_KERBEROS5, + NULL +}; + +static const char *gensec_gssapi_spnego_oids[] = { + GENSEC_OID_SPNEGO, + NULL +}; + +/* As a server, this could in theory accept any GSSAPI mech */ +static const struct gensec_security_ops gensec_gssapi_spnego_security_ops = { + .name = "gssapi_spnego", + .sasl_name = "GSS-SPNEGO", + .auth_type = DCERPC_AUTH_TYPE_SPNEGO, + .oid = gensec_gssapi_spnego_oids, + .client_start = gensec_gssapi_client_start, + .server_start = gensec_gssapi_server_start, + .magic = gensec_magic_check_krb5_oid, + .update_send = gensec_gssapi_update_send, + .update_recv = gensec_gssapi_update_recv, + .session_key = gensec_gssapi_session_key, + .session_info = gensec_gssapi_session_info, + .sign_packet = gensec_gssapi_sign_packet, + .check_packet = gensec_gssapi_check_packet, + .seal_packet = gensec_gssapi_seal_packet, + .unseal_packet = gensec_gssapi_unseal_packet, + .max_input_size = gensec_gssapi_max_input_size, + .max_wrapped_size = gensec_gssapi_max_wrapped_size, + .wrap = gensec_gssapi_wrap, + .unwrap = gensec_gssapi_unwrap, + .have_feature = gensec_gssapi_have_feature, + .expire_time = gensec_gssapi_expire_time, + .final_auth_type = gensec_gssapi_final_auth_type, + .enabled = false, + .kerberos = true, + .priority = GENSEC_GSSAPI +}; + +/* As a server, this could in theory accept any GSSAPI mech */ +static const struct gensec_security_ops gensec_gssapi_krb5_security_ops = { + .name = "gssapi_krb5", + .auth_type = DCERPC_AUTH_TYPE_KRB5, + .oid = gensec_gssapi_krb5_oids, + .client_start = gensec_gssapi_client_start, + .server_start = gensec_gssapi_server_start, + .magic = gensec_magic_check_krb5_oid, + .update_send = gensec_gssapi_update_send, + .update_recv = gensec_gssapi_update_recv, + .session_key = gensec_gssapi_session_key, + .session_info = gensec_gssapi_session_info, + .sig_size = gensec_gssapi_sig_size, + .sign_packet = gensec_gssapi_sign_packet, + .check_packet = gensec_gssapi_check_packet, + .seal_packet = gensec_gssapi_seal_packet, + .unseal_packet = gensec_gssapi_unseal_packet, + .max_input_size = gensec_gssapi_max_input_size, + .max_wrapped_size = gensec_gssapi_max_wrapped_size, + .wrap = gensec_gssapi_wrap, + .unwrap = gensec_gssapi_unwrap, + .have_feature = gensec_gssapi_have_feature, + .expire_time = gensec_gssapi_expire_time, + .final_auth_type = gensec_gssapi_final_auth_type, + .enabled = true, + .kerberos = true, + .priority = GENSEC_GSSAPI +}; + +/* As a server, this could in theory accept any GSSAPI mech */ +static const struct gensec_security_ops gensec_gssapi_sasl_krb5_security_ops = { + .name = "gssapi_krb5_sasl", + .sasl_name = "GSSAPI", + .client_start = gensec_gssapi_sasl_client_start, + .server_start = gensec_gssapi_sasl_server_start, + .update_send = gensec_gssapi_update_send, + .update_recv = gensec_gssapi_update_recv, + .session_key = gensec_gssapi_session_key, + .session_info = gensec_gssapi_session_info, + .max_input_size = gensec_gssapi_max_input_size, + .max_wrapped_size = gensec_gssapi_max_wrapped_size, + .wrap = gensec_gssapi_wrap, + .unwrap = gensec_gssapi_unwrap, + .have_feature = gensec_gssapi_have_feature, + .expire_time = gensec_gssapi_expire_time, + .final_auth_type = gensec_gssapi_final_auth_type, + .enabled = true, + .kerberos = true, + .priority = GENSEC_GSSAPI +}; + +_PUBLIC_ NTSTATUS gensec_gssapi_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret; + + ret = gensec_register(ctx, &gensec_gssapi_spnego_security_ops); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register '%s' gensec backend!\n", + gensec_gssapi_spnego_security_ops.name)); + return ret; + } + + ret = gensec_register(ctx, &gensec_gssapi_krb5_security_ops); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register '%s' gensec backend!\n", + gensec_gssapi_krb5_security_ops.name)); + return ret; + } + + ret = gensec_register(ctx, &gensec_gssapi_sasl_krb5_security_ops); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register '%s' gensec backend!\n", + gensec_gssapi_sasl_krb5_security_ops.name)); + return ret; + } + + return ret; +} diff --git a/source4/auth/gensec/gensec_gssapi.h b/source4/auth/gensec/gensec_gssapi.h new file mode 100644 index 0000000..d788b5e --- /dev/null +++ b/source4/auth/gensec/gensec_gssapi.h @@ -0,0 +1,69 @@ +/* + Unix SMB/CIFS implementation. + + Kerberos backend for GENSEC + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + Copyright (C) Stefan Metzmacher <metze@samba.org> 2004-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/>. +*/ + +/* This structure described here, so the RPC-PAC test can get at the PAC provided */ + +enum gensec_gssapi_sasl_state +{ + STAGE_GSS_NEG, + STAGE_SASL_SSF_NEG, + STAGE_SASL_SSF_ACCEPT, + STAGE_DONE +}; + +#define NEG_SEAL 0x4 +#define NEG_SIGN 0x2 +#define NEG_NONE 0x1 + +struct gensec_gssapi_state { + gss_ctx_id_t gssapi_context; + gss_name_t server_name; + gss_name_t client_name; + OM_uint32 gss_want_flags, gss_got_flags; + + gss_cred_id_t delegated_cred_handle; + + NTTIME expire_time; + + /* gensec_gssapi only */ + gss_OID gss_oid; + + struct gss_channel_bindings_struct *input_chan_bindings; + struct smb_krb5_context *smb_krb5_context; + struct gssapi_creds_container *client_cred; + struct gssapi_creds_container *server_cred; + + bool sasl; /* We have two different mechs in this file: One + * for SASL wrapped GSSAPI and another for normal + * GSSAPI */ + enum gensec_gssapi_sasl_state sasl_state; + uint8_t sasl_protection; /* What was negotiated at the SASL + * layer, independent of the GSSAPI + * layer... */ + + size_t max_wrap_buf_size; + int gss_exchange_count; + size_t sig_size; + + char *target_principal; +}; diff --git a/source4/auth/gensec/gensec_krb5.c b/source4/auth/gensec/gensec_krb5.c new file mode 100644 index 0000000..104e463 --- /dev/null +++ b/source4/auth/gensec/gensec_krb5.c @@ -0,0 +1,1133 @@ +/* + Unix SMB/CIFS implementation. + + Kerberos backend for GENSEC + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004 + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Luke Howard 2002-2003 + Copyright (C) Stefan Metzmacher 2004-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 <tevent.h> +#include "lib/util/tevent_ntstatus.h" +#include "system/kerberos.h" +#include "auth/kerberos/kerberos.h" +#include "auth/auth.h" +#include "lib/tsocket/tsocket.h" +#include "librpc/gen_ndr/dcerpc.h" +#include "auth/credentials/credentials.h" +#include "auth/credentials/credentials_krb5.h" +#include "auth/kerberos/kerberos_credentials.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" +#include "auth/gensec/gensec_proto.h" +#include "auth/gensec/gensec_toplevel_proto.h" +#include "param/param.h" +#include "auth/auth_sam_reply.h" +#include "lib/util/util_net.h" +#include "../lib/util/asn1.h" +#include "auth/kerberos/pac_utils.h" +#include "gensec_krb5.h" +#include "gensec_krb5_internal.h" +#include "gensec_krb5_helpers.h" + +_PUBLIC_ NTSTATUS gensec_krb5_init(TALLOC_CTX *); + +static int gensec_krb5_destroy(struct gensec_krb5_state *gensec_krb5_state) +{ + if (!gensec_krb5_state->smb_krb5_context) { + /* We can't clean anything else up unless we started up this far */ + return 0; + } + if (gensec_krb5_state->enc_ticket.length) { + smb_krb5_free_data_contents(gensec_krb5_state->smb_krb5_context->krb5_context, + &gensec_krb5_state->enc_ticket); + } + + if (gensec_krb5_state->ticket) { + krb5_free_ticket(gensec_krb5_state->smb_krb5_context->krb5_context, + gensec_krb5_state->ticket); + } + + /* ccache freed in a child destructor */ + + krb5_free_keyblock(gensec_krb5_state->smb_krb5_context->krb5_context, + gensec_krb5_state->keyblock); + + if (gensec_krb5_state->auth_context) { + krb5_auth_con_free(gensec_krb5_state->smb_krb5_context->krb5_context, + gensec_krb5_state->auth_context); + } + + return 0; +} + +static NTSTATUS gensec_krb5_start(struct gensec_security *gensec_security, bool gssapi) +{ + krb5_error_code ret; + struct gensec_krb5_state *gensec_krb5_state; + struct cli_credentials *creds; + const struct tsocket_address *tlocal_addr, *tremote_addr; + krb5_address my_krb5_addr, peer_krb5_addr; + + creds = gensec_get_credentials(gensec_security); + if (!creds) { + return NT_STATUS_INVALID_PARAMETER; + } + + gensec_krb5_state = talloc_zero(gensec_security, struct gensec_krb5_state); + if (!gensec_krb5_state) { + return NT_STATUS_NO_MEMORY; + } + + gensec_security->private_data = gensec_krb5_state; + gensec_krb5_state->gssapi = gssapi; + + talloc_set_destructor(gensec_krb5_state, gensec_krb5_destroy); + + if (cli_credentials_get_krb5_context(creds, + gensec_security->settings->lp_ctx, &gensec_krb5_state->smb_krb5_context)) { + talloc_free(gensec_krb5_state); + return NT_STATUS_INTERNAL_ERROR; + } + + ret = krb5_auth_con_init(gensec_krb5_state->smb_krb5_context->krb5_context, &gensec_krb5_state->auth_context); + if (ret) { + DEBUG(1,("gensec_krb5_start: krb5_auth_con_init failed (%s)\n", + smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, + ret, gensec_krb5_state))); + talloc_free(gensec_krb5_state); + return NT_STATUS_INTERNAL_ERROR; + } + + ret = krb5_auth_con_setflags(gensec_krb5_state->smb_krb5_context->krb5_context, + gensec_krb5_state->auth_context, + KRB5_AUTH_CONTEXT_DO_SEQUENCE); + if (ret) { + DEBUG(1,("gensec_krb5_start: krb5_auth_con_setflags failed (%s)\n", + smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, + ret, gensec_krb5_state))); + talloc_free(gensec_krb5_state); + return NT_STATUS_INTERNAL_ERROR; + } + + tlocal_addr = gensec_get_local_address(gensec_security); + if (tlocal_addr) { + ssize_t sockaddr_ret; + struct samba_sockaddr addr; + bool ok; + + addr.sa_socklen = sizeof(addr.u); + sockaddr_ret = tsocket_address_bsd_sockaddr( + tlocal_addr, &addr.u.sa, addr.sa_socklen); + if (sockaddr_ret < 0) { + talloc_free(gensec_krb5_state); + return NT_STATUS_INTERNAL_ERROR; + } + addr.sa_socklen = sockaddr_ret; + ok = smb_krb5_sockaddr_to_kaddr(&addr.u.ss, &my_krb5_addr); + if (!ok) { + DBG_WARNING("smb_krb5_sockaddr_to_kaddr (local) failed\n"); + talloc_free(gensec_krb5_state); + return NT_STATUS_INTERNAL_ERROR; + } + } + + tremote_addr = gensec_get_remote_address(gensec_security); + if (tremote_addr) { + ssize_t sockaddr_ret; + struct samba_sockaddr addr; + bool ok; + + addr.sa_socklen = sizeof(addr.u); + sockaddr_ret = tsocket_address_bsd_sockaddr( + tremote_addr, &addr.u.sa, addr.sa_socklen); + if (sockaddr_ret < 0) { + talloc_free(gensec_krb5_state); + return NT_STATUS_INTERNAL_ERROR; + } + addr.sa_socklen = sockaddr_ret; + ok = smb_krb5_sockaddr_to_kaddr(&addr.u.ss, &peer_krb5_addr); + if (!ok) { + DBG_WARNING("smb_krb5_sockaddr_to_kaddr (remote) failed\n"); + talloc_free(gensec_krb5_state); + return NT_STATUS_INTERNAL_ERROR; + } + } + + ret = krb5_auth_con_setaddrs(gensec_krb5_state->smb_krb5_context->krb5_context, + gensec_krb5_state->auth_context, + tlocal_addr ? &my_krb5_addr : NULL, + tremote_addr ? &peer_krb5_addr : NULL); + if (ret) { + DEBUG(1,("gensec_krb5_start: krb5_auth_con_setaddrs failed (%s)\n", + smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, + ret, gensec_krb5_state))); + talloc_free(gensec_krb5_state); + return NT_STATUS_INTERNAL_ERROR; + } + + return NT_STATUS_OK; +} + +static NTSTATUS gensec_krb5_common_server_start(struct gensec_security *gensec_security, bool gssapi) +{ + NTSTATUS nt_status; + struct gensec_krb5_state *gensec_krb5_state; + + nt_status = gensec_krb5_start(gensec_security, gssapi); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data; + gensec_krb5_state->state_position = GENSEC_KRB5_SERVER_START; + + return NT_STATUS_OK; +} + +static NTSTATUS gensec_krb5_server_start(struct gensec_security *gensec_security) +{ + return gensec_krb5_common_server_start(gensec_security, false); +} + +static NTSTATUS gensec_fake_gssapi_krb5_server_start(struct gensec_security *gensec_security) +{ + return gensec_krb5_common_server_start(gensec_security, true); +} + +static NTSTATUS gensec_krb5_common_client_start(struct gensec_security *gensec_security, bool gssapi) +{ + const char *hostname; + struct gensec_krb5_state *gensec_krb5_state; + NTSTATUS nt_status; + hostname = gensec_get_target_hostname(gensec_security); + if (!hostname) { + DEBUG(3, ("No hostname for target computer passed in, cannot use kerberos for this connection\n")); + return NT_STATUS_INVALID_PARAMETER; + } + if (is_ipaddress(hostname)) { + DEBUG(2, ("Cannot do krb5 to an IP address")); + return NT_STATUS_INVALID_PARAMETER; + } + if (strcmp(hostname, "localhost") == 0) { + DEBUG(2, ("krb5 to 'localhost' does not make sense")); + return NT_STATUS_INVALID_PARAMETER; + } + + nt_status = gensec_krb5_start(gensec_security, gssapi); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data; + gensec_krb5_state->state_position = GENSEC_KRB5_CLIENT_START; + gensec_krb5_state->ap_req_options = AP_OPTS_USE_SUBKEY; + + if (gensec_krb5_state->gssapi) { + /* The Fake GSSAPI model emulates Samba3, which does not do mutual authentication */ + if (gensec_setting_bool(gensec_security->settings, "gensec_fake_gssapi_krb5", "mutual", false)) { + gensec_krb5_state->ap_req_options |= AP_OPTS_MUTUAL_REQUIRED; + } + } else { + /* The wrapping for KPASSWD (a user of the raw KRB5 API) should be mutually authenticated */ + if (gensec_setting_bool(gensec_security->settings, "gensec_krb5", "mutual", true)) { + gensec_krb5_state->ap_req_options |= AP_OPTS_MUTUAL_REQUIRED; + } + } + return NT_STATUS_OK; +} + +static NTSTATUS gensec_krb5_common_client_creds(struct gensec_security *gensec_security, + struct tevent_context *ev) +{ + struct gensec_krb5_state *gensec_krb5_state; + krb5_error_code ret; + struct ccache_container *ccache_container; + const char *error_string; + const char *principal; + const char *hostname; + krb5_data in_data = { .length = 0 }; + krb5_data *in_data_p = NULL; +#ifdef SAMBA4_USES_HEIMDAL + struct tevent_context *previous_ev; +#endif + + if (lpcfg_parm_bool(gensec_security->settings->lp_ctx, + NULL, "gensec_krb5", "send_authenticator_checksum", true)) { + in_data_p = &in_data; + } + + gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data; + + principal = gensec_get_target_principal(gensec_security); + hostname = gensec_get_target_hostname(gensec_security); + + ret = cli_credentials_get_ccache(gensec_get_credentials(gensec_security), + ev, + gensec_security->settings->lp_ctx, &ccache_container, &error_string); + switch (ret) { + case 0: + break; + case KRB5KDC_ERR_PREAUTH_FAILED: + case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN: + return NT_STATUS_LOGON_FAILURE; + case KRB5_KDC_UNREACH: + DEBUG(3, ("Cannot reach a KDC we require to contact %s: %s\n", principal, error_string)); + return NT_STATUS_INVALID_PARAMETER; /* Make SPNEGO ignore us, we can't go any further here */ + case KRB5_CC_NOTFOUND: + case KRB5_CC_END: + DEBUG(3, ("Error preparing credentials we require to contact %s : %s\n", principal, error_string)); + return NT_STATUS_INVALID_PARAMETER; /* Make SPNEGO ignore us, we can't go any further here */ + default: + DEBUG(1, ("gensec_krb5_start: Aquiring initiator credentials failed: %s\n", error_string)); + return NT_STATUS_UNSUCCESSFUL; + } + +#ifdef SAMBA4_USES_HEIMDAL + /* Do this every time, in case we have weird recursive issues here */ + ret = smb_krb5_context_set_event_ctx(gensec_krb5_state->smb_krb5_context, ev, &previous_ev); + if (ret != 0) { + DEBUG(1, ("gensec_krb5_start: Setting event context failed\n")); + return NT_STATUS_NO_MEMORY; + } +#endif + if (principal) { + krb5_principal target_principal; + ret = krb5_parse_name(gensec_krb5_state->smb_krb5_context->krb5_context, principal, + &target_principal); + if (ret == 0) { + krb5_creds this_cred; + krb5_creds *cred; + + ZERO_STRUCT(this_cred); + ret = krb5_cc_get_principal(gensec_krb5_state->smb_krb5_context->krb5_context, + ccache_container->ccache, + &this_cred.client); + if (ret != 0) { + krb5_free_principal(gensec_krb5_state->smb_krb5_context->krb5_context, + target_principal); + return NT_STATUS_UNSUCCESSFUL; + } + + ret = krb5_copy_principal(gensec_krb5_state->smb_krb5_context->krb5_context, + target_principal, + &this_cred.server); + krb5_free_principal(gensec_krb5_state->smb_krb5_context->krb5_context, + target_principal); + if (ret != 0) { + krb5_free_cred_contents(gensec_krb5_state->smb_krb5_context->krb5_context, + &this_cred); + return NT_STATUS_UNSUCCESSFUL; + } + this_cred.times.endtime = 0; + + ret = krb5_get_credentials(gensec_krb5_state->smb_krb5_context->krb5_context, + 0, + ccache_container->ccache, + &this_cred, + &cred); + krb5_free_cred_contents(gensec_krb5_state->smb_krb5_context->krb5_context, + &this_cred); + if (ret != 0) { + return NT_STATUS_UNSUCCESSFUL; + } + + ret = krb5_mk_req_extended(gensec_krb5_state->smb_krb5_context->krb5_context, + &gensec_krb5_state->auth_context, + gensec_krb5_state->ap_req_options, + in_data_p, + cred, + &gensec_krb5_state->enc_ticket); + } + } else { + ret = krb5_mk_req(gensec_krb5_state->smb_krb5_context->krb5_context, + &gensec_krb5_state->auth_context, + gensec_krb5_state->ap_req_options, + discard_const_p(char, gensec_get_target_service(gensec_security)), + discard_const_p(char, hostname), + in_data_p, ccache_container->ccache, + &gensec_krb5_state->enc_ticket); + } + +#ifdef SAMBA4_USES_HEIMDAL + smb_krb5_context_remove_event_ctx(gensec_krb5_state->smb_krb5_context, previous_ev, ev); +#endif + + switch (ret) { + case 0: + return NT_STATUS_OK; + case KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN: + DEBUG(3, ("Server [%s] is not registered with our KDC: %s\n", + hostname, smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, gensec_krb5_state))); + return NT_STATUS_INVALID_PARAMETER; /* Make SPNEGO ignore us, we can't go any further here */ + case KRB5_KDC_UNREACH: + DEBUG(3, ("Cannot reach a KDC we require to contact host [%s]: %s\n", + hostname, smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, gensec_krb5_state))); + return NT_STATUS_INVALID_PARAMETER; /* Make SPNEGO ignore us, we can't go any further here */ + case KRB5KDC_ERR_PREAUTH_FAILED: + case KRB5KRB_AP_ERR_TKT_EXPIRED: + case KRB5_CC_END: + /* Too much clock skew - we will need to kinit to re-skew the clock */ + case KRB5KRB_AP_ERR_SKEW: + case KRB5_KDCREP_SKEW: + DEBUG(3, ("kerberos (mk_req) failed: %s\n", + smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, gensec_krb5_state))); + FALL_THROUGH; + /* just don't print a message for these really ordinary messages */ + case KRB5_FCC_NOFILE: + case KRB5_CC_NOTFOUND: + case ENOENT: + + return NT_STATUS_UNSUCCESSFUL; + break; + + default: + DEBUG(0, ("kerberos: %s\n", + smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, gensec_krb5_state))); + return NT_STATUS_UNSUCCESSFUL; + } +} + +static NTSTATUS gensec_krb5_client_start(struct gensec_security *gensec_security) +{ + return gensec_krb5_common_client_start(gensec_security, false); +} + +static NTSTATUS gensec_fake_gssapi_krb5_client_start(struct gensec_security *gensec_security) +{ + return gensec_krb5_common_client_start(gensec_security, true); +} + + +/* + generate a krb5 GSS-API wrapper packet given a ticket +*/ +static DATA_BLOB gensec_gssapi_gen_krb5_wrap(TALLOC_CTX *mem_ctx, const DATA_BLOB *ticket, const uint8_t tok_id[2]) +{ + struct asn1_data *data; + DATA_BLOB ret = data_blob_null; + + data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); + if (!data || !ticket->data) { + return ret; + } + + if (!asn1_push_tag(data, ASN1_APPLICATION(0))) goto err; + if (!asn1_write_OID(data, GENSEC_OID_KERBEROS5)) goto err; + + if (!asn1_write(data, tok_id, 2)) goto err; + if (!asn1_write(data, ticket->data, ticket->length)) goto err; + if (!asn1_pop_tag(data)) goto err; + + + if (!asn1_extract_blob(data, mem_ctx, &ret)) { + goto err; + } + asn1_free(data); + + return ret; + + err: + + DEBUG(1, ("Failed to build krb5 wrapper at offset %d\n", + (int)asn1_current_ofs(data))); + asn1_free(data); + return ret; +} + +/* + parse a krb5 GSS-API wrapper packet giving a ticket +*/ +static bool gensec_gssapi_parse_krb5_wrap(TALLOC_CTX *mem_ctx, const DATA_BLOB *blob, DATA_BLOB *ticket, uint8_t tok_id[2]) +{ + bool ret = false; + struct asn1_data *data = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); + int data_remaining; + + if (!data) { + return false; + } + + if (!asn1_load(data, *blob)) goto err; + if (!asn1_start_tag(data, ASN1_APPLICATION(0))) goto err; + if (!asn1_check_OID(data, GENSEC_OID_KERBEROS5)) goto err; + + data_remaining = asn1_tag_remaining(data); + + if (data_remaining < 3) { + asn1_set_error(data); + } else { + if (!asn1_read(data, tok_id, 2)) goto err; + data_remaining -= 2; + *ticket = data_blob_talloc(mem_ctx, NULL, data_remaining); + if (!asn1_read(data, ticket->data, ticket->length)) goto err; + } + + if (!asn1_end_tag(data)) goto err; + + ret = !asn1_has_error(data); + + err: + + asn1_free(data); + + return ret; +} + +static NTSTATUS gensec_krb5_update_internal(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + struct tevent_context *ev, + const DATA_BLOB in, DATA_BLOB *out) +{ + struct gensec_krb5_state *gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data; + krb5_error_code ret = 0; + NTSTATUS nt_status; + + switch (gensec_krb5_state->state_position) { + case GENSEC_KRB5_CLIENT_START: + { + DATA_BLOB unwrapped_out; + + nt_status = gensec_krb5_common_client_creds(gensec_security, ev); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + if (gensec_krb5_state->gssapi) { + unwrapped_out = data_blob_talloc(out_mem_ctx, gensec_krb5_state->enc_ticket.data, gensec_krb5_state->enc_ticket.length); + + /* wrap that up in a nice GSS-API wrapping */ + *out = gensec_gssapi_gen_krb5_wrap(out_mem_ctx, &unwrapped_out, TOK_ID_KRB_AP_REQ); + } else { + *out = data_blob_talloc(out_mem_ctx, gensec_krb5_state->enc_ticket.data, gensec_krb5_state->enc_ticket.length); + } + if (gensec_krb5_state->ap_req_options & AP_OPTS_MUTUAL_REQUIRED) { + gensec_krb5_state->state_position = GENSEC_KRB5_CLIENT_MUTUAL_AUTH; + nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED; + } else { + gensec_krb5_state->state_position = GENSEC_KRB5_DONE; + nt_status = NT_STATUS_OK; + } + return nt_status; + } + + case GENSEC_KRB5_CLIENT_MUTUAL_AUTH: + { + DATA_BLOB unwrapped_in; + krb5_data inbuf; + krb5_ap_rep_enc_part *repl = NULL; + uint8_t tok_id[2]; + + if (gensec_krb5_state->gssapi) { + if (!gensec_gssapi_parse_krb5_wrap(out_mem_ctx, &in, &unwrapped_in, tok_id)) { + DEBUG(1,("gensec_gssapi_parse_krb5_wrap(mutual authentication) failed to parse\n")); + dump_data_pw("Mutual authentication message:\n", in.data, in.length); + return NT_STATUS_INVALID_PARAMETER; + } + } else { + unwrapped_in = in; + } + /* TODO: check the tok_id */ + + inbuf.data = (char *)unwrapped_in.data; + inbuf.length = unwrapped_in.length; + ret = krb5_rd_rep(gensec_krb5_state->smb_krb5_context->krb5_context, + gensec_krb5_state->auth_context, + &inbuf, &repl); + if (ret) { + DEBUG(1,("krb5_rd_rep (mutual authentication) failed (%s)\n", + smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, out_mem_ctx))); + dump_data_pw("Mutual authentication message:\n", (uint8_t *)inbuf.data, inbuf.length); + nt_status = NT_STATUS_ACCESS_DENIED; + } else { + *out = data_blob(NULL, 0); + nt_status = NT_STATUS_OK; + gensec_krb5_state->state_position = GENSEC_KRB5_DONE; + } + if (repl) { + krb5_free_ap_rep_enc_part(gensec_krb5_state->smb_krb5_context->krb5_context, repl); + } + return nt_status; + } + + case GENSEC_KRB5_SERVER_START: + { + DATA_BLOB unwrapped_in; + DATA_BLOB unwrapped_out = data_blob(NULL, 0); + krb5_data inbuf, outbuf; + uint8_t tok_id[2]; + struct keytab_container *keytab; + krb5_principal server_in_keytab; + const char *error_string; + enum credentials_obtained obtained; + + if (!in.data) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* Grab the keytab, however generated */ + ret = cli_credentials_get_keytab(gensec_get_credentials(gensec_security), + gensec_security->settings->lp_ctx, &keytab); + if (ret) { + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + /* This ensures we lookup the correct entry in that + * keytab. A NULL principal is acceptable, and means + * that the krb5 libs should search the keytab at + * accept time for any matching key */ + ret = principal_from_credentials(out_mem_ctx, gensec_get_credentials(gensec_security), + gensec_krb5_state->smb_krb5_context, + &server_in_keytab, &obtained, &error_string); + + if (ret) { + DEBUG(2,("Failed to make credentials from principal: %s\n", error_string)); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + if (keytab->password_based || obtained < CRED_SPECIFIED) { + /* + * Use match-by-key in this case (matches + * cli_credentials_get_server_gss_creds() + * behaviour). No need to free the memory, + * this is handled with a talloc destructor. + */ + server_in_keytab = NULL; + } + + /* Parse the GSSAPI wrapping, if it's there... (win2k3 allows it to be omited) */ + if (gensec_krb5_state->gssapi + && gensec_gssapi_parse_krb5_wrap(out_mem_ctx, &in, &unwrapped_in, tok_id)) { + inbuf.data = (char *)unwrapped_in.data; + inbuf.length = unwrapped_in.length; + } else { + inbuf.data = (char *)in.data; + inbuf.length = in.length; + } + + ret = smb_krb5_rd_req_decoded(gensec_krb5_state->smb_krb5_context->krb5_context, + &gensec_krb5_state->auth_context, + &inbuf, + keytab->keytab, + server_in_keytab, + &outbuf, + &gensec_krb5_state->ticket, + &gensec_krb5_state->keyblock); + + if (ret) { + DBG_WARNING("smb_krb5_rd_req_decoded failed\n"); + return NT_STATUS_LOGON_FAILURE; + } + unwrapped_out.data = (uint8_t *)outbuf.data; + unwrapped_out.length = outbuf.length; + gensec_krb5_state->state_position = GENSEC_KRB5_DONE; + /* wrap that up in a nice GSS-API wrapping */ + if (gensec_krb5_state->gssapi) { + *out = gensec_gssapi_gen_krb5_wrap(out_mem_ctx, &unwrapped_out, TOK_ID_KRB_AP_REP); + } else { + *out = data_blob_talloc(out_mem_ctx, outbuf.data, outbuf.length); + } + smb_krb5_free_data_contents(gensec_krb5_state->smb_krb5_context->krb5_context, + &outbuf); + return NT_STATUS_OK; + } + + case GENSEC_KRB5_DONE: + default: + /* Asking too many times... */ + return NT_STATUS_INVALID_PARAMETER; + } +} + +struct gensec_krb5_update_state { + NTSTATUS status; + DATA_BLOB out; +}; + +static struct tevent_req *gensec_krb5_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_security, + const DATA_BLOB in) +{ + struct tevent_req *req = NULL; + struct gensec_krb5_update_state *state = NULL; + NTSTATUS status; + + req = tevent_req_create(mem_ctx, &state, + struct gensec_krb5_update_state); + if (req == NULL) { + return NULL; + } + + status = gensec_krb5_update_internal(gensec_security, + state, ev, in, + &state->out); + state->status = status; + if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static NTSTATUS gensec_krb5_update_recv(struct tevent_req *req, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + struct gensec_krb5_update_state *state = + tevent_req_data(req, + struct gensec_krb5_update_state); + NTSTATUS status; + + *out = data_blob_null; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *out = state->out; + talloc_steal(out_mem_ctx, state->out.data); + status = state->status; + tevent_req_received(req); + return status; +} + +static NTSTATUS gensec_krb5_session_key(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + DATA_BLOB *session_key) +{ + struct gensec_krb5_state *gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data; + krb5_context context = gensec_krb5_state->smb_krb5_context->krb5_context; + krb5_auth_context auth_context = gensec_krb5_state->auth_context; + krb5_error_code err = -1; + bool remote = false; + bool ok; + + if (gensec_krb5_state->state_position != GENSEC_KRB5_DONE) { + return NT_STATUS_NO_USER_SESSION_KEY; + } + + switch (gensec_security->gensec_role) { + case GENSEC_CLIENT: + remote = false; + break; + case GENSEC_SERVER: + remote = true; + break; + } + + ok = smb_krb5_get_smb_session_key(mem_ctx, + context, + auth_context, + session_key, + remote); + if (!ok) { + DEBUG(10, ("KRB5 error getting session key %d\n", err)); + return NT_STATUS_NO_USER_SESSION_KEY; + } + + return NT_STATUS_OK; +} + +#ifdef SAMBA4_USES_HEIMDAL +static NTSTATUS gensec_krb5_session_info(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + struct auth_session_info **_session_info) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + struct gensec_krb5_state *gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data; + krb5_context context = gensec_krb5_state->smb_krb5_context->krb5_context; + struct auth_session_info *session_info = NULL; + + krb5_principal client_principal; + char *principal_string = NULL; + + DATA_BLOB pac_blob, *pac_blob_ptr = NULL; + krb5_data pac_data; + + krb5_error_code ret; + + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) { + return NT_STATUS_NO_MEMORY; + } + + ret = krb5_ticket_get_client(context, gensec_krb5_state->ticket, &client_principal); + if (ret) { + DEBUG(5, ("krb5_ticket_get_client failed to get client principal: %s\n", + smb_get_krb5_error_message(context, + ret, tmp_ctx))); + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + ret = krb5_unparse_name(gensec_krb5_state->smb_krb5_context->krb5_context, + client_principal, &principal_string); + if (ret) { + DEBUG(1, ("Unable to parse client principal: %s\n", + smb_get_krb5_error_message(context, + ret, tmp_ctx))); + krb5_free_principal(context, client_principal); + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + ret = krb5_ticket_get_authorization_data_type(context, gensec_krb5_state->ticket, + KRB5_AUTHDATA_WIN2K_PAC, + &pac_data); + + if (ret) { + /* NO pac */ + DEBUG(5, ("krb5_ticket_get_authorization_data_type failed to find PAC: %s\n", + smb_get_krb5_error_message(context, + ret, tmp_ctx))); + } else { + /* Found pac */ + pac_blob = data_blob_talloc(tmp_ctx, pac_data.data, pac_data.length); + smb_krb5_free_data_contents(context, &pac_data); + if (!pac_blob.data) { + free(principal_string); + krb5_free_principal(context, client_principal); + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + /* decode and verify the pac */ + nt_status = kerberos_decode_pac(gensec_krb5_state, + pac_blob, + gensec_krb5_state->smb_krb5_context->krb5_context, + NULL, gensec_krb5_state->keyblock, + client_principal, + gensec_krb5_state->ticket->ticket.authtime, NULL); + + if (!NT_STATUS_IS_OK(nt_status)) { + free(principal_string); + krb5_free_principal(context, client_principal); + talloc_free(tmp_ctx); + return nt_status; + } + + pac_blob_ptr = &pac_blob; + } + + nt_status = gensec_generate_session_info_pac(tmp_ctx, + gensec_security, + gensec_krb5_state->smb_krb5_context, + pac_blob_ptr, principal_string, + gensec_get_remote_address(gensec_security), + &session_info); + + free(principal_string); + krb5_free_principal(context, client_principal); + + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(tmp_ctx); + return nt_status; + } + + nt_status = gensec_krb5_session_key(gensec_security, session_info, &session_info->session_key); + + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(tmp_ctx); + return nt_status; + } + + *_session_info = talloc_steal(mem_ctx, session_info); + + talloc_free(tmp_ctx); + return NT_STATUS_OK; +} +#else /* MIT KERBEROS */ +static NTSTATUS gensec_krb5_session_info(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + struct auth_session_info **psession_info) +{ + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + struct gensec_krb5_state *gensec_krb5_state = + (struct gensec_krb5_state *)gensec_security->private_data; + krb5_context context = gensec_krb5_state->smb_krb5_context->krb5_context; + struct auth_session_info *session_info = NULL; + + krb5_principal client_principal; + char *principal_string = NULL; + + krb5_authdata **auth_pac_data = NULL; + DATA_BLOB pac_blob, *pac_blob_ptr = NULL; + + krb5_error_code code; + + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + code = krb5_copy_principal(context, + gensec_krb5_state->ticket->enc_part2->client, + &client_principal); + if (code != 0) { + DBG_INFO("krb5_copy_principal failed to copy client " + "principal: %s\n", + smb_get_krb5_error_message(context, code, tmp_ctx)); + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + code = krb5_unparse_name(context, client_principal, &principal_string); + if (code != 0) { + DBG_WARNING("Unable to parse client principal: %s\n", + smb_get_krb5_error_message(context, code, tmp_ctx)); + krb5_free_principal(context, client_principal); + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + code = krb5_find_authdata(context, + gensec_krb5_state->ticket->enc_part2->authorization_data, + NULL, + KRB5_AUTHDATA_WIN2K_PAC, + &auth_pac_data); + if (code != 0) { + /* NO pac */ + DBG_INFO("krb5_find_authdata failed to find PAC: %s\n", + smb_get_krb5_error_message(context, code, tmp_ctx)); + } else { + krb5_timestamp ticket_authtime = + gensec_krb5_state->ticket->enc_part2->times.authtime; + + /* Found pac */ + pac_blob = data_blob_talloc(tmp_ctx, + auth_pac_data[0]->contents, + auth_pac_data[0]->length); + krb5_free_authdata(context, auth_pac_data); + if (pac_blob.data == NULL) { + free(principal_string); + krb5_free_principal(context, client_principal); + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + /* decode and verify the pac */ + status = kerberos_decode_pac(gensec_krb5_state, + pac_blob, + context, + NULL, + gensec_krb5_state->keyblock, + client_principal, + ticket_authtime, + NULL); + + if (!NT_STATUS_IS_OK(status)) { + free(principal_string); + krb5_free_principal(context, client_principal); + talloc_free(tmp_ctx); + return status; + } + + pac_blob_ptr = &pac_blob; + } + krb5_free_principal(context, client_principal); + + status = gensec_generate_session_info_pac(tmp_ctx, + gensec_security, + gensec_krb5_state->smb_krb5_context, + pac_blob_ptr, + principal_string, + gensec_get_remote_address(gensec_security), + &session_info); + SAFE_FREE(principal_string); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(tmp_ctx); + return status; + } + + status = gensec_krb5_session_key(gensec_security, + session_info, + &session_info->session_key); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(tmp_ctx); + return status; + } + + *psession_info = talloc_steal(mem_ctx, session_info); + talloc_free(tmp_ctx); + + return NT_STATUS_OK; +} +#endif /* SAMBA4_USES_HEIMDAL */ + +static NTSTATUS gensec_krb5_wrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + struct gensec_krb5_state *gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data; + krb5_context context = gensec_krb5_state->smb_krb5_context->krb5_context; + krb5_auth_context auth_context = gensec_krb5_state->auth_context; + krb5_error_code ret; + krb5_data input, output; + input.length = in->length; + input.data = (char *)in->data; + + if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) { + ret = krb5_mk_priv(context, auth_context, &input, &output, NULL); + if (ret) { + DEBUG(1, ("krb5_mk_priv failed: %s\n", + smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, + ret, mem_ctx))); + return NT_STATUS_ACCESS_DENIED; + } + *out = data_blob_talloc(mem_ctx, output.data, output.length); + + smb_krb5_free_data_contents(context, &output); + } else { + return NT_STATUS_ACCESS_DENIED; + } + return NT_STATUS_OK; +} + +static NTSTATUS gensec_krb5_unwrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + struct gensec_krb5_state *gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data; + krb5_context context = gensec_krb5_state->smb_krb5_context->krb5_context; + krb5_auth_context auth_context = gensec_krb5_state->auth_context; + krb5_error_code ret; + krb5_data input, output; + krb5_replay_data replay; + input.length = in->length; + input.data = (char *)in->data; + + if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) { + ret = krb5_rd_priv(context, auth_context, &input, &output, &replay); + if (ret) { + DEBUG(1, ("krb5_rd_priv failed: %s\n", + smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, + ret, mem_ctx))); + return NT_STATUS_ACCESS_DENIED; + } + *out = data_blob_talloc(mem_ctx, output.data, output.length); + + smb_krb5_free_data_contents(context, &output); + } else { + return NT_STATUS_ACCESS_DENIED; + } + return NT_STATUS_OK; +} + +static bool gensec_krb5_have_feature(struct gensec_security *gensec_security, + uint32_t feature) +{ + struct gensec_krb5_state *gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data; + if (feature & GENSEC_FEATURE_SESSION_KEY) { + return true; + } + if (gensec_krb5_state->gssapi) { + return false; + } + + /* + * krb5_mk_priv provides SIGN and SEAL + */ + if (feature & GENSEC_FEATURE_SIGN) { + return true; + } + if (feature & GENSEC_FEATURE_SEAL) { + return true; + } + + return false; +} + +static const char *gensec_krb5_final_auth_type(struct gensec_security *gensec_security) +{ + return GENSEC_FINAL_AUTH_TYPE_KRB5; +} + +static const char *gensec_krb5_oids[] = { + GENSEC_OID_KERBEROS5, + GENSEC_OID_KERBEROS5_OLD, + NULL +}; + +static const struct gensec_security_ops gensec_fake_gssapi_krb5_security_ops = { + .name = "fake_gssapi_krb5", + .auth_type = DCERPC_AUTH_TYPE_KRB5, + .oid = gensec_krb5_oids, + .client_start = gensec_fake_gssapi_krb5_client_start, + .server_start = gensec_fake_gssapi_krb5_server_start, + .update_send = gensec_krb5_update_send, + .update_recv = gensec_krb5_update_recv, + .magic = gensec_magic_check_krb5_oid, + .session_key = gensec_krb5_session_key, + .session_info = gensec_krb5_session_info, + .have_feature = gensec_krb5_have_feature, + .final_auth_type = gensec_krb5_final_auth_type, + .enabled = false, + .kerberos = true, + .priority = GENSEC_KRB5, +}; + +static const struct gensec_security_ops gensec_krb5_security_ops = { + .name = "krb5", + .client_start = gensec_krb5_client_start, + .server_start = gensec_krb5_server_start, + .update_send = gensec_krb5_update_send, + .update_recv = gensec_krb5_update_recv, + .session_key = gensec_krb5_session_key, + .session_info = gensec_krb5_session_info, + .have_feature = gensec_krb5_have_feature, + .wrap = gensec_krb5_wrap, + .unwrap = gensec_krb5_unwrap, + .final_auth_type = gensec_krb5_final_auth_type, + .enabled = true, + .kerberos = true, + .priority = GENSEC_KRB5 +}; + +_PUBLIC_ NTSTATUS gensec_krb5_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret; + + ret = gensec_register(ctx, &gensec_krb5_security_ops); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register '%s' gensec backend!\n", + gensec_krb5_security_ops.name)); + return ret; + } + + ret = gensec_register(ctx, &gensec_fake_gssapi_krb5_security_ops); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register '%s' gensec backend!\n", + gensec_fake_gssapi_krb5_security_ops.name)); + return ret; + } + + return ret; +} diff --git a/source4/auth/gensec/gensec_krb5.h b/source4/auth/gensec/gensec_krb5.h new file mode 100644 index 0000000..ee684be --- /dev/null +++ b/source4/auth/gensec/gensec_krb5.h @@ -0,0 +1,10 @@ +/* See gensec_krb5_util.c for the license */ + +krb5_error_code smb_krb5_rd_req_decoded(krb5_context context, + krb5_auth_context *auth_context, + const krb5_data *inbuf, + krb5_keytab keytab, + krb5_principal acceptor_principal, + krb5_data *outbuf, + krb5_ticket **ticket, + krb5_keyblock **keyblock); diff --git a/source4/auth/gensec/gensec_krb5_heimdal.c b/source4/auth/gensec/gensec_krb5_heimdal.c new file mode 100644 index 0000000..bc0d970 --- /dev/null +++ b/source4/auth/gensec/gensec_krb5_heimdal.c @@ -0,0 +1,102 @@ +/* + * Copyright (c) 1997 - 2006 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* This file for code taken from the Heimdal code, to preserve licence */ +/* Modified by Andrew Bartlett <abartlet@samba.org> */ + +#include "includes.h" +#include "system/kerberos.h" +#include "auth/kerberos/kerberos.h" +#include "gensec_krb5.h" + +/* Taken from accept_sec_context.c,v 1.65 */ +krb5_error_code smb_krb5_rd_req_decoded(krb5_context context, + krb5_auth_context *auth_context, + const krb5_data *inbuf, + krb5_keytab keytab, + krb5_principal acceptor_principal, + krb5_data *outbuf, + krb5_ticket **ticket, + krb5_keyblock **keyblock) +{ + krb5_rd_req_in_ctx in = NULL; + krb5_rd_req_out_ctx out = NULL; + krb5_error_code kret; + + *keyblock = NULL; + *ticket = NULL; + outbuf->length = 0; + outbuf->data = NULL; + + kret = krb5_rd_req_in_ctx_alloc(context, &in); + if (kret == 0) + kret = krb5_rd_req_in_set_keytab(context, in, keytab); + if (kret) { + if (in) + krb5_rd_req_in_ctx_free(context, in); + return kret; + } + + kret = krb5_rd_req_ctx(context, + auth_context, + inbuf, + acceptor_principal, + in, &out); + krb5_rd_req_in_ctx_free(context, in); + if (kret) { + return kret; + } + + /* + * We need to remember some data on the context_handle. + */ + kret = krb5_rd_req_out_get_ticket(context, out, + ticket); + if (kret == 0) { + kret = krb5_rd_req_out_get_keyblock(context, out, + keyblock); + } + krb5_rd_req_out_ctx_free(context, out); + + if (kret == 0) { + kret = krb5_mk_rep(context, *auth_context, outbuf); + } + + if (kret) { + krb5_free_ticket(context, *ticket); + krb5_free_keyblock(context, *keyblock); + krb5_data_free(outbuf); + } + + return kret; +} diff --git a/source4/auth/gensec/gensec_krb5_helpers.c b/source4/auth/gensec/gensec_krb5_helpers.c new file mode 100644 index 0000000..21f2f1e --- /dev/null +++ b/source4/auth/gensec/gensec_krb5_helpers.c @@ -0,0 +1,72 @@ +/* + Unix SMB/CIFS implementation. + + Kerberos backend for GENSEC + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004 + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Luke Howard 2002-2003 + Copyright (C) Stefan Metzmacher 2004-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 "auth/auth.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" +#include "gensec_krb5_internal.h" +#include "gensec_krb5_helpers.h" +#include "system/kerberos.h" +#include "auth/kerberos/kerberos.h" + +static struct gensec_krb5_state *get_private_state(const struct gensec_security *gensec_security) +{ + struct gensec_krb5_state *gensec_krb5_state = NULL; + + if (strcmp(gensec_security->ops->name, "krb5") != 0) { + /* We require that the krb5 mechanism is being used. */ + return NULL; + } + + gensec_krb5_state = talloc_get_type(gensec_security->private_data, + struct gensec_krb5_state); + return gensec_krb5_state; +} + +/* + * Returns 1 if our ticket has the initial flag set, 0 if not, and -1 in case of + * error. + */ +int gensec_krb5_initial_ticket(const struct gensec_security *gensec_security) +{ + struct gensec_krb5_state *gensec_krb5_state = NULL; + + gensec_krb5_state = get_private_state(gensec_security); + if (gensec_krb5_state == NULL) { + return -1; + } + + if (gensec_krb5_state->ticket == NULL) { + /* We don't have a ticket */ + return -1; + } + +#ifdef SAMBA4_USES_HEIMDAL + return gensec_krb5_state->ticket->ticket.flags.initial; +#else /* MIT KERBEROS */ + return (gensec_krb5_state->ticket->enc_part2->flags & TKT_FLG_INITIAL) ? 1 : 0; +#endif /* SAMBA4_USES_HEIMDAL */ +} diff --git a/source4/auth/gensec/gensec_krb5_helpers.h b/source4/auth/gensec/gensec_krb5_helpers.h new file mode 100644 index 0000000..d7b694d --- /dev/null +++ b/source4/auth/gensec/gensec_krb5_helpers.h @@ -0,0 +1,32 @@ +/* + Unix SMB/CIFS implementation. + + Kerberos backend for GENSEC + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004 + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Luke Howard 2002-2003 + Copyright (C) Stefan Metzmacher 2004-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/>. +*/ + +struct gensec_security; + +/* + * Returns 1 if our ticket has the initial flag set, 0 if not, and -1 in case of + * error. + */ +int gensec_krb5_initial_ticket(const struct gensec_security *gensec_security); diff --git a/source4/auth/gensec/gensec_krb5_internal.h b/source4/auth/gensec/gensec_krb5_internal.h new file mode 100644 index 0000000..0bb796f --- /dev/null +++ b/source4/auth/gensec/gensec_krb5_internal.h @@ -0,0 +1,47 @@ +/* + Unix SMB/CIFS implementation. + + Kerberos backend for GENSEC + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004 + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Luke Howard 2002-2003 + Copyright (C) Stefan Metzmacher 2004-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 "auth/gensec/gensec.h" +#include "system/kerberos.h" +#include "auth/kerberos/kerberos.h" + +enum GENSEC_KRB5_STATE { + GENSEC_KRB5_SERVER_START, + GENSEC_KRB5_CLIENT_START, + GENSEC_KRB5_CLIENT_MUTUAL_AUTH, + GENSEC_KRB5_DONE +}; + +struct gensec_krb5_state { + enum GENSEC_KRB5_STATE state_position; + struct smb_krb5_context *smb_krb5_context; + krb5_auth_context auth_context; + krb5_data enc_ticket; + krb5_keyblock *keyblock; + krb5_ticket *ticket; + bool gssapi; + krb5_flags ap_req_options; +}; diff --git a/source4/auth/gensec/gensec_krb5_mit.c b/source4/auth/gensec/gensec_krb5_mit.c new file mode 100644 index 0000000..f7b3129 --- /dev/null +++ b/source4/auth/gensec/gensec_krb5_mit.c @@ -0,0 +1,102 @@ + +#include "includes.h" +#include "system/kerberos.h" +#include "auth/kerberos/kerberos.h" +#include "gensec_krb5.h" + +static krb5_error_code smb_krb5_get_longterm_key(krb5_context context, + krb5_const_principal server, + krb5_kvno kvno, + krb5_enctype etype, + krb5_keytab keytab, + krb5_keyblock **keyblock_out) +{ + krb5_error_code code = EINVAL; + + krb5_keytab_entry kt_entry; + + code = krb5_kt_get_entry(context, + keytab, + server, + kvno, + etype, + &kt_entry); + if (code != 0) { + return code; + } + + code = krb5_copy_keyblock(context, + &kt_entry.key, + keyblock_out); + krb5_free_keytab_entry_contents(context, &kt_entry); + + return code; +} + +krb5_error_code smb_krb5_rd_req_decoded(krb5_context context, + krb5_auth_context *auth_context, + const krb5_data *request, + krb5_keytab keytab, + krb5_principal acceptor_principal, + krb5_data *reply, + krb5_ticket **pticket, + krb5_keyblock **pkeyblock) +{ + krb5_error_code code; + krb5_flags ap_req_options = 0; + krb5_ticket *ticket = NULL; + krb5_keyblock *keyblock = NULL; + + *pticket = NULL; + *pkeyblock = NULL; + reply->length = 0; + reply->data = NULL; + + code = krb5_rd_req(context, + auth_context, + request, + acceptor_principal, + keytab, + &ap_req_options, + &ticket); + if (code != 0) { + DBG_ERR("krb5_rd_req failed: %s\n", + error_message(code)); + return code; + } + + /* + * Get the long term key from the keytab to be able to verify the PAC + * signature. + * + * FIXME: Use ticket->enc_part.kvno ??? + * Getting the latest kvno with passing 0 fixes: + * make -j test TESTS="samba4.winbind.pac.ad_member" + */ + code = smb_krb5_get_longterm_key(context, + ticket->server, + 0, /* kvno */ + ticket->enc_part.enctype, + keytab, + &keyblock); + if (code != 0) { + DBG_ERR("smb_krb5_get_longterm_key failed: %s\n", + error_message(code)); + krb5_free_ticket(context, ticket); + + return code; + } + + code = krb5_mk_rep(context, *auth_context, reply); + if (code != 0) { + DBG_ERR("krb5_mk_rep failed: %s\n", + error_message(code)); + krb5_free_ticket(context, ticket); + krb5_free_keyblock(context, keyblock); + } + + *pticket = ticket; + *pkeyblock = keyblock; + + return code; +} diff --git a/source4/auth/gensec/gensec_tstream.c b/source4/auth/gensec/gensec_tstream.c new file mode 100644 index 0000000..c828170 --- /dev/null +++ b/source4/auth/gensec/gensec_tstream.c @@ -0,0 +1,616 @@ +/* + Unix SMB/CIFS implementation. + + tstream based generic authentication interface + + Copyright (c) 2010 Stefan Metzmacher + Copyright (c) 2010 Andreas Schneider <asn@redhat.com> + + 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/network.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_proto.h" +#include "auth/gensec/gensec_tstream.h" +#include "lib/tsocket/tsocket.h" +#include "lib/tsocket/tsocket_internal.h" +#include "auth/gensec/gensec_toplevel_proto.h" + +static const struct tstream_context_ops tstream_gensec_ops; + +struct tstream_gensec { + struct tstream_context *plain_stream; + + struct gensec_security *gensec_security; + + int error; + + struct { + size_t max_unwrapped_size; + size_t max_wrapped_size; + } write; + + struct { + off_t ofs; + size_t left; + DATA_BLOB unwrapped; + } read; +}; + +_PUBLIC_ NTSTATUS _gensec_create_tstream(TALLOC_CTX *mem_ctx, + struct gensec_security *gensec_security, + struct tstream_context *plain_stream, + struct tstream_context **_gensec_stream, + const char *location) +{ + struct tstream_context *gensec_stream; + struct tstream_gensec *tgss; + + gensec_stream = tstream_context_create(mem_ctx, + &tstream_gensec_ops, + &tgss, + struct tstream_gensec, + location); + if (gensec_stream == NULL) { + return NT_STATUS_NO_MEMORY; + } + + tgss->plain_stream = plain_stream; + tgss->gensec_security = gensec_security; + tgss->error = 0; + + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN) && + !gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) { + talloc_free(gensec_stream); + return NT_STATUS_INVALID_PARAMETER; + } + + tgss->write.max_unwrapped_size = gensec_max_input_size(gensec_security); + tgss->write.max_wrapped_size = gensec_max_wrapped_size(gensec_security); + + ZERO_STRUCT(tgss->read); + + *_gensec_stream = gensec_stream; + return NT_STATUS_OK; +} + +static ssize_t tstream_gensec_pending_bytes(struct tstream_context *stream) +{ + struct tstream_gensec *tgss = + tstream_context_data(stream, + struct tstream_gensec); + + if (tgss->error != 0) { + errno = tgss->error; + return -1; + } + + return tgss->read.left; +} + +struct tstream_gensec_readv_state { + struct tevent_context *ev; + struct tstream_context *stream; + + struct iovec *vector; + int count; + + struct { + bool asked_for_hdr; + uint8_t hdr[4]; + bool asked_for_blob; + DATA_BLOB blob; + } wrapped; + + int ret; +}; + +static void tstream_gensec_readv_wrapped_next(struct tevent_req *req); + +static struct tevent_req *tstream_gensec_readv_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *stream, + struct iovec *vector, + size_t count) +{ + struct tstream_gensec *tgss = + tstream_context_data(stream, + struct tstream_gensec); + struct tevent_req *req; + struct tstream_gensec_readv_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct tstream_gensec_readv_state); + if (!req) { + return NULL; + } + + if (tgss->error != 0) { + tevent_req_error(req, tgss->error); + return tevent_req_post(req, ev); + } + + state->ev = ev; + state->stream = stream; + state->ret = 0; + + /* + * we make a copy of the vector so we can change the structure + */ + state->vector = talloc_array(state, struct iovec, count); + if (tevent_req_nomem(state->vector, req)) { + return tevent_req_post(req, ev); + } + memcpy(state->vector, vector, sizeof(struct iovec) * count); + state->count = count; + + tstream_gensec_readv_wrapped_next(req); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + return req; +} + +static int tstream_gensec_readv_next_vector(struct tstream_context *unix_stream, + void *private_data, + TALLOC_CTX *mem_ctx, + struct iovec **_vector, + size_t *_count); +static void tstream_gensec_readv_wrapped_done(struct tevent_req *subreq); + +static void tstream_gensec_readv_wrapped_next(struct tevent_req *req) +{ + struct tstream_gensec_readv_state *state = + tevent_req_data(req, + struct tstream_gensec_readv_state); + struct tstream_gensec *tgss = + tstream_context_data(state->stream, + struct tstream_gensec); + struct tevent_req *subreq; + + /* + * copy the pending buffer first + */ + while (tgss->read.left > 0 && state->count > 0) { + uint8_t *base = (uint8_t *)state->vector[0].iov_base; + size_t len = MIN(tgss->read.left, state->vector[0].iov_len); + + memcpy(base, tgss->read.unwrapped.data + tgss->read.ofs, len); + + base += len; + state->vector[0].iov_base = (char *) base; + state->vector[0].iov_len -= len; + + tgss->read.ofs += len; + tgss->read.left -= len; + + if (state->vector[0].iov_len == 0) { + state->vector += 1; + state->count -= 1; + } + + state->ret += len; + } + + if (state->count == 0) { + tevent_req_done(req); + return; + } + + data_blob_free(&tgss->read.unwrapped); + ZERO_STRUCT(state->wrapped); + + subreq = tstream_readv_pdu_send(state, state->ev, + tgss->plain_stream, + tstream_gensec_readv_next_vector, + state); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, tstream_gensec_readv_wrapped_done, req); +} + +static int tstream_gensec_readv_next_vector(struct tstream_context *unix_stream, + void *private_data, + TALLOC_CTX *mem_ctx, + struct iovec **_vector, + size_t *_count) +{ + struct tstream_gensec_readv_state *state = + talloc_get_type_abort(private_data, + struct tstream_gensec_readv_state); + struct iovec *vector; + size_t count = 1; + + /* we need to get a message header */ + vector = talloc_array(mem_ctx, struct iovec, count); + if (!vector) { + return -1; + } + + if (!state->wrapped.asked_for_hdr) { + state->wrapped.asked_for_hdr = true; + vector[0].iov_base = (char *)state->wrapped.hdr; + vector[0].iov_len = sizeof(state->wrapped.hdr); + } else if (!state->wrapped.asked_for_blob) { + uint32_t msg_len; + + state->wrapped.asked_for_blob = true; + + msg_len = RIVAL(state->wrapped.hdr, 0); + + /* + * I got a Windows 2012R2 server responding with + * a message of 0x1b28a33. + */ + if (msg_len > 0x0FFFFFFF) { + errno = EMSGSIZE; + return -1; + } + + if (msg_len == 0) { + errno = EMSGSIZE; + return -1; + } + + state->wrapped.blob = data_blob_talloc(state, NULL, msg_len); + if (state->wrapped.blob.data == NULL) { + return -1; + } + + vector[0].iov_base = (char *)state->wrapped.blob.data; + vector[0].iov_len = state->wrapped.blob.length; + } else { + *_vector = NULL; + *_count = 0; + return 0; + } + + *_vector = vector; + *_count = count; + return 0; +} + +static void tstream_gensec_readv_wrapped_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct tstream_gensec_readv_state *state = + tevent_req_data(req, + struct tstream_gensec_readv_state); + struct tstream_gensec *tgss = + tstream_context_data(state->stream, + struct tstream_gensec); + int ret; + int sys_errno; + NTSTATUS status; + + ret = tstream_readv_pdu_recv(subreq, &sys_errno); + TALLOC_FREE(subreq); + if (ret == -1) { + tgss->error = sys_errno; + tevent_req_error(req, sys_errno); + return; + } + + status = gensec_unwrap(tgss->gensec_security, + state, + &state->wrapped.blob, + &tgss->read.unwrapped); + if (!NT_STATUS_IS_OK(status)) { + tgss->error = EIO; + tevent_req_error(req, tgss->error); + return; + } + + data_blob_free(&state->wrapped.blob); + + talloc_steal(tgss, tgss->read.unwrapped.data); + tgss->read.left = tgss->read.unwrapped.length; + tgss->read.ofs = 0; + + tstream_gensec_readv_wrapped_next(req); +} + +static int tstream_gensec_readv_recv(struct tevent_req *req, int *perrno) +{ + struct tstream_gensec_readv_state *state = + tevent_req_data(req, + struct tstream_gensec_readv_state); + int ret; + + ret = tsocket_simple_int_recv(req, perrno); + if (ret == 0) { + ret = state->ret; + } + + tevent_req_received(req); + return ret; +} + +struct tstream_gensec_writev_state { + struct tevent_context *ev; + struct tstream_context *stream; + + struct iovec *vector; + int count; + + struct { + off_t ofs; + size_t left; + DATA_BLOB blob; + } unwrapped; + + struct { + uint8_t hdr[4]; + DATA_BLOB blob; + struct iovec iov[2]; + } wrapped; + + int ret; +}; + +static void tstream_gensec_writev_wrapped_next(struct tevent_req *req); + +static struct tevent_req *tstream_gensec_writev_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *stream, + const struct iovec *vector, + size_t count) +{ + struct tstream_gensec *tgss = + tstream_context_data(stream, + struct tstream_gensec); + struct tevent_req *req; + struct tstream_gensec_writev_state *state; + int i; + int total; + int chunk; + + req = tevent_req_create(mem_ctx, &state, + struct tstream_gensec_writev_state); + if (req == NULL) { + return NULL; + } + + if (tgss->error != 0) { + tevent_req_error(req, tgss->error); + return tevent_req_post(req, ev); + } + + state->ev = ev; + state->stream = stream; + state->ret = 0; + + /* + * we make a copy of the vector so we can change the structure + */ + state->vector = talloc_array(state, struct iovec, count); + if (tevent_req_nomem(state->vector, req)) { + return tevent_req_post(req, ev); + } + memcpy(state->vector, vector, sizeof(struct iovec) * count); + state->count = count; + + total = 0; + for (i = 0; i < count; i++) { + /* + * the generic tstream code makes sure that + * this never wraps. + */ + total += vector[i].iov_len; + } + + /* + * We may need to send data in chunks. + */ + chunk = MIN(total, tgss->write.max_unwrapped_size); + + state->unwrapped.blob = data_blob_talloc(state, NULL, chunk); + if (tevent_req_nomem(state->unwrapped.blob.data, req)) { + return tevent_req_post(req, ev); + } + + tstream_gensec_writev_wrapped_next(req); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + return req; +} + +static void tstream_gensec_writev_wrapped_done(struct tevent_req *subreq); + +static void tstream_gensec_writev_wrapped_next(struct tevent_req *req) +{ + struct tstream_gensec_writev_state *state = + tevent_req_data(req, + struct tstream_gensec_writev_state); + struct tstream_gensec *tgss = + tstream_context_data(state->stream, + struct tstream_gensec); + struct tevent_req *subreq; + NTSTATUS status; + + data_blob_free(&state->wrapped.blob); + + state->unwrapped.left = state->unwrapped.blob.length; + state->unwrapped.ofs = 0; + + /* + * first fill our buffer + */ + while (state->unwrapped.left > 0 && state->count > 0) { + uint8_t *base = (uint8_t *)state->vector[0].iov_base; + size_t len = MIN(state->unwrapped.left, state->vector[0].iov_len); + + memcpy(state->unwrapped.blob.data + state->unwrapped.ofs, base, len); + + base += len; + state->vector[0].iov_base = (char *) base; + state->vector[0].iov_len -= len; + + state->unwrapped.ofs += len; + state->unwrapped.left -= len; + + if (state->vector[0].iov_len == 0) { + state->vector += 1; + state->count -= 1; + } + + state->ret += len; + } + + if (state->unwrapped.ofs == 0) { + tevent_req_done(req); + return; + } + + state->unwrapped.blob.length = state->unwrapped.ofs; + + status = gensec_wrap(tgss->gensec_security, + state, + &state->unwrapped.blob, + &state->wrapped.blob); + if (!NT_STATUS_IS_OK(status)) { + tgss->error = EIO; + tevent_req_error(req, tgss->error); + return; + } + + RSIVAL(state->wrapped.hdr, 0, state->wrapped.blob.length); + + state->wrapped.iov[0].iov_base = (void *)state->wrapped.hdr; + state->wrapped.iov[0].iov_len = sizeof(state->wrapped.hdr); + state->wrapped.iov[1].iov_base = (void *)state->wrapped.blob.data; + state->wrapped.iov[1].iov_len = state->wrapped.blob.length; + + subreq = tstream_writev_send(state, state->ev, + tgss->plain_stream, + state->wrapped.iov, 2); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, + tstream_gensec_writev_wrapped_done, + req); +} + +static void tstream_gensec_writev_wrapped_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct tstream_gensec_writev_state *state = + tevent_req_data(req, + struct tstream_gensec_writev_state); + struct tstream_gensec *tgss = + tstream_context_data(state->stream, + struct tstream_gensec); + int sys_errno; + int ret; + + ret = tstream_writev_recv(subreq, &sys_errno); + TALLOC_FREE(subreq); + if (ret == -1) { + tgss->error = sys_errno; + tevent_req_error(req, sys_errno); + return; + } + + tstream_gensec_writev_wrapped_next(req); +} + +static int tstream_gensec_writev_recv(struct tevent_req *req, + int *perrno) +{ + struct tstream_gensec_writev_state *state = + tevent_req_data(req, + struct tstream_gensec_writev_state); + int ret; + + ret = tsocket_simple_int_recv(req, perrno); + if (ret == 0) { + ret = state->ret; + } + + tevent_req_received(req); + return ret; +} + +struct tstream_gensec_disconnect_state { + uint8_t _dummy; +}; + +static struct tevent_req *tstream_gensec_disconnect_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct tstream_context *stream) +{ + struct tstream_gensec *tgss = + tstream_context_data(stream, + struct tstream_gensec); + struct tevent_req *req; + struct tstream_gensec_disconnect_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct tstream_gensec_disconnect_state); + if (req == NULL) { + return NULL; + } + + if (tgss->error != 0) { + tevent_req_error(req, tgss->error); + return tevent_req_post(req, ev); + } + + /* + * The caller is responsible to do the real disconnect + * on the plain stream! + */ + tgss->plain_stream = NULL; + tgss->error = ENOTCONN; + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static int tstream_gensec_disconnect_recv(struct tevent_req *req, + int *perrno) +{ + int ret; + + ret = tsocket_simple_int_recv(req, perrno); + + tevent_req_received(req); + return ret; +} + +static const struct tstream_context_ops tstream_gensec_ops = { + .name = "gensec", + + .pending_bytes = tstream_gensec_pending_bytes, + + .readv_send = tstream_gensec_readv_send, + .readv_recv = tstream_gensec_readv_recv, + + .writev_send = tstream_gensec_writev_send, + .writev_recv = tstream_gensec_writev_recv, + + .disconnect_send = tstream_gensec_disconnect_send, + .disconnect_recv = tstream_gensec_disconnect_recv, +}; diff --git a/source4/auth/gensec/gensec_tstream.h b/source4/auth/gensec/gensec_tstream.h new file mode 100644 index 0000000..18389d4 --- /dev/null +++ b/source4/auth/gensec/gensec_tstream.h @@ -0,0 +1,40 @@ +/* + Unix SMB/CIFS implementation. + + tstream based generic authentication interface + + Copyright (c) 2010 Stefan Metzmacher + Copyright (c) 2010 Andreas Schneider <asn@redhat.com> + + 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/>. +*/ + +#ifndef _GENSEC_TSTREAM_H_ +#define _GENSEC_TSTREAM_H_ + +struct gensec_context; +struct tstream_context; + +NTSTATUS _gensec_create_tstream(TALLOC_CTX *mem_ctx, + struct gensec_security *gensec_security, + struct tstream_context *plain_tstream, + struct tstream_context **gensec_tstream, + const char *location); +#define gensec_create_tstream(mem_ctx, gensec_security, \ + plain_tstream, gensec_tstream) \ + _gensec_create_tstream(mem_ctx, gensec_security, \ + plain_tstream, gensec_tstream, \ + __location__) + +#endif /* _GENSEC_TSTREAM_H_ */ diff --git a/source4/auth/gensec/pygensec.c b/source4/auth/gensec/pygensec.c new file mode 100644 index 0000000..dd63fa5 --- /dev/null +++ b/source4/auth/gensec/pygensec.c @@ -0,0 +1,779 @@ +/* + Unix SMB/CIFS implementation. + Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2009 + + 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 <Python.h> +#include "python/py3compat.h" +#include "includes.h" +#include "python/modules.h" +#include "param/pyparam.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" /* TODO: remove this */ +#include "auth/credentials/pycredentials.h" +#include "libcli/util/pyerrors.h" +#include "python/modules.h" +#include <pytalloc.h> +#include <tevent.h> +#include "librpc/rpc/pyrpc_util.h" + +static PyObject *py_get_name_by_authtype(PyObject *self, PyObject *args) +{ + int type; + const char *name; + struct gensec_security *security; + + if (!PyArg_ParseTuple(args, "i", &type)) + return NULL; + + security = pytalloc_get_type(self, struct gensec_security); + + name = gensec_get_name_by_authtype(security, type); + if (name == NULL) + Py_RETURN_NONE; + + return PyUnicode_FromString(name); +} + +static struct gensec_settings *settings_from_object(TALLOC_CTX *mem_ctx, PyObject *object) +{ + struct gensec_settings *s; + PyObject *py_hostname, *py_lp_ctx; + + if (!PyDict_Check(object)) { + PyErr_SetString(PyExc_ValueError, "settings should be a dictionary"); + return NULL; + } + + s = talloc_zero(mem_ctx, struct gensec_settings); + if (!s) return NULL; + + py_hostname = PyDict_GetItemString(object, "target_hostname"); + if (!py_hostname) { + PyErr_SetString(PyExc_ValueError, "settings.target_hostname not found"); + return NULL; + } + + py_lp_ctx = PyDict_GetItemString(object, "lp_ctx"); + if (!py_lp_ctx) { + PyErr_SetString(PyExc_ValueError, "settings.lp_ctx not found"); + return NULL; + } + + s->target_hostname = PyUnicode_AsUTF8(py_hostname); + s->lp_ctx = lpcfg_from_py_object(s, py_lp_ctx); + return s; +} + +static PyObject *py_gensec_start_client(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + NTSTATUS status; + PyObject *self; + struct gensec_settings *settings; + const char *kwnames[] = { "settings", NULL }; + PyObject *py_settings = Py_None; + struct gensec_security *gensec; + TALLOC_CTX *frame; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O", discard_const_p(char *, kwnames), &py_settings)) + return NULL; + + frame = talloc_stackframe(); + + if (py_settings != Py_None) { + settings = settings_from_object(frame, py_settings); + if (settings == NULL) { + PyErr_NoMemory(); + TALLOC_FREE(frame); + return NULL; + } + } else { + settings = talloc_zero(frame, struct gensec_settings); + if (settings == NULL) { + PyErr_NoMemory(); + TALLOC_FREE(frame); + return NULL; + } + + settings->lp_ctx = loadparm_init_global(true); + if (settings->lp_ctx == NULL) { + PyErr_NoMemory(); + TALLOC_FREE(frame); + return NULL; + } + } + + status = gensec_init(); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + TALLOC_FREE(frame); + return NULL; + } + + status = gensec_client_start(frame, &gensec, settings); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + TALLOC_FREE(frame); + return NULL; + } + + self = pytalloc_steal(type, gensec); + TALLOC_FREE(frame); + + return (PyObject *)self; +} + +static PyObject *py_gensec_start_server(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + NTSTATUS status; + PyObject *self; + struct gensec_settings *settings = NULL; + const char *kwnames[] = { "settings", "auth_context", NULL }; + PyObject *py_settings = Py_None; + PyObject *py_auth_context = Py_None; + struct gensec_security *gensec; + struct auth4_context *auth_context = NULL; + TALLOC_CTX *frame; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OO", discard_const_p(char *, kwnames), &py_settings, &py_auth_context)) + return NULL; + + frame = talloc_stackframe(); + + if (py_settings != Py_None) { + settings = settings_from_object(frame, py_settings); + if (settings == NULL) { + PyErr_NoMemory(); + TALLOC_FREE(frame); + return NULL; + } + } else { + settings = talloc_zero(frame, struct gensec_settings); + if (settings == NULL) { + PyErr_NoMemory(); + TALLOC_FREE(frame); + return NULL; + } + + settings->lp_ctx = loadparm_init_global(true); + if (settings->lp_ctx == NULL) { + PyErr_NoMemory(); + TALLOC_FREE(frame); + return NULL; + } + } + + if (py_auth_context != Py_None) { + bool ok = py_check_dcerpc_type(py_auth_context, + "samba.auth", + "AuthContext"); + if (!ok) { + return NULL; + } + + auth_context = pytalloc_get_type(py_auth_context, + struct auth4_context); + if (!auth_context) { + PyErr_Format(PyExc_TypeError, + "Expected auth.AuthContext for auth_context argument, got %s", + pytalloc_get_name(py_auth_context)); + return NULL; + } + } + + status = gensec_init(); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + TALLOC_FREE(frame); + return NULL; + } + + status = gensec_server_start(frame, settings, auth_context, &gensec); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + TALLOC_FREE(frame); + return NULL; + } + + self = pytalloc_steal(type, gensec); + TALLOC_FREE(frame); + + return self; +} + +static PyObject *py_gensec_set_target_hostname(PyObject *self, PyObject *args) +{ + struct gensec_security *security = pytalloc_get_type(self, struct gensec_security); + char *target_hostname; + NTSTATUS status; + + if (!PyArg_ParseTuple(args, "s", &target_hostname)) + return NULL; + + status = gensec_set_target_hostname(security, target_hostname); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + return NULL; + } + + Py_RETURN_NONE; +} + +static PyObject *py_gensec_set_target_service(PyObject *self, PyObject *args) +{ + struct gensec_security *security = pytalloc_get_type(self, struct gensec_security); + char *target_service; + NTSTATUS status; + + if (!PyArg_ParseTuple(args, "s", &target_service)) + return NULL; + + status = gensec_set_target_service(security, target_service); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + return NULL; + } + + Py_RETURN_NONE; +} + +static PyObject *py_gensec_set_target_service_description(PyObject *self, PyObject *args) +{ + struct gensec_security *security = pytalloc_get_type(self, struct gensec_security); + char *target_service_description; + NTSTATUS status; + + if (!PyArg_ParseTuple(args, "s", &target_service_description)) + return NULL; + + status = gensec_set_target_service_description(security, + target_service_description); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + return NULL; + } + + Py_RETURN_NONE; +} + +static PyObject *py_gensec_set_credentials(PyObject *self, PyObject *args) +{ + PyObject *py_creds = Py_None; + struct cli_credentials *creds; + struct gensec_security *security = pytalloc_get_type(self, struct gensec_security); + NTSTATUS status; + + if (!PyArg_ParseTuple(args, "O", &py_creds)) + return NULL; + + creds = PyCredentials_AsCliCredentials(py_creds); + if (!creds) { + PyErr_Format( + PyExc_TypeError, + "Expected samba.credentials for credentials argument, " + "got %s", pytalloc_get_name(py_creds)); + return NULL; + } + + status = gensec_set_credentials(security, creds); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + return NULL; + } + + Py_RETURN_NONE; +} + +static PyObject *py_gensec_session_info(PyObject *self, + PyObject *Py_UNUSED(ignored)) +{ + TALLOC_CTX *mem_ctx; + NTSTATUS status; + PyObject *py_session_info; + struct gensec_security *security = pytalloc_get_type(self, struct gensec_security); + struct auth_session_info *info; + if (security->ops == NULL) { + PyErr_SetString(PyExc_RuntimeError, "no mechanism selected"); + return NULL; + } + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + return PyErr_NoMemory(); + } + + status = gensec_session_info(security, mem_ctx, &info); + if (NT_STATUS_IS_ERR(status)) { + talloc_free(mem_ctx); + PyErr_SetNTSTATUS(status); + return NULL; + } + + py_session_info = py_return_ndr_struct("samba.dcerpc.auth", "session_info", + info, info); + talloc_free(mem_ctx); + return py_session_info; +} + +static PyObject *py_gensec_session_key(PyObject *self, + PyObject *Py_UNUSED(ignored)) +{ + TALLOC_CTX *mem_ctx; + NTSTATUS status; + struct gensec_security *security = pytalloc_get_type(self, struct gensec_security); + DATA_BLOB session_key = data_blob_null; + static PyObject *session_key_obj = NULL; + + if (security->ops == NULL) { + PyErr_SetString(PyExc_RuntimeError, "no mechanism selected"); + return NULL; + } + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + return PyErr_NoMemory(); + } + + status = gensec_session_key(security, mem_ctx, &session_key); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(mem_ctx); + PyErr_SetNTSTATUS(status); + return NULL; + } + + session_key_obj = PyBytes_FromStringAndSize((const char *)session_key.data, + session_key.length); + talloc_free(mem_ctx); + return session_key_obj; +} + +static PyObject *py_gensec_start_mech_by_name(PyObject *self, PyObject *args) +{ + char *name; + struct gensec_security *security = pytalloc_get_type(self, struct gensec_security); + NTSTATUS status; + + if (!PyArg_ParseTuple(args, "s", &name)) + return NULL; + + status = gensec_start_mech_by_name(security, name); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + return NULL; + } + + Py_RETURN_NONE; +} + +static PyObject *py_gensec_start_mech_by_sasl_name(PyObject *self, PyObject *args) +{ + char *sasl_name; + struct gensec_security *security = pytalloc_get_type(self, struct gensec_security); + NTSTATUS status; + + if (!PyArg_ParseTuple(args, "s", &sasl_name)) + return NULL; + + status = gensec_start_mech_by_sasl_name(security, sasl_name); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + return NULL; + } + + Py_RETURN_NONE; +} + +static PyObject *py_gensec_start_mech_by_authtype(PyObject *self, PyObject *args) +{ + int authtype, level; + struct gensec_security *security = pytalloc_get_type(self, struct gensec_security); + NTSTATUS status; + if (!PyArg_ParseTuple(args, "ii", &authtype, &level)) + return NULL; + + status = gensec_start_mech_by_authtype(security, authtype, level); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + return NULL; + } + + Py_RETURN_NONE; +} + +static PyObject *py_gensec_want_feature(PyObject *self, PyObject *args) +{ + int feature; + struct gensec_security *security = pytalloc_get_type(self, struct gensec_security); + /* This is i (and declared as an int above) by design, as they are handled as an integer in python */ + if (!PyArg_ParseTuple(args, "i", &feature)) + return NULL; + + gensec_want_feature(security, feature); + + Py_RETURN_NONE; +} + +static PyObject *py_gensec_have_feature(PyObject *self, PyObject *args) +{ + int feature; + struct gensec_security *security = pytalloc_get_type(self, struct gensec_security); + /* This is i (and declared as an int above) by design, as they are handled as an integer in python */ + if (!PyArg_ParseTuple(args, "i", &feature)) + return NULL; + + if (gensec_have_feature(security, feature)) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; +} + +static PyObject *py_gensec_set_max_update_size(PyObject *self, PyObject *args) +{ + struct gensec_security *security = pytalloc_get_type(self, struct gensec_security); + unsigned int max_update_size = 0; + + if (!PyArg_ParseTuple(args, "I", &max_update_size)) + return NULL; + + gensec_set_max_update_size(security, max_update_size); + + Py_RETURN_NONE; +} + +static PyObject *py_gensec_max_update_size(PyObject *self, + PyObject *Py_UNUSED(ignored)) +{ + struct gensec_security *security = pytalloc_get_type(self, struct gensec_security); + unsigned int max_update_size = gensec_max_update_size(security); + + return PyLong_FromLong(max_update_size); +} + +static PyObject *py_gensec_update(PyObject *self, PyObject *args) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx; + DATA_BLOB in, out; + PyObject *py_bytes, *result, *py_in; + struct gensec_security *security = pytalloc_get_type(self, struct gensec_security); + PyObject *finished_processing; + char *data = NULL; + Py_ssize_t len; + int err; + + if (!PyArg_ParseTuple(args, "O", &py_in)) + return NULL; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + return PyErr_NoMemory(); + } + + err = PyBytes_AsStringAndSize(py_in, &data, &len); + if (err) { + talloc_free(mem_ctx); + return NULL; + } + + /* + * Make a copy of the input buffer, as gensec_update may modify its + * input argument. + */ + in = data_blob_talloc(mem_ctx, data, len); + if (!in.data) { + talloc_free(mem_ctx); + return PyErr_NoMemory(); + } + + status = gensec_update(security, mem_ctx, in, &out); + + if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED) + && !NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + talloc_free(mem_ctx); + return NULL; + } + py_bytes = PyBytes_FromStringAndSize((const char *)out.data, + out.length); + talloc_free(mem_ctx); + + if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + finished_processing = Py_False; + } else { + finished_processing = Py_True; + } + + result = PyTuple_Pack(2, finished_processing, py_bytes); + Py_XDECREF(py_bytes); + return result; +} + +static PyObject *py_gensec_wrap(PyObject *self, PyObject *args) +{ + NTSTATUS status; + + TALLOC_CTX *mem_ctx; + DATA_BLOB in, out; + PyObject *ret, *py_in; + struct gensec_security *security = pytalloc_get_type(self, struct gensec_security); + + if (!PyArg_ParseTuple(args, "O", &py_in)) + return NULL; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + return PyErr_NoMemory(); + } + + if (!PyBytes_Check(py_in)) { + talloc_free(mem_ctx); + PyErr_Format(PyExc_TypeError, "bytes expected"); + return NULL; + } + in.data = (uint8_t *)PyBytes_AsString(py_in); + in.length = PyBytes_Size(py_in); + + status = gensec_wrap(security, mem_ctx, &in, &out); + + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + talloc_free(mem_ctx); + return NULL; + } + + ret = PyBytes_FromStringAndSize((const char *)out.data, out.length); + talloc_free(mem_ctx); + return ret; +} + + +static PyObject *py_gensec_unwrap(PyObject *self, PyObject *args) +{ + NTSTATUS status; + + TALLOC_CTX *mem_ctx; + DATA_BLOB in, out; + PyObject *ret, *py_in; + struct gensec_security *security = pytalloc_get_type(self, struct gensec_security); + char *data = NULL; + Py_ssize_t len; + int err; + + if (!PyArg_ParseTuple(args, "O", &py_in)) + return NULL; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + return PyErr_NoMemory(); + } + + err = PyBytes_AsStringAndSize(py_in, &data, &len); + if (err) { + talloc_free(mem_ctx); + return NULL; + } + + /* + * Make a copy of the input buffer, as gensec_unwrap may modify its + * input argument. + */ + in = data_blob_talloc(mem_ctx, data, len); + if (!in.data) { + talloc_free(mem_ctx); + return PyErr_NoMemory(); + } + + status = gensec_unwrap(security, mem_ctx, &in, &out); + + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + talloc_free(mem_ctx); + return NULL; + } + + ret = PyBytes_FromStringAndSize((const char *)out.data, out.length); + talloc_free(mem_ctx); + return ret; +} + +static PyObject *py_gensec_sig_size(PyObject *self, PyObject *args) +{ + struct gensec_security *security = pytalloc_get_type(self, struct gensec_security); + Py_ssize_t data_size = 0; + size_t sig_size = 0; + + if (!PyArg_ParseTuple(args, "n", &data_size)) { + return NULL; + } + + sig_size = gensec_sig_size(security, data_size); + + return PyLong_FromSize_t(sig_size); +} + +static PyObject *py_gensec_sign_packet(PyObject *self, PyObject *args) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx = NULL; + Py_ssize_t data_length = 0; + Py_ssize_t pdu_length = 0; + DATA_BLOB data, pdu, sig; + PyObject *py_sig; + struct gensec_security *security = pytalloc_get_type(self, struct gensec_security); + + if (!PyArg_ParseTuple(args, "z#z#", &data.data, &data_length, &pdu.data, &pdu_length)) { + return NULL; + } + data.length = data_length; + pdu.length = pdu_length; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + return PyErr_NoMemory(); + } + + status = gensec_sign_packet(security, mem_ctx, + data.data, data.length, + pdu.data, pdu.length, &sig); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + talloc_free(mem_ctx); + return NULL; + } + + py_sig = PyBytes_FromStringAndSize((const char *)sig.data, sig.length); + talloc_free(mem_ctx); + return py_sig; +} + +static PyObject *py_gensec_check_packet(PyObject *self, PyObject *args) +{ + NTSTATUS status; + Py_ssize_t data_length = 0; + Py_ssize_t pdu_length = 0; + Py_ssize_t sig_length = 0; + DATA_BLOB data, pdu, sig; + struct gensec_security *security = pytalloc_get_type(self, struct gensec_security); + + if (!PyArg_ParseTuple(args, "z#z#z#", + &data.data, &data_length, + &pdu.data, &pdu_length, + &sig.data, &sig_length)) { + return NULL; + } + data.length = data_length; + pdu.length = pdu_length; + sig.length = sig_length; + + status = gensec_check_packet(security, + data.data, data.length, + pdu.data, pdu.length, &sig); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + return NULL; + } + + Py_RETURN_NONE; +} + +static PyMethodDef py_gensec_security_methods[] = { + { "start_client", PY_DISCARD_FUNC_SIG(PyCFunction, + py_gensec_start_client), + METH_VARARGS|METH_KEYWORDS|METH_CLASS, + "S.start_client(settings) -> gensec" }, + { "start_server", PY_DISCARD_FUNC_SIG(PyCFunction, + py_gensec_start_server), + METH_VARARGS|METH_KEYWORDS|METH_CLASS, + "S.start_server(auth_ctx, settings) -> gensec" }, + { "set_credentials", (PyCFunction)py_gensec_set_credentials, METH_VARARGS, + "S.set_credentials(credentials)" }, + { "set_target_hostname", (PyCFunction)py_gensec_set_target_hostname, METH_VARARGS, + "S.set_target_hostname(target_hostname) \n This sets the Kerberos target hostname to obtain a ticket for." }, + { "set_target_service", (PyCFunction)py_gensec_set_target_service, METH_VARARGS, + "S.set_target_service(target_service) \n This sets the Kerberos target service to obtain a ticket for. The default value is 'host'" }, + { "set_target_service_description", (PyCFunction)py_gensec_set_target_service_description, METH_VARARGS, + "S.set_target_service_description(target_service_description) \n This description is set server-side and used in authentication and authorization logs. The default value is that provided to set_target_service() or None."}, + { "session_info", (PyCFunction)py_gensec_session_info, METH_NOARGS, + "S.session_info() -> info" }, + { "session_key", (PyCFunction)py_gensec_session_key, METH_NOARGS, + "S.session_key() -> key" }, + { "start_mech_by_name", (PyCFunction)py_gensec_start_mech_by_name, METH_VARARGS, + "S.start_mech_by_name(name)" }, + { "start_mech_by_sasl_name", (PyCFunction)py_gensec_start_mech_by_sasl_name, METH_VARARGS, + "S.start_mech_by_sasl_name(name)" }, + { "start_mech_by_authtype", (PyCFunction)py_gensec_start_mech_by_authtype, METH_VARARGS, + "S.start_mech_by_authtype(authtype, level)" }, + { "get_name_by_authtype", (PyCFunction)py_get_name_by_authtype, METH_VARARGS, + "S.get_name_by_authtype(authtype) -> name\nLookup an auth type." }, + { "want_feature", (PyCFunction)py_gensec_want_feature, METH_VARARGS, + "S.want_feature(feature)\n Request that GENSEC negotiate a particular feature." }, + { "have_feature", (PyCFunction)py_gensec_have_feature, METH_VARARGS, + "S.have_feature()\n Return True if GENSEC negotiated a particular feature." }, + { "set_max_update_size", (PyCFunction)py_gensec_set_max_update_size, METH_VARARGS, + "S.set_max_update_size(max_size) \n Some mechs can fragment update packets, needs to be use before the mech is started." }, + { "max_update_size", (PyCFunction)py_gensec_max_update_size, METH_NOARGS, + "S.max_update_size() \n Return the current max_update_size." }, + { "update", (PyCFunction)py_gensec_update, METH_VARARGS, + "S.update(blob_in) -> (finished, blob_out)\nPerform one step in a GENSEC dance. Repeat with new packets until finished is true or exception." }, + { "wrap", (PyCFunction)py_gensec_wrap, METH_VARARGS, + "S.wrap(blob_in) -> blob_out\nPackage one clear packet into a wrapped GENSEC packet." }, + { "unwrap", (PyCFunction)py_gensec_unwrap, METH_VARARGS, + "S.unwrap(blob_in) -> blob_out\nPerform one wrapped GENSEC packet into a clear packet." }, + { "sig_size", (PyCFunction)py_gensec_sig_size, METH_VARARGS, + "S.sig_size(data_size) -> sig_size\nSize of the DCERPC packet signature" }, + { "sign_packet", (PyCFunction)py_gensec_sign_packet, METH_VARARGS, + "S.sign_packet(data, whole_pdu) -> sig\nSign a DCERPC packet." }, + { "check_packet", (PyCFunction)py_gensec_check_packet, METH_VARARGS, + "S.check_packet(data, whole_pdu, sig)\nCheck a DCERPC packet." }, + {0} +}; + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + .m_name = "gensec", + .m_doc = "Generic Security Interface.", + .m_size = -1, +}; + +static PyTypeObject Py_Security = { + .tp_name = "gensec.Security", + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_methods = py_gensec_security_methods, +}; + +MODULE_INIT_FUNC(gensec) +{ + PyObject *m; + + if (pytalloc_BaseObject_PyType_Ready(&Py_Security) < 0) + return NULL; + + m = PyModule_Create(&moduledef); + if (m == NULL) + return NULL; + + PyModule_AddObject(m, "FEATURE_SESSION_KEY", PyLong_FromLong(GENSEC_FEATURE_SESSION_KEY)); + PyModule_AddObject(m, "FEATURE_SIGN", PyLong_FromLong(GENSEC_FEATURE_SIGN)); + PyModule_AddObject(m, "FEATURE_SEAL", PyLong_FromLong(GENSEC_FEATURE_SEAL)); + PyModule_AddObject(m, "FEATURE_DCE_STYLE", PyLong_FromLong(GENSEC_FEATURE_DCE_STYLE)); + PyModule_AddObject(m, "FEATURE_ASYNC_REPLIES", PyLong_FromLong(GENSEC_FEATURE_ASYNC_REPLIES)); + PyModule_AddObject(m, "FEATURE_DATAGRAM_MODE", PyLong_FromLong(GENSEC_FEATURE_DATAGRAM_MODE)); + PyModule_AddObject(m, "FEATURE_SIGN_PKT_HEADER", PyLong_FromLong(GENSEC_FEATURE_SIGN_PKT_HEADER)); + PyModule_AddObject(m, "FEATURE_NEW_SPNEGO", PyLong_FromLong(GENSEC_FEATURE_NEW_SPNEGO)); + + Py_INCREF(&Py_Security); + PyModule_AddObject(m, "Security", (PyObject *)&Py_Security); + + return m; +} diff --git a/source4/auth/gensec/wscript_build b/source4/auth/gensec/wscript_build new file mode 100644 index 0000000..20271f1 --- /dev/null +++ b/source4/auth/gensec/wscript_build @@ -0,0 +1,41 @@ +#!/usr/bin/env python + +bld.SAMBA_SUBSYSTEM('gensec_util', + source='gensec_tstream.c', + deps='tevent-util tevent samba-util LIBTSOCKET', + autoproto='gensec_proto.h') + +gensec_krb5_sources = 'gensec_krb5_heimdal.c' +if bld.CONFIG_SET('SAMBA_USES_MITKDC'): + gensec_krb5_sources = 'gensec_krb5_mit.c' + +bld.SAMBA_MODULE('gensec_krb5', + source='gensec_krb5.c ' + gensec_krb5_sources, + subsystem='gensec', + init_function='gensec_krb5_init', + deps='samba-credentials authkrb5 com_err', + internal_module=False, + enabled=bld.AD_DC_BUILD_IS_ENABLED() + ) + +bld.SAMBA_SUBSYSTEM('gensec_krb5_helpers', + source='gensec_krb5_helpers.c', + deps='gensec_krb5', + enabled=bld.AD_DC_BUILD_IS_ENABLED()) + +bld.SAMBA_MODULE('gensec_gssapi', + source='gensec_gssapi.c', + subsystem='gensec', + init_function='gensec_gssapi_init', + deps='gssapi samba-credentials authkrb5 com_err' + ) + + +pytalloc_util = bld.pyembed_libname('pytalloc-util') +pyparam_util = bld.pyembed_libname('pyparam_util') + +bld.SAMBA_PYTHON('pygensec', + source='pygensec.c', + deps='gensec %s %s' % (pytalloc_util, pyparam_util), + realname='samba/gensec.so' + ) |