summaryrefslogtreecommitdiffstats
path: root/source4/auth/gensec
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-05 17:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-05 17:47:29 +0000
commit4f5791ebd03eaec1c7da0865a383175b05102712 (patch)
tree8ce7b00f7a76baa386372422adebbe64510812d4 /source4/auth/gensec
parentInitial commit. (diff)
downloadsamba-4f5791ebd03eaec1c7da0865a383175b05102712.tar.xz
samba-4f5791ebd03eaec1c7da0865a383175b05102712.zip
Adding upstream version 2:4.17.12+dfsg.upstream/2%4.17.12+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source4/auth/gensec')
-rw-r--r--source4/auth/gensec/gensec_gssapi.c1729
-rw-r--r--source4/auth/gensec/gensec_gssapi.h69
-rw-r--r--source4/auth/gensec/gensec_krb5.c1133
-rw-r--r--source4/auth/gensec/gensec_krb5.h10
-rw-r--r--source4/auth/gensec/gensec_krb5_heimdal.c102
-rw-r--r--source4/auth/gensec/gensec_krb5_helpers.c72
-rw-r--r--source4/auth/gensec/gensec_krb5_helpers.h32
-rw-r--r--source4/auth/gensec/gensec_krb5_internal.h47
-rw-r--r--source4/auth/gensec/gensec_krb5_mit.c102
-rw-r--r--source4/auth/gensec/gensec_tstream.c616
-rw-r--r--source4/auth/gensec/gensec_tstream.h40
-rw-r--r--source4/auth/gensec/pygensec.c779
-rw-r--r--source4/auth/gensec/wscript_build41
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'
+ )