summaryrefslogtreecommitdiffstats
path: root/source4/auth
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--source4/auth/auth.h210
-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
-rw-r--r--source4/auth/kerberos/kerberos-notes.txt760
-rw-r--r--source4/auth/kerberos/kerberos-porting-to-mit-notes.txt803
-rw-r--r--source4/auth/kerberos/kerberos.h87
-rw-r--r--source4/auth/kerberos/kerberos_credentials.h37
-rw-r--r--source4/auth/kerberos/kerberos_pac.c496
-rw-r--r--source4/auth/kerberos/kerberos_util.c648
-rw-r--r--source4/auth/kerberos/krb5_init_context.c885
-rw-r--r--source4/auth/kerberos/krb5_init_context.h79
-rw-r--r--source4/auth/kerberos/srv_keytab.c406
-rw-r--r--source4/auth/kerberos/wscript_build26
-rw-r--r--source4/auth/ntlm/auth.c847
-rw-r--r--source4/auth/ntlm/auth_anonymous.c161
-rw-r--r--source4/auth/ntlm/auth_developer.c216
-rw-r--r--source4/auth/ntlm/auth_sam.c1210
-rw-r--r--source4/auth/ntlm/auth_server_service.c29
-rw-r--r--source4/auth/ntlm/auth_simple.c217
-rw-r--r--source4/auth/ntlm/auth_util.c183
-rw-r--r--source4/auth/ntlm/auth_winbind.c322
-rw-r--r--source4/auth/ntlm/wscript_build51
-rw-r--r--source4/auth/pyauth.c541
-rw-r--r--source4/auth/pyauth.h29
-rw-r--r--source4/auth/sam.c1737
-rw-r--r--source4/auth/samba_server_gensec.c152
-rw-r--r--source4/auth/session.c427
-rw-r--r--source4/auth/session.h79
-rw-r--r--source4/auth/system_session.c432
-rw-r--r--source4/auth/tests/heimdal_unwrap_des.c1244
-rw-r--r--source4/auth/tests/kerberos.c123
-rw-r--r--source4/auth/tests/sam.c2746
-rw-r--r--source4/auth/unix_token.c228
-rw-r--r--source4/auth/wscript_build94
-rw-r--r--source4/auth/wscript_configure4
46 files changed, 20281 insertions, 0 deletions
diff --git a/source4/auth/auth.h b/source4/auth/auth.h
new file mode 100644
index 0000000..6b7db99
--- /dev/null
+++ b/source4/auth/auth.h
@@ -0,0 +1,210 @@
+/*
+ Unix SMB/CIFS implementation.
+ Standardised Authentication types
+ Copyright (C) Andrew Bartlett 2001
+ Copyright (C) Stefan Metzmacher 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/>.
+*/
+
+#ifndef _SAMBA_AUTH_H
+#define _SAMBA_AUTH_H
+
+#include "librpc/gen_ndr/ndr_krb5pac.h"
+#include "librpc/gen_ndr/auth.h"
+#include "../auth/common_auth.h"
+
+extern const char *krbtgt_attrs[];
+extern const char *server_attrs[];
+extern const char *user_attrs[];
+
+union netr_Validation;
+struct netr_SamBaseInfo;
+struct netr_SamInfo3;
+struct loadparm_context;
+
+/* modules can use the following to determine if the interface has changed
+ * please increment the version number after each interface change
+ * with a comment and maybe update struct auth_critical_sizes.
+ */
+/* version 1 - version from samba 3.0 - metze */
+/* version 2 - initial samba4 version - metze */
+/* version 3 - subsequent samba4 version - abartlet */
+/* version 4 - subsequent samba4 version - metze */
+/* version 0 - till samba4 is stable - metze */
+#define AUTH4_INTERFACE_VERSION 0
+
+struct auth_method_context;
+struct auth4_context;
+struct auth_session_info;
+struct ldb_dn;
+struct smb_krb5_context;
+
+struct auth_operations {
+ const char *name;
+
+ /* Given the user supplied info, check if this backend want to handle the password checking */
+
+ NTSTATUS (*want_check)(struct auth_method_context *ctx, TALLOC_CTX *mem_ctx,
+ const struct auth_usersupplied_info *user_info);
+
+ /* Given the user supplied info, check a password */
+
+ struct tevent_req *(*check_password_send)(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct auth_method_context *ctx,
+ const struct auth_usersupplied_info *user_info);
+ NTSTATUS (*check_password_recv)(struct tevent_req *subreq,
+ TALLOC_CTX *mem_ctx,
+ struct auth_user_info_dc **interim_info,
+ bool *authoritative);
+};
+
+struct auth_method_context {
+ struct auth_method_context *prev, *next;
+ struct auth4_context *auth_ctx;
+ const struct auth_operations *ops;
+ int depth;
+ void *private_data;
+};
+
+/* this structure is used by backends to determine the size of some critical types */
+struct auth_critical_sizes {
+ int interface_version;
+ int sizeof_auth_operations;
+ int sizeof_auth_methods;
+ int sizeof_auth_context;
+ int sizeof_auth_usersupplied_info;
+ int sizeof_auth_user_info_dc;
+};
+
+ NTSTATUS encrypt_user_info(TALLOC_CTX *mem_ctx, struct auth4_context *auth_context,
+ enum auth_password_state to_state,
+ const struct auth_usersupplied_info *user_info_in,
+ const struct auth_usersupplied_info **user_info_encrypted);
+
+#include "auth/session.h"
+#include "auth/unix_token_proto.h"
+#include "auth/system_session_proto.h"
+#include "libcli/security/security.h"
+
+struct ldb_message;
+struct ldb_context;
+struct gensec_security;
+struct cli_credentials;
+
+NTSTATUS auth_get_challenge(struct auth4_context *auth_ctx, uint8_t chal[8]);
+NTSTATUS authsam_account_ok(TALLOC_CTX *mem_ctx,
+ struct ldb_context *sam_ctx,
+ uint32_t logon_parameters,
+ struct ldb_dn *domain_dn,
+ struct ldb_message *msg,
+ const char *logon_workstation,
+ const char *name_for_logs,
+ bool allow_domain_trust,
+ bool password_change);
+
+struct auth_session_info *system_session(struct loadparm_context *lp_ctx);
+NTSTATUS authsam_make_user_info_dc(TALLOC_CTX *mem_ctx, struct ldb_context *sam_ctx,
+ const char *netbios_name,
+ const char *domain_name,
+ const char *dns_domain_name,
+ struct ldb_dn *domain_dn,
+ struct ldb_message *msg,
+ DATA_BLOB user_sess_key, DATA_BLOB lm_sess_key,
+ struct auth_user_info_dc **_user_info_dc);
+NTSTATUS authsam_update_user_info_dc(TALLOC_CTX *mem_ctx,
+ struct ldb_context *sam_ctx,
+ struct auth_user_info_dc *user_info_dc);
+NTSTATUS auth_system_session_info(TALLOC_CTX *parent_ctx,
+ struct loadparm_context *lp_ctx,
+ struct auth_session_info **_session_info) ;
+
+NTSTATUS auth_context_create_methods(TALLOC_CTX *mem_ctx, const char * const *methods,
+ struct tevent_context *ev,
+ struct imessaging_context *msg,
+ struct loadparm_context *lp_ctx,
+ struct ldb_context *sam_ctx,
+ struct auth4_context **auth_ctx);
+const char **auth_methods_from_lp(TALLOC_CTX *mem_ctx, struct loadparm_context *lp_ctx);
+
+NTSTATUS auth_context_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct imessaging_context *msg,
+ struct loadparm_context *lp_ctx,
+ struct auth4_context **auth_ctx);
+NTSTATUS auth_context_create_for_netlogon(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct imessaging_context *msg,
+ struct loadparm_context *lp_ctx,
+ struct auth4_context **auth_ctx);
+
+NTSTATUS auth_check_password(struct auth4_context *auth_ctx,
+ TALLOC_CTX *mem_ctx,
+ const struct auth_usersupplied_info *user_info,
+ struct auth_user_info_dc **user_info_dc,
+ uint8_t *pauthoritative);
+NTSTATUS auth4_init(void);
+NTSTATUS auth_register(TALLOC_CTX *mem_ctx, const struct auth_operations *ops);
+NTSTATUS server_service_auth_init(TALLOC_CTX *ctx);
+struct tevent_req *authenticate_ldap_simple_bind_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct imessaging_context *msg,
+ struct loadparm_context *lp_ctx,
+ struct tsocket_address *remote_address,
+ struct tsocket_address *local_address,
+ bool using_tls,
+ const char *dn,
+ const char *password);
+NTSTATUS authenticate_ldap_simple_bind_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct auth_session_info **session_info);
+NTSTATUS authenticate_ldap_simple_bind(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct imessaging_context *msg,
+ struct loadparm_context *lp_ctx,
+ struct tsocket_address *remote_address,
+ struct tsocket_address *local_address,
+ bool using_tls,
+ const char *dn,
+ const char *password,
+ struct auth_session_info **session_info);
+
+struct tevent_req *auth_check_password_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct auth4_context *auth_ctx,
+ const struct auth_usersupplied_info *user_info);
+NTSTATUS auth_check_password_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct auth_user_info_dc **user_info_dc,
+ uint8_t *pauthoritative);
+
+NTSTATUS auth_context_set_challenge(struct auth4_context *auth_ctx, const uint8_t chal[8], const char *set_by);
+
+NTSTATUS samba_server_gensec_start(TALLOC_CTX *mem_ctx,
+ struct tevent_context *event_ctx,
+ struct imessaging_context *msg_ctx,
+ struct loadparm_context *lp_ctx,
+ struct cli_credentials *server_credentials,
+ const char *target_service,
+ struct gensec_security **gensec_context);
+NTSTATUS samba_server_gensec_krb5_start(TALLOC_CTX *mem_ctx,
+ struct tevent_context *event_ctx,
+ struct imessaging_context *msg_ctx,
+ struct loadparm_context *lp_ctx,
+ struct cli_credentials *server_credentials,
+ const char *target_service,
+ struct gensec_security **gensec_context);
+
+#endif /* _SMBAUTH_H_ */
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'
+ )
diff --git a/source4/auth/kerberos/kerberos-notes.txt b/source4/auth/kerberos/kerberos-notes.txt
new file mode 100644
index 0000000..6954129
--- /dev/null
+++ b/source4/auth/kerberos/kerberos-notes.txt
@@ -0,0 +1,760 @@
+Copyright Andrew Bartlett <abartlet@samba.org> 2005-2009
+Copyright Donald T. Davis <don@mit.edu>
+
+Released under the GPLv3
+
+Important context for porting to MIT
+------------------------------------
+
+This document should be read in conjunction with the Samba4 source code.
+DAL and KDC requirements are expressed (as an implementation against Heimdal's
+HDB abstraction layer) in Samba4's source4/kdc/hdb-samba4.c in particular.
+hbd-samba4.c is the biggest piece of samba-to-krb glue layer, so the main
+part of the port to MIT is to replace hdb-samba4 with a similar glue layer
+that's designed for MIT's code.
+
+PAC requirements are implemeneted in source4/kdc/pac-glue.c
+
+The plugins (both of the above are Heimdal plugins) for the above are loaded
+in source4/kdc/kdc.c
+
+For GSSAPI requirements, see auth/gensec/gensec_gssapi.c (the consumer of
+GSSAPI in Samba4)
+
+For Kerberos requirements, see auth/kerberos/krb5_init_context.c .
+
+Samba has its own credentials system, wrapping GSS creds, just as GSS
+creds wrap around krb5 creds. For the interaction between Samba4 credentials
+system and GSSAPI and Kerberos see auth/credentials/credentials_krb5.c .
+
+AllowedWorkstationNames and Krb5
+--------------------------------
+
+Microsoft uses the clientAddresses *multiple value* field in the krb5
+protocol (particularly the AS_REQ) to communicate the client's netbios
+name (legacy undotted name, <14 chars)
+
+This is (my guess) to support the userWorkstations field (in user's AD record).
+The idea is to support client-address restrictions, as was standard in NT:
+The AD authentication server I imagine checks the netbios address against
+this userWorkstations value (BTW, the NetLogon server does this, too).
+
+The checking of this field implies a little of the next question:
+
+Is a DAL the layer we need?
+---------------------------
+
+Looking at what we need to pass around, I don't think
+the DAL is even the right layer; what we really want
+is to create an account-authorization abstraction layer
+(e.g., is this account permitted to login to this computer,
+at this time?).
+Here is how we ended up doing this in Heimdal:
+ * We created a separate plugin, with this API:
+ typedef struct hdb_entry_ex {
+ void *ctx;
+ hdb_entry entry;
+ void (*free_entry)(krb5_context, struct hdb_entry_ex *);
+ } hdb_entry_ex;
+
+ * The void *ctx is a "private pointer," provided by the 'get' method's
+ hdb_entry_ex retval. The APIs below use the void *ctx so as to find
+ additional information about the user, not contained in the hdb_entry
+ structure. Both the provider and the APIs below understand how to cast
+ the private void *ctx pointer.
+
+ typedef krb5_error_code
+ (*krb5plugin_windc_pac_generate)(void *, krb5_context,
+ struct hdb_entry_ex *, krb5_pac*);
+ typedef krb5_error_code
+ (*krb5plugin_windc_pac_verify)(void *, krb5_context,
+ const krb5_principal,
+ struct hdb_entry_ex *,
+ struct hdb_entry_ex *,
+ krb5_pac *);
+ typedef krb5_error_code
+ (*krb5plugin_windc_client_access)(void *,
+ krb5_context,
+ struct hdb_entry_ex *,
+ KDC_REQ *, krb5_data *);
+
+ * (The krb5_data* here is critical, so that samba's KDC can return
+ the right NTSTATUS code in the 'error string' returned to the client.
+ Otherwise, the windows client won't get the right error message to
+ the user (such as 'password expired' etc). The pure Kerberos error
+ is not enough)
+
+ typedef struct krb5plugin_windc_ftable {
+ int minor_version;
+ krb5_error_code (*init)(krb5_context, void **);
+ void (*fini)(void *);
+ rb5plugin_windc_pac_generate pac_generate;
+ krb5plugin_windc_pac_verify pac_verify;
+ krb5plugin_windc_client_access client_access;
+ } krb5plugin_windc_ftable;
+ This API has some heimdal-specific stuff, that'll have to change when we port the plugin to MIT krb.
+ * 1st callback (pac_generate) creates an initial PAC from the user's AD record.
+ * 2nd callback (pac_verify) check that a PAC is correctly signed, add additional groups (for cross-realm tickets) and re-sign with the key of the target kerberos service's account
+ * 3rd callback (client_access) perform additional access checks, such as allowedWorkstations and account expiry.
+ * for example, to register this plugin, use the kdc's standard
+ plugin-system at Samba4's initialisation:
+ /* first, setup the table of callback pointers */
+ /* Registar WinDC hooks */
+ ret = krb5_plugin_register(krb5_context,
+ PLUGIN_TYPE_DATA, "windc",
+ &windc_plugin_table);
+ /* once registered, the KDC will invoke the callbacks */
+ /* while preparing each new ticket (TGT or app-tkt) */
+ * an alternate way to register the plugin is with a config-file that names
+ a DSO (Dynamically Shared Object).
+
+
+This plugin helps bridge an important gap: The user's AD record is much
+richer than the Heimdal HDB format allows, so we do AD-specific access
+control checks in an AD-specific layer (ie, the plugin), not in the
+DB-agnostic KDC server.
+
+In Novell's pure DAL approach, the DAL only read in the principalName as
+the key, so it had trouble performing access-control decisions on things
+other than the name (like the addresses).
+
+There is another, currently unhandled challenge in this area - the need to handle
+bad password counts (and good password notification), so that a single policy can
+be applied against all means of checking a password (NTLM, Kerberos, LDAP Simple
+bind etc)
+
+The Original work by Novell in creating a DAL did not seem to provide a way to
+update the PW counts information. Nevertheless, we know that this is very much
+required (and may have been addressed in Simo's subsequent IPA-KDC design),
+because in Samba3+eDirectory, great lengths are taken to update this information.
+
+GSSAPI layer requirements
+-------------------------
+
+Welcome to the wonderful world of canonicalisation
+
+The MIT Krb5 libs (including GSSAPI) do not support kinit returning a different
+realm to what the client asked for, even just in case differences.
+
+Heimdal has the same problem, and this too applies to the krb5 layer, not
+just gssapi.
+
+there's two kinds of name-canonicalization that can occur:
+ * lower-to-upper case conversion, because Windows domain names are
+ usually in upper case;
+ * an unrecognizable subsitution of names, such as might happen when
+ a user requests a ticket for a NetBIOS domain name, but gets back
+ a ticket for the corresponging FQDN.
+
+As developers, we should test if the AD KDC's name-canonicalisation
+can be turned off with the KDCOption flags in the AS-REQ or TGS-REQ;
+Windows clients always send the Canonicalize flags as KDCOption values.
+
+Old Clients (samba3 and HPUX clients) use 'selfmade' gssapi/krb5 tokens
+for use in the CIFS session setup. these hand-crafted ASN.1 packets don't
+follow rfc1964 perfectly, so server-side krblib code has to be flexible
+enough to accept these bent tokens.
+It turns out that Windows' GSSAPI server-side code is sloppy about checking
+some GSSAPI tokens' checksums. During initial work to implement an AD client,
+it was easier to make an acceptable solution (to Windows servers) than to
+correctly implement the GSSAPI specification, particularly on top of the
+(inflexible) MIT Kerberos API. It did not seem possible to write a correct,
+separate GSSAPI implementation on top of MIT Kerberos's public krb5lib API,
+and at the time, the effort did not need to extend beyond what Windows would
+require.
+
+The upshot is that old Samba3 clients send GSSAPI tokens bearing incorrect
+checksums, which AD's Krb5lib cheerfully accepts (but accepts the good checksums,
+too). Similarly, Samba4's heimdal krb5lib accepts these incorrect checksums.
+Accordingly, if MIT's krb5lib wants to interoperate with the old Samba3 clients,
+then MIT's library will have to do the same.
+
+Because these old clients use krb5_mk_req()
+the app-servers get a chksum field depending on the encryption type, but that's
+wrong for GSSAPI (see rfc 1964 section 1.1.1). The Checksum type 8003 should
+be used in the Authenticator of the AP-REQ! That (correct use of the 8003 type)
+would allows the channel bindings, the GCC_C_* req_flags and optional delegation
+tickets to be passed from the client to the server. However windows doesn't
+seem to care whether the checksum is of the wrong type, and for CIFS SessionSetups,
+it seems that the req_flags are just set to 0.
+This deviant checksum can't work for LDAP connections with sign or seal, or
+for any DCERPC connection, because those connections do not require the
+negotiation of GSS-Wrap paraemters (signing or sealing of whole payloads).
+Note: CIFS has an independent SMB signing mechanism, using the Kerberos key.
+
+see heimdal/lib/gssapi/krb5/accept_sec_context.c, lines 390-450 or so.
+
+This bug-compatibility is likely to be controversial in the kerberos community,
+but a similar need for bug-compatibility arose around MIT's & Heimdal's both
+failing to support TGS_SUBKEYs correctly, and there are numerous other cases.
+see https://lists.anl.gov/pipermail/ietf-krb-wg/2009-May/007630.html
+
+So MIT's krb5lib needs to also support old clients!
+
+Principal Names, long and short names
+-------------------------------------
+
+As far as servicePrincipalNames are concerned, these are not
+canonicalised by AD's KDC, except as regards the realm in the reply.
+That is, the client gets back the principal it asked for, with
+the realm portion 'fixed' to uppercase, long form.
+Heimdal doesn't canonicalize names, but Samba4 does some canonicalization:
+For hostnames and usernames, Samba4 canonicalizes the requested name only
+for the LDAP principal-lookup, but then Samba4 returns the retrieved LDAP
+record with the request's original, uncanonicalized hostname replacing the
+canonicalized name that actually was retrieved.
+AB says that for usernames, Samba4 used to return the canonicalized username,
+as retrieved from LDAP. The reason for the different treatment was that
+the user needs to present his own canonicalized username to servers, for
+ACL-matching. For hostnames this isn't necessary.
+So, for bug-compatibility, we may need to optionally disable any
+namne-canonicalization that MIT's KDC does.
+
+The short name of the realm seems to be accepted for at least AS_REQ
+operations, but the AD KDC always performs realm-canonicalisation,
+which converts the short realm-name to the canonical long form.
+So, this causes pain for current krb client libraries.
+
+The canonicalisation of names matters not only for the KDC, but also
+for code that has to deal with keytabs.
+With credential-caches, when canonicalization leads to cache-misses,
+the client just asks for new credentials for the variant server-name.
+This could happen, for example, if the user asks to access the server
+twice, using different variants of the server-name.
+
+We also need to handle type 10 names (NT-ENTERPRISE), which are a full
+principal name in the principal field, unrelated to the realm.
+The principal field contains both principal & realm names, while the
+realm field contains a realm name, too, possibly different.
+For example, an NT-ENTERPRISE principal name might look like:
+joeblow@microsoft.com@NTDEV.MICROSOFT.COM ,
+<--principal field-->|<----realm name--->|
+
+Where joe@microsoft.com is the leading portion, and NTDEV.MICROSOFT.COM is
+the realm. This is used for the 'email address-like login-name' feature of AD.
+
+HOST/ Aliases
+-------------
+
+There is another post somewhere (ref lost for the moment) that details
+where in active directory the list of stored aliases for HOST/ is.
+This list is read & parsed by the AD KDC, so as to allow any of these
+aliased ticket-requests to use the HOST/ key.
+
+Samba4 currently has set:
+sPNMappings: host=ldap,dns,cifs,http (but dns's presence is a bug, somehow)
+
+AD actually has ~50 entries:
+
+sPNMappings: host=alerter,appmgmt,cisvc,clipsrv,browser,dhcp,dnscache,replicat
+ or,eventlog,eventsystem,policyagent,oakley,dmserver,dns,mcsvc,fax,msiserver,i
+ as,messenger,netlogon,netman,netdde,netddedsm,nmagent,plugplay,protectedstora
+ ge,rasman,rpclocator,rpc,rpcss,remoteaccess,rsvp,samss,scardsvr,scesrv,seclog
+ on,scm,dcom,cifs,spooler,snmp,schedule,tapisrv,trksvr,trkwks,ups,time,wins,ww
+ w,http,w3svc,iisadmin,msdtc
+
+Domain members that expect the longer list will break in damb4, as of 6/09.
+AB says he'll try to fix this right away.
+
+For example, this is how HTTP/, and CIFS/ can use HOST/ without
+any explicit entry in the servicePrincipalName attribute
+
+
+For example, the application-server might have (on its AD record):
+servicePrincipalName: HOST/my.computer@MY.REALM
+
+but the client asks for a ticket to cifs/my.computer@MY.REALM
+AD looks in LDAP for both name-variants
+AD then transposes cifs -> host after performing the lookup in the
+directory (for the original name), then looks for host/my.computer@MY.REALM
+
+for hostnames & usernames, alternate names appear as extra values in
+the multivalued "principal name" attributes:
+ - For hostnames, the other names (other than it's short name, implied
+ from the CN), is stored in the servicePrincipalName
+ - For usernames, the other names are stored in the userPrincipalName
+ attribute, and can be full e-mail address like names, such as
+ joe@microsoft.com (see above).
+
+Jean-Baptiste.Marchand@hsc.fr reminds me:
+> This is the SPNMappings attribute in Active Directory:
+> http://msdn.microsoft.com/library/en-us/adschema/adschema/a_spnmappings.asp
+
+We implement this in hdb-ldb.
+
+Implicit names for Win2000 Accounts
+-----------------------------------
+AD's records for servers are keyed by CN or by servicePrincipalName,
+but for win2k boxes, these records don't include servicePrincipalName,
+so, the CN attribute is used instead.
+Despite not having a servicePrincipalName on accounts created
+by computers running win2000, it appears we are expected
+to have an implicit mapping from host/computer.full.name and
+host/computer to the computer's entry in the AD LDAP database
+(ie, be able to obtain tickets for that host name in the KDC).
+
+Returned Salt for PreAuthentication
+-----------------------------------
+
+When the KDC replies for pre-authentication, it returns the Salt,
+which may be in the form of a principalName that is in no way
+connected with the current names. (ie, even if the userPrincipalName
+and samAccountName are renamed, the old salt is returned).
+
+This is the kerberos standard salt, kept in the 'Key'. The
+AD generation rules are found in a Mail from Luke Howard dated
+10 Nov 2004. The MIT glue layer doesn't really need to care about
+these salt-handling details; the samba4 code & the LDAP backend
+will conspire to make sure that MIT's KDC gets correct salts.
+
+
+From: Luke Howard <lukeh@padl.com>
+Organization: PADL Software Pty Ltd
+To: lukeh@padl.com
+Date: Wed, 10 Nov 2004 13:31:21 +1100
+Cc: huaraz@moeller.plus.com, samba-technical@lists.samba.org
+Subject: Re: Samba-3.0.7-1.3E Active Directory Issues
+-------
+
+Did some more testing, it appears the behaviour has another
+explanation. It appears that the standard Kerberos password salt
+algorithm is applied in Windows 2003, just that the source principal
+name is different.
+
+Here is what I've been able to deduce from creating a bunch of
+different accounts:
+[SAM name in this mail means the AD attribute samAccountName .
+ E.g., jbob for a user and jbcomputer$ for a computer.]
+
+[UPN is the AD userPrincipalName attribute. For example, jbob@mydomain.com]
+
+Type of account Principal for Salting
+========================================================================
+Computer Account host/<SAM-Name-Without-$>.realm@REALM
+User Account Without UPN <SAM-Name>@REALM
+User Account With UPN <LHS-Of-UPN>@REALM
+
+Note that if the computer account's SAM account name does not include
+the trailing '$', then the entire SAM account name is used as input to
+the salting principal. Setting a UPN for a computer account has no
+effect.
+
+It seems to me odd that the RHS of the UPN is not used in the salting
+principal. For example, a user with UPN foo@mydomain.com in the realm
+MYREALM.COM would have a salt of MYREALM.COMfoo. Perhaps this is to
+allow a user's UPN suffix to be changed without changing the salt. And
+perhaps using the UPN for salting signifies a move away SAM names and
+their associated constraints.
+
+For more information on how UPNs relate to the Kerberos protocol,
+see:
+
+http://www.ietf.org/proceedings/01dec/I-D/draft-ietf-krb-wg-kerberos-referrals-02.txt
+
+-- Luke
+
+
+
+Heimdal oddities
+----------------
+
+Heimdal is built such that it should be able to serve multiple realms
+at the same time. This isn't relevant for Samba's use, but it shows
+up in a lot of generalisations throughout the code.
+
+Samba4's code originally tried internally to make it possible to use
+Heimdal's multi-realms-per-KDC ability, but this was ill-conceived,
+and AB has recently (6/09) ripped the last of that multi-realms
+stuff out of samba4. AB says that in AD, it's not really possible
+to make this work; several AD components structurally assume that
+there's one realm per KDC. However, we do use this to support
+canonicalization of realm-names: case variations, plus long-vs-short
+variants of realm-names.
+
+Other odd things:
+ - Heimdal supports multiple passwords on a client account: Samba4
+ seems to call hdb_next_enctype2key() in the pre-authentication
+ routines to allow multiple passwords per account in krb5.
+ (I think this was intended to allow multiple salts).
+ AD doesn't support this, so the MIT port shouldn't bother with
+ this.
+
+State Machine safety when using Kerberos and GSSAPI libraries
+-------------------------------------------------------------
+
+Samba's client-side & app-server-side libraries are built on a giant
+state machine, and as such have very different
+requirements to those traditionally expressed for kerberos and GSSAPI
+libraries.
+
+Samba requires all of the libraries it uses to be state machine safe in
+their use of internal data. This does not mean thread safe, and an
+application could be thread safe, but not state machine safe (if it
+instead used thread-local variables).
+
+So, what does it mean for a library to be state machine safe? This is
+mostly a question of context, and how the library manages whatever
+internal state machines it has. If the library uses a context
+variable, passed in by the caller, which contains all the information
+about the current state of the library, then it is safe. An example
+of this state is the sequence number and session keys for an ongoing
+encrypted session).
+
+The other issue affecting state machines is 'blocking' (waiting for a
+read on a network socket). Samba's non-blocking I/O doesn't like
+waiting for libkrb5 to go away for awhile to talk to the KDC.
+
+Samba4 provides a hook 'send_to_kdc', that allows Samba4 to take over the
+IO handling, and run other events in the meantime. This uses a
+'nested event context' (which presents the challenges that the kerberos
+library might be called again, while still in the send_to_kdc hook).
+
+Heimdal has this 'state machine safety' in parts, and we have modified
+the lorikeet branch to improve this behviour, when using a new,
+non-standard API to tunnelling a ccache (containing a set of tickets)
+through the gssapi, by temporarily casting the ccache pointer to a
+gss credential pointer.
+This new API is Heimdal's samba4-requested gss_krb5_import_cred() fcn;
+this will have to be rewritten or ported in the MIT port.
+
+This replaces an older scheme using the KRB5_CCACHE
+environment variable to get the same job done. This tunnelling trick
+enables a command-line app-client to run kinit tacitly, before running
+GSSAPI for service-authentication. This tunnelling trick avoids the
+more usual approach of keeping the ccache pointer in a global variable.
+
+No longer true; the krb5_context global is gone now:
+[Heimdal uses a per-context variable for the 'krb5_auth_context', which
+controls the ongoing encrypted connection, but does use global
+variables for the ubiquitous krb5_context parameter.]
+
+The modification that has added most to 'state machine safety' of
+GSSAPI is the addition of the gss_krb5_acquire_creds() function. This
+allows the caller to specify a keytab and ccache, for use by the
+GSSAPI code. Therefore there is no need to use global variables to
+communicate this information about keytab & ccache.
+
+At a more theoritical level (simply counting static and global
+variables) Heimdal is not state machine safe for the GSSAPI layer.
+(Heimdal is now (6/09) much more nearly free of globals.)
+The Krb5 layer alone is much closer, as far as I can tell, blocking
+excepted. .
+
+
+As an alternate to fixing MIT Kerberos for better safety in this area,
+a new design might be implemented in Samba, where blocking read/write
+is made to the KDC in another (fork()ed) child process, and the results
+passed back to the parent process for use in other non-blocking operations.
+
+To deal with blocking, we could have a fork()ed child per context,
+using the 'GSSAPI export context' function to transfer
+the GSSAPI state back into the main code for the wrap()/unwrap() part
+of the operation. This will still hit issues of static storage (one
+gss_krb5_context per process, and multiple GSSAPI encrypted sessions
+at a time) but these may not matter in practice.
+
+This approach has long been controversial in the Samba team.
+An alternate way would be to be implement E_AGAIN in libkrb5: similar
+to the way to way read() works with incomplete operations. to do this
+in libkrb5 would be difficult, but valuable.
+
+In the short-term, we deal with blocking by taking over the network
+send() and recv() functions, therefore making them 'semi-async'. This
+doesn't apply to DNS yet.These thread-safety context-variables will
+probably present porting problems, during the MIT port. This will
+probably be most of the work in the port to MIT.
+
+
+
+GSSAPI and Kerberos extensions
+------------------------------
+
+This is a general list of the other extensions we have made to / need from
+the kerberos libraries
+
+ - DCE_STYLE : Microsoft's hard-coded 3-msg Challenge/Response handshake
+ emulates DCE's preference for C/R. Microsoft calls this DCE_STYLE.
+ MIT already has this nowadays (6/09).
+
+ - gsskrb5_get_initiator_subkey() (return the exact key that Samba3
+ has always asked for. gsskrb5_get_subkey() might do what we need
+ anyway). This is necessary, because in some spots, Microsoft uses
+ raw Kerberos keys, outside the Kerberos protocls, and not using Kerberos
+ wrappings etc. Ie, as a direct input to MD5 and ARCFOUR, without using
+ the make_priv() or make_safe() calls.
+
+ - gsskrb5_acquire_creds() (takes keytab and/or ccache as input
+ parameters, see keytab and state machine discussion in prev section)
+
+Not needed anymore, because MIT's code now handles PACs fully:
+ - gss_krb5_copy_service_keyblock() (get the key used to actually
+ encrypt the ticket to the server, because the same key is used for
+ the PAC validation).
+ - gsskrb5_extract_authtime_from_sec_context (get authtime from
+ kerberos ticket)
+ - gsskrb5_extract_authz_data_from_sec_context (get authdata from
+ ticket, ie the PAC. Must unwrap the data if in an AD-IFRELEVENT)]
+The new function to handle the PAC fully
+ - gsskrb5_extract_authz_data_from_sec_context()
+
+Samba still needs this one:
+ - gsskrb5_wrap_size (find out how big the wrapped packet will be,
+ given input length).
+
+Keytab requirements
+-------------------
+
+Because windows machine account handling is very different to the
+traditional 'MIT' keytab operation.
+This starts when we look at the basics of the secrets handling:
+
+Samba file-servers can have many server-name simultaneously (kindof
+like web servers' software virtual hosting), but since these servers
+are running in AD, these names are free to be set up to all share
+the same secret key. In AD, host-sharing server names almost always
+share a secret key like this. In samba3, this key-sharing was optional, so
+some samba3 hosts' keytabs did hold multiple keys. samba4 abandons this
+traditional "old MIT" style of keytab, and only supports one key per keytab,
+and multiple server-names can use that keytab key in common.
+Heimdal offered "in-memory keytabs" for servers that use passwords.
+These server-side passwords were held in a Samba LDB database called secrets.ldb,
+and the heimdal library would be supplied the password from the ldb file and
+would construct an in-memory keytab struct containing the password,
+just as if the library had read an MIT-style keytab file.
+Unfortunately, only later, at recv_auth() time, would the heimdal library
+convert the PW into a salted-&-hashed AES key, by hashing 10,000 times with
+SHA-1. So, nowadays, this password-based in-memory keytab is seen as too
+slow, and is falling into disuse.
+
+Traditional 'MIT' behaviour is to use a keytab, containing salted key
+data, extracted from the KDC. (In this model, there is no 'service
+password', instead the keys are often simply application of random
+bytes). Heimdal also implements this behaviour.
+
+The windows model is very different - instead of sharing a keytab with
+each member server, a random utf-16 pseudo-textual password is stored
+for the whole machine.
+The password is set with non-kerberos mechanisms (particularly SAMR,
+a DCE-RPC service) and when interacting on a kerberos basis, the
+password is salted by the member server (ie, an AD server-host).
+(That is, no salt information appears to be conveyed from the AD KDC
+to the member server. ie, the member server must use the rule's
+described in Luke's mail above).
+
+pre-win7 AD and samba3/4 both use SAMR, an older protocol, to jumpstart
+the member server's PW-sharing with AD (the "windows domain-join process").
+This PW-sharing transfers only the PW's utf-16 text, without any salting
+or hashing, so that non-krb security mechanisms can use the same utf-16
+text PW. for windows 7, this domain-joining uses LDAP for PW-setting.
+
+In dealing with this model, we use both the traditional file
+keytab and in-MEMORY keytabs.
+
+When dealing with a windows KDC, the behaviour regarding case
+sensitivity and canonacolisation must be accomidated. This means that
+an incoming request to a member server may have a wide variety of
+service principal names. These include:
+
+machine$@REALM (samba clients)
+HOST/foo.bar@realm (win2k clients)
+HOST/foo@realm (win2k clients, using netbios)
+cifs/foo.bar@realm (winxp clients)
+cifs/foo@realm (winxp clients, using netbios)
+
+as well as all case variations on the above.
+
+Heimdal's GSSAPI expects to get a principal-name & a keytab, possibly containing
+multiple principals' different keys. However, AD has a different problem to
+solve, which is that the client may know the member-server by a non-canonicalized
+principal name, yet AD knows the keytab contains exactly one key, indexed by
+the canonical name. So, GSSAPI is unprepared to canonicalize the server-name
+that the cliet requested, and is also overprepared to do an unnecessary search
+through the keytab by principal-name. So samba's server-side GSSAPI calls game
+the GSSAPI, by supplying the server's known canonical name, and the one-key keytab.
+this doesn't really affect the port to mit-krb.
+
+Because the number of U/L case combinations got 'too hard' to put into a keytab in the
+traditional way (with the client to specify the name), we either
+pre-compute the keys into a traditional keytab or make an in-MEMORY
+keytab at run time. In both cases we specify the principal name to
+GSSAPI, which avoids the need to store duplicate principals.
+
+We use a 'private' keytab in our private dir, referenced from the
+secrets.ldb by default.
+
+Extra Heimdal functions used
+----------------------------
+these fcns didn't exist in the MIT code, years ago, when samba started.
+AB will try to build a final list of these fcns.
+
+(an attempt to list some of the Heimdal-specific functions I know we use)
+
+krb5_free_keyblock_contents()
+
+also a raft of prinicpal manipulation functions:
+
+Prncipal Manipulation
+---------------------
+
+Samba makes extensive use of the principal manipulation functions in
+Heimdal, including the known structure behind krb_principal and
+krb5_realm (a char *). for example,
+krb5_parse_name_flags(smb_krb5_context->krb5_context, name,
+ KRB5_PRINCIPAL_PARSE_MUST_REALM, &principal);
+krb5_princ_realm(smb_krb5_context->krb5_context, principal);
+krb5_unparse_name_flags(smb_krb5_context->krb5_context, principal,
+ KRB5_PRINCIPAL_UNPARSE_NO_REALM, &new_princ);
+These are needed for juggling the AD variant-structures for server names.
+
+Authz data extraction
+---------------------
+
+We use krb5_ticket_get_authorization_data_type(), and expect it to
+return the correct authz data, even if wrapped in an AD-IFRELEVENT container.
+
+KDC/hdb Extensions
+--------------
+
+We have modified Heimdal's 'hdb' interface to specify the 'class' of
+Principal being requested. This allows us to correctly behave with
+the different 'classes' of Principal name. This is necessary because
+of the AD structure, which uses very different record-structures
+for user-principals, trust principals & server-principals.
+
+We currently define 3 classes:
+ - client (kinit)
+ - server (tgt)
+ - krbtgt (kinit, tgt) the kdc's own ldap record
+
+I also now specify the kerberos principal as an explict parameter to LDB_fetch(),
+not an in/out value on the struct hdb_entry parameter itself.
+
+Private Data pointer (and windc hooks) (see above):
+ In addition, I have added a new interface hdb_fetch_ex(), which
+ returns a structure including a private data-pointer, which may be used
+ by the windc plugin inferface functions. The windc plugin provides
+ the hook for the PAC, as well as a function for the main access control routines.
+
+ A new windc plugin function should be added to increment the bad password counter
+ on failure.
+
+libkdc (doesn't matter for IPA; Samba invokes the Heimdal kdc as a library call,
+but this is just a convenience, and the MIT port can do otherwise w/o trouble.)
+------
+
+Samba4 needs to be built as a single binary (design requirement), and
+this should include the KDC. Samba also (and perhaps more
+importantly) needs to control the configuration environment of the
+KDC.
+
+The interface we have defined for libkdc allow for packet injection
+into the post-socket layer, with a defined krb5_context and
+kdb5_kdc_configuration structure. These effectively redirect the
+kerberos warnings, logging and database calls as we require.
+
+Using our socket lib (para 3 does matter for the send_to_kdc() plugin).
+See also the discussion about state machine safety above)
+--------------------
+
+An important detail in the use of libkdc is that we use samba4's own socket
+lib. This allows the KDC code to be as portable as the rest of samba
+(this cuts both ways), but far more importantly it ensures a
+consistancy in the handling of requests, binding to sockets etc.
+
+To handle TCP, we use of our socket layer in much the same way as
+we deal with TCP for CIFS. Tridge created a generic packet handling
+layer for this.
+
+For the client, samba4 likewise must take over the socket functions,
+so that our single thread smbd will not lock up talking to itself.
+(We allow processing while waiting for packets in our socket routines).
+send_to_kdc() presents to its caller the samba-style socket interface,
+but the MIT port will reimplement send_to_kdc(), and this routine will
+use internally the same socket library that MIT-krb uses.
+
+Kerberos logging support (this will require porting attention)
+------------------------
+
+Samba4 now (optionally in the main code, required for the KDC) uses the
+krb5_log_facility from Heimdal. This allows us to redirect the
+warnings and status from the KDC (and client/server kerberos code) to
+Samba's DEBUG() system.
+
+Similarly important is the Heimdal-specific krb5_get_error_string()
+function, which does a lot to reduce the 'administrator pain' level,
+by providing specific, english text-string error messages instead of
+just error code translations. (this isn't necessarty for the port,
+but it's more useful than MIT's default err-handling; make sure
+this works for MIT-krb)
+
+
+Short name rules
+----------------
+
+Samba is highly likely to be misconfigured, in many weird and
+interesting ways. As such, we have a patch for Heimdal that avoids
+DNS lookups on names without a . in them. This should avoid some
+delay and root server load. (this may need to be ported to MIT.)
+
+PAC Correctness
+---------------
+
+We now put the PAC into the TGT, not just the service ticket.
+
+Forwarded tickets
+-----------------
+
+We extract forwarded tickets from the GSSAPI layer, and put
+them into the memory-based credentials cache.
+We can then use them for proxy work.
+
+
+Kerberos TODO
+=============
+
+(Feel free to contribute to any of these tasks, or ask
+abartlet@samba.org about them).
+
+Lockout Control (still undone in samba4 on heimdal)
+--------------
+
+We need to get (either if PADL publishes their patch, or write our
+own) access control hooks in the Heimdal KDC. We need to lockout
+accounts (eg, after 10 failed PW-attemps), and perform other controls.
+This is standard AD behavior, that samba4 needs to get right, whether
+heimdal or MIT-krb is doing the ticket work.
+
+Gssmonger
+---------
+
+Microsoft has released a krb-specific testsuite called gssmonger,
+which tests interop. We should compile it against lorikeet-heimdal,
+MIT and see if we can build a 'Samba4' server for it.
+GSSMonger wasn't intended to be Windows-specific.
+
+Kpasswd server (kpasswd server is now finished, but not testsuite)
+--------------
+
+I have a partial kpasswd server which needs finishing, and a we need a
+client testsuite written, either via the krb5 API or directly against
+GENSEC and the ASN.1 routines.
+Samba4 likes to test failure-modes, not just successful behavior.
+
+Currently it only works for Heimdal, not MIT clients. This may be due
+to call ordering constraints.
+
+
+Correct TCP support
+-------------------
+
+Samba4 socket-library's current TCP support does not send back 'too large'
+error messages if the high bit is set. This is needed for a proposed extension
+mechanism (SSL-armored kinit, by Leif Johansson <leifj@it.su.se>),
+but is likewise unsupported in both current Heimdal and MIT.
+
+=========================================================================
+AB says MIT's 1.7 announcement about AD support covers Luke Howard's
+changes. It all should be easy for IPA to exploit/use during the port
+of Samba4 to MIT.
+AB says Likewise software will likely give us their freeware NTLM/MIT-krb
+implementation.
diff --git a/source4/auth/kerberos/kerberos-porting-to-mit-notes.txt b/source4/auth/kerberos/kerberos-porting-to-mit-notes.txt
new file mode 100644
index 0000000..9b478bb
--- /dev/null
+++ b/source4/auth/kerberos/kerberos-porting-to-mit-notes.txt
@@ -0,0 +1,803 @@
+Copyright Andrew Bartlett <abartlet@samba.org> 2005-2009
+Copyright Donald T. Davis <don@mit.edu> 2009
+
+Released under the GPLv3
+"Porting Samba4 to MIT-Krb"
+
+
+ From Idmwiki
+
+
+IPA v3 will use a version of Samba4 built on top of MIT's Kerberos
+implementation, instead of Heimdal's version of Kerberos.
+
+Task list summary for porting changes needed, from Andrew Bartlett:
+
+ * Rewrite or extend the LDAP driver that MIT-KDC will use.
+ * MIT KDC changes: rewrite DAL, add TGS-KBAC, enable PACs,...
+ * Full thread-safety for MIT's library code,
+ * Many small changes
+
+Task list, without explanations (the list with explanations is in the
+later sections of this document):
+
+Porting Samba4 to MIT-krb comprises four main chunks of work:
+ 1. Rewrite or extend the LDAP driver that MIT-KDC will use:
+ a. Our LDAP driver for the KDB needs to know how to do
+ Samba4's intricate canonicalization of server names,
+ user-names, and realm names.
+ b. AD-style aliases for HOST/ service names.
+ c. Implicit names for Win2k accounts.
+ d. Principal "types": client / server / krbtgs
+ e. Most or all of this code is in 3 source files,
+ ~1000 lines in all;
+ 2. MIT KDC changes
+ a. Rewrite the MIT KDC's Data-Abstraction Layer (DAL),
+ mostly because he MIT KDC needs to see& manipulate
+ more LDAP detail, on Samba4's behalf;
+ b. Add HBAC to the KDC's TGT-issuance, so that Samba4
+ can refuse TGTs to kinit, based on time-of-day&
+ IP-addr constraints;
+ c. turn on MIT-krb 1.7's PAC handling
+ d. add bad-password counts, for unified account-lockouts
+ across all authT methods (Krb, NTLM, LDAP simple bind,
+ etc)
+ 3. Make sure MIT's library code is more fully thread-safe,
+ by replacing all global and static variables with context
+ parameters for the library routines. This may already be
+ done.
+ 4. Many small changes (~15)
+ a. some extensions to MIT's libkrb5& GSSAPI libraries,
+ including GSSAPI ticket-forwarding
+ b. some refitting in Samba4's use of the MIT libraries;
+ c. make sure Samba4's portable socket API works,
+ including "packet too large" errors;
+ d. MIT's GSSAPI code should support some legacy Samba3
+ clients that present incorrectly-calculated checksums;
+ e. Samba4 app-server-host holds aUTF-16 PW, plus a
+ key bitstring;
+ f. in-memory-only credentials cache;
+ g. in-memory-only keytab (nice to have);
+ h. get OSS NTLM authT library (Likewise Software?);
+ i. special Heimdal-specific functions;
+ j. principal-manipulation functions;
+ k. special check for misconfigured Samba4 hostnames;
+ l. improved krb error-messages;
+ m. improved krb logging
+ n. MS GSSMonger test-suite
+ o. testsuite for kpasswd daemon
+
+0. Introduction: This document should be read alongside the Samba4
+source code, as follows:
+
+ * For DAL and KDC requirements, please see Samba4's
+ source4/kdc/hdb-samba4.c in particular. This file
+ is an implementation against Heimdal's HDB abstraction
+ layer, and is the biggest part of the samba-to-krb
+ glue layer, so the main part of the port to MIT is
+ to replace hdb-samba4 with a similar glue layer
+ that's designed for MIT's code.
+ * Samba4's PAC requirements are implemeneted in
+ source4/kdc/pac-glue.c
+ * Both of the above two layers are Heimdal plugins, and
+ both get loaded in source4/kdc/kdc.c
+ * For GSSAPI requirements, see auth/gensec/gensec_gssapi.c
+ (the consumer of GSSAPI in Samba4)
+ * For Kerberos library requirements, see
+ auth/kerberos/krb5_init_context.c
+ * Samba has its own credentials system, wrapping GSS creds,
+ just as GSS creds wrap around krb5 creds. For the
+ interaction between Samba4 credential system and GSSAPI
+ and Kerberos, see auth/credentials/credentials_krb5.
+
+1. Rewrite or extend the LDAP driver that MIT-KDC will use.
+
+ a. IPA'sLDAP driver for the KDB needs to know how to do
+ Samba4's intricate canonicalization of server names,
+ user-names, and realm names.
+ For hostnames& usernames, alternate names appear in
+ LDAP as extra values in the multivalued "principal name"
+ attributes:
+ * For a hostname, the alternate names (other than
+ the short name, implied from the CN), are stored in
+ the servicePrincipalName
+ * For a username, the alternate names are stored in
+ the userPrincipalName attribute, and can be long
+ email-address-like names, such as joe@microsoft.com
+ (see "Type 10 names," below).
+ GSSAPI layer requirements: Welcome to the wonderful
+ world of canonicalisation. The MIT Krb5 libs (including
+ GSSAPI) do not enable the AS to send kinit a TGT containing
+ a different realm-name than what the client asked for,
+ even in U/L case differences. Heimdal has the same problem,
+ and this applies to the krb5 layer too, not just GSSAPI.
+ There are two kinds of name-canonicalization that can
+ occur on Windows:
+ * Lower-to-upper case conversion, because Windows domain
+ names are usually in upper case;
+ * An unrecognizable subsitution of names, such as might
+ happen when a user requests a ticket for a NetBIOS domain
+ name, but gets back a ticket for the corresponging FQDN.
+ As developers, we should test if the AD KDC's name-canonical-
+ isation can be turned off with the KDCOption flags in the
+ AS-REQ or TGS-REQ; Windows clients always send the
+ Canonicalize flags as KDCOption values.
+ Principal Names, long and short names:
+ AD's KDC does not canonicalize servicePrincipalNames, except
+ for the realm in the KDC reply. That is, the client gets
+ back the principal it asked for, with the realm portion
+ 'fixed' to uppercase, long form.
+ Samba4 does some canonicalization, though Heimdal doesn't
+ canonicalize names itself: For hostnames and usernames,
+ Samba4 canonicalizes the requested name only for the LDAP
+ principal-lookup, but then Samba4 returns the retrieved LDAP
+ record with the request's original, uncanonicalized hostname
+ replacing the canonicalized name that actually was found.
+ Usernames: AndrewB says that Samba4 used to return
+ the canonicalized username exactly as retrieved from LDAP.
+ The reason Samba4 treated usernames differently was that
+ the user needs to present his own canonicalized username
+ to servers, for ACL-matching. For hostnames this isn't
+ necessary.
+ Realm-names: AD seems to accept a realm's short name
+ in krb-requests, at least for AS_REQ operations, but the
+ AD KDC always performs realm-canonicalisation, which
+ converts the short realm-name to the canonical long form.
+ So, this causes pain for current krb client libraries.
+ Punchline: For bug-compatibility, we may need to
+ selectively or optionally disable the MIT-KDC's name-
+ canonicalization.
+ Application-code:
+ Name-canonicalisation matters not only for the KDC, but
+ also for app-server-code that has to deal with keytabs.
+ Further, with credential-caches, canonicalization can
+ lead to cache-misses, but then the client just asks for
+ new credentials for the variant server-name. This could
+ happen, for example, if the user asks to access the
+ server twice, using different variants of the server-name.
+ Doubled realm-names: We also need to handle type 10
+ names (NT-ENTERPRISE), which are a full principal name
+ in the principal field, unrelated to the realm. The
+ principal field contains both principal& realm names,
+ while the realm field contains a realm name, too, possibly
+ different. For example, an NT-ENTERPRISE principal name
+ might look like: joeblow@microsoft.com@NTDEV.MICROSOFT.COM ,
+ <--principal field-->|<----realm name--->|
+ Where joe@microsoft.com is the leading portion, and
+ NTDEV.MICROSOFT.COM is the realm. This is used for the
+ 'email address-like login-name' feature of AD.
+ b.AD-style aliases for HOST/ service names.
+ AD keeps a list of service-prefixed aliases for the host's
+ principal name. The AD KDC reads& parses this list, so
+ as to allow the aliased services to share the HOST/ key.
+ This means that every ticket-request for a service-alias
+ gets a service-ticket encrypted in the HOST/ key.
+ For example, this is how HTTP/ and CIFS/ can use the
+ HOST/ AD-LDAP entry, without any explicitly CIFS-prefixed
+ entry in the host's servicePrincipalName attribute. In the
+ app-server host's AD record, the servicePrincipalName says
+ only HOST/my.computer@MY.REALM , but the client asks
+ for CIFS/my.omputer@MY.REALM tickets. So, AD looks in
+ LDAP for both name-variants, and finds the HOST/ version,
+ In AD's reply, AD replaces the HOST/ prefix with CIFS/ .
+ We implement this in hdb-ldb.
+ (TBD: Andrew, is this correct?:)
+ List of HOST/ aliases: Samba4 currently uses only a small
+ set of HOST/ aliases: sPNMappings: host=ldap,dns,cifs,http .
+ Also, dns's presence in this list is a bug, somehow.
+ AD's real list has 53 entries:
+ sPNMappings: host=alerter,appmgmt,cisvc,clipsrv,browser,
+ dhcp,dnscache,replicator,eventlog,eventsystem,policyagent,
+ oakley,dmserver,dns,mcsvc,fax,msiserver,ias,messenger,
+ netlogon,netman,netdde,netddedsm,nmagent,plugplay,
+ protectedstorage,rasman,rpclocator,rpc,rpcss,remoteaccess,
+ rsvp,samss,scardsvr,scesrv,seclogon,scm,dcom,cifs,spooler,
+ snmp,schedule,tapisrv,trksvr,trkwks,ups,time,wins,www,
+ http,w3svc,iisadmin,msdtc
+ Domain members that expect the longer list will break in
+ Samba4, as of 6/09. AB says he'll try to fix this right
+ away. There is another post somewhere (ref lost for the
+ moment) that details where in active directory the long
+ list of stored aliases for HOST/ is.
+ c.Implicit names for Win2000 Accounts: AD keys its
+ server-records by CN or by servicePrincipalName, but a
+ win2k box's server-entry in LDAP doesn't include the
+ servicePrincipalName attribute, So, win2k server-accounts
+ are keyed by the CN attribute instead. Because AD's LDAP
+ doesn't have a servicePrincipalName for win2k servers'
+ entries, Samba4 has to have an implicit mapping from
+ host/computer.full.name and from host/computer, to the
+ computer's CN-keyed entry in the AD LDAP database, so to
+ be able to find the win2k server's host name in the KDB.
+ d.Principal "types":
+ We have modified Heimdal's 'hdb' interface to specify
+ the 'class' of Principal being requested. This allows
+ us to correctly behave with the different 'classes' of
+ Principal name. This is necessary because of AD's LDAP
+ structure, which uses very different record-structures
+ for user-principals, trust principals& server-principals.
+ We currently define 3 classes:
+ * client (kinit)
+ * server (tgt)
+ * krbtgt the TGS's own ldap record
+ Samba4 also now specifies the kerberos principal as an
+ explicit parameter to LDB_fetch(), not an in/out value
+ on the struct hdb_entry parameter itself.
+ e. Most or all of this LDAP driver code is in three source
+ files, ~1000 lines in all. These files are in
+ samba4/kdc :
+ * hdb-samba4.c (samba4-to-kdb glue-layer plugin)
+ * pac-glue.c (samba4's pac glue-layer plugin)
+ * kdc.c (loads the above two plugins).
+
+2. MIT KDC changes
+
+ a.Data-Abstraction Layer (DAL): It would be good to
+ rewrite or circumvent the MIT KDC's DAL, mostly because
+ the MIT KDC needs to see& manipulate more LDAP detail,
+ on Samba4's behalf. AB says the MIT DAL may serve well-
+ enough, though, mostly as is. AB says Samba4 will need
+ the private pointer part of the KDC plugin API, though,
+ or the PAC generation won't work (see sec.2.c, below).
+ * MIT's DAL calls lack context parameters (as of 2006),
+ so presumably they rely instead on global storage, and
+ aren't fully thread-safe.
+ * In Novell's pure DAL approach, the DAL only read in the
+ principalName as the key, so it had trouble performing
+ access-control decisions on things other than the user's
+ name (like the addresses).
+ * Here's why Samba4 needs more entry detail than the DAL
+ provides: The AS needs to have ACL rules that will allow
+ a TGT to a user only when the user logs in from the
+ right desktop addresses, and at the right times of day.
+ This coarse-granularity access-control could be enforced
+ directly by the KDC's LDAP driver, without Samba having
+ to see the entry's pertinent authZ attributes. But,
+ there's a notable exception: a user whose TGT has
+ expired, and who wants to change his password, should
+ be allowed a restricted-use TGT that gives him access
+ to the kpasswd service. This ACL-logic could be buried
+ in the LDAP driver, in the same way as the TGS ACL could
+ be enforced down there, but to do so would just be even
+ uglier than it was to put the TGS's ACL-logic in the driver.
+ * Yet another complaint is that the DAL always pulls an
+ entire LDAP entry, non-selectively. The current DAL
+ is OK for Samba4's purposes, because Samba4 only reads,
+ and doesn't write, the KDB. But this all-or-nothing
+ retrieval hurts the KDC's performance, and would do so
+ even more, if Samba had to use the DAL to change KDB
+ entries.
+ b.Add HBAC to the KDC's TGT-issuance, so that Samba4 can
+ refuse TGTs to kinit, based on time-of-day& IP-address
+ constraints. AB asks, "Is a DAL the layer we need?"
+ Looking at what we need to pass around, AB doesn't think
+ the DAL is the right layer; what we really want instead
+ is to create an account-authorization abstraction layer
+ (e.g., is this account permitted to login to this computer,
+ at this time?). Samba4 ended up doing account-authorization
+ inside Heimdal, via a specialized KDC plugin. For a summary
+ description of this plugin API, see Appendix 2.
+ c. Turn on MIT-krb 1.7'sPAC handling.
+ In addition, I have added a new interface hdb_fetch_ex(),
+ which returns a structure including a private data-pointer,
+ which may be used by the windc plugin inferface functions.
+ The windc plugin provides the hook for the PAC.
+ d. Samba4 needsaccess control hooks in the Heimdal& MIT
+ KDCs. We need to lockout accounts (eg, after 10 failed PW-
+ attemps), and perform other controls. This is standard
+ AD behavior, that Samba4 needs to get right, whether
+ Heimdal or MIT-krb is doing the ticket work.
+ - If PADL doesn't publish their patch for this,
+ we'll need to write our own.
+ - The windc plugin proivides a function for the main
+ access control routines. A new windc plugin function
+ should be added to increment the bad password counter
+ on failure.
+ - Samba4 doesn't yet handle bad password counts (or good
+ password notification), so that a single policy can be
+ applied against all means of checking a password (NTLM,
+ Kerberos, LDAP Simple Bind, etc). Novell's original DAL
+ did not provide a way to update the PW counts information.
+ - Nevertheless, we know that this is very much required in
+ AD, because Samba3 + eDirectory goes to great lengths to
+ update this information. This may have been addressed in
+ Simo's subsequent IPA-KDC design),
+ * AllowedWorkstationNames and Krb5: Microsoft uses the
+ clientAddresses *multiple value* field in the krb5
+ protocol (particularly the AS_REQ) to communicate the
+ client's netbios name (legacy undotted name,<14 chars)
+ AB guesses that this is to support the userWorkstations
+ field (in user's AD record). The idea is to support
+ client-address restrictions, as was standard in NT:
+ The AD authentication server probably checks the netbios
+ address against this userWorkstations value (BTW, the
+ NetLogon server does this, too).
+
+3. State Machine safety
+when using Kerberos and GSSAPI libraries
+
+ * Samba's client-side& app-server-side libraries are built
+ on a giant state machine, and as such have very different
+ requirements to those traditionally expressed for kerberos
+ and GSSAPI libraries.
+ * Samba requires all of the libraries it uses to be "state
+ machine safe" in their use of internal data. This does not
+ necessarily mean "thread safe," and an application could be
+ thread safe, but not state machine safe (if it instead used
+ thread-local variables). so, if MIT's libraries were made
+ thread-safe only by inserting spinlock() code, then the MIT
+ libraries aren't yet "state machine safe."
+ * So, what does it mean for a library to be state machine safe?
+ This is mostly a question of context, and how the library manages
+ whatever internal state machines it has. If the library uses a
+ context variable, passed in by the caller, which contains all
+ the information about the current state of the library, then it
+ is safe. An example of this state is the sequence number and
+ session keys for an ongoing encrypted session).
+ * The other issue affecting state machines is 'blocking' (waiting for a
+ read on a network socket). Samba's non-blocking I/O doesn't like
+ waiting for libkrb5 to go away for awhile to talk to the KDC.
+ * Samba4 provides a hook 'send_to_kdc', that allows Samba4 to take over the
+ IO handling, and run other events in the meantime. This uses a
+ 'nested event context' (which presents the challenges that the kerberos
+ library might be called again, while still in the send_to_kdc hook).
+ * Heimdal has this 'state machine safety' in parts, and we have modified
+ Samba4's lorikeet branch to improve this behaviour, when using a new,
+ non-standard API to tunnelling a ccache (containing a set of tickets)
+ through the gssapi, by temporarily casting the ccache pointer to a
+ gss credential pointer. This new API is Heimdal's samba4-requested
+ gss_krb5_import_cred() fcn; this will have to be rewritten or ported
+ in the MIT port.
+ * This tunnelling trick replaces an older scheme using the KRB5_CCACHE
+ environment variable to get the same job done. The tunnelling trick
+ enables a command-line app-client to run kinit tacitly, before running
+ GSSAPI for service-authentication. The tunnelling trick avoids the
+ more usual approach of keeping the ccache pointer in a global variable.
+ * [Heimdal uses a per-context variable for the 'krb5_auth_context',
+ which controls the ongoing encrypted connection, but does use global
+ variables for the ubiquitous krb5_context parameter. (No longer true,
+ because the krb5_context global is gone now.)]
+ * The modification that has added most to 'state machine safety' of
+ GSSAPI is the addition of the gss_krb5_acquire_creds() function.
+ This allows the caller to specify a keytab and ccache, for use by
+ the GSSAPI code. Therefore there is no need to use global variables
+ to communicate this information about keytab& ccache.
+ * At a more theoretical level (simply counting static and global
+ variables) Heimdal is not state machine safe for the GSSAPI layer.
+ (But Heimdal is now (6/09) much more nearly free of globals.)
+ The Krb5 layer alone is much closer, as far as I can tell, blocking
+ excepted. .
+ * As an alternate to fixing MIT Kerberos for better safety in this area,
+ a new design might be implemented in Samba, where blocking read/write
+ is made to the KDC in another (fork()ed) child process, and the results
+ passed back to the parent process for use in other non-blocking operations.
+ * To deal with blocking, we could have a fork()ed child per context,
+ using the 'GSSAPI export context' function to transfer
+ the GSSAPI state back into the main code for the wrap()/unwrap() part
+ of the operation. This will still hit issues of static storage (one
+ gss_krb5_context per process, and multiple GSSAPI encrypted sessions
+ at a time) but these may not matter in practice.
+ * This approach has long been controversial in the Samba team.
+ An alternate way would be to be implement E_AGAIN in libkrb5: similar
+ to the way to way read() works with incomplete operations. to do this
+ in libkrb5 would be difficult, but valuable.
+ * In the short-term, we deal with blocking by taking over the network
+ send() and recv() functions, therefore making them 'semi-async'. This
+ doesn't apply to DNS yet.These thread-safety context-variables will
+ probably present porting problems, during the MIT port. This will
+ probably be most of the work in the port to MIT.
+ This may require more thorough thread-safe-ing work on the MIT libraries.
+
+4. Many small changes (~15)
+
+ a. Some extensions to MIT'slibkrb5& GSSAPI libraries, including
+ GSSAPI ticket-forwarding: This is a general list of the other
+ extensions Samba4 has made to / need from the kerberos libraries
+ * DCE_STYLE : Microsoft's hard-coded 3-msg Challenge/Response handshake
+ emulates DCE's preference for C/R. Microsoft calls this DCE_STYLE.
+ MIT already has this nowadays (6/09).
+ * gsskrb5_get_initiator_subkey() (return the exact key that Samba3
+ has always asked for. gsskrb5_get_subkey() might do what we need
+ anyway). This routine is necessary, because in some spots,
+ Microsoft uses raw Kerberos keys, outside the Kerberos protocols,
+ as a direct input to MD5 and ARCFOUR, without using the make_priv()
+ or make_safe() calls, and without GSSAPI wrappings etc.
+ * gsskrb5_acquire_creds() (takes keytab and/or ccache as input
+ parameters, see keytab and state machine discussion in prev section)
+ * The new function to handle the PAC fully
+ gsskrb5_extract_authz_data_from_sec_context()
+ need to test that MIT's PAC-handling code checks the PAC's signature.
+ * gsskrb5_wrap_size (Samba still needs this one, for finding out how
+ big the wrapped packet will be, given input length).
+ b. Some refitting in Samba4's use of the MIT libraries;
+ c. Make sure Samba4'sportable socket API works:
+ * An important detail in the use of libkdc is that we use samba4's
+ own socket lib. This allows the KDC code to be as portable as
+ the rest of samba, but more importantly it ensures consistancy
+ in the handling of requests, binding to sockets etc.
+ * To handle TCP, we use of our socket layer in much the same way as
+ we deal with TCP for CIFS. Tridge created a generic packet handling
+ layer for this.
+ * For the client, samba4 likewise must take over the socket functions,
+ so that our single thread smbd will not lock up talking to itself.
+ (We allow processing while waiting for packets in our socket routines).
+ send_to_kdc() presents to its caller the samba-style socket interface,
+ but the MIT port will reimplement send_to_kdc(), and this routine will
+ use internally the same socket library that MIT-krb uses.
+ * The interface we have defined for libkdc allows for packet injection
+ into the post-socket layer, with a defined krb5_context and
+ kdb5_kdc_configuration structure. These effectively redirect the
+ kerberos warnings, logging and database calls as we require.
+ * Samba4 socket-library's current TCP support does not send back
+ 'too large' error messages if the high bit is set. This is
+ needed for a proposed extension mechanism (SSL-armored kinit,
+ by Leif Johansson<leifj@it.su.se>), but is currently unsupported
+ in both Heimdal and MIT.
+ d. MIT's GSSAPI code should support some legacy Samba3
+ clients that presentincorrectly-calculated checksums.
+ * Old Clients (samba3 and HPUX clients) use 'selfmade'
+ gssapi/krb5 tokens for use in the CIFS session setup.
+ These hand-crafted ASN.1 packets don't follow rfc1964
+ (GSSAPI) perfectly, so server-side krblib code has to
+ be flexible enough to accept these bent tokens.
+ * It turns out that Windows' GSSAPI server-side code is
+ sloppy about checking some GSSAPI tokens' checksums.
+ During initial work to implement an AD client, it was
+ easier to make an acceptable solution (acceptable to
+ Windows servers) than to correctly implement the
+ GSSAPI specification, particularly on top of the
+ (inflexible) MIT Kerberos API. It did not seem
+ possible to write a correct, separate GSSAPI
+ implementation on top of MIT Kerberos's public
+ krb5lib API, and at the time, the effort did not
+ need to extend beyond what Windows would require.
+ * The upshot is that old Samba3 clients send GSSAPI
+ tokens bearing incorrect checksums, which AD's
+ GSSAPI library cheerfully accepts (but accepts
+ the good checksums, too). Similarly, Samba4's
+ Heimdal krb5lib accepts these incorrect checksums.
+ Accordingly, if MIT's krb5lib wants to interoperate
+ with the old Samba3 clients, then MIT's library will
+ have to do the same.
+ * Because these old clients use krb5_mk_req()
+ the app-servers get a chksum field depending on the
+ encryption type, but that's wrong for GSSAPI (see
+ rfc 1964 section 1.1.1). The Checksum type 8003
+ should be used in the Authenticator of the AP-REQ!
+ That (correct use of the 8003 type) would allow
+ the channel bindings, the GCC_C_* req_flags and
+ optional delegation tickets to be passed from the
+ client to the server. However windows doesn't seem
+ to care whether the checksum is of the wrong type,
+ and for CIFS SessionSetups, it seems that the
+ req_flags are just set to 0. This deviant checksum
+ can't work for LDAP connections with sign or seal,
+ or for any DCERPC connection, because those
+ connections do not require the negotiation of
+ GSS-Wrap paraemters (signing or sealing of whole
+ payloads). Note: CIFS has an independent SMB
+ signing mechanism, using the Kerberos key.
+ * For the code that handles the incorrect& correct
+ checksums, see heimdal/lib/gssapi/krb5/accept_sec_context.c,
+ lines 390-450 or so.
+ * This bug-compatibility is likely to be controversial
+ in the kerberos community, but a similar need for bug-
+ compatibility arose around MIT's& Heimdal's both
+ failing to support TGS_SUBKEYs correctly, and there
+ are numerous other cases.
+ seehttps://lists.anl.gov/pipermail/ietf-krb-wg/2009-May/007630.html
+ * So, MIT's krb5lib needs to also support old clients!
+ e. Samba4 app-server-host holds aUTF-16 PW, plus a key bitstring;
+ See Appendix 1, "Keytab Requirements."
+ f.In-memory-only credentials cache for forwarded tickets
+ Samba4 extracts forwarded tickets from the GSSAPI layer,
+ and puts them into the memory-based credentials cache.
+ We can then use them for proxy work. This needs to be
+ ported, if the MIT library doesn't do it yet.
+ g.In-memory-only keytab (nice to have):
+ Heimdal used to offer "in-memory keytabs" for servers that use
+ passwords. These server-side passwords were held in a Samba LDB
+ database called secrets.ldb . The heimdal library would fetch
+ the server's password from the ldb file and would construct an
+ in-memory keytab struct containing the password, somewhat as if
+ the library had read an MIT-style keytab file. Unfortunately,
+ only later, at recv_auth() time, would the Heimdal library convert
+ the server-PW into a salted-&-hashed AES key, by hashing 10,000
+ times with SHA-1. Naturally, this is really too slow for recv_auth(),
+ which runs when an app-server authenticates a client's app-service-
+ request. So, nowadays, this password-based in-memory keytab is
+ falling into disuse.
+ h. Get OSSNTLM authT library: AB says Likewise software
+ probably will give us their freeware "NTLM for MIT-krb"
+ implementation.
+ i. Special Heimdal-specific functions; These functions didn't
+ exist in the MIT code, years ago, when Samba started. AB
+ will try to build a final list of these functions:
+ * krb5_free_keyblock_contents()
+ *
+ j.Principal-manipulation functions: Samba makes extensive
+ use of the principal manipulation functions in Heimdal,
+ including the known structure behind krb_principal and
+ krb5_realm (a char *). For example,
+ * krb5_parse_name_flags(smb_krb5_context->krb5_context, name,
+ KRB5_PRINCIPAL_PARSE_REQUIRE_REALM,&principal);
+ * krb5_unparse_name_flags(smb_krb5_context->krb5_context, principal,
+ KRB5_PRINCIPAL_UNPARSE_NO_REALM,&new_princ);
+ * krb5_principal_get_realm()
+ * krb5_principal_set_realm()
+ These are needed for juggling the AD variant-structures
+ for server names.
+ k. SpecialShort name rules check for misconfigured Samba4
+ hostnames; Samba is highly likely to be misconfigured, in
+ many weird and interesting ways. So, we have a patch for
+ Heimdal that avoids DNS lookups on names without a "." in
+ them. This should avoid some delay and root server load.
+ (This errors need to be caught in MIT's library.)
+ l.Improved krb error-messages;
+ krb5_get_error_string(): This Heimdal-specific function
+ does a lot to reduce the 'administrator pain' level, by
+ providing specific, English text-string error messages
+ instead of just error code translations. (This isn't
+ necessary for the port, but it's more useful than MIT's
+ default err-handling; Make sure this works for MIT-krb)
+ m.Improved Kerberos logging support:
+ krb5_log_facility(): Samba4 now uses this Heimdal function,
+ which allows us to redirect the warnings and status from
+ the KDC (and client/server Kerberos code) to Samba's DEBUG()
+ system. Samba uses this logging routine optionally in the
+ main code, but it's required for KDC errors.
+ n. MSGSSMonger test-suite: Microsoft has released a krb-specific
+ testsuite called gssmonger, which tests interoperability. We
+ should compile it against lorikeet-heimdal& MIT and see if we
+ can build a 'Samba4' server for it. GSSMonger wasn't intended
+ to be Windows-specific.
+ o.Testsuite for kpasswd daemon: I have a partial kpasswd server
+ which needs finishing, and a Samba4 needs a client testsuite
+ written, either via the krb5 API or directly against GENSEC and
+ the ASN.1 routines. Samba4 likes to test failure-modes, not
+ just successful behavior. Currently Samba4's kpasswd only works
+ for Heimdal, not MIT clients. This may be due to call-ordering
+ constraints.
+
+
+Appendix 1: Keytab Requirements
+
+ Traditional 'MIT' keytab operation is very different from AD's
+ account-handling for application-servers:
+ a. Host PWs vs service-keys:
+ * Traditional 'MIT' behaviour is for the app-server to use a keytab
+ containing several named random-bitstring service-keys, created
+ by the KDC. An MIT-style keytab holds a different service-key
+ for every kerberized application-service that the server offers
+ to clients. Heimdal also implements this behaviour. MIT's model
+ doesn't use AD's UTF-16 'service password', and no salting is
+ necessary for service-keys, because each service-key is random
+ enough to withstand an exhaustive key-search attack.
+ * In the Windows model, the server key's construction is very
+ different: The app-server itself, not the KDC, generates a
+ random UTF-16 pseudo-textual password, and sends this password
+ to the KDC using SAMR, a DCE-RPC "domain-joining" protocol (but
+ for windows 7, see below). Then, the KDC shares this server-
+ password with every application service on the whole machine.
+ * Only when the app-server uses kerberos does the password get
+ salted by the member server (ie, an AD server-host). (That
+ is, no salt information appears to be conveyed from the AD KDC
+ to the member server, and the member server must use the rules
+ described in Luke's mail, in Appendix 3, below). The salted-
+ and-hashed version of the server-host's PW gets stored in the
+ server-host's keytab.
+ * Samba file-servers can have many server-names simultaneously
+ (kind of like web servers' software-virtual-hosting), but since
+ these servers are running in AD, these names can be set up to
+ all share the same secret key. In AD, co-located server names
+ almost always share a secret key like this. In samba3, this
+ key-sharing was optional, so some samba3 hosts' keytabs did
+ hold multiple keys. Samba4 abandons this traditional "old MIT"
+ style of keytab, and only supports one key per keytab, and
+ multiple server-names can use that keytab key in common. In
+ dealing with this model, Samba4 uses both the traditional file
+ keytab and an in-MEMORY keytabs.
+ * Pre-Windows7 AD and samba3/4 both use SAMR, an older protocol,
+ to jumpstart the member server's PW-sharing with AD (the "windows
+ domain-join process"). This PW-sharing transfers only the PW's
+ UTF-16 text, without any salting or hashing, so that non-krb
+ security mechanisms can use the same utf-16 text PW. For
+ Windows 7, this domain-joining uses LDAP for PW-setting.
+ b. Flexible server-naming
+ * The other big difference between AD's keytabs and MIT's is that
+ Windows offers a lot more flexibility about service-principals'
+ names. When the kerberos server-side library receives Windows-style tickets
+ from an app-client, MIT's krb library (or GSSAPI) must accommodate
+ Windows' flexibility about case-sensitivity and canonicalization.
+ This means that an incoming application-request to a member server
+ may use a wide variety of service-principal names. These include:
+ machine$@REALM (samba clients)
+ HOST/foo.bar@realm (win2k clients)
+ cifs/foo.bar@realm (winxp clients)
+ HOST/foo@realm (win2k clients, using netbios)
+ cifs/foo@realm (winxp clients, using netbios),
+ as well as all upper/lower-case variations on the above.
+ c. Keytabs& Name-canonicalization
+ * Heimdal's GSSAPI expects to to be called with a principal-name& a keytab,
+ possibly containing multiple principals' different keys. However, AD has
+ a different problem to solve, which is that the client may know the member-
+ server by a non-canonicalized principal name, yet AD knows the keytab
+ contains exactly one key, indexed by the canonical name. So, GSSAPI is
+ unprepared to canonicalize the server-name that the cliet requested, and
+ is also overprepared to do an unnecessary search through the keytab by
+ principal-name. So Samba's server-side GSSAPI calls have to "game" the
+ GSSAPI, by supplying the server's known canonical name, with the one-key
+ keytab. This doesn't really affect IPA's port of Samba4 to MIT-krb.
+ * Because the number of U/L case combinations got 'too hard' to put into
+ a keytab in the traditional way (with the client to specify the name),
+ we either pre-compute the keys into a traditional keytab or make an
+ in-MEMORY keytab at run time. In both cases we specify the principal
+ name to GSSAPI, which avoids the need to store duplicate principals.
+ * We use a 'private' keytab in our private dir, referenced from the
+ secrets.ldb by default.
+
+Appendix 2: KDC Plugin for Account-Authorization
+
+Here is how Samba4 ended up doing account-authorization in
+Heimdal, via a specialized KDC plugin. This plugin helps
+bridge an important gap: The user's AD record is much richer
+than the Heimdal HDB format allows, so we do AD-specific
+access-control checks in the plugin's AD-specific layer,
+not in the DB-agnostic KDC server:
+ * We created a separate KDC plugin, with this API:
+ typedef struct
+ hdb_entry_ex { void *ctx;
+ hdb_entry entry;
+ void (*free_entry)(krb5_context, struct hdb_entry_ex *);
+ } hdb_entry_ex;
+ The void *ctx is a "private pointer," provided by the
+ 'get' method's hdb_entry_ex retval. The APIs below use
+ the void *ctx so as to find additional information about
+ the user, not contained in the hdb_entry structure.
+ Both the provider and the APIs below understand how to
+ cast the private void *ctx pointer.
+ typedef krb5_error_code
+ (*krb5plugin_windc_pac_generate)(void * krb5_context,
+ struct hdb_entry_ex *,
+ krb5_pac*);
+ typedef krb5_error_code
+ (*krb5plugin_windc_pac_verify)(void * krb5_context,
+ const krb5_principal,
+ struct hdb_entry_ex *,
+ struct hdb_entry_ex *,
+ krb5_pac *);
+ typedef krb5_error_code
+ (*krb5plugin_windc_client_access)(void * krb5_context,
+ struct hdb_entry_ex *,
+ KDC_REQ *,
+ krb5_data *);
+ The krb5_data* here is critical, so that samba's KDC can return
+ the right NTSTATUS code in the 'error string' returned to the
+ client. Otherwise, the windows client won't get the right error
+ message to the user (such as 'password expired' etc). The pure
+ Kerberos error is not enough)
+ typedef struct
+ krb5plugin_windc_ftable { int minor_version;
+ krb5_error_code (*init)(krb5_context, void **);
+ void (*fini)(void *);
+ krb5plugin_windc_pac_generate pac_generate;
+ krb5plugin_windc_pac_verify pac_verify;
+ krb5plugin_windc_client_access client_access;
+ } krb5plugin_windc_ftable;
+ This API has some Heimdal-specific stuff, that'll
+ have to change when we port this KDC plugin to MIT krb.
+ * 1st callback (pac_generate) creates an initial PAC from the user's AD record.
+ * 2nd callback (pac_verify) checks that a PAC is correctly signed,
+ adds additional groups (for cross-realm tickets)
+ and re-signs with the key of the target kerberos
+ service's account
+ * 3rd callback (client_access) performs additional access checks, such as
+ allowedWorkstations and account expiry.
+ * For example, to register this plugin, use the kdc's standard
+ plugin-system at Samba4's initialisation:
+ /* first, setup the table of callback pointers */
+ /* Registar WinDC hooks */
+ ret = krb5_plugin_register(krb5_context, PLUGIN_TYPE_DATA,
+ "windc",&windc_plugin_table);
+ /* once registered, the KDC will invoke the callbacks */
+ /* while preparing each new ticket (TGT or app-tkt) */
+ * An alternative way to register the plugin is with a
+ config-file that names a DSO (Dynamically Shared Object).
+
+Appendix 3: Samba4 stuff that doesn't need to get ported.
+
+Heimdal oddities
+* Heimdal is built such that it should be able to serve multiple realms
+ at the same time. This isn't relevant for Samba's use, but it shows
+ up in a lot of generalisations throughout the code.
+* Samba4's code originally tried internally to make it possible to use
+ Heimdal's multi-realms-per-KDC ability, but this was ill-conceived,
+ and AB has recently (6/09) ripped the last of that multi-realms
+ stuff out of samba4. AB says that in AD, it's not really possible
+ to make this work; several AD components structurally assume that
+ there's one realm per KDC. However, we do use this to support
+ canonicalization of realm-names: case variations, plus long-vs-short
+ variants of realm-names. No MIT porting task here, as long as MIT kdc
+ doesn't refuse to do some LDAP lookups (eg, alias' realm-name looks
+ wrong).
+* Heimdal supports multiple passwords on a client account: Samba4
+ seems to call hdb_next_enctype2key() in the pre-authentication
+ routines, to allow multiple passwords per account in krb5.
+ (I think this was intended to allow multiple salts). AD doesn't
+ support this, so the MIT port shouldn't bother with this.
+Not needed anymore, because MIT's code now handles PACs fully:
+* gss_krb5_copy_service_keyblock() (get the key used to actually
+ encrypt the ticket to the server, because the same key is used for
+ the PAC validation).
+* gsskrb5_extract_authtime_from_sec_context (get authtime from
+ kerberos ticket)
+* gsskrb5_extract_authz_data_from_sec_context (get authdata from
+ ticket, ie the PAC. Must unwrap the data if in an AD-IFRELEVANT)]
+Authz data extraction
+* We use krb5_ticket_get_authorization_data_type(), and expect
+ it to return the correct authz data, even if wrapped in an
+ AD-IFRELEVANT container. This doesn't need to be ported to MIT.
+ This should be obsoleted by MIT's new PAC code.
+libkdc
+* Samba4 needs to be built as a single binary (design requirement),
+ and this should include the KDC. Samba also (and perhaps more
+ importantly) needs to control the configuration environment of
+ the KDC.
+* But, libkdc doesn't matter for IPA; Samba invokes the Heimdal kdc
+ as a library call, but this is just a convenience, and the MIT
+ port can do otherwise w/o trouble.)
+Returned Salt for PreAuthentication
+ When the AD-KDC replies to pre-authentication, it returns the
+ salt, which may be in the form of a principalName that is in no
+ way connected with the current names. (ie, even if the
+ userPrincipalName and samAccountName are renamed, the old salt
+ is returned).
+ This is the kerberos standard salt, kept in the 'Key'. The
+ AD generation rules are found in a Mail from Luke Howard dated
+ 10 Nov 2004. The MIT glue layer doesn't really need to care about
+ these salt-handling details; the samba4 code& the LDAP backend
+ will conspire to make sure that MIT's KDC gets correct salts.
+ >
+ > From: Luke Howard<lukeh@padl.com>
+ > Organization: PADL Software Pty Ltd
+ > To: lukeh@padl.com
+ > Date: Wed, 10 Nov 2004 13:31:21 +1100
+ > Cc: huaraz@moeller.plus.com, samba-technical@lists.samba.org
+ > Subject: Re: Samba-3.0.7-1.3E Active Directory Issues
+ > -------
+ >
+ > Did some more testing, it appears the behaviour has another
+ > explanation. It appears that the standard Kerberos password salt
+ > algorithm is applied in Windows 2003, just that the source principal
+ > name is different.
+ >
+ > Here is what I've been able to deduce from creating a bunch of
+ > different accounts:
+ > [SAM name in this mail means the AD attribute samAccountName .
+ > E.g., jbob for a user and jbcomputer$ for a computer.]
+ >
+ > [UPN is the AD userPrincipalName attribute. For example, jbob@mydomain.com]
+ > Type of account Principal for Salting
+ > ========================================================================
+ > Computer Account host/<SAM-Name-Without-$>.realm@REALM
+ > User Account Without UPN<SAM-Name>@REALM
+ > User Account With UPN<LHS-Of-UPN>@REALM
+ >
+ > Note that if the computer account's SAM account name does not include
+ > the trailing '$', then the entire SAM account name is used as input to
+ > the salting principal. Setting a UPN for a computer account has no
+ > effect.
+ >
+ > It seems to me odd that the RHS of the UPN is not used in the salting
+ > principal. For example, a user with UPN foo@mydomain.com in the realm
+ > MYREALM.COM would have a salt of MYREALM.COMfoo. Perhaps this is to
+ > allow a user's UPN suffix to be changed without changing the salt. And
+ > perhaps using the UPN for salting signifies a move away SAM names and
+ > their associated constraints.
+ >
+ > For more information on how UPNs relate to the Kerberos protocol,
+ > see:
+ >
+ > http://www.ietf.org/proceedings/01dec/I-D/draft-ietf-krb-wg-kerberos-referrals-02.txt
+ >
+ > -- Luke
diff --git a/source4/auth/kerberos/kerberos.h b/source4/auth/kerberos/kerberos.h
new file mode 100644
index 0000000..33ee4f3
--- /dev/null
+++ b/source4/auth/kerberos/kerberos.h
@@ -0,0 +1,87 @@
+/*
+ Unix SMB/CIFS implementation.
+ simple kerberos5 routines for active directory
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Luke Howard 2002-2003
+
+ 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 _AUTH_KERBEROS_H_
+#define _AUTH_KERBEROS_H_
+
+#if defined(HAVE_KRB5)
+
+#include "system/kerberos.h"
+#include "auth/kerberos/krb5_init_context.h"
+#include "librpc/gen_ndr/krb5pac.h"
+#include "lib/krb5_wrap/krb5_samba.h"
+
+struct auth_user_info_dc;
+struct cli_credentials;
+
+struct ccache_container {
+ struct smb_krb5_context *smb_krb5_context;
+ krb5_ccache ccache;
+};
+
+struct keytab_container {
+ struct smb_krb5_context *smb_krb5_context;
+ krb5_keytab keytab;
+ bool password_based;
+};
+
+/* not really ASN.1, but RFC 1964 */
+#define TOK_ID_KRB_AP_REQ ((const uint8_t *)"\x01\x00")
+#define TOK_ID_KRB_AP_REP ((const uint8_t *)"\x02\x00")
+#define TOK_ID_KRB_ERROR ((const uint8_t *)"\x03\x00")
+#define TOK_ID_GSS_GETMIC ((const uint8_t *)"\x01\x01")
+#define TOK_ID_GSS_WRAP ((const uint8_t *)"\x02\x01")
+
+#define ENC_ALL_TYPES (ENC_RC4_HMAC_MD5 | \
+ ENC_HMAC_SHA1_96_AES128 | ENC_HMAC_SHA1_96_AES256)
+
+#ifndef HAVE_KRB5_SET_DEFAULT_TGS_KTYPES
+krb5_error_code krb5_set_default_tgs_ktypes(krb5_context ctx, const krb5_enctype *enc);
+#endif
+
+#if defined(HAVE_KRB5_AUTH_CON_SETKEY) && !defined(HAVE_KRB5_AUTH_CON_SETUSERUSERKEY)
+krb5_error_code krb5_auth_con_setuseruserkey(krb5_context context, krb5_auth_context auth_context, krb5_keyblock *keyblock);
+#endif
+
+#if defined(HAVE_KRB5_PRINCIPAL_GET_COMP_STRING) && !defined(HAVE_KRB5_PRINC_COMPONENT)
+const krb5_data *krb5_princ_component(krb5_context context, krb5_principal principal, int i );
+#endif
+
+/* Samba wrapper function for krb5 functionality. */
+ krb5_error_code kerberos_encode_pac(TALLOC_CTX *mem_ctx,
+ struct PAC_DATA *pac_data,
+ krb5_context context,
+ const krb5_keyblock *krbtgt_keyblock,
+ const krb5_keyblock *service_keyblock,
+ DATA_BLOB *pac);
+ krb5_error_code kerberos_create_pac(TALLOC_CTX *mem_ctx,
+ struct auth_user_info_dc *user_info_dc,
+ krb5_context context,
+ const krb5_keyblock *krbtgt_keyblock,
+ const krb5_keyblock *service_keyblock,
+ krb5_principal client_principal,
+ time_t tgs_authtime,
+ DATA_BLOB *pac);
+
+#include "auth/kerberos/proto.h"
+
+#endif /* HAVE_KRB5 */
+
+#endif /* _AUTH_KERBEROS_H_ */
diff --git a/source4/auth/kerberos/kerberos_credentials.h b/source4/auth/kerberos/kerberos_credentials.h
new file mode 100644
index 0000000..362edf7
--- /dev/null
+++ b/source4/auth/kerberos/kerberos_credentials.h
@@ -0,0 +1,37 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Kerberos utility functions for GENSEC
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2010
+
+ 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/>.
+*/
+
+krb5_error_code kinit_to_ccache(TALLOC_CTX *parent_ctx,
+ struct cli_credentials *credentials,
+ struct smb_krb5_context *smb_krb5_context,
+ struct tevent_context *event_ctx,
+ krb5_ccache ccache,
+ enum credentials_obtained *obtained,
+ const char **error_string);
+
+/* Manually prototyped here to avoid needing krb5 headers in most callers */
+krb5_error_code principal_from_credentials(TALLOC_CTX *parent_ctx,
+ struct cli_credentials *credentials,
+ struct smb_krb5_context *smb_krb5_context,
+ krb5_principal *princ,
+ enum credentials_obtained *obtained,
+ const char **error_string);
diff --git a/source4/auth/kerberos/kerberos_pac.c b/source4/auth/kerberos/kerberos_pac.c
new file mode 100644
index 0000000..156a140
--- /dev/null
+++ b/source4/auth/kerberos/kerberos_pac.c
@@ -0,0 +1,496 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Create and parse the krb5 PAC
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005,2008
+ 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 "system/kerberos.h"
+#include "auth/auth.h"
+#include "auth/kerberos/kerberos.h"
+#include "librpc/gen_ndr/ndr_krb5pac.h"
+#include <ldb.h>
+#include "auth/auth_sam_reply.h"
+#include "auth/credentials/credentials.h"
+#include "auth/kerberos/kerberos_util.h"
+#include "auth/kerberos/pac_utils.h"
+
+ krb5_error_code kerberos_encode_pac(TALLOC_CTX *mem_ctx,
+ struct PAC_DATA *pac_data,
+ krb5_context context,
+ const krb5_keyblock *krbtgt_keyblock,
+ const krb5_keyblock *service_keyblock,
+ DATA_BLOB *pac)
+{
+ NTSTATUS nt_status;
+ krb5_error_code ret;
+ enum ndr_err_code ndr_err;
+ DATA_BLOB zero_blob = data_blob(NULL, 0);
+ DATA_BLOB tmp_blob = data_blob(NULL, 0);
+ struct PAC_SIGNATURE_DATA *kdc_checksum = NULL;
+ struct PAC_SIGNATURE_DATA *srv_checksum = NULL;
+ uint32_t i;
+
+ /* First, just get the keytypes filled in (and lengths right, eventually) */
+ for (i=0; i < pac_data->num_buffers; i++) {
+ if (pac_data->buffers[i].type != PAC_TYPE_KDC_CHECKSUM) {
+ continue;
+ }
+ kdc_checksum = &pac_data->buffers[i].info->kdc_cksum,
+ ret = smb_krb5_make_pac_checksum(mem_ctx,
+ &zero_blob,
+ context,
+ krbtgt_keyblock,
+ &kdc_checksum->type,
+ &kdc_checksum->signature);
+ if (ret) {
+ DEBUG(2, ("making krbtgt PAC checksum failed: %s\n",
+ smb_get_krb5_error_message(context, ret, mem_ctx)));
+ talloc_free(pac_data);
+ return ret;
+ }
+ }
+
+ for (i=0; i < pac_data->num_buffers; i++) {
+ if (pac_data->buffers[i].type != PAC_TYPE_SRV_CHECKSUM) {
+ continue;
+ }
+ srv_checksum = &pac_data->buffers[i].info->srv_cksum;
+ ret = smb_krb5_make_pac_checksum(mem_ctx,
+ &zero_blob,
+ context,
+ service_keyblock,
+ &srv_checksum->type,
+ &srv_checksum->signature);
+ if (ret) {
+ DEBUG(2, ("making service PAC checksum failed: %s\n",
+ smb_get_krb5_error_message(context, ret, mem_ctx)));
+ talloc_free(pac_data);
+ return ret;
+ }
+ }
+
+ if (!kdc_checksum) {
+ DEBUG(2, ("Invalid PAC constructed for signing, no KDC checksum present!"));
+ return EINVAL;
+ }
+ if (!srv_checksum) {
+ DEBUG(2, ("Invalid PAC constructed for signing, no SRV checksum present!"));
+ return EINVAL;
+ }
+
+ /* But wipe out the actual signatures */
+ memset(kdc_checksum->signature.data, '\0', kdc_checksum->signature.length);
+ memset(srv_checksum->signature.data, '\0', srv_checksum->signature.length);
+
+ ndr_err = ndr_push_struct_blob(&tmp_blob, mem_ctx,
+ pac_data,
+ (ndr_push_flags_fn_t)ndr_push_PAC_DATA);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ nt_status = ndr_map_error2ntstatus(ndr_err);
+ DEBUG(1, ("PAC (presig) push failed: %s\n", nt_errstr(nt_status)));
+ talloc_free(pac_data);
+ return EINVAL;
+ }
+
+ /* Then sign the result of the previous push, where the sig was zero'ed out */
+ ret = smb_krb5_make_pac_checksum(mem_ctx,
+ &tmp_blob,
+ context,
+ service_keyblock,
+ &srv_checksum->type,
+ &srv_checksum->signature);
+
+ if (ret) {
+ DBG_WARNING("making krbtgt PAC srv_checksum failed: %s\n",
+ smb_get_krb5_error_message(context, ret, mem_ctx));
+ talloc_free(pac_data);
+ return ret;
+ }
+
+ /* Then sign Server checksum */
+ ret = smb_krb5_make_pac_checksum(mem_ctx,
+ &srv_checksum->signature,
+ context,
+ krbtgt_keyblock,
+ &kdc_checksum->type,
+ &kdc_checksum->signature);
+ if (ret) {
+ DBG_WARNING("making krbtgt PAC kdc_checksum failed: %s\n",
+ smb_get_krb5_error_message(context, ret, mem_ctx));
+ talloc_free(pac_data);
+ return ret;
+ }
+
+ /* And push it out again, this time to the world. This relies on determanistic pointer values */
+ ndr_err = ndr_push_struct_blob(&tmp_blob, mem_ctx,
+ pac_data,
+ (ndr_push_flags_fn_t)ndr_push_PAC_DATA);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ nt_status = ndr_map_error2ntstatus(ndr_err);
+ DEBUG(1, ("PAC (final) push failed: %s\n", nt_errstr(nt_status)));
+ talloc_free(pac_data);
+ return EINVAL;
+ }
+
+ *pac = tmp_blob;
+
+ return ret;
+}
+
+
+ krb5_error_code kerberos_create_pac(TALLOC_CTX *mem_ctx,
+ struct auth_user_info_dc *user_info_dc,
+ krb5_context context,
+ const krb5_keyblock *krbtgt_keyblock,
+ const krb5_keyblock *service_keyblock,
+ krb5_principal client_principal,
+ time_t tgs_authtime,
+ DATA_BLOB *pac)
+{
+ NTSTATUS nt_status;
+ krb5_error_code ret;
+ struct PAC_DATA *pac_data = talloc(mem_ctx, struct PAC_DATA);
+ struct netr_SamInfo3 *sam3;
+ union PAC_INFO *u_LOGON_INFO;
+ struct PAC_LOGON_INFO *LOGON_INFO;
+ union PAC_INFO *u_LOGON_NAME;
+ struct PAC_LOGON_NAME *LOGON_NAME;
+ union PAC_INFO *u_KDC_CHECKSUM;
+ union PAC_INFO *u_SRV_CHECKSUM;
+
+ char *name;
+
+ enum {
+ PAC_BUF_LOGON_INFO = 0,
+ PAC_BUF_LOGON_NAME = 1,
+ PAC_BUF_SRV_CHECKSUM = 2,
+ PAC_BUF_KDC_CHECKSUM = 3,
+ PAC_BUF_NUM_BUFFERS = 4
+ };
+
+ if (!pac_data) {
+ return ENOMEM;
+ }
+
+ pac_data->num_buffers = PAC_BUF_NUM_BUFFERS;
+ pac_data->version = 0;
+
+ pac_data->buffers = talloc_array(pac_data,
+ struct PAC_BUFFER,
+ pac_data->num_buffers);
+ if (!pac_data->buffers) {
+ talloc_free(pac_data);
+ return ENOMEM;
+ }
+
+ /* LOGON_INFO */
+ u_LOGON_INFO = talloc_zero(pac_data->buffers, union PAC_INFO);
+ if (!u_LOGON_INFO) {
+ talloc_free(pac_data);
+ return ENOMEM;
+ }
+ pac_data->buffers[PAC_BUF_LOGON_INFO].type = PAC_TYPE_LOGON_INFO;
+ pac_data->buffers[PAC_BUF_LOGON_INFO].info = u_LOGON_INFO;
+
+ /* LOGON_NAME */
+ u_LOGON_NAME = talloc_zero(pac_data->buffers, union PAC_INFO);
+ if (!u_LOGON_NAME) {
+ talloc_free(pac_data);
+ return ENOMEM;
+ }
+ pac_data->buffers[PAC_BUF_LOGON_NAME].type = PAC_TYPE_LOGON_NAME;
+ pac_data->buffers[PAC_BUF_LOGON_NAME].info = u_LOGON_NAME;
+ LOGON_NAME = &u_LOGON_NAME->logon_name;
+
+ /* SRV_CHECKSUM */
+ u_SRV_CHECKSUM = talloc_zero(pac_data->buffers, union PAC_INFO);
+ if (!u_SRV_CHECKSUM) {
+ talloc_free(pac_data);
+ return ENOMEM;
+ }
+ pac_data->buffers[PAC_BUF_SRV_CHECKSUM].type = PAC_TYPE_SRV_CHECKSUM;
+ pac_data->buffers[PAC_BUF_SRV_CHECKSUM].info = u_SRV_CHECKSUM;
+
+ /* KDC_CHECKSUM */
+ u_KDC_CHECKSUM = talloc_zero(pac_data->buffers, union PAC_INFO);
+ if (!u_KDC_CHECKSUM) {
+ talloc_free(pac_data);
+ return ENOMEM;
+ }
+ pac_data->buffers[PAC_BUF_KDC_CHECKSUM].type = PAC_TYPE_KDC_CHECKSUM;
+ pac_data->buffers[PAC_BUF_KDC_CHECKSUM].info = u_KDC_CHECKSUM;
+
+ /* now the real work begins... */
+
+ LOGON_INFO = talloc_zero(u_LOGON_INFO, struct PAC_LOGON_INFO);
+ if (!LOGON_INFO) {
+ talloc_free(pac_data);
+ return ENOMEM;
+ }
+ nt_status = auth_convert_user_info_dc_saminfo3(LOGON_INFO, user_info_dc, &sam3);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(1, ("Getting Samba info failed: %s\n", nt_errstr(nt_status)));
+ talloc_free(pac_data);
+ return EINVAL;
+ }
+
+ u_LOGON_INFO->logon_info.info = LOGON_INFO;
+ LOGON_INFO->info3 = *sam3;
+
+ ret = krb5_unparse_name_flags(context, client_principal,
+ KRB5_PRINCIPAL_UNPARSE_NO_REALM |
+ KRB5_PRINCIPAL_UNPARSE_DISPLAY,
+ &name);
+ if (ret) {
+ return ret;
+ }
+ LOGON_NAME->account_name = talloc_strdup(LOGON_NAME, name);
+ free(name);
+ /*
+ this logon_time field is absolutely critical. This is what
+ caused all our PAC troubles :-)
+ */
+ unix_to_nt_time(&LOGON_NAME->logon_time, tgs_authtime);
+
+ ret = kerberos_encode_pac(mem_ctx,
+ pac_data,
+ context,
+ krbtgt_keyblock,
+ service_keyblock,
+ pac);
+ talloc_free(pac_data);
+ return ret;
+}
+
+static krb5_error_code kerberos_pac_buffer_present(krb5_context context,
+ const krb5_pac pac,
+ uint32_t type)
+{
+#ifdef SAMBA4_USES_HEIMDAL
+ return krb5_pac_get_buffer(context, pac, type, NULL);
+#else /* MIT */
+ krb5_error_code ret;
+ krb5_data data;
+
+ /*
+ * MIT won't let us pass NULL for the data parameter, so we are forced
+ * to allocate a new buffer and then immediately free it.
+ */
+ ret = krb5_pac_get_buffer(context, pac, type, &data);
+ if (ret == 0) {
+ krb5_free_data_contents(context, &data);
+ }
+ return ret;
+#endif /* SAMBA4_USES_HEIMDAL */
+}
+
+krb5_error_code kerberos_pac_to_user_info_dc(TALLOC_CTX *mem_ctx,
+ krb5_pac pac,
+ krb5_context context,
+ struct auth_user_info_dc **user_info_dc,
+ struct PAC_SIGNATURE_DATA *pac_srv_sig,
+ struct PAC_SIGNATURE_DATA *pac_kdc_sig)
+{
+ NTSTATUS nt_status;
+ enum ndr_err_code ndr_err;
+ krb5_error_code ret;
+
+ DATA_BLOB pac_logon_info_in, pac_srv_checksum_in, pac_kdc_checksum_in;
+ krb5_data k5pac_logon_info_in, k5pac_srv_checksum_in, k5pac_kdc_checksum_in;
+ DATA_BLOB pac_upn_dns_info_in;
+ krb5_data k5pac_upn_dns_info_in;
+
+ union PAC_INFO info;
+ union PAC_INFO _upn_dns_info;
+ struct PAC_UPN_DNS_INFO *upn_dns_info = NULL;
+ struct auth_user_info_dc *user_info_dc_out;
+
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+
+ if (!tmp_ctx) {
+ return ENOMEM;
+ }
+
+ ret = krb5_pac_get_buffer(context, pac, PAC_TYPE_LOGON_INFO, &k5pac_logon_info_in);
+ if (ret != 0) {
+ talloc_free(tmp_ctx);
+ return EINVAL;
+ }
+
+ pac_logon_info_in = data_blob_const(k5pac_logon_info_in.data, k5pac_logon_info_in.length);
+
+ ndr_err = ndr_pull_union_blob(&pac_logon_info_in, tmp_ctx, &info,
+ PAC_TYPE_LOGON_INFO,
+ (ndr_pull_flags_fn_t)ndr_pull_PAC_INFO);
+ smb_krb5_free_data_contents(context, &k5pac_logon_info_in);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ nt_status = ndr_map_error2ntstatus(ndr_err);
+ DEBUG(0,("can't parse the PAC LOGON_INFO: %s\n", nt_errstr(nt_status)));
+ talloc_free(tmp_ctx);
+ return EINVAL;
+ }
+ if (info.logon_info.info == NULL) {
+ DEBUG(0,("can't parse the PAC LOGON_INFO: missing info pointer\n"));
+ talloc_free(tmp_ctx);
+ return EINVAL;
+ }
+
+ ret = krb5_pac_get_buffer(context, pac, PAC_TYPE_UPN_DNS_INFO,
+ &k5pac_upn_dns_info_in);
+ if (ret == ENOENT) {
+ ZERO_STRUCT(k5pac_upn_dns_info_in);
+ ret = 0;
+ }
+ if (ret != 0) {
+ talloc_free(tmp_ctx);
+ return EINVAL;
+ }
+
+ pac_upn_dns_info_in = data_blob_const(k5pac_upn_dns_info_in.data,
+ k5pac_upn_dns_info_in.length);
+
+ if (pac_upn_dns_info_in.length != 0) {
+ ndr_err = ndr_pull_union_blob(&pac_upn_dns_info_in, tmp_ctx,
+ &_upn_dns_info,
+ PAC_TYPE_UPN_DNS_INFO,
+ (ndr_pull_flags_fn_t)ndr_pull_PAC_INFO);
+ smb_krb5_free_data_contents(context, &k5pac_upn_dns_info_in);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ nt_status = ndr_map_error2ntstatus(ndr_err);
+ DEBUG(0,("can't parse the PAC UPN_DNS_INFO: %s\n",
+ nt_errstr(nt_status)));
+ talloc_free(tmp_ctx);
+ return EINVAL;
+ }
+ upn_dns_info = &_upn_dns_info.upn_dns_info;
+ }
+
+ /* Pull this right into the normal auth sysstem structures */
+ nt_status = make_user_info_dc_pac(mem_ctx,
+ info.logon_info.info,
+ upn_dns_info,
+ &user_info_dc_out);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("make_user_info_dc_pac() failed -%s\n",
+ nt_errstr(nt_status));
+ NDR_PRINT_DEBUG(PAC_LOGON_INFO, info.logon_info.info);
+ if (upn_dns_info != NULL) {
+ NDR_PRINT_DEBUG(PAC_UPN_DNS_INFO, upn_dns_info);
+ }
+ talloc_free(tmp_ctx);
+ return EINVAL;
+ }
+
+ if (pac_srv_sig) {
+ ret = krb5_pac_get_buffer(context, pac, PAC_TYPE_SRV_CHECKSUM, &k5pac_srv_checksum_in);
+ if (ret != 0) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ pac_srv_checksum_in = data_blob_const(k5pac_srv_checksum_in.data, k5pac_srv_checksum_in.length);
+
+ ndr_err = ndr_pull_struct_blob(&pac_srv_checksum_in, pac_srv_sig,
+ pac_srv_sig,
+ (ndr_pull_flags_fn_t)ndr_pull_PAC_SIGNATURE_DATA);
+ smb_krb5_free_data_contents(context, &k5pac_srv_checksum_in);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ nt_status = ndr_map_error2ntstatus(ndr_err);
+ DEBUG(0,("can't parse the KDC signature: %s\n",
+ nt_errstr(nt_status)));
+ return EINVAL;
+ }
+ }
+
+ if (pac_kdc_sig) {
+ ret = krb5_pac_get_buffer(context, pac, PAC_TYPE_KDC_CHECKSUM, &k5pac_kdc_checksum_in);
+ if (ret != 0) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ pac_kdc_checksum_in = data_blob_const(k5pac_kdc_checksum_in.data, k5pac_kdc_checksum_in.length);
+
+ ndr_err = ndr_pull_struct_blob(&pac_kdc_checksum_in, pac_kdc_sig,
+ pac_kdc_sig,
+ (ndr_pull_flags_fn_t)ndr_pull_PAC_SIGNATURE_DATA);
+ smb_krb5_free_data_contents(context, &k5pac_kdc_checksum_in);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ nt_status = ndr_map_error2ntstatus(ndr_err);
+ DEBUG(0,("can't parse the KDC signature: %s\n",
+ nt_errstr(nt_status)));
+ return EINVAL;
+ }
+ }
+
+ /*
+ * Based on the presence of a REQUESTER_SID PAC buffer, ascertain
+ * whether the ticket is a TGT. This helps the KDC and kpasswd service
+ * ensure they do not accept tickets meant for the other.
+ *
+ * This heuristic will fail for older Samba versions and Windows prior
+ * to Nov. 2021 updates, which lack support for the REQUESTER_SID PAC
+ * buffer.
+ */
+ ret = kerberos_pac_buffer_present(context, pac, PAC_TYPE_REQUESTER_SID);
+ if (ret == ENOENT) {
+ /* This probably isn't a TGT. */
+ user_info_dc_out->ticket_type = TICKET_TYPE_NON_TGT;
+ } else if (ret != 0) {
+ talloc_free(tmp_ctx);
+ return ret;
+ } else {
+ /* This probably is a TGT. */
+ user_info_dc_out->ticket_type = TICKET_TYPE_TGT;
+ }
+
+ *user_info_dc = user_info_dc_out;
+
+ return 0;
+}
+
+
+NTSTATUS kerberos_pac_blob_to_user_info_dc(TALLOC_CTX *mem_ctx,
+ DATA_BLOB pac_blob,
+ krb5_context context,
+ struct auth_user_info_dc **user_info_dc,
+ struct PAC_SIGNATURE_DATA *pac_srv_sig,
+ struct PAC_SIGNATURE_DATA *pac_kdc_sig)
+{
+ krb5_error_code ret;
+ krb5_pac pac;
+ ret = krb5_pac_parse(context,
+ pac_blob.data, pac_blob.length,
+ &pac);
+ if (ret) {
+ return map_nt_error_from_unix_common(ret);
+ }
+
+
+ ret = kerberos_pac_to_user_info_dc(mem_ctx, pac, context, user_info_dc, pac_srv_sig, pac_kdc_sig);
+ krb5_pac_free(context, pac);
+ if (ret) {
+ return map_nt_error_from_unix_common(ret);
+ }
+ return NT_STATUS_OK;
+}
diff --git a/source4/auth/kerberos/kerberos_util.c b/source4/auth/kerberos/kerberos_util.c
new file mode 100644
index 0000000..c14d8c7
--- /dev/null
+++ b/source4/auth/kerberos/kerberos_util.c
@@ -0,0 +1,648 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Kerberos utility functions for GENSEC
+
+ Copyright (C) Andrew Bartlett <abartlet@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 "system/kerberos.h"
+#include "auth/kerberos/kerberos.h"
+#include "auth/credentials/credentials.h"
+#include "auth/credentials/credentials_krb5.h"
+#include "auth/kerberos/kerberos_credentials.h"
+#include "auth/kerberos/kerberos_util.h"
+
+struct principal_container {
+ struct smb_krb5_context *smb_krb5_context;
+ krb5_principal principal;
+ const char *string_form; /* Optional */
+};
+
+static krb5_error_code free_principal(struct principal_container *pc)
+{
+ /* current heimdal - 0.6.3, which we need anyway, fixes segfaults here */
+ krb5_free_principal(pc->smb_krb5_context->krb5_context, pc->principal);
+
+ return 0;
+}
+
+
+static krb5_error_code parse_principal(TALLOC_CTX *parent_ctx,
+ const char *princ_string,
+ struct smb_krb5_context *smb_krb5_context,
+ krb5_principal *princ,
+ const char **error_string)
+{
+ int ret;
+ struct principal_container *mem_ctx;
+ if (princ_string == NULL) {
+ *princ = NULL;
+ return 0;
+ }
+
+ ret = krb5_parse_name(smb_krb5_context->krb5_context,
+ princ_string, princ);
+
+ if (ret) {
+ (*error_string) = smb_get_krb5_error_message(
+ smb_krb5_context->krb5_context,
+ ret, parent_ctx);
+ return ret;
+ }
+
+ mem_ctx = talloc(parent_ctx, struct principal_container);
+ if (!mem_ctx) {
+ (*error_string) = error_message(ENOMEM);
+ return ENOMEM;
+ }
+
+ /* This song-and-dance effectivly puts the principal
+ * into talloc, so we can't loose it. */
+ mem_ctx->smb_krb5_context = talloc_reference(mem_ctx,
+ smb_krb5_context);
+ mem_ctx->principal = *princ;
+ talloc_set_destructor(mem_ctx, free_principal);
+ return 0;
+}
+
+/* Obtain the principal set on this context. Requires a
+ * smb_krb5_context because we are doing krb5 principal parsing with
+ * the library routines. The returned princ is placed in the talloc
+ * system by means of a destructor (do *not* free). */
+
+krb5_error_code principal_from_credentials(TALLOC_CTX *parent_ctx,
+ struct cli_credentials *credentials,
+ struct smb_krb5_context *smb_krb5_context,
+ krb5_principal *princ,
+ enum credentials_obtained *obtained,
+ const char **error_string)
+{
+ krb5_error_code ret;
+ const char *princ_string;
+ TALLOC_CTX *mem_ctx = talloc_new(parent_ctx);
+ *obtained = CRED_UNINITIALISED;
+
+ if (!mem_ctx) {
+ (*error_string) = error_message(ENOMEM);
+ return ENOMEM;
+ }
+ princ_string = cli_credentials_get_principal_and_obtained(credentials,
+ mem_ctx,
+ obtained);
+ if (!princ_string) {
+ *princ = NULL;
+ return 0;
+ }
+
+ ret = parse_principal(parent_ctx, princ_string,
+ smb_krb5_context, princ, error_string);
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+/* Obtain the principal set on this context. Requires a
+ * smb_krb5_context because we are doing krb5 principal parsing with
+ * the library routines. The returned princ is placed in the talloc
+ * system by means of a destructor (do *not* free). */
+
+static krb5_error_code impersonate_principal_from_credentials(
+ TALLOC_CTX *parent_ctx,
+ struct cli_credentials *credentials,
+ struct smb_krb5_context *smb_krb5_context,
+ krb5_principal *princ,
+ const char **error_string)
+{
+ return parse_principal(parent_ctx,
+ cli_credentials_get_impersonate_principal(credentials),
+ smb_krb5_context, princ, error_string);
+}
+
+krb5_error_code smb_krb5_create_principals_array(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ const char *account_name,
+ const char *realm,
+ uint32_t num_spns,
+ const char *spns[],
+ uint32_t *pnum_principals,
+ krb5_principal **pprincipals,
+ const char **error_string)
+{
+ krb5_error_code code;
+ TALLOC_CTX *tmp_ctx;
+ uint32_t num_principals = 0;
+ krb5_principal *principals;
+ uint32_t i;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ *error_string = "Cannot allocate tmp_ctx";
+ return ENOMEM;
+ }
+
+ if (realm == NULL) {
+ *error_string = "Cannot create principal without a realm";
+ code = EINVAL;
+ goto done;
+ }
+
+ if (account_name == NULL && (num_spns == 0 || spns == NULL)) {
+ *error_string = "Cannot create principal without an account or SPN";
+ code = EINVAL;
+ goto done;
+ }
+
+ if (account_name != NULL && account_name[0] != '\0') {
+ num_principals++;
+ }
+ num_principals += num_spns;
+
+ principals = talloc_zero_array(tmp_ctx,
+ krb5_principal,
+ num_principals);
+ if (principals == NULL) {
+ *error_string = "Cannot allocate principals";
+ code = ENOMEM;
+ goto done;
+ }
+
+ for (i = 0; i < num_spns; i++) {
+ code = krb5_parse_name(context, spns[i], &(principals[i]));
+ if (code != 0) {
+ *error_string = smb_get_krb5_error_message(context,
+ code,
+ mem_ctx);
+ goto done;
+ }
+ }
+
+ if (account_name != NULL && account_name[0] != '\0') {
+ code = smb_krb5_make_principal(context,
+ &(principals[i]),
+ realm,
+ account_name,
+ NULL);
+ if (code != 0) {
+ *error_string = smb_get_krb5_error_message(context,
+ code,
+ mem_ctx);
+ goto done;
+ }
+ }
+
+ if (pnum_principals != NULL) {
+ *pnum_principals = num_principals;
+
+ if (pprincipals != NULL) {
+ *pprincipals = talloc_steal(mem_ctx, principals);
+ }
+ }
+
+ code = 0;
+done:
+ talloc_free(tmp_ctx);
+ return code;
+}
+
+/**
+ * Return a freshly allocated ccache (destroyed by destructor on child
+ * of parent_ctx), for a given set of client credentials
+ */
+
+ krb5_error_code kinit_to_ccache(TALLOC_CTX *parent_ctx,
+ struct cli_credentials *credentials,
+ struct smb_krb5_context *smb_krb5_context,
+ struct tevent_context *event_ctx,
+ krb5_ccache ccache,
+ enum credentials_obtained *obtained,
+ const char **error_string)
+{
+ krb5_error_code ret;
+ const char *password;
+ const char *self_service;
+ const char *target_service;
+ time_t kdc_time = 0;
+ krb5_principal princ;
+ krb5_principal impersonate_principal;
+ int tries;
+ TALLOC_CTX *mem_ctx = talloc_new(parent_ctx);
+ krb5_get_init_creds_opt *krb_options;
+
+ if (!mem_ctx) {
+ (*error_string) = strerror(ENOMEM);
+ return ENOMEM;
+ }
+
+ ret = principal_from_credentials(mem_ctx, credentials, smb_krb5_context, &princ, obtained, error_string);
+ if (ret) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ if (princ == NULL) {
+ (*error_string) = talloc_asprintf(credentials, "principal, username or realm was not specified in the credentials");
+ talloc_free(mem_ctx);
+ return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
+ }
+
+ ret = impersonate_principal_from_credentials(mem_ctx, credentials, smb_krb5_context, &impersonate_principal, error_string);
+ if (ret) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ self_service = cli_credentials_get_self_service(credentials);
+ target_service = cli_credentials_get_target_service(credentials);
+
+ password = cli_credentials_get_password(credentials);
+
+ /* setup the krb5 options we want */
+ if ((ret = krb5_get_init_creds_opt_alloc(smb_krb5_context->krb5_context, &krb_options))) {
+ (*error_string) = talloc_asprintf(credentials, "krb5_get_init_creds_opt_alloc failed (%s)\n",
+ smb_get_krb5_error_message(smb_krb5_context->krb5_context,
+ ret, mem_ctx));
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+#ifdef SAMBA4_USES_HEIMDAL /* Disable for now MIT reads defaults when needed */
+ /* get the defaults */
+ krb5_get_init_creds_opt_set_default_flags(smb_krb5_context->krb5_context, NULL, NULL, krb_options);
+#endif
+ /* set if we want a forwardable ticket */
+ switch (cli_credentials_get_krb_forwardable(credentials)) {
+ case CRED_AUTO_KRB_FORWARDABLE:
+ break;
+ case CRED_NO_KRB_FORWARDABLE:
+ krb5_get_init_creds_opt_set_forwardable(krb_options, FALSE);
+ break;
+ case CRED_FORCE_KRB_FORWARDABLE:
+ krb5_get_init_creds_opt_set_forwardable(krb_options, TRUE);
+ break;
+ }
+
+#ifdef SAMBA4_USES_HEIMDAL /* FIXME: MIT does not have this yet */
+ /*
+ * In order to work against windows KDCs even if we use
+ * the netbios domain name as realm, we need to add the following
+ * flags:
+ * KRB5_INIT_CREDS_NO_C_CANON_CHECK;
+ * KRB5_INIT_CREDS_NO_C_NO_EKU_CHECK;
+ *
+ * On MIT: Set pkinit_eku_checking to none
+ */
+ krb5_get_init_creds_opt_set_win2k(smb_krb5_context->krb5_context,
+ krb_options, true);
+ krb5_get_init_creds_opt_set_canonicalize(smb_krb5_context->krb5_context,
+ krb_options, true);
+#else /* MIT */
+ krb5_get_init_creds_opt_set_canonicalize(krb_options, true);
+#endif
+
+ tries = 2;
+ while (tries--) {
+#ifdef SAMBA4_USES_HEIMDAL
+ struct tevent_context *previous_ev;
+ /* Do this every time, in case we have weird recursive issues here */
+ ret = smb_krb5_context_set_event_ctx(smb_krb5_context, event_ctx, &previous_ev);
+ if (ret) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+#endif
+ if (password) {
+ if (impersonate_principal) {
+ ret = smb_krb5_kinit_s4u2_ccache(smb_krb5_context->krb5_context,
+ ccache,
+ princ,
+ password,
+ impersonate_principal,
+ self_service,
+ target_service,
+ krb_options,
+ NULL,
+ &kdc_time);
+ } else {
+ ret = smb_krb5_kinit_password_ccache(smb_krb5_context->krb5_context,
+ ccache,
+ princ,
+ password,
+ target_service,
+ krb_options,
+ NULL,
+ &kdc_time);
+ }
+ } else if (impersonate_principal) {
+ talloc_free(mem_ctx);
+ (*error_string) = "INTERNAL error: Cannot impersonate principal with just a keyblock. A password must be specified in the credentials";
+ return EINVAL;
+ } else {
+ /* No password available, try to use a keyblock instead */
+
+ krb5_keyblock keyblock;
+ const struct samr_Password *mach_pwd;
+ mach_pwd = cli_credentials_get_nt_hash(credentials, mem_ctx);
+ if (!mach_pwd) {
+ talloc_free(mem_ctx);
+ (*error_string) = "kinit_to_ccache: No password available for kinit\n";
+ krb5_get_init_creds_opt_free(smb_krb5_context->krb5_context, krb_options);
+#ifdef SAMBA4_USES_HEIMDAL
+ smb_krb5_context_remove_event_ctx(smb_krb5_context, previous_ev, event_ctx);
+#endif
+ return EINVAL;
+ }
+ ret = smb_krb5_keyblock_init_contents(smb_krb5_context->krb5_context,
+ ENCTYPE_ARCFOUR_HMAC,
+ mach_pwd->hash, sizeof(mach_pwd->hash),
+ &keyblock);
+
+ if (ret == 0) {
+ ret = smb_krb5_kinit_keyblock_ccache(smb_krb5_context->krb5_context,
+ ccache,
+ princ,
+ &keyblock,
+ target_service,
+ krb_options,
+ NULL,
+ &kdc_time);
+ krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &keyblock);
+ }
+ }
+
+#ifdef SAMBA4_USES_HEIMDAL
+ smb_krb5_context_remove_event_ctx(smb_krb5_context, previous_ev, event_ctx);
+#endif
+
+ if (ret == KRB5KRB_AP_ERR_SKEW || ret == KRB5_KDCREP_SKEW) {
+ /* Perhaps we have been given an invalid skew, so try again without it */
+ time_t t = time(NULL);
+ krb5_set_real_time(smb_krb5_context->krb5_context, t, 0);
+ } else {
+ /* not a skew problem */
+ break;
+ }
+ }
+
+ krb5_get_init_creds_opt_free(smb_krb5_context->krb5_context, krb_options);
+
+ if (ret == KRB5KRB_AP_ERR_SKEW || ret == KRB5_KDCREP_SKEW) {
+ (*error_string) = talloc_asprintf(credentials, "kinit for %s failed (%s)\n",
+ cli_credentials_get_principal(credentials, mem_ctx),
+ smb_get_krb5_error_message(smb_krb5_context->krb5_context,
+ ret, mem_ctx));
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ /* cope with ticket being in the future due to clock skew */
+ if ((unsigned)kdc_time > time(NULL)) {
+ time_t t = time(NULL);
+ int time_offset =(unsigned)kdc_time-t;
+ DEBUG(4,("Advancing clock by %d seconds to cope with clock skew\n", time_offset));
+ krb5_set_real_time(smb_krb5_context->krb5_context, t + time_offset + 1, 0);
+ }
+
+ if (ret == KRB5KDC_ERR_PREAUTH_FAILED && cli_credentials_wrong_password(credentials)) {
+ ret = kinit_to_ccache(parent_ctx,
+ credentials,
+ smb_krb5_context,
+ event_ctx,
+ ccache, obtained,
+ error_string);
+ }
+
+ if (ret) {
+ (*error_string) = talloc_asprintf(credentials, "kinit for %s failed (%s)\n",
+ cli_credentials_get_principal(credentials, mem_ctx),
+ smb_get_krb5_error_message(smb_krb5_context->krb5_context,
+ ret, mem_ctx));
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ DEBUG(10,("kinit for %s succeeded\n",
+ cli_credentials_get_principal(credentials, mem_ctx)));
+
+
+ talloc_free(mem_ctx);
+ return 0;
+}
+
+static krb5_error_code free_keytab_container(struct keytab_container *ktc)
+{
+ return krb5_kt_close(ktc->smb_krb5_context->krb5_context, ktc->keytab);
+}
+
+krb5_error_code smb_krb5_get_keytab_container(TALLOC_CTX *mem_ctx,
+ struct smb_krb5_context *smb_krb5_context,
+ krb5_keytab opt_keytab,
+ const char *keytab_name,
+ struct keytab_container **ktc)
+{
+ krb5_keytab keytab;
+ krb5_error_code ret;
+
+ if (opt_keytab) {
+ keytab = opt_keytab;
+ } else {
+ ret = krb5_kt_resolve(smb_krb5_context->krb5_context,
+ keytab_name, &keytab);
+ if (ret) {
+ DEBUG(1,("failed to open krb5 keytab: %s\n",
+ smb_get_krb5_error_message(
+ smb_krb5_context->krb5_context,
+ ret, mem_ctx)));
+ return ret;
+ }
+ }
+
+ *ktc = talloc(mem_ctx, struct keytab_container);
+ if (!*ktc) {
+ return ENOMEM;
+ }
+
+ (*ktc)->smb_krb5_context = talloc_reference(*ktc, smb_krb5_context);
+ (*ktc)->keytab = keytab;
+ (*ktc)->password_based = false;
+ talloc_set_destructor(*ktc, free_keytab_container);
+
+ return 0;
+}
+
+/*
+ * Walk the keytab, looking for entries of this principal name,
+ * with KVNO other than current kvno -1.
+ *
+ * These entries are now stale,
+ * we only keep the current and previous entries around.
+ *
+ * Inspired by the code in Samba3 for 'use kerberos keytab'.
+ */
+krb5_error_code smb_krb5_remove_obsolete_keytab_entries(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ krb5_keytab keytab,
+ uint32_t num_principals,
+ krb5_principal *principals,
+ krb5_kvno kvno,
+ bool *found_previous,
+ const char **error_string)
+{
+ TALLOC_CTX *tmp_ctx;
+ krb5_error_code code;
+ krb5_kt_cursor cursor;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ *error_string = "Cannot allocate tmp_ctx";
+ return ENOMEM;
+ }
+
+ *found_previous = true;
+
+ code = krb5_kt_start_seq_get(context, keytab, &cursor);
+ switch (code) {
+ case 0:
+ break;
+#ifdef HEIM_ERR_OPNOTSUPP
+ case HEIM_ERR_OPNOTSUPP:
+#endif
+ case ENOENT:
+ case KRB5_KT_END:
+ /* no point enumerating if there isn't anything here */
+ code = 0;
+ goto done;
+ default:
+ *error_string = talloc_asprintf(mem_ctx,
+ "failed to open keytab for read of old entries: %s\n",
+ smb_get_krb5_error_message(context, code, mem_ctx));
+ goto done;
+ }
+
+ do {
+ krb5_kvno old_kvno = kvno - 1;
+ krb5_keytab_entry entry;
+ bool matched = false;
+ uint32_t i;
+
+ code = krb5_kt_next_entry(context, keytab, &entry, &cursor);
+ if (code) {
+ break;
+ }
+
+ for (i = 0; i < num_principals; i++) {
+ krb5_boolean ok;
+
+ ok = smb_krb5_kt_compare(context,
+ &entry,
+ principals[i],
+ 0,
+ 0);
+ if (ok) {
+ matched = true;
+ break;
+ }
+ }
+
+ if (!matched) {
+ /*
+ * Free the entry, it wasn't the one we were looking
+ * for anyway
+ */
+ krb5_kt_free_entry(context, &entry);
+ /* Make sure we do not double free */
+ ZERO_STRUCT(entry);
+ continue;
+ }
+
+ /*
+ * Delete it, if it is not kvno - 1.
+ *
+ * Some keytab files store the kvno only in 8bits. Limit the
+ * compare to 8bits, so that we don't miss old keys and delete
+ * them.
+ */
+ if ((entry.vno & 0xff) != (old_kvno & 0xff)) {
+ krb5_error_code rc;
+
+ /* Release the enumeration. We are going to
+ * have to start this from the top again,
+ * because deletes during enumeration may not
+ * always be consistent.
+ *
+ * Also, the enumeration locks a FILE: keytab
+ */
+ krb5_kt_end_seq_get(context, keytab, &cursor);
+
+ code = krb5_kt_remove_entry(context, keytab, &entry);
+ krb5_kt_free_entry(context, &entry);
+
+ /* Make sure we do not double free */
+ ZERO_STRUCT(entry);
+
+ /* Deleted: Restart from the top */
+ rc = krb5_kt_start_seq_get(context, keytab, &cursor);
+ if (rc != 0) {
+ krb5_kt_free_entry(context, &entry);
+
+ /* Make sure we do not double free */
+ ZERO_STRUCT(entry);
+
+ DEBUG(1, ("failed to restart enumeration of keytab: %s\n",
+ smb_get_krb5_error_message(context,
+ code,
+ tmp_ctx)));
+
+ talloc_free(tmp_ctx);
+ return rc;
+ }
+
+ if (code != 0) {
+ break;
+ }
+
+ } else {
+ *found_previous = true;
+ }
+
+ /* Free the entry, we don't need it any more */
+ krb5_kt_free_entry(context, &entry);
+ /* Make sure we do not double free */
+ ZERO_STRUCT(entry);
+ } while (code == 0);
+
+ krb5_kt_end_seq_get(context, keytab, &cursor);
+
+ switch (code) {
+ case 0:
+ break;
+ case ENOENT:
+ case KRB5_KT_END:
+ break;
+ default:
+ *error_string = talloc_asprintf(mem_ctx,
+ "failed in deleting old entries for principal: %s\n",
+ smb_get_krb5_error_message(context,
+ code,
+ mem_ctx));
+ }
+
+ code = 0;
+done:
+ talloc_free(tmp_ctx);
+ return code;
+}
diff --git a/source4/auth/kerberos/krb5_init_context.c b/source4/auth/kerberos/krb5_init_context.c
new file mode 100644
index 0000000..48cb256
--- /dev/null
+++ b/source4/auth/kerberos/krb5_init_context.c
@@ -0,0 +1,885 @@
+/*
+ Unix SMB/CIFS implementation.
+ Wrapper for krb5_init_context
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
+ Copyright (C) Andrew Tridgell 2005
+ Copyright (C) Stefan Metzmacher 2004
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/kerberos.h"
+#include <tevent.h>
+#include "auth/kerberos/kerberos.h"
+#include "lib/socket/socket.h"
+#include "lib/stream/packet.h"
+#include "system/network.h"
+#include "param/param.h"
+#include "libcli/resolve/resolve.h"
+#include "../lib/tsocket/tsocket.h"
+#include "krb5_init_context.h"
+#ifdef SAMBA4_USES_HEIMDAL
+#include "../lib/dbwrap/dbwrap.h"
+#include "../lib/dbwrap/dbwrap_rbt.h"
+#include "../lib/util/util_tdb.h"
+#include <krb5/send_to_kdc_plugin.h>
+#endif
+
+/*
+ context structure for operations on cldap packets
+*/
+struct smb_krb5_socket {
+ struct socket_context *sock;
+
+ /* the fd event */
+ struct tevent_fd *fde;
+
+ NTSTATUS status;
+ DATA_BLOB request, reply;
+
+ struct packet_context *packet;
+
+ size_t partial_read;
+#ifdef SAMBA4_USES_HEIMDAL
+ krb5_krbhst_info *hi;
+#endif
+};
+
+static krb5_error_code smb_krb5_context_destroy(struct smb_krb5_context *ctx)
+{
+#ifdef SAMBA4_USES_HEIMDAL
+ if (ctx->pvt_log_data) {
+ /* Otherwise krb5_free_context will try and close what we
+ * have already free()ed */
+ krb5_set_warn_dest(ctx->krb5_context, NULL);
+ krb5_closelog(ctx->krb5_context,
+ (krb5_log_facility *)ctx->pvt_log_data);
+ }
+#endif
+ krb5_free_context(ctx->krb5_context);
+ return 0;
+}
+
+#ifdef SAMBA4_USES_HEIMDAL
+/* We never close down the DEBUG system, and no need to unreference the use */
+static void smb_krb5_debug_close(void *private_data) {
+ return;
+}
+#endif
+
+#ifdef SAMBA4_USES_HEIMDAL
+static void smb_krb5_debug_wrapper(
+#ifdef HAVE_KRB5_ADDLOG_FUNC_NEED_CONTEXT
+ krb5_context ctx,
+#endif /* HAVE_KRB5_ADDLOG_FUNC_NEED_CONTEXT */
+ const char *timestr, const char *msg, void *private_data)
+{
+ DEBUGC(DBGC_KERBEROS, 3, ("Kerberos: %s\n", msg));
+}
+#endif
+
+#ifdef SAMBA4_USES_HEIMDAL
+/*
+ handle recv events on a smb_krb5 socket
+*/
+static void smb_krb5_socket_recv(struct smb_krb5_socket *smb_krb5)
+{
+ TALLOC_CTX *tmp_ctx = talloc_new(smb_krb5);
+ DATA_BLOB blob;
+ size_t nread, dsize;
+
+ smb_krb5->status = socket_pending(smb_krb5->sock, &dsize);
+ if (!NT_STATUS_IS_OK(smb_krb5->status)) {
+ talloc_free(tmp_ctx);
+ return;
+ }
+
+ blob = data_blob_talloc(tmp_ctx, NULL, dsize);
+ if (blob.data == NULL && dsize != 0) {
+ smb_krb5->status = NT_STATUS_NO_MEMORY;
+ talloc_free(tmp_ctx);
+ return;
+ }
+
+ smb_krb5->status = socket_recv(smb_krb5->sock, blob.data, blob.length, &nread);
+ if (!NT_STATUS_IS_OK(smb_krb5->status)) {
+ talloc_free(tmp_ctx);
+ return;
+ }
+ blob.length = nread;
+
+ if (nread == 0) {
+ smb_krb5->status = NT_STATUS_UNEXPECTED_NETWORK_ERROR;
+ talloc_free(tmp_ctx);
+ return;
+ }
+
+ DEBUG(4,("Received smb_krb5 packet of length %d\n",
+ (int)blob.length));
+
+ talloc_steal(smb_krb5, blob.data);
+ smb_krb5->reply = blob;
+ talloc_free(tmp_ctx);
+}
+
+static NTSTATUS smb_krb5_full_packet(void *private_data, DATA_BLOB data)
+{
+ struct smb_krb5_socket *smb_krb5 = talloc_get_type(private_data, struct smb_krb5_socket);
+ talloc_steal(smb_krb5, data.data);
+ smb_krb5->reply = data;
+ smb_krb5->reply.length -= 4;
+ smb_krb5->reply.data += 4;
+ return NT_STATUS_OK;
+}
+
+/*
+ handle request timeouts
+*/
+static void smb_krb5_request_timeout(struct tevent_context *event_ctx,
+ struct tevent_timer *te, struct timeval t,
+ void *private_data)
+{
+ struct smb_krb5_socket *smb_krb5 = talloc_get_type(private_data, struct smb_krb5_socket);
+ DEBUG(5,("Timed out smb_krb5 packet\n"));
+ smb_krb5->status = NT_STATUS_IO_TIMEOUT;
+}
+
+static void smb_krb5_error_handler(void *private_data, NTSTATUS status)
+{
+ struct smb_krb5_socket *smb_krb5 = talloc_get_type(private_data, struct smb_krb5_socket);
+ smb_krb5->status = status;
+}
+
+/*
+ handle send events on a smb_krb5 socket
+*/
+static void smb_krb5_socket_send(struct smb_krb5_socket *smb_krb5)
+{
+ NTSTATUS status;
+
+ size_t len;
+
+ len = smb_krb5->request.length;
+ status = socket_send(smb_krb5->sock, &smb_krb5->request, &len);
+
+ if (!NT_STATUS_IS_OK(status)) return;
+
+ TEVENT_FD_READABLE(smb_krb5->fde);
+
+ TEVENT_FD_NOT_WRITEABLE(smb_krb5->fde);
+ return;
+}
+
+
+/*
+ handle fd events on a smb_krb5_socket
+*/
+static void smb_krb5_socket_handler(struct tevent_context *ev, struct tevent_fd *fde,
+ uint16_t flags, void *private_data)
+{
+ struct smb_krb5_socket *smb_krb5 = talloc_get_type(private_data, struct smb_krb5_socket);
+ switch (smb_krb5->hi->proto) {
+ case KRB5_KRBHST_UDP:
+ if (flags & TEVENT_FD_READ) {
+ smb_krb5_socket_recv(smb_krb5);
+ return;
+ }
+ if (flags & TEVENT_FD_WRITE) {
+ smb_krb5_socket_send(smb_krb5);
+ return;
+ }
+ /* not reached */
+ return;
+ case KRB5_KRBHST_TCP:
+ if (flags & TEVENT_FD_READ) {
+ packet_recv(smb_krb5->packet);
+ return;
+ }
+ if (flags & TEVENT_FD_WRITE) {
+ packet_queue_run(smb_krb5->packet);
+ return;
+ }
+ /* not reached */
+ return;
+ case KRB5_KRBHST_HTTP:
+ /* can't happen */
+ break;
+ }
+}
+
+static krb5_error_code smb_krb5_send_and_recv_func_int(struct smb_krb5_context *smb_krb5_context,
+ struct tevent_context *ev,
+ krb5_krbhst_info *hi,
+ struct addrinfo *ai,
+ smb_krb5_send_to_kdc_func func,
+ void *data,
+ time_t timeout,
+ const krb5_data *send_buf,
+ krb5_data *recv_buf)
+{
+ krb5_error_code ret;
+ NTSTATUS status;
+ const char *name;
+ struct addrinfo *a;
+ struct smb_krb5_socket *smb_krb5;
+
+ DATA_BLOB send_blob;
+
+ TALLOC_CTX *frame = talloc_stackframe();
+ if (frame == NULL) {
+ return ENOMEM;
+ }
+
+ send_blob = data_blob_const(send_buf->data, send_buf->length);
+
+ for (a = ai; a; a = a->ai_next) {
+ struct socket_address *remote_addr;
+ smb_krb5 = talloc(frame, struct smb_krb5_socket);
+ if (!smb_krb5) {
+ TALLOC_FREE(frame);
+ return ENOMEM;
+ }
+ smb_krb5->hi = hi;
+
+ switch (a->ai_family) {
+ case PF_INET:
+ name = "ipv4";
+ break;
+#ifdef HAVE_IPV6
+ case PF_INET6:
+ name = "ipv6";
+ break;
+#endif
+ default:
+ TALLOC_FREE(frame);
+ return EINVAL;
+ }
+
+ status = NT_STATUS_INVALID_PARAMETER;
+ switch (hi->proto) {
+ case KRB5_KRBHST_UDP:
+ status = socket_create(smb_krb5, name,
+ SOCKET_TYPE_DGRAM,
+ &smb_krb5->sock, 0);
+ break;
+ case KRB5_KRBHST_TCP:
+ status = socket_create(smb_krb5, name,
+ SOCKET_TYPE_STREAM,
+ &smb_krb5->sock, 0);
+ break;
+ case KRB5_KRBHST_HTTP:
+ TALLOC_FREE(frame);
+ return EINVAL;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(smb_krb5);
+ continue;
+ }
+
+ remote_addr = socket_address_from_sockaddr(smb_krb5, a->ai_addr, a->ai_addrlen);
+ if (!remote_addr) {
+ talloc_free(smb_krb5);
+ continue;
+ }
+
+ status = socket_connect_ev(smb_krb5->sock, NULL, remote_addr, 0, ev);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(smb_krb5);
+ continue;
+ }
+
+ /* Setup the FDE, start listening for read events
+ * from the start (otherwise we may miss a socket
+ * drop) and mark as AUTOCLOSE along with the fde */
+
+ /* Ths is equivilant to EVENT_FD_READABLE(smb_krb5->fde) */
+ smb_krb5->fde = tevent_add_fd(ev, smb_krb5->sock,
+ socket_get_fd(smb_krb5->sock),
+ TEVENT_FD_READ,
+ smb_krb5_socket_handler, smb_krb5);
+ /* its now the job of the event layer to close the socket */
+ tevent_fd_set_close_fn(smb_krb5->fde, socket_tevent_fd_close_fn);
+ socket_set_flags(smb_krb5->sock, SOCKET_FLAG_NOCLOSE);
+
+ tevent_add_timer(ev, smb_krb5,
+ timeval_current_ofs(timeout, 0),
+ smb_krb5_request_timeout, smb_krb5);
+
+ smb_krb5->status = NT_STATUS_OK;
+ smb_krb5->reply = data_blob(NULL, 0);
+
+ switch (hi->proto) {
+ case KRB5_KRBHST_UDP:
+ TEVENT_FD_WRITEABLE(smb_krb5->fde);
+ smb_krb5->request = send_blob;
+ break;
+ case KRB5_KRBHST_TCP:
+
+ smb_krb5->packet = packet_init(smb_krb5);
+ if (smb_krb5->packet == NULL) {
+ talloc_free(smb_krb5);
+ return ENOMEM;
+ }
+ packet_set_private(smb_krb5->packet, smb_krb5);
+ packet_set_socket(smb_krb5->packet, smb_krb5->sock);
+ packet_set_callback(smb_krb5->packet, smb_krb5_full_packet);
+ packet_set_full_request(smb_krb5->packet, packet_full_request_u32);
+ packet_set_error_handler(smb_krb5->packet, smb_krb5_error_handler);
+ packet_set_event_context(smb_krb5->packet, ev);
+ packet_set_fde(smb_krb5->packet, smb_krb5->fde);
+
+ smb_krb5->request = data_blob_talloc(smb_krb5, NULL, send_blob.length + 4);
+ RSIVAL(smb_krb5->request.data, 0, send_blob.length);
+ memcpy(smb_krb5->request.data+4, send_blob.data, send_blob.length);
+ packet_send(smb_krb5->packet, smb_krb5->request);
+ break;
+ case KRB5_KRBHST_HTTP:
+ TALLOC_FREE(frame);
+ return EINVAL;
+ }
+ while ((NT_STATUS_IS_OK(smb_krb5->status)) && !smb_krb5->reply.length) {
+ if (tevent_loop_once(ev) != 0) {
+ TALLOC_FREE(frame);
+ return EINVAL;
+ }
+
+ if (func) {
+ /* After each and every event loop, reset the
+ * send_to_kdc pointers to what they were when
+ * we entered this loop. That way, if a
+ * nested event has invalidated them, we put
+ * it back before we return to the heimdal
+ * code */
+ ret = smb_krb5_set_send_to_kdc_func(smb_krb5_context,
+ NULL, /* send_to_realm */
+ func,
+ data);
+ if (ret != 0) {
+ TALLOC_FREE(frame);
+ return ret;
+ }
+ }
+ }
+ if (NT_STATUS_EQUAL(smb_krb5->status, NT_STATUS_IO_TIMEOUT)) {
+ talloc_free(smb_krb5);
+ continue;
+ }
+
+ if (!NT_STATUS_IS_OK(smb_krb5->status)) {
+ struct tsocket_address *addr = socket_address_to_tsocket_address(smb_krb5, remote_addr);
+ const char *addr_string = NULL;
+ if (addr) {
+ addr_string = tsocket_address_inet_addr_string(addr, smb_krb5);
+ } else {
+ addr_string = NULL;
+ }
+ DEBUG(2,("Error reading smb_krb5 reply packet: %s from %s\n", nt_errstr(smb_krb5->status),
+ addr_string));
+ talloc_free(smb_krb5);
+ continue;
+ }
+
+ ret = krb5_data_copy(recv_buf, smb_krb5->reply.data, smb_krb5->reply.length);
+ if (ret) {
+ TALLOC_FREE(frame);
+ return ret;
+ }
+ talloc_free(smb_krb5);
+
+ break;
+ }
+ TALLOC_FREE(frame);
+ if (a) {
+ return 0;
+ }
+ return KRB5_KDC_UNREACH;
+}
+
+krb5_error_code smb_krb5_send_and_recv_func(struct smb_krb5_context *smb_krb5_context,
+ void *data,
+ krb5_krbhst_info *hi,
+ time_t timeout,
+ const krb5_data *send_buf,
+ krb5_data *recv_buf)
+{
+ krb5_error_code ret;
+ struct addrinfo *ai;
+
+ struct tevent_context *ev;
+ TALLOC_CTX *frame = talloc_stackframe();
+ if (frame == NULL) {
+ return ENOMEM;
+ }
+
+ if (data == NULL) {
+ /* If no event context was available, then create one for this loop */
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ TALLOC_FREE(frame);
+ return ENOMEM;
+ }
+ } else {
+ ev = talloc_get_type_abort(data, struct tevent_context);
+ }
+
+ ret = krb5_krbhst_get_addrinfo(smb_krb5_context->krb5_context, hi, &ai);
+ if (ret) {
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ ret = smb_krb5_send_and_recv_func_int(smb_krb5_context,
+ ev, hi, ai,
+ smb_krb5_send_and_recv_func,
+ data, timeout, send_buf, recv_buf);
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+krb5_error_code smb_krb5_send_and_recv_func_forced_tcp(struct smb_krb5_context *smb_krb5_context,
+ struct addrinfo *ai,
+ time_t timeout,
+ const krb5_data *send_buf,
+ krb5_data *recv_buf)
+{
+ krb5_error_code k5ret;
+ krb5_krbhst_info hi = {
+ .proto = KRB5_KRBHST_TCP,
+ };
+ struct tevent_context *ev;
+ TALLOC_CTX *frame = talloc_stackframe();
+ if (frame == NULL) {
+ return ENOMEM;
+ }
+
+ /* no event context is passed in, create one for this loop */
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ TALLOC_FREE(frame);
+ return ENOMEM;
+ }
+
+ /* No need to pass in send_and_recv functions, we won't nest on this private event loop */
+ k5ret = smb_krb5_send_and_recv_func_int(smb_krb5_context, ev, &hi, ai, NULL, NULL,
+ timeout, send_buf, recv_buf);
+ TALLOC_FREE(frame);
+ return k5ret;
+}
+
+static struct db_context *smb_krb5_plugin_db;
+
+struct smb_krb5_send_to_kdc_state {
+ intptr_t key_ptr;
+ struct smb_krb5_context *smb_krb5_context;
+ smb_krb5_send_to_realm_func send_to_realm;
+ smb_krb5_send_to_kdc_func send_to_kdc;
+ void *private_data;
+};
+
+static int smb_krb5_send_to_kdc_state_destructor(struct smb_krb5_send_to_kdc_state *state)
+{
+ TDB_DATA key = make_tdb_data((uint8_t *)&state->key_ptr, sizeof(state->key_ptr));
+ struct db_record *rec = NULL;
+ NTSTATUS status;
+
+ rec = dbwrap_fetch_locked(smb_krb5_plugin_db, state, key);
+ if (rec == NULL) {
+ return 0;
+ }
+
+ status = dbwrap_record_delete(rec);
+ TALLOC_FREE(rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+
+ state->smb_krb5_context = NULL;
+ return 0;
+}
+
+krb5_error_code smb_krb5_set_send_to_kdc_func(struct smb_krb5_context *smb_krb5_context,
+ smb_krb5_send_to_realm_func send_to_realm,
+ smb_krb5_send_to_kdc_func send_to_kdc,
+ void *private_data)
+{
+ intptr_t key_ptr = (intptr_t)smb_krb5_context->krb5_context;
+ TDB_DATA key = make_tdb_data((uint8_t *)&key_ptr, sizeof(key_ptr));
+ intptr_t value_ptr = (intptr_t)NULL;
+ TDB_DATA value = make_tdb_data(NULL, 0);
+ struct db_record *rec = NULL;
+ struct smb_krb5_send_to_kdc_state *state = NULL;
+ NTSTATUS status;
+
+ rec = dbwrap_fetch_locked(smb_krb5_plugin_db, smb_krb5_context, key);
+ if (rec == NULL) {
+ return ENOMEM;
+ }
+
+ value = dbwrap_record_get_value(rec);
+ if (value.dsize != 0) {
+ SMB_ASSERT(value.dsize == sizeof(value_ptr));
+ memcpy(&value_ptr, value.dptr, sizeof(value_ptr));
+ state = talloc_get_type_abort((const void *)value_ptr,
+ struct smb_krb5_send_to_kdc_state);
+ if (send_to_realm == NULL && send_to_kdc == NULL) {
+ status = dbwrap_record_delete(rec);
+ TALLOC_FREE(rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ return EINVAL;
+ }
+ return 0;
+ }
+ state->send_to_realm = send_to_realm;
+ state->send_to_kdc = send_to_kdc;
+ state->private_data = private_data;
+ TALLOC_FREE(rec);
+ return 0;
+ }
+
+ if (send_to_kdc == NULL && send_to_realm == NULL) {
+ TALLOC_FREE(rec);
+ return 0;
+ }
+
+ state = talloc_zero(smb_krb5_context,
+ struct smb_krb5_send_to_kdc_state);
+ if (state == NULL) {
+ TALLOC_FREE(rec);
+ return ENOMEM;
+ }
+ state->key_ptr = key_ptr;
+ state->smb_krb5_context = smb_krb5_context;
+ state->send_to_realm = send_to_realm;
+ state->send_to_kdc = send_to_kdc;
+ state->private_data = private_data;
+
+ value_ptr = (intptr_t)state;
+ value = make_tdb_data((uint8_t *)&value_ptr, sizeof(value_ptr));
+
+ status = dbwrap_record_store(rec, value, TDB_INSERT);
+ TALLOC_FREE(rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ return EINVAL;
+ }
+ talloc_set_destructor(state, smb_krb5_send_to_kdc_state_destructor);
+
+ return 0;
+}
+
+static krb5_error_code smb_krb5_plugin_init(krb5_context context, void **pctx)
+{
+ *pctx = NULL;
+ return 0;
+}
+
+static void smb_krb5_plugin_fini(void *ctx)
+{
+}
+
+static void smb_krb5_send_to_kdc_state_parser(TDB_DATA key, TDB_DATA value,
+ void *private_data)
+{
+ struct smb_krb5_send_to_kdc_state **state =
+ (struct smb_krb5_send_to_kdc_state **)private_data;
+ intptr_t value_ptr;
+
+ SMB_ASSERT(value.dsize == sizeof(value_ptr));
+ memcpy(&value_ptr, value.dptr, sizeof(value_ptr));
+ *state = talloc_get_type_abort((const void *)value_ptr,
+ struct smb_krb5_send_to_kdc_state);
+}
+
+static struct smb_krb5_send_to_kdc_state *
+smb_krb5_send_to_kdc_get_state(krb5_context context)
+{
+ intptr_t key_ptr = (intptr_t)context;
+ TDB_DATA key = make_tdb_data((uint8_t *)&key_ptr, sizeof(key_ptr));
+ struct smb_krb5_send_to_kdc_state *state = NULL;
+ NTSTATUS status;
+
+ status = dbwrap_parse_record(smb_krb5_plugin_db, key,
+ smb_krb5_send_to_kdc_state_parser,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NULL;
+ }
+
+ return state;
+}
+
+static krb5_error_code smb_krb5_plugin_send_to_kdc(krb5_context context,
+ void *ctx,
+ krb5_krbhst_info *ho,
+ time_t timeout,
+ const krb5_data *in,
+ krb5_data *out)
+{
+ struct smb_krb5_send_to_kdc_state *state = NULL;
+
+ state = smb_krb5_send_to_kdc_get_state(context);
+ if (state == NULL) {
+ return KRB5_PLUGIN_NO_HANDLE;
+ }
+
+ if (state->send_to_kdc == NULL) {
+ return KRB5_PLUGIN_NO_HANDLE;
+ }
+
+ return state->send_to_kdc(state->smb_krb5_context,
+ state->private_data,
+ ho, timeout, in, out);
+}
+
+static krb5_error_code smb_krb5_plugin_send_to_realm(krb5_context context,
+ void *ctx,
+ krb5_const_realm realm,
+ time_t timeout,
+ const krb5_data *in,
+ krb5_data *out)
+{
+ struct smb_krb5_send_to_kdc_state *state = NULL;
+
+ state = smb_krb5_send_to_kdc_get_state(context);
+ if (state == NULL) {
+ return KRB5_PLUGIN_NO_HANDLE;
+ }
+
+ if (state->send_to_realm == NULL) {
+ return KRB5_PLUGIN_NO_HANDLE;
+ }
+
+ return state->send_to_realm(state->smb_krb5_context,
+ state->private_data,
+ realm, timeout, in, out);
+}
+
+static krb5plugin_send_to_kdc_ftable smb_krb5_plugin_ftable = {
+ KRB5_PLUGIN_SEND_TO_KDC_VERSION_2,
+ smb_krb5_plugin_init,
+ smb_krb5_plugin_fini,
+ smb_krb5_plugin_send_to_kdc,
+ smb_krb5_plugin_send_to_realm
+};
+#endif
+
+krb5_error_code
+smb_krb5_init_context_basic(TALLOC_CTX *tmp_ctx,
+ struct loadparm_context *lp_ctx,
+ krb5_context *_krb5_context)
+{
+ krb5_error_code ret;
+#ifdef SAMBA4_USES_HEIMDAL
+ char **config_files;
+ const char *config_file, *realm;
+#endif
+ krb5_context krb5_ctx;
+
+ ret = smb_krb5_init_context_common(&krb5_ctx);
+ if (ret) {
+ return ret;
+ }
+
+ /* The MIT Kerberos build relies on using the system krb5.conf file.
+ * If you really want to use another file please set KRB5_CONFIG
+ * accordingly. */
+#ifdef SAMBA4_USES_HEIMDAL
+ config_file = lpcfg_config_path(tmp_ctx, lp_ctx, "krb5.conf");
+ if (!config_file) {
+ krb5_free_context(krb5_ctx);
+ return ENOMEM;
+ }
+
+ /* Use our local krb5.conf file by default */
+ ret = krb5_prepend_config_files_default(config_file, &config_files);
+ if (ret) {
+ DEBUG(1,("krb5_prepend_config_files_default failed (%s)\n",
+ smb_get_krb5_error_message(krb5_ctx, ret, tmp_ctx)));
+ krb5_free_context(krb5_ctx);
+ return ret;
+ }
+
+ ret = krb5_set_config_files(krb5_ctx, config_files);
+ krb5_free_config_files(config_files);
+ if (ret) {
+ DEBUG(1,("krb5_set_config_files failed (%s)\n",
+ smb_get_krb5_error_message(krb5_ctx, ret, tmp_ctx)));
+ krb5_free_context(krb5_ctx);
+ return ret;
+ }
+
+ /*
+ * This is already called in smb_krb5_init_context_common(),
+ * but krb5_set_config_files() may resets it.
+ */
+ krb5_set_dns_canonicalize_hostname(krb5_ctx, false);
+
+ realm = lpcfg_realm(lp_ctx);
+ if (realm != NULL) {
+ ret = krb5_set_default_realm(krb5_ctx, realm);
+ if (ret) {
+ DEBUG(1,("krb5_set_default_realm failed (%s)\n",
+ smb_get_krb5_error_message(krb5_ctx, ret, tmp_ctx)));
+ krb5_free_context(krb5_ctx);
+ return ret;
+ }
+ }
+
+ if (smb_krb5_plugin_db == NULL) {
+ /*
+ * while krb5_plugin_register() takes a krb5_context,
+ * plugins are registered into a global list, so
+ * we only do that once
+ *
+ * We maintain a separate dispatch table for per
+ * krb5_context state.
+ */
+ ret = krb5_plugin_register(krb5_ctx, PLUGIN_TYPE_DATA,
+ KRB5_PLUGIN_SEND_TO_KDC,
+ &smb_krb5_plugin_ftable);
+ if (ret) {
+ DEBUG(1,("krb5_plugin_register(KRB5_PLUGIN_SEND_TO_KDC) failed (%s)\n",
+ smb_get_krb5_error_message(krb5_ctx, ret, tmp_ctx)));
+ krb5_free_context(krb5_ctx);
+ return ret;
+ }
+ smb_krb5_plugin_db = db_open_rbt(NULL);
+ if (smb_krb5_plugin_db == NULL) {
+ DEBUG(1,("db_open_rbt() failed\n"));
+ krb5_free_context(krb5_ctx);
+ return ENOMEM;
+ }
+ }
+#endif
+ *_krb5_context = krb5_ctx;
+ return 0;
+}
+
+krb5_error_code smb_krb5_init_context(void *parent_ctx,
+ struct loadparm_context *lp_ctx,
+ struct smb_krb5_context **smb_krb5_context)
+{
+ krb5_error_code ret;
+ TALLOC_CTX *tmp_ctx;
+ krb5_context kctx;
+#ifdef SAMBA4_USES_HEIMDAL
+ krb5_log_facility *logf;
+#endif
+
+ tmp_ctx = talloc_new(parent_ctx);
+ *smb_krb5_context = talloc_zero(tmp_ctx, struct smb_krb5_context);
+
+ if (!*smb_krb5_context || !tmp_ctx) {
+ talloc_free(tmp_ctx);
+ return ENOMEM;
+ }
+
+ ret = smb_krb5_init_context_basic(tmp_ctx, lp_ctx, &kctx);
+ if (ret) {
+ DEBUG(1,("smb_krb5_context_init_basic failed (%s)\n",
+ error_message(ret)));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ (*smb_krb5_context)->krb5_context = kctx;
+
+ talloc_set_destructor(*smb_krb5_context, smb_krb5_context_destroy);
+
+#ifdef SAMBA4_USES_HEIMDAL
+ /* TODO: Should we have a different name here? */
+ ret = krb5_initlog(kctx, "Samba", &logf);
+
+ if (ret) {
+ DEBUG(1,("krb5_initlog failed (%s)\n",
+ smb_get_krb5_error_message(kctx, ret, tmp_ctx)));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ (*smb_krb5_context)->pvt_log_data = logf;
+
+ ret = krb5_addlog_func(kctx, logf, 0 /* min */, -1 /* max */,
+ smb_krb5_debug_wrapper,
+ smb_krb5_debug_close, NULL);
+ if (ret) {
+ DEBUG(1,("krb5_addlog_func failed (%s)\n",
+ smb_get_krb5_error_message(kctx, ret, tmp_ctx)));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ krb5_set_warn_dest(kctx, logf);
+#endif
+ talloc_steal(parent_ctx, *smb_krb5_context);
+ talloc_free(tmp_ctx);
+
+ return 0;
+}
+
+#ifdef SAMBA4_USES_HEIMDAL
+krb5_error_code smb_krb5_context_set_event_ctx(struct smb_krb5_context *smb_krb5_context,
+ struct tevent_context *ev,
+ struct tevent_context **previous_ev)
+{
+ int ret;
+ if (!ev) {
+ return EINVAL;
+ }
+
+ *previous_ev = smb_krb5_context->current_ev;
+
+ smb_krb5_context->current_ev = talloc_reference(smb_krb5_context, ev);
+ if (!smb_krb5_context->current_ev) {
+ return ENOMEM;
+ }
+
+ /* Set use of our socket lib */
+ ret = smb_krb5_set_send_to_kdc_func(smb_krb5_context,
+ NULL, /* send_to_realm */
+ smb_krb5_send_and_recv_func,
+ ev);
+ if (ret) {
+ TALLOC_CTX *tmp_ctx = talloc_new(NULL);
+ DEBUG(1,("smb_krb5_set_send_recv_func failed (%s)\n",
+ smb_get_krb5_error_message(smb_krb5_context->krb5_context, ret, tmp_ctx)));
+ talloc_free(tmp_ctx);
+ talloc_unlink(smb_krb5_context, smb_krb5_context->current_ev);
+ smb_krb5_context->current_ev = NULL;
+ return ret;
+ }
+ return 0;
+}
+
+krb5_error_code smb_krb5_context_remove_event_ctx(struct smb_krb5_context *smb_krb5_context,
+ struct tevent_context *previous_ev,
+ struct tevent_context *ev)
+{
+ int ret;
+ talloc_unlink(smb_krb5_context, ev);
+ /* If there was a mismatch with things happening on a stack, then don't wipe things */
+ smb_krb5_context->current_ev = previous_ev;
+ /* Set use of our socket lib */
+ ret = smb_krb5_set_send_to_kdc_func(smb_krb5_context,
+ NULL, /* send_to_realm */
+ smb_krb5_send_and_recv_func,
+ previous_ev);
+ if (ret) {
+ TALLOC_CTX *tmp_ctx = talloc_new(NULL);
+ DEBUG(1,("smb_krb5_set_send_recv_func failed (%s)\n",
+ smb_get_krb5_error_message(smb_krb5_context->krb5_context, ret, tmp_ctx)));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ return 0;
+}
+#endif
diff --git a/source4/auth/kerberos/krb5_init_context.h b/source4/auth/kerberos/krb5_init_context.h
new file mode 100644
index 0000000..c7f7cfd
--- /dev/null
+++ b/source4/auth/kerberos/krb5_init_context.h
@@ -0,0 +1,79 @@
+/*
+ Unix SMB/CIFS implementation.
+ simple kerberos5 routines for active directory
+ Copyright (C) Andrew Bartlett 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/>.
+*/
+
+#ifndef _KRB5_INIT_CONTEXT_H_
+#define _KRB5_INIT_CONTEXT_H_
+
+struct smb_krb5_context {
+ krb5_context krb5_context;
+ void *pvt_log_data;
+ struct tevent_context *current_ev;
+};
+
+struct tevent_context;
+struct loadparm_context;
+
+krb5_error_code
+smb_krb5_init_context_basic(TALLOC_CTX *tmp_ctx,
+ struct loadparm_context *lp_ctx,
+ krb5_context *_krb5_context);
+
+krb5_error_code smb_krb5_init_context(void *parent_ctx,
+ struct loadparm_context *lp_ctx,
+ struct smb_krb5_context **smb_krb5_context);
+
+#ifdef SAMBA4_USES_HEIMDAL
+typedef krb5_error_code (*smb_krb5_send_to_realm_func)(struct smb_krb5_context *smb_krb5_context,
+ void *,
+ krb5_const_realm realm,
+ time_t,
+ const krb5_data *,
+ krb5_data *);
+typedef krb5_error_code (*smb_krb5_send_to_kdc_func)(struct smb_krb5_context *smb_krb5_context,
+ void *,
+ krb5_krbhst_info *,
+ time_t,
+ const krb5_data *,
+ krb5_data *);
+
+krb5_error_code smb_krb5_set_send_to_kdc_func(struct smb_krb5_context *smb_krb5_context,
+ smb_krb5_send_to_realm_func send_to_realm,
+ smb_krb5_send_to_kdc_func send_to_kdc,
+ void *private_data);
+
+krb5_error_code smb_krb5_send_and_recv_func(struct smb_krb5_context *smb_krb5_context,
+ void *data,
+ krb5_krbhst_info *hi,
+ time_t timeout,
+ const krb5_data *send_buf,
+ krb5_data *recv_buf);
+krb5_error_code smb_krb5_send_and_recv_func_forced_tcp(struct smb_krb5_context *smb_krb5_context,
+ struct addrinfo *ai,
+ time_t timeout,
+ const krb5_data *send_buf,
+ krb5_data *recv_buf);
+krb5_error_code smb_krb5_context_set_event_ctx(struct smb_krb5_context *smb_krb5_context,
+ struct tevent_context *ev,
+ struct tevent_context **previous_ev);
+krb5_error_code smb_krb5_context_remove_event_ctx(struct smb_krb5_context *smb_krb5_context,
+ struct tevent_context *previous_ev,
+ struct tevent_context *ev);
+#endif
+
+#endif /* _KRB5_INIT_CONTEXT_H_ */
diff --git a/source4/auth/kerberos/srv_keytab.c b/source4/auth/kerberos/srv_keytab.c
new file mode 100644
index 0000000..52e1e22
--- /dev/null
+++ b/source4/auth/kerberos/srv_keytab.c
@@ -0,0 +1,406 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Kerberos utility functions
+
+ Copyright (C) Andrew Bartlett <abartlet@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/>.
+*/
+
+/**
+ * @file srv_keytab.c
+ *
+ * @brief Kerberos keytab utility functions
+ *
+ */
+
+#include "includes.h"
+#include "system/kerberos.h"
+#include "auth/credentials/credentials.h"
+#include "auth/kerberos/kerberos.h"
+#include "auth/kerberos/kerberos_util.h"
+#include "auth/kerberos/kerberos_srv_keytab.h"
+
+static void keytab_principals_free(krb5_context context,
+ uint32_t num_principals,
+ krb5_principal *set)
+{
+ uint32_t i;
+
+ for (i = 0; i < num_principals; i++) {
+ krb5_free_principal(context, set[i]);
+ }
+}
+
+static krb5_error_code keytab_add_keys(TALLOC_CTX *parent_ctx,
+ uint32_t num_principals,
+ krb5_principal *principals,
+ krb5_principal salt_princ,
+ int kvno,
+ const char *password_s,
+ krb5_context context,
+ krb5_enctype *enctypes,
+ krb5_keytab keytab,
+ const char **error_string)
+{
+ unsigned int i, p;
+ krb5_error_code ret;
+ krb5_data password;
+ char *unparsed;
+
+ password.data = discard_const_p(char, password_s);
+ password.length = strlen(password_s);
+
+ for (i = 0; enctypes[i]; i++) {
+ krb5_keytab_entry entry;
+
+ ZERO_STRUCT(entry);
+
+ ret = smb_krb5_create_key_from_string(context,
+ salt_princ,
+ NULL,
+ &password,
+ enctypes[i],
+ KRB5_KT_KEY(&entry));
+ if (ret != 0) {
+ *error_string = talloc_strdup(parent_ctx,
+ "Failed to create key from string");
+ return ret;
+ }
+
+ entry.vno = kvno;
+
+ for (p = 0; p < num_principals; p++) {
+ unparsed = NULL;
+ entry.principal = principals[p];
+ ret = krb5_kt_add_entry(context, keytab, &entry);
+ if (ret != 0) {
+ char *k5_error_string =
+ smb_get_krb5_error_message(context,
+ ret, NULL);
+ krb5_unparse_name(context,
+ principals[p], &unparsed);
+ *error_string = talloc_asprintf(parent_ctx,
+ "Failed to add enctype %d entry for "
+ "%s(kvno %d) to keytab: %s\n",
+ (int)enctypes[i], unparsed,
+ kvno, k5_error_string);
+
+ free(unparsed);
+ talloc_free(k5_error_string);
+ krb5_free_keyblock_contents(context,
+ KRB5_KT_KEY(&entry));
+ return ret;
+ }
+
+ DEBUG(5, ("Added key (kvno %d) to keytab (enctype %d)\n",
+ kvno, (int)enctypes[i]));
+ }
+ krb5_free_keyblock_contents(context, KRB5_KT_KEY(&entry));
+ }
+ return 0;
+}
+
+static krb5_error_code create_keytab(TALLOC_CTX *parent_ctx,
+ const char *samAccountName,
+ const char *realm,
+ const char *saltPrincipal,
+ int kvno,
+ const char *new_secret,
+ const char *old_secret,
+ uint32_t supp_enctypes,
+ uint32_t num_principals,
+ krb5_principal *principals,
+ krb5_context context,
+ krb5_keytab keytab,
+ bool add_old,
+ const char **perror_string)
+{
+ krb5_error_code ret;
+ krb5_principal salt_princ = NULL;
+ krb5_enctype *enctypes;
+ TALLOC_CTX *mem_ctx;
+ const char *error_string = NULL;
+
+ if (!new_secret) {
+ /* There is no password here, so nothing to do */
+ return 0;
+ }
+
+ mem_ctx = talloc_new(parent_ctx);
+ if (!mem_ctx) {
+ *perror_string = talloc_strdup(parent_ctx,
+ "unable to allocate tmp_ctx for create_keytab");
+ return ENOMEM;
+ }
+
+ /* The salt used to generate these entries may be different however,
+ * fetch that */
+ ret = krb5_parse_name(context, saltPrincipal, &salt_princ);
+ if (ret) {
+ *perror_string = smb_get_krb5_error_message(context,
+ ret,
+ parent_ctx);
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ ret = ms_suptypes_to_ietf_enctypes(mem_ctx, supp_enctypes, &enctypes);
+ if (ret) {
+ *perror_string = talloc_asprintf(parent_ctx,
+ "create_keytab: generating list of "
+ "encryption types failed (%s)\n",
+ smb_get_krb5_error_message(context,
+ ret, mem_ctx));
+ goto done;
+ }
+
+ ret = keytab_add_keys(mem_ctx,
+ num_principals,
+ principals,
+ salt_princ, kvno, new_secret,
+ context, enctypes, keytab, &error_string);
+ if (ret) {
+ *perror_string = talloc_steal(parent_ctx, error_string);
+ goto done;
+ }
+
+ if (old_secret && add_old && kvno != 0) {
+ ret = keytab_add_keys(mem_ctx,
+ num_principals,
+ principals,
+ salt_princ, kvno - 1, old_secret,
+ context, enctypes, keytab, &error_string);
+ if (ret) {
+ *perror_string = talloc_steal(parent_ctx, error_string);
+ }
+ }
+
+done:
+ krb5_free_principal(context, salt_princ);
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+/**
+ * @brief Update a Kerberos keytab and removes any obsolete keytab entries.
+ *
+ * If the keytab does not exist, this function will create one.
+ *
+ * @param[in] parent_ctx Talloc memory context
+ * @param[in] context Kerberos context
+ * @param[in] keytab_name Keytab to open
+ * @param[in] samAccountName User account to update
+ * @param[in] realm Kerberos realm
+ * @param[in] SPNs Service principal names to update
+ * @param[in] num_SPNs Length of SPNs
+ * @param[in] saltPrincipal Salt used for AES encryption.
+ * Required, unless delete_all_kvno is set.
+ * @param[in] old_secret Old password
+ * @param[in] new_secret New password
+ * @param[in] kvno Current key version number
+ * @param[in] supp_enctypes msDS-SupportedEncryptionTypes bit-field
+ * @param[in] delete_all_kvno Removes all obsolete entries, without
+ * recreating the keytab.
+ * @param[out] _keytab If supplied, returns the keytab
+ * @param[out] perror_string Error string on failure
+ *
+ * @return 0 on success, errno on failure
+ */
+krb5_error_code smb_krb5_update_keytab(TALLOC_CTX *parent_ctx,
+ krb5_context context,
+ const char *keytab_name,
+ const char *samAccountName,
+ const char *realm,
+ const char **SPNs,
+ int num_SPNs,
+ const char *saltPrincipal,
+ const char *new_secret,
+ const char *old_secret,
+ int kvno,
+ uint32_t supp_enctypes,
+ bool delete_all_kvno,
+ krb5_keytab *_keytab,
+ const char **perror_string)
+{
+ krb5_keytab keytab;
+ krb5_error_code ret;
+ bool found_previous = false;
+ TALLOC_CTX *tmp_ctx;
+ krb5_principal *principals = NULL;
+ uint32_t num_principals = 0;
+ char *upper_realm;
+ const char *error_string = NULL;
+
+ if (keytab_name == NULL) {
+ return ENOENT;
+ }
+
+ ret = krb5_kt_resolve(context, keytab_name, &keytab);
+ if (ret) {
+ *perror_string = smb_get_krb5_error_message(context,
+ ret, parent_ctx);
+ return ret;
+ }
+
+ DEBUG(5, ("Opened keytab %s\n", keytab_name));
+
+ tmp_ctx = talloc_new(parent_ctx);
+ if (!tmp_ctx) {
+ *perror_string = talloc_strdup(parent_ctx,
+ "Failed to allocate memory context");
+ return ENOMEM;
+ }
+
+ upper_realm = strupper_talloc(tmp_ctx, realm);
+ if (upper_realm == NULL) {
+ *perror_string = talloc_strdup(parent_ctx,
+ "Cannot allocate memory to upper case realm");
+ talloc_free(tmp_ctx);
+ return ENOMEM;
+ }
+
+ ret = smb_krb5_create_principals_array(tmp_ctx,
+ context,
+ samAccountName,
+ upper_realm,
+ num_SPNs,
+ SPNs,
+ &num_principals,
+ &principals,
+ &error_string);
+ if (ret != 0) {
+ *perror_string = talloc_asprintf(parent_ctx,
+ "Failed to load principals from ldb message: %s\n",
+ error_string);
+ goto done;
+ }
+
+ ret = smb_krb5_remove_obsolete_keytab_entries(tmp_ctx,
+ context,
+ keytab,
+ num_principals,
+ principals,
+ kvno,
+ &found_previous,
+ &error_string);
+ if (ret != 0) {
+ *perror_string = talloc_asprintf(parent_ctx,
+ "Failed to remove old principals from keytab: %s\n",
+ error_string);
+ goto done;
+ }
+
+ if (!delete_all_kvno) {
+ /* Create a new keytab. If during the cleanout we found
+ * entries for kvno -1, then don't try and duplicate them.
+ * Otherwise, add kvno, and kvno -1 */
+ if (saltPrincipal == NULL) {
+ *perror_string = talloc_strdup(parent_ctx,
+ "No saltPrincipal provided");
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = create_keytab(tmp_ctx,
+ samAccountName, upper_realm, saltPrincipal,
+ kvno, new_secret, old_secret,
+ supp_enctypes,
+ num_principals,
+ principals,
+ context, keytab,
+ found_previous ? false : true,
+ &error_string);
+ if (ret) {
+ *perror_string = talloc_steal(parent_ctx, error_string);
+ }
+ }
+
+ if (ret == 0 && _keytab != NULL) {
+ /* caller wants the keytab handle back */
+ *_keytab = keytab;
+ }
+
+done:
+ keytab_principals_free(context, num_principals, principals);
+ if (ret != 0 || _keytab == NULL) {
+ krb5_kt_close(context, keytab);
+ }
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/**
+ * @brief Wrapper around smb_krb5_update_keytab() for creating an in-memory keytab
+ *
+ * @param[in] parent_ctx Talloc memory context
+ * @param[in] context Kerberos context
+ * @param[in] new_secret New password
+ * @param[in] samAccountName User account to update
+ * @param[in] realm Kerberos realm
+ * @param[in] salt_principal Salt used for AES encryption.
+ * Required, unless delete_all_kvno is set.
+ * @param[in] kvno Current key version number
+ * @param[out] keytab If supplied, returns the keytab
+ * @param[out] keytab_name Returns the created keytab name
+ *
+ * @return 0 on success, errno on failure
+ */
+krb5_error_code smb_krb5_create_memory_keytab(TALLOC_CTX *parent_ctx,
+ krb5_context context,
+ const char *new_secret,
+ const char *samAccountName,
+ const char *realm,
+ const char *salt_principal,
+ int kvno,
+ krb5_keytab *keytab,
+ const char **keytab_name)
+{
+ krb5_error_code ret;
+ TALLOC_CTX *mem_ctx = talloc_new(parent_ctx);
+ const char *rand_string;
+ const char *error_string = NULL;
+ if (!mem_ctx) {
+ return ENOMEM;
+ }
+
+ rand_string = generate_random_str(mem_ctx, 16);
+ if (!rand_string) {
+ talloc_free(mem_ctx);
+ return ENOMEM;
+ }
+
+ *keytab_name = talloc_asprintf(mem_ctx, "MEMORY:%s", rand_string);
+ if (*keytab_name == NULL) {
+ talloc_free(mem_ctx);
+ return ENOMEM;
+ }
+
+ ret = smb_krb5_update_keytab(mem_ctx, context,
+ *keytab_name, samAccountName, realm,
+ NULL, 0, salt_principal, new_secret, NULL,
+ kvno, ENC_ALL_TYPES,
+ false, keytab, &error_string);
+ if (ret == 0) {
+ talloc_steal(parent_ctx, *keytab_name);
+ } else {
+ DEBUG(0, ("Failed to create in-memory keytab: %s\n",
+ error_string));
+ *keytab_name = NULL;
+ }
+ talloc_free(mem_ctx);
+ return ret;
+}
diff --git a/source4/auth/kerberos/wscript_build b/source4/auth/kerberos/wscript_build
new file mode 100644
index 0000000..5f14bed
--- /dev/null
+++ b/source4/auth/kerberos/wscript_build
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+
+bld.SAMBA_SUBSYSTEM('KRB_INIT_CTX',
+ source='krb5_init_context.c',
+ deps='gssapi krb5samba dbwrap samba-util'
+ )
+
+bld.SAMBA_LIBRARY('authkrb5',
+ source='kerberos_pac.c',
+ autoproto='proto.h',
+ public_deps='ndr-krb5pac krb5samba samba_socket LIBCLI_RESOLVE asn1',
+ deps='common_auth tevent LIBPACKET ndr ldb krb5samba KRB_INIT_CTX KRB5_PAC samba-errors',
+ private_library=True
+ )
+
+bld.SAMBA_SUBSYSTEM('KERBEROS_UTIL',
+ autoproto='kerberos_util.h',
+ source='kerberos_util.c',
+ deps='authkrb5 krb5samba com_err CREDENTIALS_KRB5',
+ )
+
+bld.SAMBA_SUBSYSTEM('KERBEROS_SRV_KEYTAB',
+ autoproto='kerberos_srv_keytab.h',
+ source='srv_keytab.c',
+ deps='authkrb5',
+ )
diff --git a/source4/auth/ntlm/auth.c b/source4/auth/ntlm/auth.c
new file mode 100644
index 0000000..09d660a
--- /dev/null
+++ b/source4/auth/ntlm/auth.c
@@ -0,0 +1,847 @@
+/*
+ Unix SMB/CIFS implementation.
+ Password and authentication handling
+ Copyright (C) Andrew Bartlett 2001-2002
+ Copyright (C) Stefan Metzmacher 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/util/dlinklist.h"
+#include "auth/auth.h"
+#include "auth/ntlm/auth_proto.h"
+#include "param/param.h"
+#include "dsdb/samdb/samdb.h"
+#include "libcli/wbclient/wbclient.h"
+#include "lib/util/samba_modules.h"
+#include "auth/credentials/credentials.h"
+#include "system/kerberos.h"
+#include "auth/kerberos/kerberos.h"
+#include "auth/kerberos/kerberos_util.h"
+#include "libds/common/roles.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+static NTSTATUS auth_generate_session_info_wrapper(struct auth4_context *auth_context,
+ TALLOC_CTX *mem_ctx,
+ void *server_returned_info,
+ const char *original_user_name,
+ uint32_t session_info_flags,
+ struct auth_session_info **session_info);
+
+/***************************************************************************
+ Set a fixed challenge
+***************************************************************************/
+_PUBLIC_ NTSTATUS auth_context_set_challenge(struct auth4_context *auth_ctx, const uint8_t chal[8], const char *set_by)
+{
+ auth_ctx->challenge.set_by = talloc_strdup(auth_ctx, set_by);
+ NT_STATUS_HAVE_NO_MEMORY(auth_ctx->challenge.set_by);
+
+ auth_ctx->challenge.data = data_blob_talloc(auth_ctx, chal, 8);
+ NT_STATUS_HAVE_NO_MEMORY(auth_ctx->challenge.data.data);
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Try to get a challenge out of the various authentication modules.
+ Returns a const char of length 8 bytes.
+****************************************************************************/
+_PUBLIC_ NTSTATUS auth_get_challenge(struct auth4_context *auth_ctx, uint8_t chal[8])
+{
+
+ if (auth_ctx->challenge.data.length == 8) {
+ DEBUG(5, ("auth_get_challenge: returning previous challenge by module %s (normal)\n",
+ auth_ctx->challenge.set_by));
+ memcpy(chal, auth_ctx->challenge.data.data, 8);
+ return NT_STATUS_OK;
+ }
+
+ if (!auth_ctx->challenge.set_by) {
+ generate_random_buffer(chal, 8);
+
+ auth_ctx->challenge.data = data_blob_talloc(auth_ctx, chal, 8);
+ NT_STATUS_HAVE_NO_MEMORY(auth_ctx->challenge.data.data);
+ auth_ctx->challenge.set_by = "random";
+ }
+
+ DEBUG(10,("auth_get_challenge: challenge set by %s\n",
+ auth_ctx->challenge.set_by));
+
+ return NT_STATUS_OK;
+}
+
+/**
+ * Check a user's Plaintext, LM or NTLM password.
+ * (sync version)
+ *
+ * Check a user's password, as given in the user_info struct and return various
+ * interesting details in the user_info_dc struct.
+ *
+ * The return value takes precedence over the contents of the user_info_dc
+ * struct. When the return is other than NT_STATUS_OK the contents
+ * of that structure is undefined.
+ *
+ * @param auth_ctx Supplies the challenges and some other data.
+ * Must be created with auth_context_create(), and the challenges should be
+ * filled in, either at creation or by calling the challenge geneation
+ * function auth_get_challenge().
+ *
+ * @param user_info Contains the user supplied components, including the passwords.
+ *
+ * @param mem_ctx The parent memory context for the user_info_dc structure
+ *
+ * @param user_info_dc If successful, contains information about the authentication,
+ * including a SAM_ACCOUNT struct describing the user.
+ *
+ * @return An NTSTATUS with NT_STATUS_OK or an appropriate error.
+ *
+ **/
+
+_PUBLIC_ NTSTATUS auth_check_password(struct auth4_context *auth_ctx,
+ TALLOC_CTX *mem_ctx,
+ const struct auth_usersupplied_info *user_info,
+ struct auth_user_info_dc **user_info_dc,
+ uint8_t *pauthoritative)
+{
+ struct tevent_req *subreq;
+ struct tevent_context *ev;
+ bool ok;
+ NTSTATUS status;
+
+ /*TODO: create a new event context here! */
+ ev = auth_ctx->event_ctx;
+
+ /*
+ * We are authoritative by default
+ */
+ *pauthoritative = 1;
+
+ subreq = auth_check_password_send(mem_ctx,
+ ev,
+ auth_ctx,
+ user_info);
+ if (subreq == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ok = tevent_req_poll(subreq, ev);
+ if (!ok) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ status = auth_check_password_recv(subreq, mem_ctx,
+ user_info_dc, pauthoritative);
+ TALLOC_FREE(subreq);
+
+ return status;
+}
+
+struct auth_check_password_state {
+ struct tevent_context *ev;
+ struct auth4_context *auth_ctx;
+ const struct auth_usersupplied_info *user_info;
+ struct auth_user_info_dc *user_info_dc;
+ struct auth_method_context *method;
+ uint8_t authoritative;
+};
+
+static void auth_check_password_next(struct tevent_req *req);
+
+/**
+ * Check a user's Plaintext, LM or NTLM password.
+ * async send hook
+ *
+ * Check a user's password, as given in the user_info struct and return various
+ * interesting details in the user_info_dc struct.
+ *
+ * The return value takes precedence over the contents of the user_info_dc
+ * struct. When the return is other than NT_STATUS_OK the contents
+ * of that structure is undefined.
+ *
+ * @param mem_ctx The memory context the request should operate on
+ *
+ * @param ev The tevent context the request should operate on
+ *
+ * @param auth_ctx Supplies the challenges and some other data. Must
+ * be created with make_auth_context(), and the
+ * challenges should be filled in, either at creation
+ * or by calling the challenge generation function
+ * auth_get_challenge().
+ *
+ * @param user_info Contains the user supplied components, including the passwords.
+ *
+ * @return The request handle or NULL on no memory error.
+ *
+ **/
+
+_PUBLIC_ struct tevent_req *auth_check_password_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct auth4_context *auth_ctx,
+ const struct auth_usersupplied_info *user_info)
+{
+ struct tevent_req *req;
+ struct auth_check_password_state *state;
+ /* if all the modules say 'not for me' this is reasonable */
+ NTSTATUS nt_status;
+ uint8_t chal[8];
+
+ DEBUG(3,("auth_check_password_send: "
+ "Checking password for unmapped user [%s]\\[%s]@[%s]\n",
+ user_info->client.domain_name, user_info->client.account_name,
+ user_info->workstation_name));
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct auth_check_password_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ /*
+ * We are authoritative by default.
+ */
+ state->ev = ev;
+ state->auth_ctx = auth_ctx;
+ state->user_info = user_info;
+ state->authoritative = 1;
+
+ if (user_info->mapped.account_name == NULL) {
+ struct auth_usersupplied_info *user_info_tmp;
+
+ /*
+ * We don't really do any mapping here.
+ *
+ * It's up to the backends to do mappings
+ * for their authentication.
+ */
+ user_info_tmp = talloc_zero(state, struct auth_usersupplied_info);
+ if (tevent_req_nomem(user_info_tmp, req)) {
+ return tevent_req_post(req, ev);;
+ }
+
+ /*
+ * The lifetime of user_info is longer than
+ * user_info_tmp, so we don't need to copy the
+ * strings.
+ */
+ *user_info_tmp = *user_info;
+ user_info_tmp->mapped.domain_name = user_info->client.domain_name;
+ user_info_tmp->mapped.account_name = user_info->client.account_name;
+
+ user_info = user_info_tmp;
+ state->user_info = user_info_tmp;
+ }
+
+ DEBUGADD(3,("auth_check_password_send: "
+ "user is: [%s]\\[%s]@[%s]\n",
+ user_info->mapped.domain_name,
+ user_info->mapped.account_name,
+ user_info->workstation_name));
+
+ nt_status = auth_get_challenge(auth_ctx, chal);
+ if (tevent_req_nterror(req, nt_status)) {
+ DEBUG(0,("auth_check_password_send: "
+ "Invalid challenge (length %u) stored for "
+ "this auth context set_by %s - cannot continue: %s\n",
+ (unsigned)auth_ctx->challenge.data.length,
+ auth_ctx->challenge.set_by,
+ nt_errstr(nt_status)));
+ return tevent_req_post(req, ev);
+ }
+
+ if (auth_ctx->challenge.set_by) {
+ DEBUG(10,("auth_check_password_send: "
+ "auth_context challenge created by %s\n",
+ auth_ctx->challenge.set_by));
+ }
+
+ DEBUG(10, ("auth_check_password_send: challenge is: \n"));
+ dump_data(5, auth_ctx->challenge.data.data,
+ auth_ctx->challenge.data.length);
+
+ state->method = state->auth_ctx->methods;
+ auth_check_password_next(req);
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void auth_check_password_done(struct tevent_req *subreq);
+
+static void auth_check_password_next(struct tevent_req *req)
+{
+ struct auth_check_password_state *state =
+ tevent_req_data(req, struct auth_check_password_state);
+ struct tevent_req *subreq = NULL;
+ NTSTATUS status;
+
+ if (state->method == NULL) {
+ state->authoritative = 0;
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_USER);
+ return;
+ }
+
+ /* check if the module wants to check the password */
+ status = state->method->ops->want_check(state->method, state,
+ state->user_info);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)) {
+ DEBUG(11,("auth_check_password_send: "
+ "%s doesn't want to check\n",
+ state->method->ops->name));
+ state->method = state->method->next;
+ auth_check_password_next(req);
+ return;
+ }
+
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ subreq = state->method->ops->check_password_send(
+ state, state->ev, state->method, state->user_info);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, auth_check_password_done, req);
+}
+
+static void auth_check_password_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct auth_check_password_state *state =
+ tevent_req_data(req,
+ struct auth_check_password_state);
+ bool authoritative = true;
+ NTSTATUS status;
+
+ status = state->method->ops->check_password_recv(subreq, state,
+ &state->user_info_dc,
+ &authoritative);
+ TALLOC_FREE(subreq);
+ if (!authoritative ||
+ NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)) {
+ DEBUG(11,("auth_check_password_send: "
+ "%s passes to the next method\n",
+ state->method->ops->name));
+ state->method = state->method->next;
+ auth_check_password_next(req);
+ return;
+ }
+
+ /* the backend has handled the request */
+
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+/**
+ * Check a user's Plaintext, LM or NTLM password.
+ * async receive function
+ *
+ * The return value takes precedence over the contents of the user_info_dc
+ * struct. When the return is other than NT_STATUS_OK the contents
+ * of that structure is undefined.
+ *
+ *
+ * @param req The async request state
+ *
+ * @param mem_ctx The parent memory context for the user_info_dc structure
+ *
+ * @param user_info_dc If successful, contains information about the authentication,
+ * including a SAM_ACCOUNT struct describing the user.
+ *
+ * @return An NTSTATUS with NT_STATUS_OK or an appropriate error.
+ *
+ **/
+
+_PUBLIC_ NTSTATUS auth_check_password_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct auth_user_info_dc **user_info_dc,
+ uint8_t *pauthoritative)
+{
+ struct auth_check_password_state *state =
+ tevent_req_data(req, struct auth_check_password_state);
+ NTSTATUS status = NT_STATUS_OK;
+
+ *pauthoritative = state->authoritative;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ /*
+ * Please try not to change this string, it is probably in use
+ * in audit logging tools
+ */
+ DEBUG(2,("auth_check_password_recv: "
+ "%s authentication for user [%s\\%s] "
+ "FAILED with error %s, authoritative=%u\n",
+ (state->method ? state->method->ops->name : "NO_METHOD"),
+ state->user_info->mapped.domain_name,
+ state->user_info->mapped.account_name,
+ nt_errstr(status), state->authoritative));
+
+ log_authentication_event(state->auth_ctx->msg_ctx,
+ state->auth_ctx->lp_ctx,
+ &state->auth_ctx->start_time,
+ state->user_info, status,
+ NULL, NULL, NULL);
+ tevent_req_received(req);
+ return status;
+ }
+
+ DEBUG(5,("auth_check_password_recv: "
+ "%s authentication for user [%s\\%s] succeeded\n",
+ state->method->ops->name,
+ state->user_info_dc->info->domain_name,
+ state->user_info_dc->info->account_name));
+
+ log_authentication_event(state->auth_ctx->msg_ctx,
+ state->auth_ctx->lp_ctx,
+ &state->auth_ctx->start_time,
+ state->user_info, status,
+ state->user_info_dc->info->domain_name,
+ state->user_info_dc->info->account_name,
+ &state->user_info_dc->sids[0]);
+
+ *user_info_dc = talloc_move(mem_ctx, &state->user_info_dc);
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+struct auth_check_password_wrapper_state {
+ uint8_t authoritative;
+ struct auth_user_info_dc *user_info_dc;
+};
+
+static void auth_check_password_wrapper_done(struct tevent_req *subreq);
+
+static struct tevent_req *auth_check_password_wrapper_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct auth4_context *auth_ctx,
+ const struct auth_usersupplied_info *user_info)
+{
+ struct tevent_req *req = NULL;
+ struct auth_check_password_wrapper *state = NULL;
+ struct tevent_req *subreq = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct auth_check_password_wrapper_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ subreq = auth_check_password_send(state, ev, auth_ctx, user_info);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ auth_check_password_wrapper_done,
+ req);
+
+ return req;
+}
+
+static void auth_check_password_wrapper_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct auth_check_password_wrapper_state *state =
+ tevent_req_data(req,
+ struct auth_check_password_wrapper_state);
+ NTSTATUS status;
+
+ status = auth_check_password_recv(subreq, state,
+ &state->user_info_dc,
+ &state->authoritative);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS auth_check_password_wrapper_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ uint8_t *pauthoritative,
+ void **server_returned_info,
+ DATA_BLOB *user_session_key,
+ DATA_BLOB *lm_session_key)
+{
+ struct auth_check_password_wrapper_state *state =
+ tevent_req_data(req,
+ struct auth_check_password_wrapper_state);
+ struct auth_user_info_dc *user_info_dc = state->user_info_dc;
+ NTSTATUS status = NT_STATUS_OK;
+
+ *pauthoritative = state->authoritative;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ talloc_steal(mem_ctx, user_info_dc);
+ *server_returned_info = user_info_dc;
+
+ if (user_session_key) {
+ DEBUG(10, ("Got NT session key of length %u\n",
+ (unsigned)user_info_dc->user_session_key.length));
+ *user_session_key = user_info_dc->user_session_key;
+ talloc_steal(mem_ctx, user_session_key->data);
+ user_info_dc->user_session_key = data_blob_null;
+ }
+
+ if (lm_session_key) {
+ DEBUG(10, ("Got LM session key of length %u\n",
+ (unsigned)user_info_dc->lm_session_key.length));
+ *lm_session_key = user_info_dc->lm_session_key;
+ talloc_steal(mem_ctx, lm_session_key->data);
+ user_info_dc->lm_session_key = data_blob_null;
+ }
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+ /* Wrapper because we don't want to expose all callers to needing to
+ * know that session_info is generated from the main ldb, and because
+ * we need to break a depenency loop between the DCE/RPC layer and the
+ * generation of unix tokens via IRPC */
+static NTSTATUS auth_generate_session_info_wrapper(struct auth4_context *auth_context,
+ TALLOC_CTX *mem_ctx,
+ void *server_returned_info,
+ const char *original_user_name,
+ uint32_t session_info_flags,
+ struct auth_session_info **session_info)
+{
+ NTSTATUS status;
+ struct auth_user_info_dc *user_info_dc = talloc_get_type_abort(server_returned_info, struct auth_user_info_dc);
+
+ if (user_info_dc->info->authenticated) {
+ session_info_flags |= AUTH_SESSION_INFO_AUTHENTICATED;
+ }
+
+ status = auth_generate_session_info(mem_ctx, auth_context->lp_ctx,
+ auth_context->sam_ctx, user_info_dc,
+ session_info_flags, session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if ((session_info_flags & AUTH_SESSION_INFO_UNIX_TOKEN)
+ && NT_STATUS_IS_OK(status)) {
+ status = auth_session_info_fill_unix(auth_context->lp_ctx,
+ original_user_name,
+ *session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(*session_info);
+ }
+ }
+ return status;
+}
+
+/* Wrapper because we don't want to expose all callers to needing to
+ * know anything about the PAC or auth subsystem internal structures
+ * before we output a struct auth session_info */
+static NTSTATUS auth_generate_session_info_pac(struct auth4_context *auth_ctx,
+ TALLOC_CTX *mem_ctx,
+ struct smb_krb5_context *smb_krb5_context,
+ DATA_BLOB *pac_blob,
+ const char *principal_name,
+ const struct tsocket_address *remote_address,
+ uint32_t session_info_flags,
+ struct auth_session_info **session_info)
+{
+ NTSTATUS status;
+ struct auth_user_info_dc *user_info_dc;
+ TALLOC_CTX *tmp_ctx;
+
+ if (!pac_blob) {
+ /*
+ * This should already be catched at the main
+ * gensec layer, but better check twice
+ */
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ tmp_ctx = talloc_named(mem_ctx, 0, "gensec_gssapi_session_info context");
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+
+ status = kerberos_pac_blob_to_user_info_dc(tmp_ctx,
+ *pac_blob,
+ smb_krb5_context->krb5_context,
+ &user_info_dc, NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return status;
+ }
+
+ if (user_info_dc->info->authenticated) {
+ session_info_flags |= AUTH_SESSION_INFO_AUTHENTICATED;
+ }
+
+ status = auth_generate_session_info_wrapper(auth_ctx, mem_ctx,
+ user_info_dc,
+ user_info_dc->info->account_name,
+ session_info_flags, session_info);
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+/***************************************************************************
+ Make a auth_info struct for the auth subsystem
+ - Allow the caller to specify the methods to use, including optionally the SAM to use
+***************************************************************************/
+_PUBLIC_ NTSTATUS auth_context_create_methods(TALLOC_CTX *mem_ctx, const char * const *methods,
+ struct tevent_context *ev,
+ struct imessaging_context *msg,
+ struct loadparm_context *lp_ctx,
+ struct ldb_context *sam_ctx,
+ struct auth4_context **auth_ctx)
+{
+ int i;
+ struct auth4_context *ctx;
+
+ auth4_init();
+
+ if (!ev) {
+ DEBUG(0,("auth_context_create: called with out event context\n"));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ ctx = talloc_zero(mem_ctx, struct auth4_context);
+ NT_STATUS_HAVE_NO_MEMORY(ctx);
+ ctx->challenge.data = data_blob(NULL, 0);
+ ctx->methods = NULL;
+ ctx->event_ctx = ev;
+ ctx->msg_ctx = msg;
+ ctx->lp_ctx = lp_ctx;
+ ctx->start_time = timeval_current();
+
+ if (sam_ctx) {
+ ctx->sam_ctx = sam_ctx;
+ } else {
+ ctx->sam_ctx = samdb_connect(ctx,
+ ctx->event_ctx,
+ ctx->lp_ctx,
+ system_session(ctx->lp_ctx),
+ NULL,
+ 0);
+ }
+
+ for (i=0; methods && methods[i] ; i++) {
+ struct auth_method_context *method;
+
+ method = talloc(ctx, struct auth_method_context);
+ NT_STATUS_HAVE_NO_MEMORY(method);
+
+ method->ops = auth_backend_byname(methods[i]);
+ if (!method->ops) {
+ DEBUG(1,("auth_context_create: failed to find method=%s\n",
+ methods[i]));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ method->auth_ctx = ctx;
+ method->depth = i;
+ DLIST_ADD_END(ctx->methods, method);
+ }
+
+ ctx->check_ntlm_password_send = auth_check_password_wrapper_send;
+ ctx->check_ntlm_password_recv = auth_check_password_wrapper_recv;
+ ctx->get_ntlm_challenge = auth_get_challenge;
+ ctx->set_ntlm_challenge = auth_context_set_challenge;
+ ctx->generate_session_info = auth_generate_session_info_wrapper;
+ ctx->generate_session_info_pac = auth_generate_session_info_pac;
+
+ *auth_ctx = ctx;
+
+ return NT_STATUS_OK;
+}
+
+const char **auth_methods_from_lp(TALLOC_CTX *mem_ctx, struct loadparm_context *lp_ctx)
+{
+ char **auth_methods = NULL;
+
+ switch (lpcfg_server_role(lp_ctx)) {
+ case ROLE_STANDALONE:
+ auth_methods = str_list_make(mem_ctx, "anonymous sam_ignoredomain", NULL);
+ break;
+ case ROLE_DOMAIN_MEMBER:
+ case ROLE_DOMAIN_BDC:
+ case ROLE_DOMAIN_PDC:
+ case ROLE_ACTIVE_DIRECTORY_DC:
+ case ROLE_IPA_DC:
+ auth_methods = str_list_make(mem_ctx, "anonymous sam winbind sam_ignoredomain", NULL);
+ break;
+ }
+ return discard_const_p(const char *, auth_methods);
+}
+
+/***************************************************************************
+ Make a auth_info struct for the auth subsystem
+ - Uses default auth_methods, depending on server role and smb.conf settings
+***************************************************************************/
+_PUBLIC_ NTSTATUS auth_context_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct imessaging_context *msg,
+ struct loadparm_context *lp_ctx,
+ struct auth4_context **auth_ctx)
+{
+ NTSTATUS status;
+ const char **auth_methods;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ auth_methods = auth_methods_from_lp(tmp_ctx, lp_ctx);
+ if (!auth_methods) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ status = auth_context_create_methods(mem_ctx, auth_methods, ev, msg, lp_ctx, NULL, auth_ctx);
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+_PUBLIC_ NTSTATUS auth_context_create_for_netlogon(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct imessaging_context *msg,
+ struct loadparm_context *lp_ctx,
+ struct auth4_context **auth_ctx)
+{
+ NTSTATUS status;
+ char **_auth_methods = NULL;
+ const char **auth_methods = NULL;
+
+ /*
+ * Here we only allow 'sam winbind' instead of
+ * the 'anonymous sam winbind sam_ignoredomain'
+ * we typically use for authentication from clients.
+ */
+ _auth_methods = str_list_make(mem_ctx, "sam winbind", NULL);
+ if (_auth_methods == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ auth_methods = discard_const_p(const char *, _auth_methods);
+
+ status = auth_context_create_methods(mem_ctx, auth_methods, ev, msg,
+ lp_ctx, NULL, auth_ctx);
+ talloc_free(_auth_methods);
+ return status;
+}
+
+/* the list of currently registered AUTH backends */
+static struct auth_backend {
+ const struct auth_operations *ops;
+} *backends = NULL;
+static int num_backends;
+
+/*
+ register a AUTH backend.
+
+ The 'name' can be later used by other backends to find the operations
+ structure for this backend.
+*/
+_PUBLIC_ NTSTATUS auth_register(TALLOC_CTX *mem_ctx,
+ const struct auth_operations *ops)
+{
+ struct auth_operations *new_ops;
+
+ if (auth_backend_byname(ops->name) != NULL) {
+ /* its already registered! */
+ DEBUG(0,("AUTH backend '%s' already registered\n",
+ ops->name));
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ backends = talloc_realloc(mem_ctx, backends,
+ struct auth_backend, num_backends+1);
+ NT_STATUS_HAVE_NO_MEMORY(backends);
+
+ new_ops = (struct auth_operations *)talloc_memdup(backends, ops, sizeof(*ops));
+ NT_STATUS_HAVE_NO_MEMORY(new_ops);
+ new_ops->name = talloc_strdup(new_ops, ops->name);
+ NT_STATUS_HAVE_NO_MEMORY(new_ops->name);
+
+ backends[num_backends].ops = new_ops;
+
+ num_backends++;
+
+ DEBUG(3,("AUTH backend '%s' registered\n",
+ ops->name));
+
+ return NT_STATUS_OK;
+}
+
+/*
+ return the operations structure for a named backend of the specified type
+*/
+const struct auth_operations *auth_backend_byname(const char *name)
+{
+ int i;
+
+ for (i=0;i<num_backends;i++) {
+ if (strcmp(backends[i].ops->name, name) == 0) {
+ return backends[i].ops;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ return the AUTH interface version, and the size of some critical types
+ This can be used by backends to either detect compilation errors, or provide
+ multiple implementations for different smbd compilation options in one module
+*/
+const struct auth_critical_sizes *auth_interface_version(void)
+{
+ static const struct auth_critical_sizes critical_sizes = {
+ AUTH4_INTERFACE_VERSION,
+ sizeof(struct auth_operations),
+ sizeof(struct auth_method_context),
+ sizeof(struct auth4_context),
+ sizeof(struct auth_usersupplied_info),
+ sizeof(struct auth_user_info_dc)
+ };
+
+ return &critical_sizes;
+}
+
+_PUBLIC_ NTSTATUS auth4_init(void)
+{
+ static bool initialized = false;
+#define _MODULE_PROTO(init) extern NTSTATUS init(TALLOC_CTX *);
+ STATIC_auth4_MODULES_PROTO;
+ init_module_fn static_init[] = { STATIC_auth4_MODULES };
+
+ if (initialized) return NT_STATUS_OK;
+ initialized = true;
+
+ run_init_functions(NULL, static_init);
+
+ return NT_STATUS_OK;
+}
diff --git a/source4/auth/ntlm/auth_anonymous.c b/source4/auth/ntlm/auth_anonymous.c
new file mode 100644
index 0000000..a25aaca
--- /dev/null
+++ b/source4/auth/ntlm/auth_anonymous.c
@@ -0,0 +1,161 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Anonymous Authentification
+
+ 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 "auth/auth.h"
+#include "auth/ntlm/auth_proto.h"
+#include "param/param.h"
+#include "lib/util/tevent_ntstatus.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+_PUBLIC_ NTSTATUS auth4_anonymous_init(TALLOC_CTX *);
+
+/**
+ * Return a anonymous logon for anonymous users (username = "")
+ *
+ * Typically used as the first module in the auth chain, this allows
+ * anonymou logons to be dealt with in one place. Non-anonymou logons 'fail'
+ * and pass onto the next module.
+ **/
+static NTSTATUS anonymous_want_check(struct auth_method_context *ctx,
+ TALLOC_CTX *mem_ctx,
+ const struct auth_usersupplied_info *user_info)
+{
+ if (user_info->client.account_name && *user_info->client.account_name) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ switch (user_info->password_state) {
+ case AUTH_PASSWORD_PLAIN:
+ if (user_info->password.plaintext != NULL &&
+ strlen(user_info->password.plaintext) > 0)
+ {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+ break;
+ case AUTH_PASSWORD_HASH:
+ if (user_info->password.hash.lanman != NULL) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+ if (user_info->password.hash.nt != NULL) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+ break;
+ case AUTH_PASSWORD_RESPONSE:
+ if (user_info->password.response.lanman.length == 1) {
+ if (user_info->password.response.lanman.data[0] != '\0') {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+ } else if (user_info->password.response.lanman.length > 1) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+ if (user_info->password.response.nt.length > 0) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+ break;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/**
+ * Return a anonymous logon for anonymous users (username = "")
+ *
+ * Typically used as the first module in the auth chain, this allows
+ * anonymou logons to be dealt with in one place. Non-anonymou logons 'fail'
+ * and pass onto the next module.
+ **/
+
+struct anonymous_check_password_state {
+ struct auth_user_info_dc *user_info_dc;
+};
+
+static struct tevent_req *anonymous_check_password_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct auth_method_context *ctx,
+ const struct auth_usersupplied_info *user_info)
+{
+ struct tevent_req *req = NULL;
+ struct anonymous_check_password_state *state = NULL;
+ NTSTATUS status;
+
+ req = tevent_req_create(
+ mem_ctx,
+ &state,
+ struct anonymous_check_password_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ status = auth_anonymous_user_info_dc(
+ state,
+ lpcfg_netbios_name(ctx->auth_ctx->lp_ctx),
+ &state->user_info_dc);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static NTSTATUS anonymous_check_password_recv(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct auth_user_info_dc **interim_info,
+ bool *authoritative)
+{
+ struct anonymous_check_password_state *state = tevent_req_data(
+ req, struct anonymous_check_password_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+ *interim_info = talloc_move(mem_ctx, &state->user_info_dc);
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+
+static const struct auth_operations anonymous_auth_ops = {
+ .name = "anonymous",
+ .want_check = anonymous_want_check,
+ .check_password_send = anonymous_check_password_send,
+ .check_password_recv = anonymous_check_password_recv,
+};
+
+_PUBLIC_ NTSTATUS auth4_anonymous_init(TALLOC_CTX *ctx)
+{
+ NTSTATUS ret;
+
+ ret = auth_register(ctx, &anonymous_auth_ops);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(0,("Failed to register 'anonymous' auth backend!\n"));
+ return ret;
+ }
+
+ return ret;
+}
diff --git a/source4/auth/ntlm/auth_developer.c b/source4/auth/ntlm/auth_developer.c
new file mode 100644
index 0000000..6e92252
--- /dev/null
+++ b/source4/auth/ntlm/auth_developer.c
@@ -0,0 +1,216 @@
+/*
+ Unix SMB/CIFS implementation.
+ Generic authentication types
+ Copyright (C) Andrew Bartlett 2001-2002
+ Copyright (C) Jelmer Vernooij 2002
+ Copyright (C) Stefan Metzmacher 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 "auth/auth.h"
+#include "auth/ntlm/auth_proto.h"
+#include "libcli/security/security.h"
+#include "lib/util/tevent_ntstatus.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+#undef strncasecmp
+
+_PUBLIC_ NTSTATUS auth4_developer_init(TALLOC_CTX *);
+
+static NTSTATUS name_to_ntstatus_want_check(struct auth_method_context *ctx,
+ TALLOC_CTX *mem_ctx,
+ const struct auth_usersupplied_info *user_info)
+{
+ return NT_STATUS_OK;
+}
+
+/**
+ * Return an error based on username
+ *
+ * This function allows the testing of obsure errors, as well as the generation
+ * of NT_STATUS -> DOS error mapping tables.
+ *
+ * This module is of no value to end-users.
+ *
+ * The password is ignored.
+ *
+ * @return An NTSTATUS value based on the username
+ **/
+
+static NTSTATUS name_to_ntstatus_check_password(struct auth_method_context *ctx,
+ TALLOC_CTX *mem_ctx,
+ const struct auth_usersupplied_info *user_info,
+ struct auth_user_info_dc **_user_info_dc,
+ bool *authoritative)
+{
+ NTSTATUS nt_status;
+ struct auth_user_info_dc *user_info_dc;
+ struct auth_user_info *info;
+ uint32_t error_num;
+ const char *user;
+
+ user = user_info->client.account_name;
+
+ if (strncasecmp("NT_STATUS", user, strlen("NT_STATUS")) == 0) {
+ nt_status = nt_status_string_to_code(user);
+ } else {
+ error_num = strtoul(user, NULL, 16);
+ DEBUG(5,("name_to_ntstatus_check_password: Error for user %s was 0x%08X\n", user, error_num));
+ nt_status = NT_STATUS(error_num);
+ }
+ NT_STATUS_NOT_OK_RETURN(nt_status);
+
+ user_info_dc = talloc_zero(mem_ctx, struct auth_user_info_dc);
+ NT_STATUS_HAVE_NO_MEMORY(user_info_dc);
+
+ /* This returns a pointer to a struct dom_sid, which is the
+ * same as a 1 element list of struct dom_sid */
+ user_info_dc->num_sids = 1;
+ user_info_dc->sids = dom_sid_parse_talloc(user_info_dc, SID_NT_ANONYMOUS);
+ NT_STATUS_HAVE_NO_MEMORY(user_info_dc->sids);
+
+ /* annoying, but the Anonymous really does have a session key,
+ and it is all zeros! */
+ user_info_dc->user_session_key = data_blob_talloc(user_info_dc, NULL, 16);
+ NT_STATUS_HAVE_NO_MEMORY(user_info_dc->user_session_key.data);
+
+ user_info_dc->lm_session_key = data_blob_talloc(user_info_dc, NULL, 16);
+ NT_STATUS_HAVE_NO_MEMORY(user_info_dc->lm_session_key.data);
+
+ data_blob_clear(&user_info_dc->user_session_key);
+ data_blob_clear(&user_info_dc->lm_session_key);
+
+ user_info_dc->info = info = talloc_zero(user_info_dc, struct auth_user_info);
+ NT_STATUS_HAVE_NO_MEMORY(user_info_dc->info);
+
+ info->account_name = talloc_asprintf(user_info_dc, "NAME TO NTSTATUS %s ANONYMOUS LOGON", user);
+ NT_STATUS_HAVE_NO_MEMORY(info->account_name);
+
+ info->domain_name = talloc_strdup(user_info_dc, "NT AUTHORITY");
+ NT_STATUS_HAVE_NO_MEMORY(info->domain_name);
+
+ info->full_name = talloc_asprintf(user_info_dc, "NAME TO NTSTATUS %s Anonymous Logon", user);
+ NT_STATUS_HAVE_NO_MEMORY(info->full_name);
+
+ info->logon_script = talloc_strdup(user_info_dc, "");
+ NT_STATUS_HAVE_NO_MEMORY(info->logon_script);
+
+ info->profile_path = talloc_strdup(user_info_dc, "");
+ NT_STATUS_HAVE_NO_MEMORY(info->profile_path);
+
+ info->home_directory = talloc_strdup(user_info_dc, "");
+ NT_STATUS_HAVE_NO_MEMORY(info->home_directory);
+
+ info->home_drive = talloc_strdup(user_info_dc, "");
+ NT_STATUS_HAVE_NO_MEMORY(info->home_drive);
+
+ info->last_logon = 0;
+ info->last_logoff = 0;
+ info->acct_expiry = 0;
+ info->last_password_change = 0;
+ info->allow_password_change = 0;
+ info->force_password_change = 0;
+
+ info->logon_count = 0;
+ info->bad_password_count = 0;
+
+ info->acct_flags = ACB_NORMAL;
+
+ info->authenticated = true;
+
+ *_user_info_dc = user_info_dc;
+
+ return nt_status;
+}
+
+struct name_to_ntstatus_check_password_state {
+ struct auth_user_info_dc *user_info_dc;
+ bool authoritative;
+};
+
+static struct tevent_req *name_to_ntstatus_check_password_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct auth_method_context *ctx,
+ const struct auth_usersupplied_info *user_info)
+{
+ struct tevent_req *req = NULL;
+ struct name_to_ntstatus_check_password_state *state = NULL;
+ NTSTATUS status;
+
+ req = tevent_req_create(
+ mem_ctx,
+ &state,
+ struct name_to_ntstatus_check_password_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ status = name_to_ntstatus_check_password(
+ ctx,
+ state,
+ user_info,
+ &state->user_info_dc,
+ &state->authoritative);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static NTSTATUS name_to_ntstatus_check_password_recv(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct auth_user_info_dc **interim_info,
+ bool *authoritative)
+{
+ struct name_to_ntstatus_check_password_state *state = tevent_req_data(
+ req, struct name_to_ntstatus_check_password_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+ *interim_info = talloc_move(mem_ctx, &state->user_info_dc);
+ *authoritative = state->authoritative;
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+static const struct auth_operations name_to_ntstatus_auth_ops = {
+ .name = "name_to_ntstatus",
+ .want_check = name_to_ntstatus_want_check,
+ .check_password_send = name_to_ntstatus_check_password_send,
+ .check_password_recv = name_to_ntstatus_check_password_recv,
+};
+
+_PUBLIC_ NTSTATUS auth4_developer_init(TALLOC_CTX *ctx)
+{
+ NTSTATUS ret;
+
+ ret = auth_register(ctx, &name_to_ntstatus_auth_ops);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(0,("Failed to register 'name_to_ntstatus' auth backend!\n"));
+ return ret;
+ }
+
+ return ret;
+}
diff --git a/source4/auth/ntlm/auth_sam.c b/source4/auth/ntlm/auth_sam.c
new file mode 100644
index 0000000..0d50431
--- /dev/null
+++ b/source4/auth/ntlm/auth_sam.c
@@ -0,0 +1,1210 @@
+/*
+ Unix SMB/CIFS implementation.
+ Password and authentication handling
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2009
+ Copyright (C) Gerald Carter 2003
+ Copyright (C) Stefan Metzmacher 2005-2010
+
+ 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/time.h"
+#include <ldb.h>
+#include "libcli/ldap/ldap_ndr.h"
+#include "libcli/security/security.h"
+#include "auth/auth.h"
+#include "../libcli/auth/ntlm_check.h"
+#include "auth/ntlm/auth_proto.h"
+#include "auth/auth_sam.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/common/util.h"
+#include "param/param.h"
+#include "librpc/gen_ndr/ndr_irpc_c.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "lib/messaging/irpc.h"
+#include "libcli/auth/libcli_auth.h"
+#include "libds/common/roles.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "system/kerberos.h"
+#include "auth/kerberos/kerberos.h"
+#include "kdc/db-glue.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+NTSTATUS auth_sam_init(void);
+
+extern const char *user_attrs[];
+extern const char *domain_ref_attrs[];
+
+/****************************************************************************
+ Do a specific test for an smb password being correct, given a smb_password and
+ the lanman and NT responses.
+****************************************************************************/
+static NTSTATUS authsam_password_ok(struct auth4_context *auth_context,
+ TALLOC_CTX *mem_ctx,
+ const struct samr_Password *nt_pwd,
+ struct smb_krb5_context *smb_krb5_context,
+ const DATA_BLOB *stored_aes_256_key,
+ const krb5_data *salt,
+ const struct auth_usersupplied_info *user_info,
+ DATA_BLOB *user_sess_key,
+ DATA_BLOB *lm_sess_key)
+{
+ NTSTATUS status;
+
+ switch (user_info->password_state) {
+ case AUTH_PASSWORD_PLAIN:
+ {
+ const struct auth_usersupplied_info *user_info_temp;
+
+ if (nt_pwd == NULL && stored_aes_256_key != NULL && user_info->password.plaintext != NULL) {
+ bool pw_equal;
+ int krb5_ret;
+ DATA_BLOB supplied_aes_256_key;
+ krb5_keyblock key;
+ krb5_data cleartext_data = {
+ .data = user_info->password.plaintext,
+ .length = strlen(user_info->password.plaintext)
+ };
+
+ *lm_sess_key = data_blob_null;
+ *user_sess_key = data_blob_null;
+
+ krb5_ret = smb_krb5_create_key_from_string(smb_krb5_context->krb5_context,
+ NULL,
+ salt,
+ &cleartext_data,
+ ENCTYPE_AES256_CTS_HMAC_SHA1_96,
+ &key);
+ if (krb5_ret) {
+ DBG_ERR("generation of a aes256-cts-hmac-sha1-96 key for password comparison failed: %s",
+ smb_get_krb5_error_message(smb_krb5_context->krb5_context,
+ krb5_ret, mem_ctx));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ supplied_aes_256_key = data_blob_const(KRB5_KEY_DATA(&key),
+ KRB5_KEY_LENGTH(&key));
+
+ pw_equal = data_blob_equal_const_time(&supplied_aes_256_key,
+ stored_aes_256_key);
+
+ krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &key);
+ if (!pw_equal) {
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+ return NT_STATUS_OK;
+ }
+
+ status = encrypt_user_info(mem_ctx, auth_context,
+ AUTH_PASSWORD_HASH,
+ user_info, &user_info_temp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to convert plaintext password to password HASH: %s\n", nt_errstr(status)));
+ return status;
+ }
+ user_info = user_info_temp;
+
+ FALL_THROUGH;
+ }
+ case AUTH_PASSWORD_HASH:
+ *lm_sess_key = data_blob(NULL, 0);
+ *user_sess_key = data_blob(NULL, 0);
+ status = hash_password_check(mem_ctx,
+ false,
+ NULL,
+ user_info->password.hash.nt,
+ user_info->mapped.account_name,
+ NULL, nt_pwd);
+ NT_STATUS_NOT_OK_RETURN(status);
+ break;
+
+ case AUTH_PASSWORD_RESPONSE:
+ status = ntlm_password_check(mem_ctx,
+ false,
+ lpcfg_ntlm_auth(auth_context->lp_ctx),
+ user_info->logon_parameters,
+ &auth_context->challenge.data,
+ &user_info->password.response.lanman,
+ &user_info->password.response.nt,
+ user_info->mapped.account_name,
+ user_info->client.account_name,
+ user_info->client.domain_name,
+ NULL, nt_pwd,
+ user_sess_key, lm_sess_key);
+ NT_STATUS_NOT_OK_RETURN(status);
+ break;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void auth_sam_trigger_zero_password(TALLOC_CTX *mem_ctx,
+ struct imessaging_context *msg_ctx,
+ struct tevent_context *event_ctx,
+ struct netr_SendToSamBase *send_to_sam)
+{
+ struct dcerpc_binding_handle *irpc_handle;
+ struct winbind_SendToSam r;
+ struct tevent_req *req;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return;
+ }
+
+ irpc_handle = irpc_binding_handle_by_name(tmp_ctx, msg_ctx,
+ "winbind_server",
+ &ndr_table_winbind);
+ if (irpc_handle == NULL) {
+ DEBUG(1,(__location__ ": Unable to get binding handle for winbind\n"));
+ TALLOC_FREE(tmp_ctx);
+ return;
+ }
+
+ r.in.message = *send_to_sam;
+
+ /*
+ * This seem to rely on the current IRPC implementation,
+ * which delivers the message in the _send function.
+ *
+ * TODO: we need a ONE_WAY IRPC handle and register
+ * a callback and wait for it to be triggered!
+ */
+ req = dcerpc_winbind_SendToSam_r_send(tmp_ctx,
+ event_ctx,
+ irpc_handle,
+ &r);
+
+ /* we aren't interested in a reply */
+ talloc_free(req);
+ TALLOC_FREE(tmp_ctx);
+
+}
+
+/*
+ send a message to the drepl server telling it to initiate a
+ REPL_SECRET getncchanges extended op to fetch the users secrets
+ */
+static void auth_sam_trigger_repl_secret(TALLOC_CTX *mem_ctx,
+ struct imessaging_context *msg_ctx,
+ struct tevent_context *event_ctx,
+ struct ldb_dn *user_dn)
+{
+ struct dcerpc_binding_handle *irpc_handle;
+ struct drepl_trigger_repl_secret r;
+ struct tevent_req *req;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return;
+ }
+
+ irpc_handle = irpc_binding_handle_by_name(tmp_ctx, msg_ctx,
+ "dreplsrv",
+ &ndr_table_irpc);
+ if (irpc_handle == NULL) {
+ DEBUG(1,(__location__ ": Unable to get binding handle for dreplsrv\n"));
+ TALLOC_FREE(tmp_ctx);
+ return;
+ }
+
+ r.in.user_dn = ldb_dn_get_linearized(user_dn);
+
+ /*
+ * This seem to rely on the current IRPC implementation,
+ * which delivers the message in the _send function.
+ *
+ * TODO: we need a ONE_WAY IRPC handle and register
+ * a callback and wait for it to be triggered!
+ */
+ req = dcerpc_drepl_trigger_repl_secret_r_send(tmp_ctx,
+ event_ctx,
+ irpc_handle,
+ &r);
+
+ /* we aren't interested in a reply */
+ talloc_free(req);
+ TALLOC_FREE(tmp_ctx);
+}
+
+static const struct samr_Password *hide_invalid_nthash(const struct samr_Password *in)
+{
+ /*
+ * This is the result of:
+ *
+ * E_md4hash("", zero_string_hash.hash);
+ */
+ static const struct samr_Password zero_string_hash = {
+ .hash = {
+ 0x31, 0xd6, 0xcf, 0xe0, 0xd1, 0x6a, 0xe9, 0x31,
+ 0xb7, 0x3c, 0x59, 0xd7, 0xe0, 0xc0, 0x89, 0xc0,
+ }
+ };
+
+ if (in == NULL) {
+ return NULL;
+ }
+
+ /*
+ * Skip over any all-zero hashes in the history. No known software
+ * stores these but just to be sure
+ */
+ if (all_zero(in->hash, sizeof(in->hash))) {
+ return NULL;
+ }
+
+ /*
+ * This looks odd, but the password_hash module in the past has written
+ * this in the rare situation where (somehow) we didn't have an old NT
+ * hash (one of the old LM-only set paths)
+ *
+ * mem_equal_const_time() is used to avoid a timing attack
+ * when comparing secret data in the server with this constant
+ * value.
+ */
+ if (mem_equal_const_time(in->hash, zero_string_hash.hash, 16)) {
+ in = NULL;
+ }
+
+ return in;
+}
+
+/*
+ * Check that a password is OK, and update badPwdCount if required.
+ */
+
+static NTSTATUS authsam_password_check_and_record(struct auth4_context *auth_context,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *domain_dn,
+ struct ldb_message *msg,
+ const struct auth_usersupplied_info *user_info,
+ DATA_BLOB *user_sess_key,
+ DATA_BLOB *lm_sess_key,
+ bool *authoritative)
+{
+ NTSTATUS nt_status;
+ NTSTATUS auth_status;
+ TALLOC_CTX *tmp_ctx;
+ int i, ret;
+ int history_len = 0;
+ struct ldb_context *sam_ctx = auth_context->sam_ctx;
+ const char * const attrs[] = { "pwdHistoryLength", NULL };
+ struct ldb_message *dom_msg;
+ struct samr_Password *nt_pwd;
+ DATA_BLOB _aes_256_key = data_blob_null;
+ DATA_BLOB *aes_256_key = NULL;
+ krb5_data _salt = { .data = NULL, .length = 0 };
+ krb5_data *salt = NULL;
+ DATA_BLOB salt_data = data_blob_null;
+ struct smb_krb5_context *smb_krb5_context = NULL;
+ const struct ldb_val *sc_val;
+ uint32_t userAccountControl = 0;
+ uint32_t current_kvno = 0;
+ bool am_rodc;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * This call does more than what it appears to do, it also
+ * checks for the account lockout.
+ *
+ * It is done here so that all parts of Samba that read the
+ * password refuse to even operate on it if the account is
+ * locked out, to avoid mistakes like CVE-2013-4496.
+ */
+ nt_status = samdb_result_passwords(tmp_ctx, auth_context->lp_ctx,
+ msg, &nt_pwd);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ TALLOC_FREE(tmp_ctx);
+ return nt_status;
+ }
+
+ userAccountControl = ldb_msg_find_attr_as_uint(msg,
+ "userAccountControl",
+ 0);
+
+ sc_val = ldb_msg_find_ldb_val(msg, "supplementalCredentials");
+
+ if (nt_pwd == NULL && sc_val == NULL) {
+ if (samdb_rodc(auth_context->sam_ctx, &am_rodc) == LDB_SUCCESS && am_rodc) {
+ /*
+ * we don't have passwords for this
+ * account. We are an RODC, and this account
+ * may be one for which we either are denied
+ * REPL_SECRET replication or we haven't yet
+ * done the replication. We return
+ * NT_STATUS_NOT_IMPLEMENTED which tells the
+ * auth code to try the next authentication
+ * mechanism. We also send a message to our
+ * drepl server to tell it to try and
+ * replicate the secrets for this account.
+ *
+ * TODO: Should we only trigger this is detected
+ * there's a chance that the password might be
+ * replicated, we should be able to detect this
+ * based on msDS-NeverRevealGroup.
+ */
+ auth_sam_trigger_repl_secret(auth_context,
+ auth_context->msg_ctx,
+ auth_context->event_ctx,
+ msg->dn);
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+ }
+
+ /*
+ * If we don't have an NT password, pull a kerberos key
+ * instead for plaintext.
+ */
+ if (nt_pwd == NULL &&
+ sc_val != NULL &&
+ user_info->password_state == AUTH_PASSWORD_PLAIN)
+ {
+ krb5_error_code krb5_ret;
+
+ krb5_ret = smb_krb5_init_context(tmp_ctx,
+ auth_context->lp_ctx,
+ &smb_krb5_context);
+ if (krb5_ret != 0) {
+ DBG_ERR("Failed to setup krb5_context: %s!",
+ error_message(krb5_ret));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ /*
+ * Get the current salt from the record
+ */
+
+ krb5_ret = dsdb_extract_aes_256_key(smb_krb5_context->krb5_context,
+ tmp_ctx,
+ msg,
+ userAccountControl,
+ NULL, /* kvno */
+ &current_kvno, /* kvno_out */
+ &_aes_256_key,
+ &salt_data);
+ if (krb5_ret == 0) {
+ aes_256_key = &_aes_256_key;
+
+ _salt.data = (char *)salt_data.data;
+ _salt.length = salt_data.length;
+ salt = &_salt;
+ }
+ }
+
+ auth_status = authsam_password_ok(auth_context,
+ tmp_ctx,
+ nt_pwd,
+ smb_krb5_context,
+ aes_256_key,
+ salt,
+ user_info,
+ user_sess_key, lm_sess_key);
+
+ if (NT_STATUS_IS_OK(auth_status)) {
+ if (user_sess_key->data) {
+ talloc_steal(mem_ctx, user_sess_key->data);
+ }
+ if (lm_sess_key->data) {
+ talloc_steal(mem_ctx, lm_sess_key->data);
+ }
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_OK;
+ }
+ *user_sess_key = data_blob_null;
+ *lm_sess_key = data_blob_null;
+
+ if (!NT_STATUS_EQUAL(auth_status, NT_STATUS_WRONG_PASSWORD)) {
+ TALLOC_FREE(tmp_ctx);
+ return auth_status;
+ }
+
+ /*
+ * We only continue if this was a wrong password
+ * and we'll always return NT_STATUS_WRONG_PASSWORD
+ * no matter what error happens.
+ */
+
+ /* pull the domain password property attributes */
+ ret = dsdb_search_one(sam_ctx, tmp_ctx, &dom_msg, domain_dn, LDB_SCOPE_BASE,
+ attrs, 0, "objectClass=domain");
+ if (ret == LDB_SUCCESS) {
+ history_len = ldb_msg_find_attr_as_uint(dom_msg, "pwdHistoryLength", 0);
+ } else if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ DEBUG(3,("Couldn't find domain %s: %s!\n",
+ ldb_dn_get_linearized(domain_dn),
+ ldb_errstring(sam_ctx)));
+ } else {
+ DEBUG(3,("error finding domain %s: %s!\n",
+ ldb_dn_get_linearized(domain_dn),
+ ldb_errstring(sam_ctx)));
+ }
+
+ for (i = 1; i < MIN(history_len, 3); i++) {
+ const struct samr_Password *nt_history_pwd = NULL;
+ NTTIME pwdLastSet;
+ struct timeval tv_now;
+ NTTIME now;
+ int allowed_period_mins;
+ NTTIME allowed_period;
+
+ /* Reset these variables back to starting as empty */
+ aes_256_key = NULL;
+ salt = NULL;
+
+ /*
+ * Obtain the i'th old password from the NT password
+ * history for this user.
+ *
+ * We avoid issues with salts (which are not
+ * recorded for historical AES256 keys) by using the
+ * ntPwdHistory in preference.
+ */
+ nt_status = samdb_result_passwords_from_history(tmp_ctx,
+ auth_context->lp_ctx,
+ msg, i,
+ NULL,
+ &nt_history_pwd);
+
+ /*
+ * Belts and braces: note that
+ * samdb_result_passwords_from_history() currently
+ * does not fail for missing attributes, it only sets
+ * nt_history_pwd = NULL, so "break" and fall down to
+ * the bad password count upate if this happens
+ */
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ break;
+ }
+
+ nt_history_pwd = hide_invalid_nthash(nt_history_pwd);
+
+ /*
+ * We don't have an NT hash from the
+ * ntPwdHistory, but we can still perform the
+ * password check with the AES256
+ * key.
+ *
+ * However, this is the second preference as
+ * it will fail if the account was renamed
+ * prior to a password change (as we won't
+ * have the correct salt available to
+ * calculate the AES256 key).
+ */
+
+ if (nt_history_pwd == NULL && sc_val != NULL &&
+ user_info->password_state == AUTH_PASSWORD_PLAIN &&
+ current_kvno >= i)
+ {
+ krb5_error_code krb5_ret;
+ const uint32_t request_kvno = current_kvno - i;
+
+ /*
+ * Confirm we have a krb5_context set up
+ */
+ if (smb_krb5_context == NULL) {
+ /*
+ * We get here if we had a unicodePwd
+ * for the current password, no
+ * ntPwdHistory, a valid previous
+ * Kerberos history AND are processing
+ * a simple bind.
+ *
+ * This really is a corner case so
+ * favour cleaner code over trying to
+ * allow for an old password. It is
+ * more likely this is just a new
+ * account.
+ *
+ * "break" out of the loop and fall down
+ * to the bad password update
+ */
+ break;
+ }
+
+ /*
+ * Get the current salt from the record
+ */
+
+ krb5_ret = dsdb_extract_aes_256_key(smb_krb5_context->krb5_context,
+ tmp_ctx,
+ msg,
+ userAccountControl,
+ &request_kvno, /* kvno */
+ NULL, /* kvno_out */
+ &_aes_256_key,
+ &salt_data);
+ if (krb5_ret != 0) {
+ break;
+ }
+
+ aes_256_key = &_aes_256_key;
+
+ _salt.data = (char *)salt_data.data;
+ _salt.length = salt_data.length;
+ salt = &_salt;
+
+ } else if (nt_history_pwd == NULL) {
+ /*
+ * If we don't find element 'i' in the
+ * ntPwdHistory and can not fall back to the
+ * kerberos hash, we won't find 'i+1' ...
+ */
+ break;
+ }
+
+ auth_status = authsam_password_ok(auth_context, tmp_ctx,
+ nt_history_pwd,
+ smb_krb5_context,
+ aes_256_key,
+ salt,
+ user_info,
+ user_sess_key,
+ lm_sess_key);
+
+ if (!NT_STATUS_IS_OK(auth_status)) {
+ /*
+ * If this was not a correct password, try the next
+ * one from the history
+ */
+ *user_sess_key = data_blob_null;
+ *lm_sess_key = data_blob_null;
+ continue;
+ }
+
+ if (i != 1) {
+ /*
+ * The authentication was OK, but not against
+ * the previous password, which is stored at index 1.
+ *
+ * We just return the original wrong password.
+ * This skips the update of the bad pwd count,
+ * because this is almost certainly user error
+ * (or automatic login on a computer using a cached
+ * password from before the password change),
+ * not an attack.
+ */
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ if (user_info->flags & USER_INFO_INTERACTIVE_LOGON) {
+ /*
+ * The authentication was OK against the previous password,
+ * but it's not a NTLM network authentication,
+ * LDAP simple bind or something similar.
+ *
+ * We just return the original wrong password.
+ * This skips the update of the bad pwd count,
+ * because this is almost certainly user error
+ * (or automatic login on a computer using a cached
+ * password from before the password change),
+ * not an attack.
+ */
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ /*
+ * If the password was OK, it's a NTLM network authentication
+ * and it was the previous password.
+ *
+ * Now we see if it is within the grace period,
+ * so that we don't break cached sessions on other computers
+ * before the user can lock and unlock their other screens
+ * (resetting their cached password).
+ *
+ * See http://support.microsoft.com/kb/906305
+ * OldPasswordAllowedPeriod ("old password allowed period")
+ * is specified in minutes. The default is 60.
+ */
+ allowed_period_mins = lpcfg_old_password_allowed_period(auth_context->lp_ctx);
+ /*
+ * NTTIME uses 100ns units
+ */
+ allowed_period = (NTTIME) allowed_period_mins *
+ 60 * 1000*1000*10;
+ pwdLastSet = samdb_result_nttime(msg, "pwdLastSet", 0);
+ tv_now = timeval_current();
+ now = timeval_to_nttime(&tv_now);
+
+ if (now < pwdLastSet) {
+ /*
+ * time jump?
+ *
+ * We just return the original wrong password.
+ * This skips the update of the bad pwd count,
+ * because this is almost certainly user error
+ * (or automatic login on a computer using a cached
+ * password from before the password change),
+ * not an attack.
+ */
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ if ((now - pwdLastSet) >= allowed_period) {
+ /*
+ * The allowed period is over.
+ *
+ * We just return the original wrong password.
+ * This skips the update of the bad pwd count,
+ * because this is almost certainly user error
+ * (or automatic login on a computer using a cached
+ * password from before the password change),
+ * not an attack.
+ */
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ /*
+ * We finally allow the authentication with the
+ * previous password within the allowed period.
+ */
+ if (user_sess_key->data) {
+ talloc_steal(mem_ctx, user_sess_key->data);
+ }
+ if (lm_sess_key->data) {
+ talloc_steal(mem_ctx, lm_sess_key->data);
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return auth_status;
+ }
+
+ /*
+ * If we are not in the allowed period or match an old password,
+ * we didn't return early. Now update the badPwdCount et al.
+ */
+ nt_status = authsam_update_bad_pwd_count(auth_context->sam_ctx,
+ msg, domain_dn);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ /*
+ * We need to return the original
+ * NT_STATUS_WRONG_PASSWORD error, so there isn't
+ * anything more we can do than write something into
+ * the log
+ */
+ DEBUG(0, ("Failed to note bad password for user [%s]: %s\n",
+ user_info->mapped.account_name,
+ nt_errstr(nt_status)));
+ }
+
+ if (samdb_rodc(auth_context->sam_ctx, &am_rodc) == LDB_SUCCESS && am_rodc) {
+ *authoritative = false;
+ }
+
+ TALLOC_FREE(tmp_ctx);
+
+ if (NT_STATUS_IS_OK(nt_status)) {
+ nt_status = NT_STATUS_WRONG_PASSWORD;
+ }
+ return nt_status;
+}
+
+static NTSTATUS authsam_authenticate(struct auth4_context *auth_context,
+ TALLOC_CTX *mem_ctx, struct ldb_context *sam_ctx,
+ struct ldb_dn *domain_dn,
+ struct ldb_message *msg,
+ const struct auth_usersupplied_info *user_info,
+ DATA_BLOB *user_sess_key, DATA_BLOB *lm_sess_key,
+ bool *authoritative)
+{
+ NTSTATUS nt_status;
+ bool interactive = (user_info->password_state == AUTH_PASSWORD_HASH);
+ uint32_t acct_flags = samdb_result_acct_flags(msg, NULL);
+ struct netr_SendToSamBase *send_to_sam = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* You can only do an interactive login to normal accounts */
+ if (user_info->flags & USER_INFO_INTERACTIVE_LOGON) {
+ if (!(acct_flags & ACB_NORMAL)) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_SUCH_USER;
+ }
+ if (acct_flags & ACB_SMARTCARD_REQUIRED) {
+ if (acct_flags & ACB_DISABLED) {
+ DEBUG(2,("authsam_authenticate: Account for user '%s' "
+ "was disabled.\n",
+ user_info->mapped.account_name));
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_ACCOUNT_DISABLED;
+ }
+ DEBUG(2,("authsam_authenticate: Account for user '%s' "
+ "requires interactive smartcard logon.\n",
+ user_info->mapped.account_name));
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_SMARTCARD_LOGON_REQUIRED;
+ }
+ }
+
+ nt_status = authsam_password_check_and_record(auth_context, tmp_ctx,
+ domain_dn, msg,
+ user_info,
+ user_sess_key, lm_sess_key,
+ authoritative);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ TALLOC_FREE(tmp_ctx);
+ return nt_status;
+ }
+
+ nt_status = authsam_account_ok(tmp_ctx, auth_context->sam_ctx,
+ user_info->logon_parameters,
+ domain_dn,
+ msg,
+ user_info->workstation_name,
+ user_info->mapped.account_name,
+ false, false);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ TALLOC_FREE(tmp_ctx);
+ return nt_status;
+ }
+
+ nt_status = authsam_logon_success_accounting(auth_context->sam_ctx,
+ msg, domain_dn,
+ interactive,
+ tmp_ctx,
+ &send_to_sam);
+
+ if (send_to_sam != NULL) {
+ auth_sam_trigger_zero_password(tmp_ctx,
+ auth_context->msg_ctx,
+ auth_context->event_ctx,
+ send_to_sam);
+ }
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ TALLOC_FREE(tmp_ctx);
+ return nt_status;
+ }
+
+ if (user_sess_key && user_sess_key->data) {
+ talloc_steal(mem_ctx, user_sess_key->data);
+ }
+ if (lm_sess_key && lm_sess_key->data) {
+ talloc_steal(mem_ctx, lm_sess_key->data);
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return nt_status;
+}
+
+
+
+static NTSTATUS authsam_check_password_internals(struct auth_method_context *ctx,
+ TALLOC_CTX *mem_ctx,
+ const struct auth_usersupplied_info *user_info,
+ struct auth_user_info_dc **user_info_dc,
+ bool *authoritative)
+{
+ NTSTATUS nt_status;
+ int result;
+ const char *account_name = user_info->mapped.account_name;
+ struct ldb_message *msg;
+ struct ldb_dn *domain_dn;
+ DATA_BLOB user_sess_key, lm_sess_key;
+ TALLOC_CTX *tmp_ctx;
+ const char *p = NULL;
+
+ if (ctx->auth_ctx->sam_ctx == NULL) {
+ DEBUG(0, ("No SAM available, cannot log in users\n"));
+ return NT_STATUS_INVALID_SYSTEM_SERVICE;
+ }
+
+ if (!account_name || !*account_name) {
+ /* 'not for me' */
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ domain_dn = ldb_get_default_basedn(ctx->auth_ctx->sam_ctx);
+ if (domain_dn == NULL) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_SUCH_DOMAIN;
+ }
+
+ /*
+ * If we have not already mapped this user, then now is a good
+ * time to do so, before we look it up. We used to do this
+ * earlier, but in a multi-forest environment we want to do
+ * this mapping at the final domain.
+ *
+ * However, on the flip side we may have already mapped the
+ * user if this was an LDAP simple bind, in which case we
+ * really, really want to get back to exactly the same account
+ * we got the DN for.
+ */
+ if (!user_info->cracknames_called) {
+ p = strchr_m(account_name, '@');
+ } else {
+ /*
+ * This is slightly nicer than double-indenting the
+ * block below
+ */
+ p = NULL;
+ }
+
+ if (p != NULL) {
+ const char *nt4_domain = NULL;
+ const char *nt4_account = NULL;
+ bool is_my_domain = false;
+
+ nt_status = crack_name_to_nt4_name(mem_ctx,
+ ctx->auth_ctx->sam_ctx,
+ /*
+ * DRSUAPI_DS_NAME_FORMAT_UPN_FOR_LOGON ?
+ */
+ DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL,
+ account_name,
+ &nt4_domain, &nt4_account);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ is_my_domain = lpcfg_is_mydomain(ctx->auth_ctx->lp_ctx, nt4_domain);
+ if (!is_my_domain) {
+ /*
+ * This is a user within our forest,
+ * but in a different domain,
+ * we're not authoritative
+ */
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ /*
+ * Let's use the NT4 account name for the lookup.
+ */
+ account_name = nt4_account;
+ }
+
+ nt_status = authsam_search_account(tmp_ctx, ctx->auth_ctx->sam_ctx, account_name, domain_dn, &msg);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ return nt_status;
+ }
+
+ nt_status = authsam_make_user_info_dc(tmp_ctx, ctx->auth_ctx->sam_ctx,
+ lpcfg_netbios_name(ctx->auth_ctx->lp_ctx),
+ lpcfg_sam_name(ctx->auth_ctx->lp_ctx),
+ lpcfg_sam_dnsname(ctx->auth_ctx->lp_ctx),
+ domain_dn,
+ msg,
+ data_blob_null, data_blob_null,
+ user_info_dc);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ return nt_status;
+ }
+
+ result = dsdb_is_protected_user(ctx->auth_ctx->sam_ctx,
+ (*user_info_dc)->sids,
+ (*user_info_dc)->num_sids);
+ /*
+ * We also consider an error result (a negative value) as denying the
+ * authentication.
+ */
+ if (result != 0) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_ACCOUNT_RESTRICTION;
+ }
+
+ nt_status = authsam_authenticate(ctx->auth_ctx, tmp_ctx, ctx->auth_ctx->sam_ctx, domain_dn, msg, user_info,
+ &user_sess_key, &lm_sess_key, authoritative);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ return nt_status;
+ }
+
+ (*user_info_dc)->user_session_key = data_blob_talloc(*user_info_dc,
+ user_sess_key.data,
+ user_sess_key.length);
+ if (user_sess_key.data) {
+ if ((*user_info_dc)->user_session_key.data == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ (*user_info_dc)->lm_session_key = data_blob_talloc(*user_info_dc,
+ lm_sess_key.data,
+ lm_sess_key.length);
+ if (lm_sess_key.data) {
+ if ((*user_info_dc)->lm_session_key.data == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ talloc_steal(mem_ctx, *user_info_dc);
+ talloc_free(tmp_ctx);
+
+ return NT_STATUS_OK;
+}
+
+struct authsam_check_password_state {
+ struct auth_user_info_dc *user_info_dc;
+ bool authoritative;
+};
+
+static struct tevent_req *authsam_check_password_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct auth_method_context *ctx,
+ const struct auth_usersupplied_info *user_info)
+{
+ struct tevent_req *req = NULL;
+ struct authsam_check_password_state *state = NULL;
+ NTSTATUS status;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct authsam_check_password_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ /*
+ * authsam_check_password_internals() sets this to false in
+ * the rodc case, otherwise it leaves it untouched. Default to
+ * "we're authoritative".
+ */
+ state->authoritative = true;
+
+ status = authsam_check_password_internals(
+ ctx,
+ state,
+ user_info,
+ &state->user_info_dc,
+ &state->authoritative);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static NTSTATUS authsam_check_password_recv(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct auth_user_info_dc **interim_info,
+ bool *authoritative)
+{
+ struct authsam_check_password_state *state = tevent_req_data(
+ req, struct authsam_check_password_state);
+ NTSTATUS status;
+
+ *authoritative = state->authoritative;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+ *interim_info = talloc_move(mem_ctx, &state->user_info_dc);
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS authsam_ignoredomain_want_check(struct auth_method_context *ctx,
+ TALLOC_CTX *mem_ctx,
+ const struct auth_usersupplied_info *user_info)
+{
+ if (!user_info->mapped.account_name || !*user_info->mapped.account_name) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+Check SAM security (above) but with a few extra checks.
+****************************************************************************/
+static NTSTATUS authsam_want_check(struct auth_method_context *ctx,
+ TALLOC_CTX *mem_ctx,
+ const struct auth_usersupplied_info *user_info)
+{
+ const char *effective_domain = user_info->mapped.domain_name;
+ bool is_local_name = false;
+ bool is_my_domain = false;
+ const char *p = NULL;
+ struct dsdb_trust_routing_table *trt = NULL;
+ const struct lsa_TrustDomainInfoInfoEx *tdo = NULL;
+ NTSTATUS status;
+
+ if (!user_info->mapped.account_name || !*user_info->mapped.account_name) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ if (effective_domain == NULL) {
+ effective_domain = "";
+ }
+
+ is_local_name = lpcfg_is_myname(ctx->auth_ctx->lp_ctx,
+ effective_domain);
+
+ /* check whether or not we service this domain/workgroup name */
+ switch (lpcfg_server_role(ctx->auth_ctx->lp_ctx)) {
+ case ROLE_STANDALONE:
+ return NT_STATUS_OK;
+
+ case ROLE_DOMAIN_MEMBER:
+ if (is_local_name) {
+ return NT_STATUS_OK;
+ }
+
+ DBG_DEBUG("%s is not one of my local names (DOMAIN_MEMBER)\n",
+ effective_domain);
+ return NT_STATUS_NOT_IMPLEMENTED;
+
+ case ROLE_ACTIVE_DIRECTORY_DC:
+ /* handled later */
+ break;
+
+ default:
+ DBG_ERR("lpcfg_server_role() has an undefined value\n");
+ return NT_STATUS_INVALID_SERVER_STATE;
+ }
+
+ /*
+ * Now we handle the AD DC case...
+ */
+
+ is_my_domain = lpcfg_is_my_domain_or_realm(ctx->auth_ctx->lp_ctx,
+ effective_domain);
+ if (is_my_domain) {
+ return NT_STATUS_OK;
+ }
+
+ if (user_info->cracknames_called) {
+ /*
+ * The caller already did a cracknames call.
+ */
+ DBG_DEBUG("%s is not own domain name (DC)\n",
+ effective_domain);
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ if (!strequal(effective_domain, "")) {
+ DBG_DEBUG("%s is not own domain name (DC)\n",
+ effective_domain);
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ p = strchr_m(user_info->mapped.account_name, '@');
+ if (p == NULL) {
+ /*
+ * An empty to domain name should be handled
+ * as the local domain name.
+ */
+ return NT_STATUS_OK;
+ }
+
+ effective_domain = p + 1;
+ is_my_domain = lpcfg_is_my_domain_or_realm(ctx->auth_ctx->lp_ctx,
+ effective_domain);
+ if (is_my_domain) {
+ return NT_STATUS_OK;
+ }
+
+ if (strequal(effective_domain, "")) {
+ DBG_DEBUG("authsam_check_password: upn without realm (DC)\n");
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ /*
+ * as last option we check the routing table if the
+ * domain is within our forest.
+ */
+ status = dsdb_trust_routing_table_load(ctx->auth_ctx->sam_ctx,
+ mem_ctx, &trt);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("authsam_check_password: dsdb_trust_routing_table_load() %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ tdo = dsdb_trust_routing_by_name(trt, effective_domain);
+ if (tdo == NULL) {
+ DBG_DEBUG("%s is not a known TLN (DC)\n",
+ effective_domain);
+ TALLOC_FREE(trt);
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ if (!(tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST)) {
+ DBG_DEBUG("%s is not a TLN in our forest (DC)\n",
+ effective_domain);
+ TALLOC_FREE(trt);
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ /*
+ * This principal is within our forest.
+ * we'll later do a crack_name_to_nt4_name()
+ * to check if it's in our domain.
+ */
+ TALLOC_FREE(trt);
+ return NT_STATUS_OK;
+}
+
+static const struct auth_operations sam_ignoredomain_ops = {
+ .name = "sam_ignoredomain",
+ .want_check = authsam_ignoredomain_want_check,
+ .check_password_send = authsam_check_password_send,
+ .check_password_recv = authsam_check_password_recv,
+};
+
+static const struct auth_operations sam_ops = {
+ .name = "sam",
+ .want_check = authsam_want_check,
+ .check_password_send = authsam_check_password_send,
+ .check_password_recv = authsam_check_password_recv,
+};
+
+_PUBLIC_ NTSTATUS auth4_sam_init(TALLOC_CTX *);
+_PUBLIC_ NTSTATUS auth4_sam_init(TALLOC_CTX *ctx)
+{
+ NTSTATUS ret;
+
+ ret = auth_register(ctx, &sam_ops);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(0,("Failed to register 'sam' auth backend!\n"));
+ return ret;
+ }
+
+ ret = auth_register(ctx, &sam_ignoredomain_ops);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(0,("Failed to register 'sam_ignoredomain' auth backend!\n"));
+ return ret;
+ }
+
+ return ret;
+}
diff --git a/source4/auth/ntlm/auth_server_service.c b/source4/auth/ntlm/auth_server_service.c
new file mode 100644
index 0000000..7fbb1fe
--- /dev/null
+++ b/source4/auth/ntlm/auth_server_service.c
@@ -0,0 +1,29 @@
+/*
+ Unix SMB/CIFS implementation.
+ Password and authentication handling
+ Copyright (C) Andrew Bartlett 2010
+
+ 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"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+NTSTATUS server_service_auth_init(TALLOC_CTX *ctx)
+{
+ return auth4_init();
+}
diff --git a/source4/auth/ntlm/auth_simple.c b/source4/auth/ntlm/auth_simple.c
new file mode 100644
index 0000000..006e4d8
--- /dev/null
+++ b/source4/auth/ntlm/auth_simple.c
@@ -0,0 +1,217 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ auth functions
+
+ Copyright (C) Simo Sorce 2005
+ Copyright (C) Andrew Tridgell 2005
+ Copyright (C) Andrew Bartlett 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 "auth/auth.h"
+#include "dsdb/samdb/samdb.h"
+#include "lib/param/param.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+struct authenticate_ldap_simple_bind_state {
+ bool using_tls;
+ struct auth4_context *auth_context;
+ struct auth_usersupplied_info *user_info;
+ struct auth_session_info *session_info;
+};
+
+static void authenticate_ldap_simple_bind_done(struct tevent_req *subreq);
+
+_PUBLIC_ struct tevent_req *authenticate_ldap_simple_bind_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct imessaging_context *msg,
+ struct loadparm_context *lp_ctx,
+ struct tsocket_address *remote_address,
+ struct tsocket_address *local_address,
+ bool using_tls,
+ const char *dn,
+ const char *password)
+{
+ struct tevent_req *req = NULL;
+ struct authenticate_ldap_simple_bind_state *state = NULL;
+ struct auth_usersupplied_info *user_info = NULL;
+ const char *nt4_domain = NULL;
+ const char *nt4_username = NULL;
+ struct tevent_req *subreq = NULL;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct authenticate_ldap_simple_bind_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->using_tls = using_tls;
+
+ status = auth_context_create(state, ev, msg, lp_ctx,
+ &state->auth_context);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ user_info = talloc_zero(state, struct auth_usersupplied_info);
+ if (tevent_req_nomem(user_info, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->user_info = user_info;
+
+ user_info->client.account_name = dn;
+ /* No client.domain_name, use account_name instead */
+ /* user_info->mapped.* will be filled below */
+
+ user_info->workstation_name = lpcfg_netbios_name(lp_ctx);
+
+ user_info->remote_host = remote_address;
+ user_info->local_host = local_address;
+
+ user_info->service_description = "LDAP";
+
+ if (using_tls) {
+ user_info->auth_description = "simple bind/TLS";
+ } else {
+ user_info->auth_description = "simple bind";
+ }
+
+ user_info->password_state = AUTH_PASSWORD_PLAIN;
+ user_info->password.plaintext = talloc_strdup(user_info, password);
+ if (tevent_req_nomem(user_info->password.plaintext, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ user_info->flags = USER_INFO_CASE_INSENSITIVE_USERNAME |
+ USER_INFO_DONT_CHECK_UNIX_ACCOUNT;
+
+ user_info->logon_parameters =
+ MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT |
+ MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT |
+ MSV1_0_CLEARTEXT_PASSWORD_ALLOWED |
+ MSV1_0_CLEARTEXT_PASSWORD_SUPPLIED;
+
+ status = crack_auto_name_to_nt4_name(state, state->auth_context->sam_ctx,
+ dn, &nt4_domain, &nt4_username);
+ if (!NT_STATUS_IS_OK(status)) {
+ log_authentication_event(msg, lp_ctx,
+ &state->auth_context->start_time,
+ user_info, status,
+ NULL, NULL, NULL);
+ }
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ user_info->orig_client = user_info->client;
+ user_info->client.account_name = nt4_username;
+ user_info->client.domain_name = nt4_domain;
+ user_info->cracknames_called = true;
+
+ subreq = auth_check_password_send(state, ev,
+ state->auth_context,
+ state->user_info);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, authenticate_ldap_simple_bind_done, req);
+
+ return req;
+}
+
+static void authenticate_ldap_simple_bind_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct authenticate_ldap_simple_bind_state *state =
+ tevent_req_data(req,
+ struct authenticate_ldap_simple_bind_state);
+ struct auth4_context *auth_context = state->auth_context;
+ struct auth_usersupplied_info *user_info = state->user_info;
+ const char *nt4_username = user_info->mapped.account_name;
+ const struct tsocket_address *remote_address = user_info->remote_host;
+ const struct tsocket_address *local_address = user_info->local_host;
+ const char *transport_protection = AUTHZ_TRANSPORT_PROTECTION_NONE;
+ struct auth_user_info_dc *user_info_dc = NULL;
+ uint8_t authoritative = 1;
+ uint32_t flags = 0;
+ NTSTATUS nt_status;
+
+ if (state->using_tls) {
+ transport_protection = AUTHZ_TRANSPORT_PROTECTION_TLS;
+ }
+
+ nt_status = auth_check_password_recv(subreq, state,
+ &user_info_dc,
+ &authoritative);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, nt_status)) {
+ return;
+ }
+
+ flags = AUTH_SESSION_INFO_DEFAULT_GROUPS;
+ if (user_info_dc->info->authenticated) {
+ flags |= AUTH_SESSION_INFO_AUTHENTICATED;
+ }
+
+ nt_status = auth_context->generate_session_info(auth_context,
+ state,
+ user_info_dc,
+ nt4_username,
+ flags,
+ &state->session_info);
+ if (tevent_req_nterror(req, nt_status)) {
+ return;
+ }
+
+ log_successful_authz_event(auth_context->msg_ctx,
+ auth_context->lp_ctx,
+ remote_address,
+ local_address,
+ "LDAP",
+ "simple bind",
+ transport_protection,
+ state->session_info);
+
+ tevent_req_done(req);
+}
+
+_PUBLIC_ NTSTATUS authenticate_ldap_simple_bind_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct auth_session_info **session_info)
+{
+ struct authenticate_ldap_simple_bind_state *state =
+ tevent_req_data(req,
+ struct authenticate_ldap_simple_bind_state);
+ NTSTATUS status;
+
+ *session_info = NULL;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *session_info = talloc_move(mem_ctx, &state->session_info);
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
diff --git a/source4/auth/ntlm/auth_util.c b/source4/auth/ntlm/auth_util.c
new file mode 100644
index 0000000..58e97fb
--- /dev/null
+++ b/source4/auth/ntlm/auth_util.c
@@ -0,0 +1,183 @@
+/*
+ Unix SMB/CIFS implementation.
+ Authentication utility functions
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Andrew Bartlett 2001
+ Copyright (C) Jeremy Allison 2000-2001
+ Copyright (C) Rafal Szczesniak 2002
+ Copyright (C) Stefan Metzmacher 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 "libcli/auth/libcli_auth.h"
+#include "param/param.h"
+#include "auth/ntlm/auth_proto.h"
+#include "librpc/gen_ndr/drsuapi.h"
+#include "dsdb/samdb/samdb.h"
+#include "lib/crypto/gnutls_helpers.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+/****************************************************************************
+ Create an auth_usersupplied_data structure after appropriate mapping.
+****************************************************************************/
+
+NTSTATUS encrypt_user_info(TALLOC_CTX *mem_ctx, struct auth4_context *auth_context,
+ enum auth_password_state to_state,
+ const struct auth_usersupplied_info *user_info_in,
+ const struct auth_usersupplied_info **user_info_encrypted)
+{
+ int rc;
+ NTSTATUS nt_status;
+ struct auth_usersupplied_info *user_info_temp;
+ switch (to_state) {
+ case AUTH_PASSWORD_RESPONSE:
+ switch (user_info_in->password_state) {
+ case AUTH_PASSWORD_PLAIN:
+ {
+ const struct auth_usersupplied_info *user_info_temp2;
+ nt_status = encrypt_user_info(mem_ctx, auth_context,
+ AUTH_PASSWORD_HASH,
+ user_info_in, &user_info_temp2);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+ user_info_in = user_info_temp2;
+
+ FALL_THROUGH;
+ }
+ case AUTH_PASSWORD_HASH:
+ {
+ uint8_t chal[8];
+ DATA_BLOB chall_blob;
+ user_info_temp = talloc_zero(mem_ctx, struct auth_usersupplied_info);
+ if (!user_info_temp) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (!talloc_reference(user_info_temp, user_info_in)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ *user_info_temp = *user_info_in;
+ user_info_temp->password_state = to_state;
+
+ nt_status = auth_get_challenge(auth_context, chal);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+
+ chall_blob = data_blob_talloc(mem_ctx, chal, 8);
+ if (lpcfg_client_ntlmv2_auth(auth_context->lp_ctx)) {
+ DATA_BLOB names_blob = NTLMv2_generate_names_blob(mem_ctx, lpcfg_netbios_name(auth_context->lp_ctx), lpcfg_workgroup(auth_context->lp_ctx));
+ DATA_BLOB lmv2_response, ntlmv2_response, lmv2_session_key, ntlmv2_session_key;
+
+ if (!SMBNTLMv2encrypt_hash(user_info_temp,
+ user_info_in->client.account_name,
+ user_info_in->client.domain_name,
+ user_info_in->password.hash.nt->hash,
+ &chall_blob,
+ NULL, /* server_timestamp */
+ &names_blob,
+ &lmv2_response, &ntlmv2_response,
+ &lmv2_session_key, &ntlmv2_session_key)) {
+ data_blob_free(&names_blob);
+ return NT_STATUS_NO_MEMORY;
+ }
+ data_blob_free(&names_blob);
+ user_info_temp->password.response.lanman = lmv2_response;
+ user_info_temp->password.response.nt = ntlmv2_response;
+
+ data_blob_free(&lmv2_session_key);
+ data_blob_free(&ntlmv2_session_key);
+ } else {
+ DATA_BLOB blob = data_blob_talloc(mem_ctx, NULL, 24);
+ rc = SMBOWFencrypt(user_info_in->password.hash.nt->hash, chal, blob.data);
+ if (rc != 0) {
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER);
+ }
+ user_info_temp->password.response.nt = blob;
+ if (lpcfg_client_lanman_auth(auth_context->lp_ctx) && user_info_in->password.hash.lanman) {
+ DATA_BLOB lm_blob = data_blob_talloc(mem_ctx, NULL, 24);
+ rc = SMBOWFencrypt(user_info_in->password.hash.lanman->hash, chal, blob.data);
+ if (rc != 0) {
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER);
+ }
+ user_info_temp->password.response.lanman = lm_blob;
+ } else {
+ /* if not sending the LM password, send the NT password twice */
+ user_info_temp->password.response.lanman = user_info_temp->password.response.nt;
+ }
+ }
+
+ user_info_in = user_info_temp;
+
+ FALL_THROUGH;
+ }
+ case AUTH_PASSWORD_RESPONSE:
+ *user_info_encrypted = user_info_in;
+ }
+ break;
+ case AUTH_PASSWORD_HASH:
+ {
+ switch (user_info_in->password_state) {
+ case AUTH_PASSWORD_PLAIN:
+ {
+ struct samr_Password lanman;
+ struct samr_Password nt;
+
+ user_info_temp = talloc_zero(mem_ctx, struct auth_usersupplied_info);
+ if (!user_info_temp) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (!talloc_reference(user_info_temp, user_info_in)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ *user_info_temp = *user_info_in;
+ user_info_temp->password_state = to_state;
+
+ if (E_deshash(user_info_in->password.plaintext, lanman.hash)) {
+ user_info_temp->password.hash.lanman = talloc(user_info_temp,
+ struct samr_Password);
+ *user_info_temp->password.hash.lanman = lanman;
+ } else {
+ user_info_temp->password.hash.lanman = NULL;
+ }
+
+ E_md4hash(user_info_in->password.plaintext, nt.hash);
+ user_info_temp->password.hash.nt = talloc(user_info_temp,
+ struct samr_Password);
+ *user_info_temp->password.hash.nt = nt;
+
+ user_info_in = user_info_temp;
+
+ FALL_THROUGH;
+ }
+ case AUTH_PASSWORD_HASH:
+ *user_info_encrypted = user_info_in;
+ break;
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ break;
+ }
+ break;
+ }
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source4/auth/ntlm/auth_winbind.c b/source4/auth/ntlm/auth_winbind.c
new file mode 100644
index 0000000..719d877
--- /dev/null
+++ b/source4/auth/ntlm/auth_winbind.c
@@ -0,0 +1,322 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind authentication mechnism
+
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Andrew Bartlett 2001 - 2002
+ Copyright (C) Stefan Metzmacher 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 "auth/auth.h"
+#include "auth/ntlm/auth_proto.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "lib/messaging/irpc.h"
+#include "param/param.h"
+#include "nsswitch/libwbclient/wbclient.h"
+#include "auth/auth_sam_reply.h"
+#include "libcli/security/security.h"
+#include "dsdb/samdb/samdb.h"
+#include "auth/auth_sam.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+_PUBLIC_ NTSTATUS auth4_winbind_init(TALLOC_CTX *);
+
+static NTSTATUS winbind_want_check(struct auth_method_context *ctx,
+ TALLOC_CTX *mem_ctx,
+ const struct auth_usersupplied_info *user_info)
+{
+ if (!user_info->mapped.account_name || !*user_info->mapped.account_name) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ /* TODO: maybe limit the user scope to remote users only */
+ return NT_STATUS_OK;
+}
+
+struct winbind_check_password_state {
+ struct auth_method_context *ctx;
+ const struct auth_usersupplied_info *user_info;
+ struct winbind_SamLogon req;
+ struct auth_user_info_dc *user_info_dc;
+ bool authoritative;
+};
+
+static void winbind_check_password_done(struct tevent_req *subreq);
+
+/*
+ Authenticate a user with a challenge/response
+ using IRPC to the winbind task
+*/
+static struct tevent_req *winbind_check_password_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct auth_method_context *ctx,
+ const struct auth_usersupplied_info *user_info)
+{
+ struct tevent_req *req = NULL;
+ struct winbind_check_password_state *state = NULL;
+ NTSTATUS status;
+ struct dcerpc_binding_handle *irpc_handle;
+ const struct auth_usersupplied_info *user_info_new;
+ struct netr_IdentityInfo *identity_info;
+ struct imessaging_context *msg_ctx;
+ struct tevent_req *subreq = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbind_check_password_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ctx = ctx;
+ state->user_info = user_info;
+ state->authoritative = true;
+
+ msg_ctx = imessaging_client_init(state, ctx->auth_ctx->lp_ctx, ev);
+ if (msg_ctx == NULL) {
+ DEBUG(1, ("imessaging_init failed\n"));
+ tevent_req_nterror(req, NT_STATUS_INVALID_SERVER_STATE);
+ return tevent_req_post(req, ev);
+ }
+
+ irpc_handle = irpc_binding_handle_by_name(state, msg_ctx,
+ "winbind_server",
+ &ndr_table_winbind);
+ if (irpc_handle == NULL) {
+ DEBUG(0, ("Winbind authentication for [%s]\\[%s] failed, "
+ "no winbind_server running!\n",
+ user_info->client.domain_name, user_info->client.account_name));
+ tevent_req_nterror(req, NT_STATUS_NO_LOGON_SERVERS);
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * 120 seconds should be enough even for trusted domains.
+ *
+ * Currently winbindd has a much lower limit.
+ * And tests with Windows RODCs show that it
+ * returns NO_LOGON_SERVERS after 90-100 seconds
+ * if it can't reach any RWDC.
+ */
+ dcerpc_binding_handle_set_timeout(irpc_handle, 120);
+
+ if (user_info->flags & USER_INFO_INTERACTIVE_LOGON) {
+ struct netr_PasswordInfo *password_info;
+
+ status = encrypt_user_info(state, ctx->auth_ctx, AUTH_PASSWORD_HASH,
+ user_info, &user_info_new);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ user_info = user_info_new;
+
+ password_info = talloc_zero(state, struct netr_PasswordInfo);
+ if (tevent_req_nomem(password_info, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ password_info->lmpassword = *user_info->password.hash.lanman;
+ password_info->ntpassword = *user_info->password.hash.nt;
+
+ identity_info = &password_info->identity_info;
+ state->req.in.logon_level = 1;
+ state->req.in.logon.password= password_info;
+ } else {
+ struct netr_NetworkInfo *network_info;
+ uint8_t chal[8];
+
+ status = encrypt_user_info(state, ctx->auth_ctx, AUTH_PASSWORD_RESPONSE,
+ user_info, &user_info_new);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ user_info = user_info_new;
+
+ network_info = talloc_zero(state, struct netr_NetworkInfo);
+ if (tevent_req_nomem(network_info, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ status = auth_get_challenge(ctx->auth_ctx, chal);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ memcpy(network_info->challenge, chal, sizeof(network_info->challenge));
+
+ network_info->nt.length = user_info->password.response.nt.length;
+ network_info->nt.data = user_info->password.response.nt.data;
+
+ network_info->lm.length = user_info->password.response.lanman.length;
+ network_info->lm.data = user_info->password.response.lanman.data;
+
+ identity_info = &network_info->identity_info;
+ state->req.in.logon_level = 2;
+ state->req.in.logon.network = network_info;
+ }
+
+ identity_info->domain_name.string = user_info->client.domain_name;
+ identity_info->parameter_control = user_info->logon_parameters; /* see MSV1_0_* */
+ identity_info->logon_id = user_info->logon_id;
+ identity_info->account_name.string = user_info->client.account_name;
+ identity_info->workstation.string = user_info->workstation_name;
+
+ state->req.in.validation_level = 6;
+
+ subreq = dcerpc_winbind_SamLogon_r_send(state, ev, irpc_handle,
+ &state->req);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ winbind_check_password_done,
+ req);
+
+ return req;
+}
+
+static void winbind_check_password_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct winbind_check_password_state *state =
+ tevent_req_data(req,
+ struct winbind_check_password_state);
+ struct auth_method_context *ctx = state->ctx;
+ const struct auth_usersupplied_info *user_info = state->user_info;
+ struct ldb_dn *domain_dn = NULL;
+ const char *nt4_domain = NULL;
+ const char *nt4_account = NULL;
+ struct ldb_message *msg = NULL;
+ NTSTATUS status;
+
+ status = dcerpc_winbind_SamLogon_r_recv(subreq, state);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) {
+ status = NT_STATUS_NO_LOGON_SERVERS;
+ }
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ status = state->req.out.result;
+ if (!NT_STATUS_IS_OK(status)) {
+ if (!state->req.out.authoritative) {
+ state->authoritative = false;
+ }
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ status = make_user_info_dc_netlogon_validation(state,
+ user_info->client.account_name,
+ state->req.in.validation_level,
+ &state->req.out.validation,
+ true, /* This user was authenticated */
+ &state->user_info_dc);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ nt4_domain = state->user_info_dc->info->domain_name;
+ nt4_account = state->user_info_dc->info->account_name;
+
+ if (lpcfg_is_mydomain(ctx->auth_ctx->lp_ctx, nt4_domain)) {
+ domain_dn = ldb_get_default_basedn(ctx->auth_ctx->sam_ctx);
+ }
+
+ if (domain_dn != NULL) {
+ /*
+ * At best, reset the badPwdCount to 0 if the account exists.
+ * This means that lockouts happen at a badPwdCount earlier than
+ * normal, but makes it more fault tolerant.
+ */
+ status = authsam_search_account(state, ctx->auth_ctx->sam_ctx,
+ nt4_account, domain_dn, &msg);
+ if (NT_STATUS_IS_OK(status)) {
+ status = authsam_logon_success_accounting(
+ ctx->auth_ctx->sam_ctx, msg,
+ domain_dn,
+ user_info->flags & USER_INFO_INTERACTIVE_LOGON,
+ NULL, NULL);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ }
+ }
+
+ /*
+ * We need to expand group memberships within our local domain,
+ * as the token might be generated by a trusted domain, unless we're
+ * an RODC.
+ */
+ status = authsam_update_user_info_dc(state->user_info_dc,
+ ctx->auth_ctx->sam_ctx,
+ state->user_info_dc);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS winbind_check_password_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct auth_user_info_dc **user_info_dc,
+ bool *pauthoritative)
+{
+ struct winbind_check_password_state *state =
+ tevent_req_data(req,
+ struct winbind_check_password_state);
+ NTSTATUS status = NT_STATUS_OK;
+
+ *pauthoritative = state->authoritative;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *user_info_dc = talloc_move(mem_ctx, &state->user_info_dc);
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+static const struct auth_operations winbind_ops = {
+ .name = "winbind",
+ .want_check = winbind_want_check,
+ .check_password_send = winbind_check_password_send,
+ .check_password_recv = winbind_check_password_recv
+};
+
+_PUBLIC_ NTSTATUS auth4_winbind_init(TALLOC_CTX *ctx)
+{
+ NTSTATUS ret;
+
+ ret = auth_register(ctx, &winbind_ops);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(0,("Failed to register 'winbind' auth backend!\n"));
+ return ret;
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source4/auth/ntlm/wscript_build b/source4/auth/ntlm/wscript_build
new file mode 100644
index 0000000..3d96230
--- /dev/null
+++ b/source4/auth/ntlm/wscript_build
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+
+bld.SAMBA_MODULE('auth4_sam_module',
+ source='auth_sam.c',
+ subsystem='auth4',
+ init_function='auth4_sam_init',
+ deps='samdb auth4_sam NTLMSSP_COMMON samba-hostconfig RPC_NDR_IRPC MESSAGING db-glue',
+ enabled=bld.AD_DC_BUILD_IS_ENABLED()
+ )
+
+
+bld.SAMBA_MODULE('auth4_anonymous',
+ source='auth_anonymous.c',
+ subsystem='auth4',
+ init_function='auth4_anonymous_init',
+ deps='tevent'
+ )
+
+
+bld.SAMBA_MODULE('auth4_winbind',
+ source='auth_winbind.c',
+ subsystem='auth4',
+ init_function='auth4_winbind_init',
+ deps='RPC_NDR_WINBIND MESSAGING wbclient'
+ )
+
+
+bld.SAMBA_MODULE('auth4_developer',
+ source='auth_developer.c',
+ subsystem='auth4',
+ init_function='auth4_developer_init',
+ deps='tevent',
+ enabled=bld.env.DEVELOPER_MODE
+ )
+
+
+bld.SAMBA_LIBRARY('auth4',
+ source='auth.c auth_util.c auth_simple.c',
+ autoproto='auth_proto.h',
+ deps='samba-util samba-security samdb samba-credentials tevent-util LIBWBCLIENT_OLD auth_unix_token samba-modules KERBEROS_UTIL',
+ private_library=True
+ )
+
+bld.SAMBA_MODULE('service_auth',
+ source='auth_server_service.c',
+ subsystem='service',
+ init_function='server_service_auth_init',
+ deps='auth4',
+ internal_module=True
+ )
+
diff --git a/source4/auth/pyauth.c b/source4/auth/pyauth.c
new file mode 100644
index 0000000..ec6065d
--- /dev/null
+++ b/source4/auth/pyauth.c
@@ -0,0 +1,541 @@
+/*
+ Unix SMB/CIFS implementation.
+ Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2011
+
+ 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 "libcli/util/pyerrors.h"
+#include "param/param.h"
+#include "pyauth.h"
+#include "pyldb.h"
+#include "auth/system_session_proto.h"
+#include "auth/auth.h"
+#include "auth/auth_util.h"
+#include "param/pyparam.h"
+#include "libcli/security/security.h"
+#include "auth/credentials/pycredentials.h"
+#include <tevent.h>
+#include "librpc/rpc/pyrpc_util.h"
+#include "lib/events/events.h"
+
+static PyTypeObject PyAuthContext;
+
+static PyObject *PyAuthSession_FromSession(struct auth_session_info *session)
+{
+ return py_return_ndr_struct("samba.dcerpc.auth", "session_info", session, session);
+}
+
+static PyObject *py_copy_session_info(PyObject *module,
+ PyObject *args,
+ PyObject *kwargs)
+{
+ PyObject *py_session = Py_None;
+ PyObject *result = Py_None;
+ struct auth_session_info *session = NULL;
+ struct auth_session_info *session_duplicate = NULL;
+ TALLOC_CTX *frame;
+ int ret = 1;
+
+ const char * const kwnames[] = { "session_info", NULL };
+
+ ret = PyArg_ParseTupleAndKeywords(args,
+ kwargs,
+ "O",
+ discard_const_p(char *, kwnames),
+ &py_session);
+ if (!ret) {
+ return NULL;
+ }
+
+ ret = py_check_dcerpc_type(py_session,
+ "samba.dcerpc.auth",
+ "session_info");
+ if (!ret) {
+ return NULL;
+ }
+ session = pytalloc_get_type(py_session,
+ struct auth_session_info);
+ if (!session) {
+ PyErr_Format(PyExc_TypeError,
+ "Expected auth_session_info for session_info "
+ "argument got %s",
+ pytalloc_get_name(py_session));
+ return NULL;
+ }
+
+ frame = talloc_stackframe();
+ if (frame == NULL) {
+ return PyErr_NoMemory();
+ }
+
+ session_duplicate = copy_session_info(frame, session);
+ if (session_duplicate == NULL) {
+ TALLOC_FREE(frame);
+ return PyErr_NoMemory();
+ }
+
+ result = PyAuthSession_FromSession(session_duplicate);
+ TALLOC_FREE(frame);
+ return result;
+}
+
+static PyObject *py_system_session(PyObject *module, PyObject *args)
+{
+ PyObject *py_lp_ctx = Py_None;
+ struct loadparm_context *lp_ctx = NULL;
+ struct auth_session_info *session;
+ TALLOC_CTX *mem_ctx;
+ if (!PyArg_ParseTuple(args, "|O", &py_lp_ctx))
+ return NULL;
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ lp_ctx = lpcfg_from_py_object(mem_ctx, py_lp_ctx);
+ if (lp_ctx == NULL) {
+ talloc_free(mem_ctx);
+ return NULL;
+ }
+
+ session = system_session(lp_ctx);
+
+ talloc_free(mem_ctx);
+
+ return PyAuthSession_FromSession(session);
+}
+
+
+static PyObject *py_admin_session(PyObject *module, PyObject *args)
+{
+ PyObject *py_lp_ctx;
+ const char *sid;
+ struct loadparm_context *lp_ctx = NULL;
+ struct auth_session_info *session;
+ struct dom_sid *domain_sid = NULL;
+ TALLOC_CTX *mem_ctx;
+
+ if (!PyArg_ParseTuple(args, "Os", &py_lp_ctx, &sid))
+ return NULL;
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ lp_ctx = lpcfg_from_py_object(mem_ctx, py_lp_ctx);
+ if (lp_ctx == NULL) {
+ talloc_free(mem_ctx);
+ return NULL;
+ }
+
+ domain_sid = dom_sid_parse_talloc(mem_ctx, sid);
+ if (domain_sid == NULL) {
+ PyErr_Format(PyExc_RuntimeError, "Unable to parse sid %s", sid);
+ talloc_free(mem_ctx);
+ return NULL;
+ }
+ session = admin_session(NULL, lp_ctx, domain_sid);
+ talloc_free(mem_ctx);
+
+ return PyAuthSession_FromSession(session);
+}
+
+static PyObject *py_user_session(PyObject *module, PyObject *args, PyObject *kwargs)
+{
+ NTSTATUS nt_status;
+ struct auth_session_info *session;
+ TALLOC_CTX *mem_ctx;
+ const char * const kwnames[] = { "ldb", "lp_ctx", "principal", "dn", "session_info_flags", NULL };
+ struct ldb_context *ldb_ctx;
+ PyObject *py_ldb = Py_None;
+ PyObject *py_dn = Py_None;
+ PyObject *py_lp_ctx = Py_None;
+ struct loadparm_context *lp_ctx = NULL;
+ struct ldb_dn *user_dn;
+ char *principal = NULL;
+ int session_info_flags = 0; /* This is an int, because that's
+ * what we need for the python
+ * PyArg_ParseTupleAndKeywords */
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|OzOi",
+ discard_const_p(char *, kwnames),
+ &py_ldb, &py_lp_ctx, &principal, &py_dn, &session_info_flags)) {
+ return NULL;
+ }
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ ldb_ctx = pyldb_Ldb_AsLdbContext(py_ldb);
+ if (ldb_ctx == NULL) {
+ talloc_free(mem_ctx);
+ return NULL;
+ }
+
+ if (py_dn == Py_None) {
+ user_dn = NULL;
+ } else {
+ if (!pyldb_Object_AsDn(ldb_ctx, py_dn, ldb_ctx, &user_dn)) {
+ talloc_free(mem_ctx);
+ return NULL;
+ }
+ }
+
+ lp_ctx = lpcfg_from_py_object(mem_ctx, py_lp_ctx);
+ if (lp_ctx == NULL) {
+ talloc_free(mem_ctx);
+ return NULL;
+ }
+
+ nt_status = authsam_get_session_info_principal(mem_ctx, lp_ctx, ldb_ctx, principal, user_dn,
+ session_info_flags, &session);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(mem_ctx);
+ PyErr_NTSTATUS_IS_ERR_RAISE(nt_status);
+ }
+
+ talloc_steal(NULL, session);
+ talloc_free(mem_ctx);
+
+ return PyAuthSession_FromSession(session);
+}
+
+static PyObject *py_session_info_fill_unix(PyObject *module,
+ PyObject *args,
+ PyObject *kwargs)
+{
+ NTSTATUS nt_status;
+ char *user_name = NULL;
+ struct loadparm_context *lp_ctx = NULL;
+ struct auth_session_info *session_info;
+ PyObject *py_lp_ctx = Py_None;
+ PyObject *py_session = Py_None;
+ TALLOC_CTX *frame;
+
+ const char * const kwnames[] = { "session_info",
+ "user_name",
+ "lp_ctx",
+ NULL };
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "Oz|O",
+ discard_const_p(char *, kwnames),
+ &py_session,
+ &user_name,
+ &py_lp_ctx)) {
+ return NULL;
+ }
+
+ if (!py_check_dcerpc_type(py_session,
+ "samba.dcerpc.auth",
+ "session_info")) {
+ return NULL;
+ }
+ session_info = pytalloc_get_type(py_session,
+ struct auth_session_info);
+ if (!session_info) {
+ PyErr_Format(PyExc_TypeError,
+ "Expected auth_session_info for session_info argument got %s",
+ pytalloc_get_name(py_session));
+ return NULL;
+ }
+
+ frame = talloc_stackframe();
+
+ lp_ctx = lpcfg_from_py_object(frame, py_lp_ctx);
+ if (lp_ctx == NULL) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ nt_status = auth_session_info_fill_unix(lp_ctx,
+ user_name,
+ session_info);
+ TALLOC_FREE(frame);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ PyErr_NTSTATUS_IS_ERR_RAISE(nt_status);
+ }
+
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *py_session_info_set_unix(PyObject *module,
+ PyObject *args,
+ PyObject *kwargs)
+{
+ NTSTATUS nt_status;
+ char *user_name = NULL;
+ int uid = -1;
+ int gid = -1;
+ struct loadparm_context *lp_ctx = NULL;
+ struct auth_session_info *session_info;
+ PyObject *py_lp_ctx = Py_None;
+ PyObject *py_session = Py_None;
+ TALLOC_CTX *frame;
+
+ const char * const kwnames[] = { "session_info",
+ "user_name",
+ "uid",
+ "gid",
+ "lp_ctx",
+ NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "Ozii|O",
+ discard_const_p(char *, kwnames),
+ &py_session,
+ &user_name,
+ &uid,
+ &gid,
+ &py_lp_ctx)) {
+ return NULL;
+ }
+
+ if (!py_check_dcerpc_type(py_session,
+ "samba.dcerpc.auth",
+ "session_info")) {
+ return NULL;
+ }
+ session_info = pytalloc_get_type(py_session,
+ struct auth_session_info);
+ if (!session_info) {
+ PyErr_Format(PyExc_TypeError,
+ "Expected auth_session_info for session_info "
+ "argument got %s",
+ pytalloc_get_name(py_session));
+ return NULL;
+ }
+
+ frame = talloc_stackframe();
+
+ lp_ctx = lpcfg_from_py_object(frame, py_lp_ctx);
+ if (lp_ctx == NULL) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ nt_status = auth_session_info_set_unix(lp_ctx,
+ user_name,
+ uid,
+ gid,
+ session_info);
+ TALLOC_FREE(frame);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ PyErr_NTSTATUS_IS_ERR_RAISE(nt_status);
+ }
+
+ Py_RETURN_NONE;
+}
+
+
+static const char **PyList_AsStringList(TALLOC_CTX *mem_ctx, PyObject *list,
+ const char *paramname)
+{
+ const char **ret;
+ Py_ssize_t i;
+ if (!PyList_Check(list)) {
+ PyErr_Format(PyExc_TypeError, "%s is not a list", paramname);
+ return NULL;
+ }
+ ret = talloc_array(NULL, const char *, PyList_Size(list)+1);
+ if (ret == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ for (i = 0; i < PyList_Size(list); i++) {
+ const char *value;
+ Py_ssize_t size;
+ PyObject *item = PyList_GetItem(list, i);
+ if (!PyUnicode_Check(item)) {
+ PyErr_Format(PyExc_TypeError, "%s should be strings", paramname);
+ return NULL;
+ }
+ value = PyUnicode_AsUTF8AndSize(item, &size);
+ if (value == NULL) {
+ talloc_free(ret);
+ return NULL;
+ }
+ ret[i] = talloc_strndup(ret, value, size);
+ }
+ ret[i] = NULL;
+ return ret;
+}
+
+static PyObject *PyAuthContext_FromContext(struct auth4_context *auth_context)
+{
+ return pytalloc_reference(&PyAuthContext, auth_context);
+}
+
+static PyObject *py_auth_context_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+{
+ PyObject *py_lp_ctx = Py_None;
+ PyObject *py_ldb = Py_None;
+ PyObject *py_auth_context = Py_None;
+ PyObject *py_methods = Py_None;
+ TALLOC_CTX *mem_ctx;
+ struct auth4_context *auth_context;
+ struct loadparm_context *lp_ctx;
+ struct tevent_context *ev;
+ struct ldb_context *ldb = NULL;
+ NTSTATUS nt_status;
+ const char **methods;
+
+ const char *const kwnames[] = {"lp_ctx", "ldb", "methods", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args,
+ kwargs,
+ "|OOO",
+ discard_const_p(char *, kwnames),
+ &py_lp_ctx,
+ &py_ldb,
+ &py_methods))
+ return NULL;
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ if (py_ldb != Py_None) {
+ ldb = pyldb_Ldb_AsLdbContext(py_ldb);
+ if (ldb == NULL) {
+ talloc_free(mem_ctx);
+ return NULL;
+ }
+ }
+
+ lp_ctx = lpcfg_from_py_object(mem_ctx, py_lp_ctx);
+ if (lp_ctx == NULL) {
+ talloc_free(mem_ctx);
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ ev = s4_event_context_init(mem_ctx);
+ if (ev == NULL) {
+ talloc_free(mem_ctx);
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ if (py_methods == Py_None && py_ldb == Py_None) {
+ nt_status = auth_context_create(
+ mem_ctx, ev, NULL, lp_ctx, &auth_context);
+ } else {
+ if (py_methods != Py_None) {
+ methods = PyList_AsStringList(mem_ctx, py_methods, "methods");
+ if (methods == NULL) {
+ talloc_free(mem_ctx);
+ return NULL;
+ }
+ } else {
+ methods = auth_methods_from_lp(mem_ctx, lp_ctx);
+ }
+ nt_status = auth_context_create_methods(
+ mem_ctx, methods, ev, NULL, lp_ctx, ldb, &auth_context);
+ }
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(mem_ctx);
+ PyErr_NTSTATUS_IS_ERR_RAISE(nt_status);
+ }
+
+ if (!talloc_reference(auth_context, lp_ctx)) {
+ talloc_free(mem_ctx);
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ if (!talloc_reference(auth_context, ev)) {
+ talloc_free(mem_ctx);
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ py_auth_context = PyAuthContext_FromContext(auth_context);
+
+ talloc_free(mem_ctx);
+
+ return py_auth_context;
+}
+
+static PyTypeObject PyAuthContext = {
+ .tp_name = "AuthContext",
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_new = py_auth_context_new,
+};
+
+static PyMethodDef py_auth_methods[] = {
+ { "system_session", (PyCFunction)py_system_session, METH_VARARGS, NULL },
+ { "admin_session", (PyCFunction)py_admin_session, METH_VARARGS, NULL },
+ { "user_session", PY_DISCARD_FUNC_SIG(PyCFunction,py_user_session),
+ METH_VARARGS|METH_KEYWORDS, NULL },
+ { "session_info_fill_unix",
+ PY_DISCARD_FUNC_SIG(PyCFunction,py_session_info_fill_unix),
+ METH_VARARGS|METH_KEYWORDS,
+ NULL },
+ { "session_info_set_unix",
+ PY_DISCARD_FUNC_SIG(PyCFunction,py_session_info_set_unix),
+ METH_VARARGS|METH_KEYWORDS,
+ NULL },
+ { "copy_session_info",
+ PY_DISCARD_FUNC_SIG(PyCFunction,py_copy_session_info),
+ METH_VARARGS|METH_KEYWORDS,
+ NULL },
+ {0},
+};
+
+static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "auth",
+ .m_doc = "Authentication and authorization support.",
+ .m_size = -1,
+ .m_methods = py_auth_methods,
+};
+
+MODULE_INIT_FUNC(auth)
+{
+ PyObject *m;
+
+ if (pytalloc_BaseObject_PyType_Ready(&PyAuthContext) < 0)
+ return NULL;
+
+ m = PyModule_Create(&moduledef);
+ if (m == NULL)
+ return NULL;
+
+ Py_INCREF(&PyAuthContext);
+ PyModule_AddObject(m, "AuthContext", (PyObject *)&PyAuthContext);
+
+#define ADD_FLAG(val) PyModule_AddIntConstant(m, #val, val)
+ ADD_FLAG(AUTH_SESSION_INFO_DEFAULT_GROUPS);
+ ADD_FLAG(AUTH_SESSION_INFO_AUTHENTICATED);
+ ADD_FLAG(AUTH_SESSION_INFO_SIMPLE_PRIVILEGES);
+ ADD_FLAG(AUTH_SESSION_INFO_NTLM);
+
+ return m;
+}
diff --git a/source4/auth/pyauth.h b/source4/auth/pyauth.h
new file mode 100644
index 0000000..c01144d
--- /dev/null
+++ b/source4/auth/pyauth.h
@@ -0,0 +1,29 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba utility functions
+ Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2008
+
+ 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 _PYAUTH_H_
+#define _PYAUTH_H_
+
+#include <pytalloc.h>
+#include "auth/session.h"
+
+#define PyAuthSession_AsSession(obj) pytalloc_get_type(obj, struct auth_session_info)
+struct auth_session_info *PyObject_AsSession(PyObject *obj);
+
+#endif /* _PYAUTH_H */
diff --git a/source4/auth/sam.c b/source4/auth/sam.c
new file mode 100644
index 0000000..f2e5ced
--- /dev/null
+++ b/source4/auth/sam.c
@@ -0,0 +1,1737 @@
+/*
+ Unix SMB/CIFS implementation.
+ Password and authentication handling
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2010
+ Copyright (C) Gerald Carter 2003
+ Copyright (C) Stefan Metzmacher 2005
+ Copyright (C) Matthias Dieter Wallnöfer 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 "includes.h"
+#include "system/time.h"
+#include "auth/auth.h"
+#include <ldb.h>
+#include "dsdb/samdb/samdb.h"
+#include "libcli/security/security.h"
+#include "auth/auth_sam.h"
+#include "dsdb/common/util.h"
+#include "libcli/ldap/ldap_ndr.h"
+#include "param/param.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "lib/dbwrap/dbwrap.h"
+#include "cluster/cluster.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+#define KRBTGT_ATTRS \
+ /* required for the krb5 kdc */ \
+ "objectClass", \
+ "sAMAccountName", \
+ "userPrincipalName", \
+ "servicePrincipalName", \
+ "msDS-KeyVersionNumber", \
+ "msDS-SecondaryKrbTgtNumber", \
+ "msDS-SupportedEncryptionTypes", \
+ "supplementalCredentials", \
+ "msDS-AllowedToDelegateTo", \
+ "msDS-AllowedToActOnBehalfOfOtherIdentity", \
+ \
+ /* passwords */ \
+ "unicodePwd", \
+ \
+ "userAccountControl", \
+ "msDS-User-Account-Control-Computed", \
+ "objectSid", \
+ \
+ "pwdLastSet", \
+ "msDS-UserPasswordExpiryTimeComputed", \
+ "accountExpires", \
+ \
+ /* Needed for RODC rule processing */ \
+ "msDS-KrbTgtLinkBL"
+
+const char *krbtgt_attrs[] = {
+ KRBTGT_ATTRS, NULL
+};
+
+const char *server_attrs[] = {
+ KRBTGT_ATTRS, NULL
+};
+
+const char *user_attrs[] = {
+ /*
+ * This ordering (having msDS-ResultantPSO first) is
+ * important. By processing this attribute first it is
+ * available in the operational module for the other PSO
+ * attribute calcuations to use.
+ */
+ "msDS-ResultantPSO",
+
+ KRBTGT_ATTRS,
+
+ "logonHours",
+
+ /*
+ * To allow us to zero the badPwdCount and lockoutTime on
+ * successful logon, without database churn
+ */
+ "lockoutTime",
+
+ /*
+ * Needed for SendToSAM requests
+ */
+ "objectGUID",
+
+ /* check 'allowed workstations' */
+ "userWorkstations",
+
+ /* required for user_info_dc, not access control: */
+ "displayName",
+ "scriptPath",
+ "profilePath",
+ "homeDirectory",
+ "homeDrive",
+ "lastLogon",
+ "lastLogonTimestamp",
+ "lastLogoff",
+ "accountExpires",
+ "badPwdCount",
+ "logonCount",
+ "primaryGroupID",
+ "memberOf",
+ "badPasswordTime",
+ "lmPwdHistory",
+ "ntPwdHistory",
+ NULL,
+};
+
+/****************************************************************************
+ Check if a user is allowed to logon at this time. Note this is the
+ servers local time, as logon hours are just specified as a weekly
+ bitmask.
+****************************************************************************/
+
+static bool logon_hours_ok(struct ldb_message *msg, const char *name_for_logs)
+{
+ /* In logon hours first bit is Sunday from 12AM to 1AM */
+ const struct ldb_val *hours;
+ struct tm *utctime;
+ time_t lasttime;
+ const char *asct;
+ uint8_t bitmask, bitpos;
+
+ hours = ldb_msg_find_ldb_val(msg, "logonHours");
+ if (!hours) {
+ DEBUG(5,("logon_hours_ok: No hours restrictions for user %s\n", name_for_logs));
+ return true;
+ }
+
+ if (hours->length != 168/8) {
+ DEBUG(5,("logon_hours_ok: malformed logon hours restrictions for user %s\n", name_for_logs));
+ return true;
+ }
+
+ lasttime = time(NULL);
+ utctime = gmtime(&lasttime);
+ if (!utctime) {
+ DEBUG(1, ("logon_hours_ok: failed to get gmtime. Failing logon for user %s\n",
+ name_for_logs));
+ return false;
+ }
+
+ /* find the corresponding byte and bit */
+ bitpos = (utctime->tm_wday * 24 + utctime->tm_hour) % 168;
+ bitmask = 1 << (bitpos % 8);
+
+ if (! (hours->data[bitpos/8] & bitmask)) {
+ struct tm *t = localtime(&lasttime);
+ if (!t) {
+ asct = "INVALID TIME";
+ } else {
+ asct = asctime(t);
+ if (!asct) {
+ asct = "INVALID TIME";
+ }
+ }
+
+ DEBUG(1, ("logon_hours_ok: Account for user %s not allowed to "
+ "logon at this time (%s).\n",
+ name_for_logs, asct ));
+ return false;
+ }
+
+ asct = asctime(utctime);
+ DEBUG(5,("logon_hours_ok: user %s allowed to logon at this time (%s)\n",
+ name_for_logs, asct ? asct : "UNKNOWN TIME" ));
+
+ return true;
+}
+
+/****************************************************************************
+ Do a specific test for a SAM_ACCOUNT being valid for this connection
+ (ie not disabled, expired and the like).
+****************************************************************************/
+_PUBLIC_ NTSTATUS authsam_account_ok(TALLOC_CTX *mem_ctx,
+ struct ldb_context *sam_ctx,
+ uint32_t logon_parameters,
+ struct ldb_dn *domain_dn,
+ struct ldb_message *msg,
+ const char *logon_workstation,
+ const char *name_for_logs,
+ bool allow_domain_trust,
+ bool password_change)
+{
+ uint16_t acct_flags;
+ const char *workstation_list;
+ NTTIME acct_expiry;
+ NTTIME must_change_time;
+ struct timeval tv_now = timeval_current();
+ NTTIME now = timeval_to_nttime(&tv_now);
+
+ DEBUG(4,("authsam_account_ok: Checking SMB password for user %s\n", name_for_logs));
+
+ acct_flags = samdb_result_acct_flags(msg, "msDS-User-Account-Control-Computed");
+
+ acct_expiry = samdb_result_account_expires(msg);
+
+ /* Check for when we must change this password, taking the
+ * userAccountControl flags into account */
+ must_change_time = samdb_result_nttime(msg,
+ "msDS-UserPasswordExpiryTimeComputed", 0);
+
+ workstation_list = ldb_msg_find_attr_as_string(msg, "userWorkstations", NULL);
+
+ /* Quit if the account was disabled. */
+ if (acct_flags & ACB_DISABLED) {
+ DEBUG(2,("authsam_account_ok: Account for user '%s' was disabled.\n", name_for_logs));
+ return NT_STATUS_ACCOUNT_DISABLED;
+ }
+
+ /* Quit if the account was locked out. */
+ if (acct_flags & ACB_AUTOLOCK) {
+ DEBUG(2,("authsam_account_ok: Account for user %s was locked out.\n", name_for_logs));
+ return NT_STATUS_ACCOUNT_LOCKED_OUT;
+ }
+
+ /* Test account expire time */
+ if (now > acct_expiry) {
+ DEBUG(2,("authsam_account_ok: Account for user '%s' has expired.\n", name_for_logs));
+ DEBUG(3,("authsam_account_ok: Account expired at '%s'.\n",
+ nt_time_string(mem_ctx, acct_expiry)));
+ return NT_STATUS_ACCOUNT_EXPIRED;
+ }
+
+ /* check for immediate expiry "must change at next logon" (but not if this is a password change request) */
+ if ((must_change_time == 0) && !password_change) {
+ DEBUG(2,("sam_account_ok: Account for user '%s' password must change!.\n",
+ name_for_logs));
+ return NT_STATUS_PASSWORD_MUST_CHANGE;
+ }
+
+ /* check for expired password (but not if this is a password change request) */
+ if ((must_change_time < now) && !password_change) {
+ DEBUG(2,("sam_account_ok: Account for user '%s' password expired!.\n",
+ name_for_logs));
+ DEBUG(2,("sam_account_ok: Password expired at '%s' unix time.\n",
+ nt_time_string(mem_ctx, must_change_time)));
+ return NT_STATUS_PASSWORD_EXPIRED;
+ }
+
+ /* Test workstation. Workstation list is comma separated. */
+ if (logon_workstation && workstation_list && *workstation_list) {
+ bool invalid_ws = true;
+ int i;
+ char **workstations = str_list_make(mem_ctx, workstation_list, ",");
+
+ for (i = 0; workstations && workstations[i]; i++) {
+ DEBUG(10,("sam_account_ok: checking for workstation match '%s' and '%s'\n",
+ workstations[i], logon_workstation));
+
+ if (strequal(workstations[i], logon_workstation)) {
+ invalid_ws = false;
+ break;
+ }
+ }
+
+ talloc_free(workstations);
+
+ if (invalid_ws) {
+ return NT_STATUS_INVALID_WORKSTATION;
+ }
+ }
+
+ if (!logon_hours_ok(msg, name_for_logs)) {
+ return NT_STATUS_INVALID_LOGON_HOURS;
+ }
+
+ if (!allow_domain_trust) {
+ if (acct_flags & ACB_DOMTRUST) {
+ DEBUG(2,("sam_account_ok: Domain trust account %s denied by server\n", name_for_logs));
+ return NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT;
+ }
+ }
+ if (!(logon_parameters & MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT)) {
+ if (acct_flags & ACB_SVRTRUST) {
+ DEBUG(2,("sam_account_ok: Server trust account %s denied by server\n", name_for_logs));
+ return NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT;
+ }
+ }
+ if (!(logon_parameters & MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT)) {
+ /* TODO: this fails with current solaris client. We
+ need to work with Gordon to work out why */
+ if (acct_flags & ACB_WSTRUST) {
+ DEBUG(4,("sam_account_ok: Wksta trust account %s denied by server\n", name_for_logs));
+ return NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS authsam_domain_group_filter(TALLOC_CTX *mem_ctx,
+ char **_filter)
+{
+ char *filter = NULL;
+
+ *_filter = NULL;
+
+ filter = talloc_strdup(mem_ctx, "(&(objectClass=group)");
+ if (filter == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * Skip all builtin groups, they're added later.
+ */
+ filter = talloc_asprintf_append_buffer(filter,
+ "(!(groupType:1.2.840.113556.1.4.803:=%u))",
+ GROUP_TYPE_BUILTIN_LOCAL_GROUP);
+ if (filter == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ /*
+ * Only include security groups.
+ */
+ filter = talloc_asprintf_append_buffer(filter,
+ "(groupType:1.2.840.113556.1.4.803:=%u))",
+ GROUP_TYPE_SECURITY_ENABLED);
+ if (filter == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *_filter = filter;
+ return NT_STATUS_OK;
+}
+
+_PUBLIC_ NTSTATUS authsam_make_user_info_dc(TALLOC_CTX *mem_ctx,
+ struct ldb_context *sam_ctx,
+ const char *netbios_name,
+ const char *domain_name,
+ const char *dns_domain_name,
+ struct ldb_dn *domain_dn,
+ struct ldb_message *msg,
+ DATA_BLOB user_sess_key,
+ DATA_BLOB lm_sess_key,
+ struct auth_user_info_dc **_user_info_dc)
+{
+ NTSTATUS status;
+ struct auth_user_info_dc *user_info_dc;
+ struct auth_user_info *info;
+ const char *str = NULL;
+ char *filter = NULL;
+ /* SIDs for the account and his primary group */
+ struct dom_sid *account_sid;
+ struct dom_sid_buf buf;
+ const char *primary_group_dn;
+ DATA_BLOB primary_group_blob;
+ /* SID structures for the expanded group memberships */
+ struct dom_sid *sids = NULL;
+ unsigned int num_sids = 0, i;
+ struct dom_sid *domain_sid;
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_message_element *el;
+
+ user_info_dc = talloc_zero(mem_ctx, struct auth_user_info_dc);
+ NT_STATUS_HAVE_NO_MEMORY(user_info_dc);
+
+ tmp_ctx = talloc_new(user_info_dc);
+ if (tmp_ctx == NULL) {
+ TALLOC_FREE(user_info_dc);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ sids = talloc_array(user_info_dc, struct dom_sid, 2);
+ if (sids == NULL) {
+ TALLOC_FREE(user_info_dc);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ num_sids = 2;
+
+ account_sid = samdb_result_dom_sid(tmp_ctx, msg, "objectSid");
+ if (account_sid == NULL) {
+ TALLOC_FREE(user_info_dc);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = dom_sid_split_rid(tmp_ctx, account_sid, &domain_sid, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(user_info_dc);
+ return status;
+ }
+
+ sids[PRIMARY_USER_SID_INDEX] = *account_sid;
+ sids[PRIMARY_GROUP_SID_INDEX] = *domain_sid;
+ sid_append_rid(&sids[PRIMARY_GROUP_SID_INDEX], ldb_msg_find_attr_as_uint(msg, "primaryGroupID", ~0));
+
+ /*
+ * Filter out builtin groups from this token. We will search
+ * for builtin groups later, and not include them in the PAC
+ * or SamLogon validation info.
+ */
+ status = authsam_domain_group_filter(tmp_ctx, &filter);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(user_info_dc);
+ return status;
+ }
+
+ primary_group_dn = talloc_asprintf(
+ tmp_ctx,
+ "<SID=%s>",
+ dom_sid_str_buf(&sids[PRIMARY_GROUP_SID_INDEX], &buf));
+ if (primary_group_dn == NULL) {
+ TALLOC_FREE(user_info_dc);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ primary_group_blob = data_blob_string_const(primary_group_dn);
+
+ /* Expands the primary group - this function takes in
+ * memberOf-like values, so we fake one up with the
+ * <SID=S-...> format of DN and then let it expand
+ * them, as long as they meet the filter - so only
+ * domain groups, not builtin groups
+ *
+ * The primary group is still treated specially, so we set the
+ * 'only childs' flag to true
+ */
+ status = dsdb_expand_nested_groups(sam_ctx, &primary_group_blob, true, filter,
+ user_info_dc, &sids, &num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(user_info_dc);
+ return status;
+ }
+
+ /* Expands the additional groups */
+ el = ldb_msg_find_element(msg, "memberOf");
+ for (i = 0; el && i < el->num_values; i++) {
+ /* This function takes in memberOf values and expands
+ * them, as long as they meet the filter - so only
+ * domain groups, not builtin groups */
+ status = dsdb_expand_nested_groups(sam_ctx, &el->values[i], false, filter,
+ user_info_dc, &sids, &num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(user_info_dc);
+ return status;
+ }
+ }
+
+ user_info_dc->sids = sids;
+ user_info_dc->num_sids = num_sids;
+
+ user_info_dc->info = info = talloc_zero(user_info_dc, struct auth_user_info);
+ NT_STATUS_HAVE_NO_MEMORY(user_info_dc->info);
+
+ str = ldb_msg_find_attr_as_string(msg, "sAMAccountName", NULL);
+ info->account_name = talloc_strdup(info, str);
+ if (info->account_name == NULL) {
+ TALLOC_FREE(user_info_dc);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ str = ldb_msg_find_attr_as_string(msg, "userPrincipalName", NULL);
+ if (str == NULL && dns_domain_name != NULL) {
+ info->user_principal_name = talloc_asprintf(info, "%s@%s",
+ info->account_name,
+ dns_domain_name);
+ if (info->user_principal_name == NULL) {
+ TALLOC_FREE(user_info_dc);
+ return NT_STATUS_NO_MEMORY;
+ }
+ info->user_principal_constructed = true;
+ } else if (str != NULL) {
+ info->user_principal_name = talloc_strdup(info, str);
+ if (info->user_principal_name == NULL) {
+ TALLOC_FREE(user_info_dc);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ info->domain_name = talloc_strdup(info, domain_name);
+ if (info->domain_name == NULL) {
+ TALLOC_FREE(user_info_dc);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (dns_domain_name != NULL) {
+ info->dns_domain_name = talloc_strdup(info, dns_domain_name);
+ if (info->dns_domain_name == NULL) {
+ TALLOC_FREE(user_info_dc);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ str = ldb_msg_find_attr_as_string(msg, "displayName", "");
+ info->full_name = talloc_strdup(info, str);
+ if (info->full_name == NULL) {
+ TALLOC_FREE(user_info_dc);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ str = ldb_msg_find_attr_as_string(msg, "scriptPath", "");
+ info->logon_script = talloc_strdup(info, str);
+ if (info->logon_script == NULL) {
+ TALLOC_FREE(user_info_dc);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ str = ldb_msg_find_attr_as_string(msg, "profilePath", "");
+ info->profile_path = talloc_strdup(info, str);
+ if (info->profile_path == NULL) {
+ TALLOC_FREE(user_info_dc);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ str = ldb_msg_find_attr_as_string(msg, "homeDirectory", "");
+ info->home_directory = talloc_strdup(info, str);
+ if (info->home_directory == NULL) {
+ TALLOC_FREE(user_info_dc);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ str = ldb_msg_find_attr_as_string(msg, "homeDrive", "");
+ info->home_drive = talloc_strdup(info, str);
+ if (info->home_drive == NULL) {
+ TALLOC_FREE(user_info_dc);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ info->logon_server = talloc_strdup(info, netbios_name);
+ if (info->logon_server == NULL) {
+ TALLOC_FREE(user_info_dc);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ info->last_logon = samdb_result_nttime(msg, "lastLogon", 0);
+ info->last_logoff = samdb_result_last_logoff(msg);
+ info->acct_expiry = samdb_result_account_expires(msg);
+ info->last_password_change = samdb_result_nttime(msg,
+ "pwdLastSet", 0);
+ info->allow_password_change
+ = samdb_result_allow_password_change(sam_ctx, mem_ctx,
+ domain_dn, msg, "pwdLastSet");
+ info->force_password_change = samdb_result_nttime(msg,
+ "msDS-UserPasswordExpiryTimeComputed", 0);
+ info->logon_count = ldb_msg_find_attr_as_uint(msg, "logonCount", 0);
+ info->bad_password_count = ldb_msg_find_attr_as_uint(msg, "badPwdCount",
+ 0);
+
+ info->acct_flags = samdb_result_acct_flags(msg, "msDS-User-Account-Control-Computed");
+
+ user_info_dc->user_session_key = data_blob_talloc(user_info_dc,
+ user_sess_key.data,
+ user_sess_key.length);
+ if (user_sess_key.data) {
+ if (user_info_dc->user_session_key.data == NULL) {
+ TALLOC_FREE(user_info_dc);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ user_info_dc->lm_session_key = data_blob_talloc(user_info_dc,
+ lm_sess_key.data,
+ lm_sess_key.length);
+ if (lm_sess_key.data) {
+ if (user_info_dc->lm_session_key.data == NULL) {
+ TALLOC_FREE(user_info_dc);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (info->acct_flags & ACB_SVRTRUST) {
+ /* the SID_NT_ENTERPRISE_DCS SID gets added into the
+ PAC */
+ user_info_dc->sids = talloc_realloc(user_info_dc,
+ user_info_dc->sids,
+ struct dom_sid,
+ user_info_dc->num_sids+1);
+ if (user_info_dc->sids == NULL) {
+ TALLOC_FREE(user_info_dc);
+ return NT_STATUS_NO_MEMORY;
+ }
+ user_info_dc->sids[user_info_dc->num_sids] = global_sid_Enterprise_DCs;
+ user_info_dc->num_sids++;
+ }
+
+ if ((info->acct_flags & (ACB_PARTIAL_SECRETS_ACCOUNT | ACB_WSTRUST)) ==
+ (ACB_PARTIAL_SECRETS_ACCOUNT | ACB_WSTRUST)) {
+ /* the DOMAIN_RID_ENTERPRISE_READONLY_DCS PAC */
+ user_info_dc->sids = talloc_realloc(user_info_dc,
+ user_info_dc->sids,
+ struct dom_sid,
+ user_info_dc->num_sids+1);
+ if (user_info_dc->sids == NULL) {
+ TALLOC_FREE(user_info_dc);
+ return NT_STATUS_NO_MEMORY;
+ }
+ user_info_dc->sids[user_info_dc->num_sids] = *domain_sid;
+ sid_append_rid(&user_info_dc->sids[user_info_dc->num_sids],
+ DOMAIN_RID_ENTERPRISE_READONLY_DCS);
+ user_info_dc->num_sids++;
+ }
+
+ info->authenticated = true;
+
+ talloc_free(tmp_ctx);
+ *_user_info_dc = user_info_dc;
+
+ return NT_STATUS_OK;
+}
+
+_PUBLIC_ NTSTATUS authsam_update_user_info_dc(TALLOC_CTX *mem_ctx,
+ struct ldb_context *sam_ctx,
+ struct auth_user_info_dc *user_info_dc)
+{
+ char *filter = NULL;
+ NTSTATUS status;
+ uint32_t i;
+ uint32_t n = 0;
+
+ /*
+ * This function exists to expand group memberships
+ * in the local domain (forest), as the token
+ * may come from a different domain.
+ */
+
+ /*
+ * Filter out builtin groups from this token. We will search
+ * for builtin groups later.
+ */
+ status = authsam_domain_group_filter(mem_ctx, &filter);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(user_info_dc);
+ return status;
+ }
+
+ /*
+ * We loop only over the existing number of
+ * sids.
+ */
+ n = user_info_dc->num_sids;
+ for (i = 0; i < n; i++) {
+ struct dom_sid *sid = &user_info_dc->sids[i];
+ struct dom_sid_buf sid_buf;
+ char dn_str[sizeof(sid_buf.buf)*2];
+ DATA_BLOB dn_blob = data_blob_null;
+
+ snprintf(dn_str,
+ sizeof(dn_str),
+ "<SID=%s>",
+ dom_sid_str_buf(sid, &sid_buf));
+ dn_blob = data_blob_string_const(dn_str);
+
+ /*
+ * We already have the SID in the token, so set
+ * 'only childs' flag to true and add all
+ * groups which match the filter.
+ */
+ status = dsdb_expand_nested_groups(sam_ctx, &dn_blob,
+ true, filter,
+ user_info_dc,
+ &user_info_dc->sids,
+ &user_info_dc->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS sam_get_results_principal(struct ldb_context *sam_ctx,
+ TALLOC_CTX *mem_ctx, const char *principal,
+ const char **attrs,
+ struct ldb_dn **domain_dn,
+ struct ldb_message **msg)
+{
+ struct ldb_dn *user_dn;
+ NTSTATUS nt_status;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ int ret;
+
+ if (!tmp_ctx) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ nt_status = crack_user_principal_name(sam_ctx, tmp_ctx, principal,
+ &user_dn, domain_dn);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ return nt_status;
+ }
+
+ /* pull the user attributes */
+ ret = dsdb_search_one(sam_ctx, tmp_ctx, msg, user_dn,
+ LDB_SCOPE_BASE, attrs,
+ DSDB_SEARCH_SHOW_EXTENDED_DN | DSDB_SEARCH_NO_GLOBAL_CATALOG,
+ "(objectClass=*)");
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ talloc_steal(mem_ctx, *msg);
+ talloc_steal(mem_ctx, *domain_dn);
+ talloc_free(tmp_ctx);
+
+ return NT_STATUS_OK;
+}
+
+/* Used in the gensec_gssapi and gensec_krb5 server-side code, where the PAC isn't available, and for tokenGroups in the DSDB stack.
+
+ Supply either a principal or a DN
+*/
+NTSTATUS authsam_get_user_info_dc_principal(TALLOC_CTX *mem_ctx,
+ struct loadparm_context *lp_ctx,
+ struct ldb_context *sam_ctx,
+ const char *principal,
+ struct ldb_dn *user_dn,
+ struct auth_user_info_dc **user_info_dc)
+{
+ NTSTATUS nt_status;
+ DATA_BLOB user_sess_key = data_blob(NULL, 0);
+ DATA_BLOB lm_sess_key = data_blob(NULL, 0);
+
+ struct ldb_message *msg;
+ struct ldb_dn *domain_dn;
+
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (principal) {
+ nt_status = sam_get_results_principal(sam_ctx, tmp_ctx, principal,
+ user_attrs, &domain_dn, &msg);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ return nt_status;
+ }
+ } else if (user_dn) {
+ struct dom_sid *user_sid, *domain_sid;
+ int ret;
+ /* pull the user attributes */
+ ret = dsdb_search_one(sam_ctx, tmp_ctx, &msg, user_dn,
+ LDB_SCOPE_BASE, user_attrs,
+ DSDB_SEARCH_SHOW_EXTENDED_DN | DSDB_SEARCH_NO_GLOBAL_CATALOG,
+ "(objectClass=*)");
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_SUCH_USER;
+ } else if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ user_sid = samdb_result_dom_sid(msg, msg, "objectSid");
+
+ nt_status = dom_sid_split_rid(tmp_ctx, user_sid, &domain_sid, NULL);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+
+ domain_dn = samdb_search_dn(sam_ctx, mem_ctx, NULL,
+ "(&(objectSid=%s)(objectClass=domain))",
+ ldap_encode_ndr_dom_sid(tmp_ctx, domain_sid));
+ if (!domain_dn) {
+ struct dom_sid_buf buf;
+ DEBUG(3, ("authsam_get_user_info_dc_principal: Failed to find domain with: SID %s\n",
+ dom_sid_str_buf(domain_sid, &buf)));
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ } else {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ nt_status = authsam_make_user_info_dc(tmp_ctx, sam_ctx,
+ lpcfg_netbios_name(lp_ctx),
+ lpcfg_sam_name(lp_ctx),
+ lpcfg_sam_dnsname(lp_ctx),
+ domain_dn,
+ msg,
+ user_sess_key, lm_sess_key,
+ user_info_dc);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ return nt_status;
+ }
+
+ talloc_steal(mem_ctx, *user_info_dc);
+ talloc_free(tmp_ctx);
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * Returns the details for the Password Settings Object (PSO), if one applies
+ * the user.
+ */
+static int authsam_get_user_pso(struct ldb_context *sam_ctx,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message *user_msg,
+ struct ldb_message **pso_msg)
+{
+ const char *attrs[] = { "msDS-LockoutThreshold",
+ "msDS-LockoutObservationWindow",
+ NULL };
+ struct ldb_dn *pso_dn = NULL;
+ struct ldb_result *res = NULL;
+ int ret;
+
+ /* check if the user has a PSO that applies to it */
+ pso_dn = ldb_msg_find_attr_as_dn(sam_ctx, mem_ctx, user_msg,
+ "msDS-ResultantPSO");
+
+ if (pso_dn != NULL) {
+ ret = dsdb_search_dn(sam_ctx, mem_ctx, &res, pso_dn, attrs, 0);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ *pso_msg = res->msgs[0];
+ }
+
+ return LDB_SUCCESS;
+}
+
+/*
+ * Re-read the bad password and successful logon data for a user.
+ *
+ * The DN in the passed user record should contain the "objectGUID" in case the
+ * object DN has changed.
+ */
+NTSTATUS authsam_reread_user_logon_data(
+ struct ldb_context *sam_ctx,
+ TALLOC_CTX *mem_ctx,
+ const struct ldb_message *user_msg,
+ struct ldb_message **current)
+{
+ const struct ldb_val *v = NULL;
+ struct ldb_result *res = NULL;
+ uint16_t acct_flags = 0;
+ const char *attr_name = "msDS-User-Account-Control-Computed";
+
+ int ret;
+
+ /*
+ * Re-read the account details, using the GUID in case the DN
+ * is being changed (this is automatic in LDB because the
+ * original search also used DSDB_SEARCH_SHOW_EXTENDED_DN)
+ *
+ * We re read all the attributes in user_attrs, rather than using a
+ * subset to ensure that we can reuse existing validation code.
+ */
+ ret = dsdb_search_dn(sam_ctx,
+ mem_ctx,
+ &res,
+ user_msg->dn,
+ user_attrs,
+ DSDB_SEARCH_SHOW_EXTENDED_DN);
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("Unable to re-read account control data for %s\n",
+ ldb_dn_get_linearized(user_msg->dn));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ /*
+ * Ensure the account has not been locked out by another request
+ */
+ v = ldb_msg_find_ldb_val(res->msgs[0], attr_name);
+ if (v == NULL || v->data == NULL) {
+ DBG_ERR("No %s attribute for %s\n",
+ attr_name,
+ ldb_dn_get_linearized(user_msg->dn));
+ TALLOC_FREE(res);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ acct_flags = samdb_result_acct_flags(res->msgs[0], attr_name);
+ if (acct_flags & ACB_AUTOLOCK) {
+ DBG_WARNING(
+ "Account for user %s was locked out.\n",
+ ldb_dn_get_linearized(user_msg->dn));
+ TALLOC_FREE(res);
+ return NT_STATUS_ACCOUNT_LOCKED_OUT;
+ }
+ *current = talloc_steal(mem_ctx, res->msgs[0]);
+ TALLOC_FREE(res);
+ return NT_STATUS_OK;
+}
+
+static struct db_context *authsam_get_bad_password_db(
+ TALLOC_CTX *mem_ctx,
+ struct ldb_context *sam_ctx)
+{
+ struct loadparm_context *lp_ctx = NULL;
+ const char *db_name = "bad_password";
+ struct db_context *db_ctx = NULL;
+
+ lp_ctx = ldb_get_opaque(sam_ctx, "loadparm");
+ if (lp_ctx == NULL) {
+ DBG_ERR("Unable to get loadparm_context\n");
+ return NULL;
+ }
+
+ db_ctx = cluster_db_tmp_open(mem_ctx, lp_ctx, db_name, TDB_DEFAULT);
+ if (db_ctx == NULL) {
+ DBG_ERR("Unable to open bad password attempts database\n");
+ return NULL;
+ }
+ return db_ctx;
+}
+
+static NTSTATUS get_object_sid_as_tdb_data(
+ TALLOC_CTX *mem_ctx,
+ const struct ldb_message *msg,
+ struct dom_sid_buf *buf,
+ TDB_DATA *key)
+{
+ struct dom_sid *objectsid = NULL;
+
+ /*
+ * Convert the objectSID to a human readable form to
+ * make debugging easier
+ */
+ objectsid = samdb_result_dom_sid(mem_ctx, msg, "objectSID");
+ if (objectsid == NULL) {
+ DBG_ERR("Unable to extract objectSID\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ dom_sid_str_buf(objectsid, buf);
+ key->dptr = (unsigned char *)buf->buf;
+ key->dsize = strlen(buf->buf);
+
+ talloc_free(objectsid);
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * Add the users objectSID to the bad password attempt database
+ * to indicate that last authentication failed due to a bad password
+ */
+static NTSTATUS authsam_set_bad_password_indicator(
+ struct ldb_context *sam_ctx,
+ TALLOC_CTX *mem_ctx,
+ const struct ldb_message *msg)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ struct dom_sid_buf buf;
+ TDB_DATA key = {0};
+ TDB_DATA value = {0};
+ struct db_context *db = NULL;
+
+ TALLOC_CTX *ctx = talloc_new(mem_ctx);
+ if (ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ db = authsam_get_bad_password_db(ctx, sam_ctx);
+ if (db == NULL) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto exit;
+ }
+
+ status = get_object_sid_as_tdb_data(ctx, msg, &buf, &key);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto exit;
+ }
+
+ status = dbwrap_store(db, key, value, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Unable to store bad password indicator\n");
+ }
+exit:
+ talloc_free(ctx);
+ return status;
+}
+
+/*
+ * see if the users objectSID is in the bad password attempt database
+ */
+static NTSTATUS authsam_check_bad_password_indicator(
+ struct ldb_context *sam_ctx,
+ TALLOC_CTX *mem_ctx,
+ bool *exists,
+ const struct ldb_message *msg)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ struct dom_sid_buf buf;
+ TDB_DATA key = {0};
+ struct db_context *db = NULL;
+
+ TALLOC_CTX *ctx = talloc_new(mem_ctx);
+ if (ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ db = authsam_get_bad_password_db(ctx, sam_ctx);
+ if (db == NULL) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto exit;
+ }
+
+ status = get_object_sid_as_tdb_data(ctx, msg, &buf, &key);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto exit;
+ }
+
+ *exists = dbwrap_exists(db, key);
+exit:
+ talloc_free(ctx);
+ return status;
+}
+
+/*
+ * Remove the users objectSID to the bad password attempt database
+ * to indicate that last authentication succeeded.
+ */
+static NTSTATUS authsam_clear_bad_password_indicator(
+ struct ldb_context *sam_ctx,
+ TALLOC_CTX *mem_ctx,
+ const struct ldb_message *msg)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ struct dom_sid_buf buf;
+ TDB_DATA key = {0};
+ struct db_context *db = NULL;
+
+ TALLOC_CTX *ctx = talloc_new(mem_ctx);
+ if (ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ db = authsam_get_bad_password_db(ctx, sam_ctx);
+ if (db == NULL) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto exit;
+ }
+
+ status = get_object_sid_as_tdb_data(ctx, msg, &buf, &key);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto exit;
+ }
+
+ status = dbwrap_delete(db, key);
+ if (NT_STATUS_EQUAL(NT_STATUS_NOT_FOUND, status)) {
+ /*
+ * Ok there was no bad password indicator this is expected
+ */
+ status = NT_STATUS_OK;
+ }
+ if (NT_STATUS_IS_ERR(status)) {
+ DBG_ERR("Unable to delete bad password indicator, %s %s\n",
+ nt_errstr(status),
+ get_friendly_nt_error_msg(status));
+ }
+exit:
+ talloc_free(ctx);
+ return status;
+}
+
+NTSTATUS authsam_update_bad_pwd_count(struct ldb_context *sam_ctx,
+ struct ldb_message *msg,
+ struct ldb_dn *domain_dn)
+{
+ const char *attrs[] = { "lockoutThreshold",
+ "lockOutObservationWindow",
+ "lockoutDuration",
+ "pwdProperties",
+ NULL };
+ int ret;
+ NTSTATUS status;
+ struct ldb_result *domain_res;
+ struct ldb_message *msg_mod = NULL;
+ struct ldb_message *current = NULL;
+ struct ldb_message *pso_msg = NULL;
+ bool txn_active = false;
+ TALLOC_CTX *mem_ctx;
+
+ mem_ctx = talloc_new(msg);
+ if (mem_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dsdb_search_dn(sam_ctx, mem_ctx, &domain_res, domain_dn, attrs, 0);
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(mem_ctx);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ ret = authsam_get_user_pso(sam_ctx, mem_ctx, msg, &pso_msg);
+ if (ret != LDB_SUCCESS) {
+
+ /*
+ * fallback to using the domain defaults so that we still
+ * record the bad password attempt
+ */
+ DBG_ERR("Error (%d) checking PSO for %s\n",
+ ret, ldb_dn_get_linearized(msg->dn));
+ }
+
+ /*
+ * To ensure that the bad password count is updated atomically,
+ * we need to:
+ * begin a transaction
+ * re-read the account details,
+ * using the <GUID= part of the DN
+ * update the bad password count
+ * commit the transaction.
+ */
+
+ /*
+ * Start a new transaction
+ */
+ ret = ldb_transaction_start(sam_ctx);
+ if (ret != LDB_SUCCESS) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+ txn_active = true;
+
+ /*
+ * Re-read the account details, using the GUID in case the DN
+ * is being changed.
+ */
+ status = authsam_reread_user_logon_data(
+ sam_ctx, mem_ctx, msg, &current);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* The re-read can return account locked out, as well
+ * as an internal error
+ */
+ if (NT_STATUS_EQUAL(status, NT_STATUS_ACCOUNT_LOCKED_OUT)) {
+ /*
+ * For NT_STATUS_ACCOUNT_LOCKED_OUT we want to commit
+ * the transaction. Again to avoid cluttering the
+ * audit logs with spurious errors
+ */
+ goto exit;
+ }
+ goto error;
+ }
+
+ /*
+ * Update the bad password count and if required lock the account
+ */
+ status = dsdb_update_bad_pwd_count(
+ mem_ctx,
+ sam_ctx,
+ current,
+ domain_res->msgs[0],
+ pso_msg,
+ &msg_mod);
+ if (!NT_STATUS_IS_OK(status)) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+
+ /*
+ * Write the data back to disk if required.
+ */
+ if (msg_mod != NULL) {
+ struct ldb_request *req;
+
+ ret = ldb_build_mod_req(&req, sam_ctx, sam_ctx,
+ msg_mod,
+ NULL,
+ NULL,
+ ldb_op_default_callback,
+ NULL);
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(msg_mod);
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+
+ ret = ldb_request_add_control(req,
+ DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE,
+ false, NULL);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(req);
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+
+ /*
+ * As we're in a transaction, make the ldb request directly
+ * to avoid the nested transaction that would result if we
+ * called dsdb_autotransaction_request
+ */
+ ret = ldb_request(sam_ctx, req);
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ }
+ talloc_free(req);
+ if (ret != LDB_SUCCESS) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+ status = authsam_set_bad_password_indicator(
+ sam_ctx, mem_ctx, msg);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto error;
+ }
+ }
+ /*
+ * Note that we may not have updated the user record, but
+ * committing the transaction in that case is still the correct
+ * thing to do.
+ * If the transaction was cancelled, this would be logged by
+ * the dsdb audit log as a failure. When in fact it is expected
+ * behaviour.
+ */
+exit:
+ TALLOC_FREE(mem_ctx);
+ ret = ldb_transaction_commit(sam_ctx);
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("Error (%d) %s, committing transaction,"
+ " while updating bad password count"
+ " for (%s)\n",
+ ret,
+ ldb_errstring(sam_ctx),
+ ldb_dn_get_linearized(msg->dn));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ return status;
+
+error:
+ DBG_ERR("Failed to update badPwdCount, badPasswordTime or "
+ "set lockoutTime on %s: %s\n",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(sam_ctx) != NULL ?
+ ldb_errstring(sam_ctx) :nt_errstr(status));
+ if (txn_active) {
+ ret = ldb_transaction_cancel(sam_ctx);
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("Error rolling back transaction,"
+ " while updating bad password count"
+ " on %s: %s\n",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(sam_ctx));
+ }
+ }
+ TALLOC_FREE(mem_ctx);
+ return status;
+
+}
+
+/*
+ * msDS-LogonTimeSyncInterval is an int32_t number of days.
+ *
+ * The docs say: "the initial update, after the domain functional
+ * level is raised to DS_BEHAVIOR_WIN2003 or higher, is calculated as
+ * 14 days minus a random percentage of 5 days", but we aren't doing
+ * that. The blogosphere seems to think that this randomised update
+ * happens everytime, but [MS-ADA1] doesn't agree.
+ *
+ * Dochelp referred us to the following blog post:
+ * http://blogs.technet.com/b/askds/archive/2009/04/15/the-lastlogontimestamp-attribute-what-it-was-designed-for-and-how-it-works.aspx
+ *
+ * when msDS-LogonTimeSyncInterval is zero, the lastLogonTimestamp is
+ * not changed.
+ */
+
+static NTSTATUS authsam_calculate_lastlogon_sync_interval(
+ struct ldb_context *sam_ctx,
+ TALLOC_CTX *ctx,
+ struct ldb_dn *domain_dn,
+ NTTIME *sync_interval_nt)
+{
+ static const char *attrs[] = { "msDS-LogonTimeSyncInterval",
+ NULL };
+ int ret;
+ struct ldb_result *domain_res = NULL;
+ TALLOC_CTX *mem_ctx = NULL;
+ uint32_t sync_interval;
+
+ mem_ctx = talloc_new(ctx);
+ if (mem_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dsdb_search_dn(sam_ctx, mem_ctx, &domain_res, domain_dn, attrs,
+ 0);
+ if (ret != LDB_SUCCESS || domain_res->count != 1) {
+ TALLOC_FREE(mem_ctx);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ sync_interval = ldb_msg_find_attr_as_int(domain_res->msgs[0],
+ "msDS-LogonTimeSyncInterval",
+ 14);
+ DEBUG(5, ("sync interval is %d\n", sync_interval));
+ if (sync_interval >= 5){
+ /*
+ * Subtract "a random percentage of 5" days. Presumably this
+ * percentage is between 0 and 100, and modulus is accurate
+ * enough.
+ */
+ uint32_t r = generate_random() % 6;
+ sync_interval -= r;
+ DBG_INFO("randomised sync interval is %d (-%d)\n", sync_interval, r);
+ }
+ /* In the case where sync_interval < 5 there is no randomisation */
+
+ /*
+ * msDS-LogonTimeSyncInterval is an int32_t number of days,
+ * while lastLogonTimestamp (to be updated) is in the 64 bit
+ * 100ns NTTIME format so we must convert.
+ */
+ *sync_interval_nt = sync_interval * 24LL * 3600LL * 10000000LL;
+ TALLOC_FREE(mem_ctx);
+ return NT_STATUS_OK;
+}
+
+
+/*
+ * We only set lastLogonTimestamp if the current value is older than
+ * now - msDS-LogonTimeSyncInterval days.
+ *
+ * lastLogonTimestamp is in the 64 bit 100ns NTTIME format
+ */
+static NTSTATUS authsam_update_lastlogon_timestamp(struct ldb_context *sam_ctx,
+ struct ldb_message *msg_mod,
+ struct ldb_dn *domain_dn,
+ NTTIME old_timestamp,
+ NTTIME now,
+ NTTIME sync_interval_nt)
+{
+ int ret;
+ DEBUG(5, ("old timestamp is %lld, threshold %lld, diff %lld\n",
+ (long long int)old_timestamp,
+ (long long int)(now - sync_interval_nt),
+ (long long int)(old_timestamp - now + sync_interval_nt)));
+
+ if (sync_interval_nt == 0){
+ /*
+ * Setting msDS-LogonTimeSyncInterval to zero is how you ask
+ * that nothing happens here.
+ */
+ return NT_STATUS_OK;
+ }
+ if (old_timestamp > now){
+ DEBUG(0, ("lastLogonTimestamp is in the future! (%lld > %lld)\n",
+ (long long int)old_timestamp, (long long int)now));
+ /* then what? */
+
+ } else if (old_timestamp < now - sync_interval_nt){
+ DEBUG(5, ("updating lastLogonTimestamp to %lld\n",
+ (long long int)now));
+
+ /* The time has come to update lastLogonTimestamp */
+ ret = samdb_msg_add_int64(sam_ctx, msg_mod, msg_mod,
+ "lastLogonTimestamp", now);
+
+ if (ret != LDB_SUCCESS) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Look for the specified user in the sam, return ldb result structures
+****************************************************************************/
+
+NTSTATUS authsam_search_account(TALLOC_CTX *mem_ctx, struct ldb_context *sam_ctx,
+ const char *account_name,
+ struct ldb_dn *domain_dn,
+ struct ldb_message **ret_msg)
+{
+ int ret;
+
+ /* pull the user attributes */
+ ret = dsdb_search_one(sam_ctx, mem_ctx, ret_msg, domain_dn, LDB_SCOPE_SUBTREE,
+ user_attrs,
+ DSDB_SEARCH_SHOW_EXTENDED_DN,
+ "(&(sAMAccountName=%s)(objectclass=user))",
+ ldb_binary_encode_string(mem_ctx, account_name));
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ DEBUG(3,("sam_search_user: Couldn't find user [%s] in samdb, under %s\n",
+ account_name, ldb_dn_get_linearized(domain_dn)));
+ return NT_STATUS_NO_SUCH_USER;
+ }
+ if (ret != LDB_SUCCESS) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+/* Reset the badPwdCount to zero and update the lastLogon time. */
+NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx,
+ const struct ldb_message *msg,
+ struct ldb_dn *domain_dn,
+ bool interactive_or_kerberos,
+ TALLOC_CTX *send_to_sam_mem_ctx,
+ struct netr_SendToSamBase **send_to_sam)
+{
+ int ret;
+ NTSTATUS status;
+ int badPwdCount;
+ int dbBadPwdCount;
+ int64_t lockoutTime;
+ struct ldb_message *msg_mod;
+ TALLOC_CTX *mem_ctx;
+ struct timeval tv_now;
+ NTTIME now;
+ NTTIME lastLogonTimestamp;
+ int64_t lockOutObservationWindow;
+ NTTIME sync_interval_nt = 0;
+ bool am_rodc = false;
+ bool txn_active = false;
+ bool need_db_reread;
+
+ mem_ctx = talloc_new(msg);
+ if (mem_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * Any update of the last logon data, needs to be done inside a
+ * transaction.
+ * And the user data needs to be re-read, and the account re-checked
+ * for lockout.
+ *
+ * Howevver we have long-running transactions like replication
+ * that could otherwise grind the system to a halt so we first
+ * determine if *this* account has seen a bad password,
+ * otherwise we only start a transaction if there was a need
+ * (because a change was to be made).
+ */
+
+ status = authsam_check_bad_password_indicator(
+ sam_ctx, mem_ctx, &need_db_reread, msg);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (interactive_or_kerberos == false) {
+ /*
+ * Avoid calculating this twice, it reads the PSO. A
+ * race on this is unimportant.
+ */
+ lockOutObservationWindow
+ = samdb_result_msds_LockoutObservationWindow(
+ sam_ctx, mem_ctx, domain_dn, msg);
+ }
+
+ ret = samdb_rodc(sam_ctx, &am_rodc);
+ if (ret != LDB_SUCCESS) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+
+ if (!am_rodc) {
+ /*
+ * Avoid reading the main domain DN twice. A race on
+ * this is unimportant.
+ */
+ status = authsam_calculate_lastlogon_sync_interval(
+ sam_ctx, mem_ctx, domain_dn, &sync_interval_nt);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+ }
+
+get_transaction:
+
+ if (need_db_reread) {
+ struct ldb_message *current = NULL;
+
+ /*
+ * Start a new transaction
+ */
+ ret = ldb_transaction_start(sam_ctx);
+ if (ret != LDB_SUCCESS) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+
+ txn_active = true;
+
+ /*
+ * Re-read the account details, using the GUID
+ * embedded in DN so this is safe against a race where
+ * it is being renamed.
+ */
+ status = authsam_reread_user_logon_data(
+ sam_ctx, mem_ctx, msg, &current);
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * The re-read can return account locked out, as well
+ * as an internal error
+ */
+ if (NT_STATUS_EQUAL(status, NT_STATUS_ACCOUNT_LOCKED_OUT)) {
+ /*
+ * For NT_STATUS_ACCOUNT_LOCKED_OUT we want to commit
+ * the transaction. Again to avoid cluttering the
+ * audit logs with spurious errors
+ */
+ goto exit;
+ }
+ goto error;
+ }
+ msg = current;
+ }
+
+ lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0);
+ dbBadPwdCount = ldb_msg_find_attr_as_int(msg, "badPwdCount", 0);
+ tv_now = timeval_current();
+ now = timeval_to_nttime(&tv_now);
+
+ if (interactive_or_kerberos) {
+ badPwdCount = dbBadPwdCount;
+ } else {
+ /*
+ * We get lockOutObservationWindow above, before the
+ * transaction
+ */
+ badPwdCount = dsdb_effective_badPwdCount(
+ msg, lockOutObservationWindow, now);
+ }
+ lastLogonTimestamp =
+ ldb_msg_find_attr_as_int64(msg, "lastLogonTimestamp", 0);
+
+ DEBUG(5, ("lastLogonTimestamp is %lld\n",
+ (long long int)lastLogonTimestamp));
+
+ msg_mod = ldb_msg_new(mem_ctx);
+ if (msg_mod == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+
+ /*
+ * By using the DN from msg->dn directly, we allow LDB to
+ * prefer the embedded GUID form, so this is actually quite
+ * safe even in the case where DN has been changed
+ */
+ msg_mod->dn = msg->dn;
+
+ if (lockoutTime != 0) {
+ /*
+ * This implies "badPwdCount" = 0, see samldb_lockout_time()
+ */
+ ret = samdb_msg_add_int(sam_ctx, msg_mod, msg_mod, "lockoutTime", 0);
+ if (ret != LDB_SUCCESS) {
+ status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+ } else if (badPwdCount != 0) {
+ ret = samdb_msg_add_int(sam_ctx, msg_mod, msg_mod, "badPwdCount", 0);
+ if (ret != LDB_SUCCESS) {
+ status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+ }
+
+ if (interactive_or_kerberos ||
+ (badPwdCount != 0 && lockoutTime == 0)) {
+ ret = samdb_msg_add_int64(sam_ctx, msg_mod, msg_mod,
+ "lastLogon", now);
+ if (ret != LDB_SUCCESS) {
+ status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+ }
+
+ if (interactive_or_kerberos) {
+ int logonCount;
+
+ logonCount = ldb_msg_find_attr_as_int(msg, "logonCount", 0);
+
+ logonCount += 1;
+
+ ret = samdb_msg_add_int(sam_ctx, msg_mod, msg_mod,
+ "logonCount", logonCount);
+ if (ret != LDB_SUCCESS) {
+ status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+ } else {
+ /* Set an unset logonCount to 0 on first successful login */
+ if (ldb_msg_find_ldb_val(msg, "logonCount") == NULL) {
+ ret = samdb_msg_add_int(sam_ctx, msg_mod, msg_mod,
+ "logonCount", 0);
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(mem_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ }
+
+ if (!am_rodc) {
+ status = authsam_update_lastlogon_timestamp(
+ sam_ctx,
+ msg_mod,
+ domain_dn,
+ lastLogonTimestamp,
+ now,
+ sync_interval_nt);
+ if (!NT_STATUS_IS_OK(status)) {
+ status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+ } else {
+ /* Perform the (async) SendToSAM calls for MS-SAMS */
+ if (dbBadPwdCount != 0 && send_to_sam != NULL) {
+ struct netr_SendToSamBase *base_msg;
+ struct GUID guid = samdb_result_guid(msg, "objectGUID");
+
+ base_msg = talloc_zero(send_to_sam_mem_ctx,
+ struct netr_SendToSamBase);
+ if (base_msg == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+
+ base_msg->message_type = SendToSamResetBadPasswordCount;
+ base_msg->message_size = 16;
+ base_msg->message.reset_bad_password.guid = guid;
+ *send_to_sam = base_msg;
+ }
+ }
+
+ if (msg_mod->num_elements > 0) {
+ unsigned int i;
+ struct ldb_request *req;
+
+ /*
+ * If it turns out we are going to update the DB, go
+ * back to the start, get a transaction and the
+ * current DB state and try again
+ */
+ if (txn_active == false) {
+ need_db_reread = true;
+ goto get_transaction;
+ }
+
+ /* mark all the message elements as LDB_FLAG_MOD_REPLACE */
+ for (i=0;i<msg_mod->num_elements;i++) {
+ msg_mod->elements[i].flags = LDB_FLAG_MOD_REPLACE;
+ }
+
+ ret = ldb_build_mod_req(&req, sam_ctx, sam_ctx,
+ msg_mod,
+ NULL,
+ NULL,
+ ldb_op_default_callback,
+ NULL);
+ if (ret != LDB_SUCCESS) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+
+ ret = ldb_request_add_control(req,
+ DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE,
+ false, NULL);
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(req);
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+ /*
+ * As we're in a transaction, make the ldb request directly
+ * to avoid the nested transaction that would result if we
+ * called dsdb_autotransaction_request
+ */
+ ret = ldb_request(sam_ctx, req);
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ }
+ TALLOC_FREE(req);
+ if (ret != LDB_SUCCESS) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+ }
+ status = authsam_clear_bad_password_indicator(sam_ctx, mem_ctx, msg);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto error;
+ }
+
+ /*
+ * Note that we may not have updated the user record, but
+ * committing the transaction in that case is still the correct
+ * thing to do.
+ * If the transaction was cancelled, this would be logged by
+ * the dsdb audit log as a failure. When in fact it is expected
+ * behaviour.
+ *
+ * Thankfully both TDB and LMDB seem to optimise for the empty
+ * transaction case
+ */
+exit:
+ TALLOC_FREE(mem_ctx);
+
+ if (txn_active == false) {
+ return status;
+ }
+
+ ret = ldb_transaction_commit(sam_ctx);
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("Error (%d) %s, committing transaction,"
+ " while updating successful logon accounting"
+ " for (%s)\n",
+ ret,
+ ldb_errstring(sam_ctx),
+ ldb_dn_get_linearized(msg->dn));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ return status;
+
+error:
+ DBG_ERR("Failed to update badPwdCount, badPasswordTime or "
+ "set lockoutTime on %s: %s\n",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(sam_ctx) != NULL ?
+ ldb_errstring(sam_ctx) :nt_errstr(status));
+ if (txn_active) {
+ ret = ldb_transaction_cancel(sam_ctx);
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("Error rolling back transaction,"
+ " while updating bad password count"
+ " on %s: %s\n",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(sam_ctx));
+ }
+ }
+ TALLOC_FREE(mem_ctx);
+ return status;
+}
diff --git a/source4/auth/samba_server_gensec.c b/source4/auth/samba_server_gensec.c
new file mode 100644
index 0000000..f2b0551
--- /dev/null
+++ b/source4/auth/samba_server_gensec.c
@@ -0,0 +1,152 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Generic Authentication Interface for Samba Servers
+
+ Copyright (C) Andrew Bartlett <abartlet@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/>.
+*/
+
+/* This code sets up GENSEC in the way that all Samba servers want
+ * (becaue they have presumed access to the sam.ldb etc */
+
+#include "includes.h"
+#include "auth/auth.h"
+#include "auth/gensec/gensec.h"
+#include "param/param.h"
+
+static NTSTATUS samba_server_gensec_start_settings(TALLOC_CTX *mem_ctx,
+ struct tevent_context *event_ctx,
+ struct imessaging_context *msg_ctx,
+ struct loadparm_context *lp_ctx,
+ struct gensec_settings *settings,
+ struct cli_credentials *server_credentials,
+ const char *target_service,
+ struct gensec_security **gensec_context)
+{
+ NTSTATUS nt_status;
+ struct gensec_security *gensec_ctx;
+ struct auth4_context *auth_context;
+
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ nt_status = auth_context_create(tmp_ctx,
+ event_ctx,
+ msg_ctx,
+ lp_ctx,
+ &auth_context);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(1, ("Failed to start auth server code: %s\n", nt_errstr(nt_status)));
+ talloc_free(tmp_ctx);
+ return nt_status;
+ }
+
+ nt_status = gensec_server_start(tmp_ctx,
+ settings,
+ auth_context,
+ &gensec_ctx);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ DEBUG(1, ("Failed to start GENSEC server code: %s\n", nt_errstr(nt_status)));
+ return nt_status;
+ }
+
+ gensec_set_credentials(gensec_ctx, server_credentials);
+
+ if (target_service) {
+ gensec_set_target_service(gensec_ctx, target_service);
+ }
+ *gensec_context = talloc_steal(mem_ctx, gensec_ctx);
+ talloc_free(tmp_ctx);
+ return nt_status;
+}
+
+NTSTATUS samba_server_gensec_start(TALLOC_CTX *mem_ctx,
+ struct tevent_context *event_ctx,
+ struct imessaging_context *msg_ctx,
+ struct loadparm_context *lp_ctx,
+ struct cli_credentials *server_credentials,
+ const char *target_service,
+ struct gensec_security **gensec_context)
+{
+ struct gensec_settings *settings = NULL;
+ NTSTATUS status;
+
+ settings = lpcfg_gensec_settings(mem_ctx, lp_ctx);
+ if (settings == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = samba_server_gensec_start_settings(mem_ctx, event_ctx,
+ msg_ctx, lp_ctx,
+ settings, server_credentials,
+ target_service,
+ gensec_context);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(settings);
+ return status;
+ }
+
+ talloc_reparent(mem_ctx, *gensec_context, settings);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS samba_server_gensec_krb5_start(TALLOC_CTX *mem_ctx,
+ struct tevent_context *event_ctx,
+ struct imessaging_context *msg_ctx,
+ struct loadparm_context *lp_ctx,
+ struct cli_credentials *server_credentials,
+ const char *target_service,
+ struct gensec_security **gensec_context)
+{
+ struct gensec_settings *settings = NULL;
+ const struct gensec_security_ops **backends = NULL;
+ size_t idx = 0;
+ NTSTATUS status;
+
+ settings = lpcfg_gensec_settings(mem_ctx, lp_ctx);
+ if (settings == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ backends = talloc_zero_array(settings,
+ const struct gensec_security_ops *, 3);
+ if (backends == NULL) {
+ TALLOC_FREE(settings);
+ return NT_STATUS_NO_MEMORY;
+ }
+ settings->backends = backends;
+
+ gensec_init();
+
+ backends[idx++] = gensec_security_by_oid(NULL, GENSEC_OID_KERBEROS5);
+
+ backends[idx++] = gensec_security_by_oid(NULL, GENSEC_OID_SPNEGO);
+
+ status = samba_server_gensec_start_settings(mem_ctx, event_ctx,
+ msg_ctx, lp_ctx,
+ settings, server_credentials,
+ target_service,
+ gensec_context);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(settings);
+ return status;
+ }
+
+ talloc_reparent(mem_ctx, *gensec_context, settings);
+ return NT_STATUS_OK;
+}
diff --git a/source4/auth/session.c b/source4/auth/session.c
new file mode 100644
index 0000000..34ad557
--- /dev/null
+++ b/source4/auth/session.c
@@ -0,0 +1,427 @@
+/*
+ Unix SMB/CIFS implementation.
+ Authentication utility functions
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Andrew Bartlett 2001-2010
+ Copyright (C) Jeremy Allison 2000-2001
+ Copyright (C) Rafal Szczesniak 2002
+ Copyright (C) Stefan Metzmacher 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/auth_sam.h"
+#include "auth/credentials/credentials.h"
+#include "auth/credentials/credentials_krb5.h"
+#include "libcli/security/security.h"
+#include "libcli/auth/libcli_auth.h"
+#include "dsdb/samdb/samdb.h"
+#include "auth/session_proto.h"
+#include "system/kerberos.h"
+#include <gssapi/gssapi.h>
+#include "libcli/wbclient/wbclient.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+_PUBLIC_ struct auth_session_info *anonymous_session(TALLOC_CTX *mem_ctx,
+ struct loadparm_context *lp_ctx)
+{
+ NTSTATUS nt_status;
+ struct auth_session_info *session_info = NULL;
+ nt_status = auth_anonymous_session_info(mem_ctx, lp_ctx, &session_info);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return NULL;
+ }
+ return session_info;
+}
+
+_PUBLIC_ NTSTATUS auth_generate_session_info(TALLOC_CTX *mem_ctx,
+ struct loadparm_context *lp_ctx, /* Optional, if you don't want privilages */
+ struct ldb_context *sam_ctx, /* Optional, if you don't want local groups */
+ struct auth_user_info_dc *user_info_dc,
+ uint32_t session_info_flags,
+ struct auth_session_info **_session_info)
+{
+ struct auth_session_info *session_info;
+ NTSTATUS nt_status;
+ unsigned int i, num_sids = 0;
+
+ const char *filter;
+
+ struct dom_sid *sids = NULL;
+ const struct dom_sid *anonymous_sid, *system_sid;
+
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+
+ session_info = talloc_zero(tmp_ctx, struct auth_session_info);
+ if (session_info == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ session_info->info = talloc_reference(session_info, user_info_dc->info);
+
+ session_info->torture = talloc_zero(session_info, struct auth_user_info_torture);
+ if (session_info->torture == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ session_info->torture->num_dc_sids = user_info_dc->num_sids;
+ session_info->torture->dc_sids = talloc_reference(session_info, user_info_dc->sids);
+ if (session_info->torture->dc_sids == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* unless set otherwise, the session key is the user session
+ * key from the auth subsystem */
+ session_info->session_key = data_blob_talloc(session_info, user_info_dc->user_session_key.data, user_info_dc->user_session_key.length);
+ if (!session_info->session_key.data && session_info->session_key.length) {
+ if (session_info->session_key.data == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ anonymous_sid = dom_sid_parse_talloc(tmp_ctx, SID_NT_ANONYMOUS);
+ if (anonymous_sid == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ system_sid = dom_sid_parse_talloc(tmp_ctx, SID_NT_SYSTEM);
+ if (system_sid == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ sids = talloc_array(tmp_ctx, struct dom_sid, user_info_dc->num_sids);
+ if (sids == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ num_sids = user_info_dc->num_sids;
+
+ for (i=0; i < user_info_dc->num_sids; i++) {
+ sids[i] = user_info_dc->sids[i];
+ }
+
+ /*
+ * Finally add the "standard" sids.
+ * The only difference between guest and "anonymous"
+ * is the addition of Authenticated_Users.
+ */
+
+ if (session_info_flags & AUTH_SESSION_INFO_DEFAULT_GROUPS) {
+ sids = talloc_realloc(tmp_ctx, sids, struct dom_sid, num_sids + 2);
+ if (sids == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ sid_copy(&sids[num_sids], &global_sid_World);
+ num_sids++;
+
+ sid_copy(&sids[num_sids], &global_sid_Network);
+ num_sids++;
+ }
+
+ if (session_info_flags & AUTH_SESSION_INFO_AUTHENTICATED) {
+ sids = talloc_realloc(tmp_ctx, sids, struct dom_sid, num_sids + 1);
+ if (sids == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ sid_copy(&sids[num_sids], &global_sid_Authenticated_Users);
+ num_sids++;
+ }
+
+ if (session_info_flags & AUTH_SESSION_INFO_NTLM) {
+ sids = talloc_realloc(tmp_ctx, sids, struct dom_sid, num_sids + 1);
+ if (sids == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!dom_sid_parse(SID_NT_NTLM_AUTHENTICATION, &sids[num_sids])) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ num_sids++;
+ }
+
+
+ if (num_sids > PRIMARY_USER_SID_INDEX && dom_sid_equal(anonymous_sid, &sids[PRIMARY_USER_SID_INDEX])) {
+ /* Don't expand nested groups of system, anonymous etc*/
+ } else if (num_sids > PRIMARY_USER_SID_INDEX && dom_sid_equal(system_sid, &sids[PRIMARY_USER_SID_INDEX])) {
+ /* Don't expand nested groups of system, anonymous etc*/
+ } else if (sam_ctx) {
+ filter = talloc_asprintf(tmp_ctx, "(&(objectClass=group)(groupType:1.2.840.113556.1.4.803:=%u))",
+ GROUP_TYPE_BUILTIN_LOCAL_GROUP);
+
+ /* Search for each group in the token */
+ for (i = 0; i < num_sids; i++) {
+ struct dom_sid_buf buf;
+ const char *sid_dn;
+ DATA_BLOB sid_blob;
+
+ sid_dn = talloc_asprintf(
+ tmp_ctx,
+ "<SID=%s>",
+ dom_sid_str_buf(&sids[i], &buf));
+ if (sid_dn == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ sid_blob = data_blob_string_const(sid_dn);
+
+ /* This function takes in memberOf values and expands
+ * them, as long as they meet the filter - so only
+ * builtin groups
+ *
+ * We already have the SID in the token, so set
+ * 'only childs' flag to true */
+ nt_status = dsdb_expand_nested_groups(sam_ctx, &sid_blob, true, filter,
+ tmp_ctx, &sids, &num_sids);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ return nt_status;
+ }
+ }
+ }
+
+ nt_status = security_token_create(session_info,
+ lp_ctx,
+ num_sids,
+ sids,
+ session_info_flags,
+ &session_info->security_token);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ TALLOC_FREE(tmp_ctx);
+ return nt_status;
+ }
+
+ session_info->unique_session_token = GUID_random();
+
+ session_info->credentials = NULL;
+
+ session_info->ticket_type = user_info_dc->ticket_type;
+
+ talloc_steal(mem_ctx, session_info);
+ *_session_info = session_info;
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+
+/* Fill out the auth_session_info with a cli_credentials based on the
+ * auth_session_info we were forwarded over named pipe forwarding.
+ *
+ * NOTE: The stucture members of session_info_transport are stolen
+ * with talloc_move() into auth_session_info for long term use
+ */
+struct auth_session_info *auth_session_info_from_transport(TALLOC_CTX *mem_ctx,
+ struct auth_session_info_transport *session_info_transport,
+ struct loadparm_context *lp_ctx,
+ const char **reason)
+{
+ struct auth_session_info *session_info;
+ session_info = talloc_steal(mem_ctx, session_info_transport->session_info);
+ /*
+ * This is to allow us to check the type of this pointer using
+ * talloc_get_type()
+ */
+ talloc_set_name(session_info, "struct auth_session_info");
+#ifdef HAVE_GSS_IMPORT_CRED
+ if (session_info_transport->exported_gssapi_credentials.length) {
+ struct cli_credentials *creds;
+ OM_uint32 minor_status;
+ gss_buffer_desc cred_token;
+ gss_cred_id_t cred_handle;
+ const char *error_string;
+ int ret;
+ bool ok;
+
+ DEBUG(10, ("Delegated credentials supplied by client\n"));
+
+ cred_token.value = session_info_transport->exported_gssapi_credentials.data;
+ cred_token.length = session_info_transport->exported_gssapi_credentials.length;
+
+ ret = gss_import_cred(&minor_status,
+ &cred_token,
+ &cred_handle);
+ if (ret != GSS_S_COMPLETE) {
+ *reason = "Internal error in gss_import_cred()";
+ return NULL;
+ }
+
+ creds = cli_credentials_init(session_info);
+ if (!creds) {
+ *reason = "Out of memory in cli_credentials_init()";
+ return NULL;
+ }
+ session_info->credentials = creds;
+
+ ok = cli_credentials_set_conf(creds, lp_ctx);
+ if (!ok) {
+ *reason = "Failed to load smb.conf";
+ return NULL;
+ }
+
+ /* Just so we don't segfault trying to get at a username */
+ cli_credentials_set_anonymous(creds);
+
+ ret = cli_credentials_set_client_gss_creds(creds,
+ lp_ctx,
+ cred_handle,
+ CRED_SPECIFIED,
+ &error_string);
+ if (ret) {
+ *reason = talloc_asprintf(mem_ctx,
+ "Failed to set pipe forwarded"
+ "creds: %s\n", error_string);
+ return NULL;
+ }
+
+ /* This credential handle isn't useful for password
+ * authentication, so ensure nobody tries to do that */
+ cli_credentials_set_kerberos_state(creds,
+ CRED_USE_KERBEROS_REQUIRED,
+ CRED_SPECIFIED);
+
+ }
+#endif
+ return session_info;
+}
+
+
+/* Create a auth_session_info_transport from an auth_session_info.
+ *
+ * NOTE: Members of the auth_session_info_transport structure are
+ * talloc_referenced() into this structure, and should not be changed.
+ */
+NTSTATUS auth_session_info_transport_from_session(TALLOC_CTX *mem_ctx,
+ struct auth_session_info *session_info,
+ struct tevent_context *event_ctx,
+ struct loadparm_context *lp_ctx,
+ struct auth_session_info_transport **transport_out)
+{
+
+ struct auth_session_info_transport *session_info_transport
+ = talloc_zero(mem_ctx, struct auth_session_info_transport);
+ if (!session_info_transport) {
+ return NT_STATUS_NO_MEMORY;
+ };
+ session_info_transport->session_info = talloc_reference(session_info_transport, session_info);
+ if (!session_info_transport->session_info) {
+ return NT_STATUS_NO_MEMORY;
+ };
+#ifdef HAVE_GSS_EXPORT_CRED
+ if (session_info->credentials) {
+ struct gssapi_creds_container *gcc;
+ OM_uint32 gret;
+ OM_uint32 minor_status;
+ gss_buffer_desc cred_token;
+ const char *error_string;
+ int ret;
+
+ ret = cli_credentials_get_client_gss_creds(session_info->credentials,
+ event_ctx,
+ lp_ctx,
+ &gcc, &error_string);
+ if (ret != 0) {
+ *transport_out = session_info_transport;
+ return NT_STATUS_OK;
+ }
+
+ gret = gss_export_cred(&minor_status,
+ gcc->creds,
+ &cred_token);
+ if (gret != GSS_S_COMPLETE) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (cred_token.length) {
+ session_info_transport->exported_gssapi_credentials
+ = data_blob_talloc(session_info_transport,
+ cred_token.value,
+ cred_token.length);
+ gss_release_buffer(&minor_status, &cred_token);
+ NT_STATUS_HAVE_NO_MEMORY(session_info_transport->exported_gssapi_credentials.data);
+ }
+ }
+#endif
+ *transport_out = session_info_transport;
+ return NT_STATUS_OK;
+}
+
+
+/* Produce a session_info for an arbitary DN or principal in the local
+ * DB, assuming the local DB holds all the groups
+ *
+ * Supply either a principal or a DN
+ */
+NTSTATUS authsam_get_session_info_principal(TALLOC_CTX *mem_ctx,
+ struct loadparm_context *lp_ctx,
+ struct ldb_context *sam_ctx,
+ const char *principal,
+ struct ldb_dn *user_dn,
+ uint32_t session_info_flags,
+ struct auth_session_info **session_info)
+{
+ NTSTATUS nt_status;
+ struct auth_user_info_dc *user_info_dc;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ nt_status = authsam_get_user_info_dc_principal(tmp_ctx, lp_ctx, sam_ctx,
+ principal, user_dn,
+ &user_info_dc);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ return nt_status;
+ }
+
+ nt_status = auth_generate_session_info(tmp_ctx, lp_ctx, sam_ctx,
+ user_info_dc, session_info_flags,
+ session_info);
+
+ if (NT_STATUS_IS_OK(nt_status)) {
+ talloc_steal(mem_ctx, *session_info);
+ }
+ talloc_free(tmp_ctx);
+ return nt_status;
+}
+
+/**
+ * prints a struct auth_session_info security token to debug output.
+ */
+void auth_session_info_debug(int dbg_lev,
+ const struct auth_session_info *session_info)
+{
+ if (!session_info) {
+ DEBUG(dbg_lev, ("Session Info: (NULL)\n"));
+ return;
+ }
+
+ security_token_debug(DBGC_AUTH, dbg_lev,
+ session_info->security_token);
+}
diff --git a/source4/auth/session.h b/source4/auth/session.h
new file mode 100644
index 0000000..97a8aba
--- /dev/null
+++ b/source4/auth/session.h
@@ -0,0 +1,79 @@
+/*
+ Unix SMB/CIFS implementation.
+ Process and provide the logged on user's authorization token
+ Copyright (C) Andrew Bartlett 2001
+ Copyright (C) Stefan Metzmacher 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/>.
+*/
+
+#ifndef _SAMBA_AUTH_SESSION_H
+#define _SAMBA_AUTH_SESSION_H
+
+#include "librpc/gen_ndr/security.h"
+#include "librpc/gen_ndr/netlogon.h"
+#include "librpc/gen_ndr/auth.h"
+
+struct tevent_context;
+struct ldb_context;
+struct ldb_dn;
+/* Create a security token for a session SYSTEM (the most
+ * trusted/prvilaged account), including the local machine account as
+ * the off-host credentials */
+struct auth_session_info *system_session(struct loadparm_context *lp_ctx) ;
+
+NTSTATUS auth_anonymous_user_info_dc(TALLOC_CTX *mem_ctx,
+ const char *netbios_name,
+ struct auth_user_info_dc **interim_info);
+NTSTATUS auth_generate_session_info(TALLOC_CTX *mem_ctx,
+ struct loadparm_context *lp_ctx, /* Optional, if you don't want privilages */
+ struct ldb_context *sam_ctx, /* Optional, if you don't want local groups */
+ struct auth_user_info_dc *interim_info,
+ uint32_t session_info_flags,
+ struct auth_session_info **session_info);
+NTSTATUS auth_anonymous_session_info(TALLOC_CTX *parent_ctx,
+ struct loadparm_context *lp_ctx,
+ struct auth_session_info **session_info);
+struct auth_session_info *auth_session_info_from_transport(TALLOC_CTX *mem_ctx,
+ struct auth_session_info_transport *session_info_transport,
+ struct loadparm_context *lp_ctx,
+ const char **reason);
+NTSTATUS auth_session_info_transport_from_session(TALLOC_CTX *mem_ctx,
+ struct auth_session_info *session_info,
+ struct tevent_context *event_ctx,
+ struct loadparm_context *lp_ctx,
+ struct auth_session_info_transport **transport_out);
+
+/* Produce a session_info for an arbitary DN or principal in the local
+ * DB, assuming the local DB holds all the groups
+ *
+ * Supply either a principal or a DN
+ */
+NTSTATUS authsam_get_session_info_principal(TALLOC_CTX *mem_ctx,
+ struct loadparm_context *lp_ctx,
+ struct ldb_context *sam_ctx,
+ const char *principal,
+ struct ldb_dn *user_dn,
+ uint32_t session_info_flags,
+ struct auth_session_info **session_info);
+
+struct auth_session_info *anonymous_session(TALLOC_CTX *mem_ctx,
+ struct loadparm_context *lp_ctx);
+
+struct auth_session_info *admin_session(TALLOC_CTX *mem_ctx,
+ struct loadparm_context *lp_ctx,
+ struct dom_sid *domain_sid);
+
+
+#endif /* _SAMBA_AUTH_SESSION_H */
diff --git a/source4/auth/system_session.c b/source4/auth/system_session.c
new file mode 100644
index 0000000..17cfc4b
--- /dev/null
+++ b/source4/auth/system_session.c
@@ -0,0 +1,432 @@
+/*
+ Unix SMB/CIFS implementation.
+ Authentication utility functions
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Andrew Bartlett 2001-2010
+ Copyright (C) Jeremy Allison 2000-2001
+ Copyright (C) Rafal Szczesniak 2002
+ Copyright (C) Stefan Metzmacher 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 "libcli/security/security.h"
+#include "auth/credentials/credentials.h"
+#include "param/param.h"
+#include "auth/auth.h" /* for auth_user_info_dc */
+#include "auth/session.h"
+#include "auth/system_session_proto.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+/*
+ prevent the static system session being freed
+ */
+static int system_session_destructor(struct auth_session_info *info)
+{
+ return -1;
+}
+
+/* Create a security token for a session SYSTEM (the most
+ * trusted/privileged account), including the local machine account as
+ * the off-host credentials
+ */
+_PUBLIC_ struct auth_session_info *system_session(struct loadparm_context *lp_ctx)
+{
+ static struct auth_session_info *static_session;
+ NTSTATUS nt_status;
+
+ if (static_session) {
+ return static_session;
+ }
+
+ /*
+ * Use NULL here, not the autofree context for this
+ * static pointer. The destructor prevents freeing this
+ * memory anyway.
+ */
+ nt_status = auth_system_session_info(NULL,
+ lp_ctx,
+ &static_session);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ TALLOC_FREE(static_session);
+ return NULL;
+ }
+ talloc_set_destructor(static_session, system_session_destructor);
+ return static_session;
+}
+
+NTSTATUS auth_system_session_info(TALLOC_CTX *parent_ctx,
+ struct loadparm_context *lp_ctx,
+ struct auth_session_info **_session_info)
+{
+ NTSTATUS nt_status;
+ struct auth_user_info_dc *user_info_dc = NULL;
+ struct auth_session_info *session_info = NULL;
+ TALLOC_CTX *mem_ctx = NULL;
+ bool ok;
+
+ mem_ctx = talloc_new(parent_ctx);
+ if (mem_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ nt_status = auth_system_user_info_dc(mem_ctx, lpcfg_netbios_name(lp_ctx),
+ &user_info_dc);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(mem_ctx);
+ return nt_status;
+ }
+
+ /* references the user_info_dc into the session_info */
+ nt_status = auth_generate_session_info(parent_ctx, NULL, NULL, user_info_dc, AUTH_SESSION_INFO_SIMPLE_PRIVILEGES, &session_info);
+ talloc_free(mem_ctx);
+
+ NT_STATUS_NOT_OK_RETURN(nt_status);
+
+ session_info->credentials = cli_credentials_init(session_info);
+ if (!session_info->credentials) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ok = cli_credentials_set_conf(session_info->credentials, lp_ctx);
+ if (!ok) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ cli_credentials_set_machine_account_pending(session_info->credentials, lp_ctx);
+ *_session_info = session_info;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS auth_system_user_info_dc(TALLOC_CTX *mem_ctx, const char *netbios_name,
+ struct auth_user_info_dc **_user_info_dc)
+{
+ struct auth_user_info_dc *user_info_dc;
+ struct auth_user_info *info;
+
+ user_info_dc = talloc_zero(mem_ctx, struct auth_user_info_dc);
+ NT_STATUS_HAVE_NO_MEMORY(user_info_dc);
+
+ /* This returns a pointer to a struct dom_sid, which is the
+ * same as a 1 element list of struct dom_sid */
+ user_info_dc->num_sids = 1;
+ user_info_dc->sids = dom_sid_dup(user_info_dc, &global_sid_System);
+ NT_STATUS_HAVE_NO_MEMORY(user_info_dc->sids);
+
+ /* annoying, but the Anonymous really does have a session key,
+ and it is all zeros! */
+ user_info_dc->user_session_key = data_blob_talloc(user_info_dc, NULL, 16);
+ NT_STATUS_HAVE_NO_MEMORY(user_info_dc->user_session_key.data);
+
+ user_info_dc->lm_session_key = data_blob_talloc(user_info_dc, NULL, 16);
+ NT_STATUS_HAVE_NO_MEMORY(user_info_dc->lm_session_key.data);
+
+ data_blob_clear(&user_info_dc->user_session_key);
+ data_blob_clear(&user_info_dc->lm_session_key);
+
+ user_info_dc->info = info = talloc_zero(user_info_dc, struct auth_user_info);
+ NT_STATUS_HAVE_NO_MEMORY(user_info_dc->info);
+
+ info->account_name = talloc_strdup(info, "SYSTEM");
+ NT_STATUS_HAVE_NO_MEMORY(info->account_name);
+
+ info->domain_name = talloc_strdup(info, "NT AUTHORITY");
+ NT_STATUS_HAVE_NO_MEMORY(info->domain_name);
+
+ info->full_name = talloc_strdup(info, "System");
+ NT_STATUS_HAVE_NO_MEMORY(info->full_name);
+
+ info->logon_script = talloc_strdup(info, "");
+ NT_STATUS_HAVE_NO_MEMORY(info->logon_script);
+
+ info->profile_path = talloc_strdup(info, "");
+ NT_STATUS_HAVE_NO_MEMORY(info->profile_path);
+
+ info->home_directory = talloc_strdup(info, "");
+ NT_STATUS_HAVE_NO_MEMORY(info->home_directory);
+
+ info->home_drive = talloc_strdup(info, "");
+ NT_STATUS_HAVE_NO_MEMORY(info->home_drive);
+
+ info->logon_server = talloc_strdup(info, netbios_name);
+ NT_STATUS_HAVE_NO_MEMORY(info->logon_server);
+
+ info->last_logon = 0;
+ info->last_logoff = 0;
+ info->acct_expiry = 0;
+ info->last_password_change = 0;
+ info->allow_password_change = 0;
+ info->force_password_change = 0;
+
+ info->logon_count = 0;
+ info->bad_password_count = 0;
+
+ info->acct_flags = ACB_NORMAL;
+
+ info->authenticated = true;
+
+ *_user_info_dc = user_info_dc;
+
+ return NT_STATUS_OK;
+}
+
+
+static NTSTATUS auth_domain_admin_user_info_dc(TALLOC_CTX *mem_ctx,
+ const char *netbios_name,
+ const char *domain_name,
+ struct dom_sid *domain_sid,
+ struct auth_user_info_dc **_user_info_dc)
+{
+ struct auth_user_info_dc *user_info_dc;
+ struct auth_user_info *info;
+
+ user_info_dc = talloc_zero(mem_ctx, struct auth_user_info_dc);
+ NT_STATUS_HAVE_NO_MEMORY(user_info_dc);
+
+ user_info_dc->num_sids = 7;
+ user_info_dc->sids = talloc_array(user_info_dc, struct dom_sid, user_info_dc->num_sids);
+
+ user_info_dc->sids[PRIMARY_USER_SID_INDEX] = *domain_sid;
+ sid_append_rid(&user_info_dc->sids[PRIMARY_USER_SID_INDEX], DOMAIN_RID_ADMINISTRATOR);
+
+ user_info_dc->sids[PRIMARY_GROUP_SID_INDEX] = *domain_sid;
+ sid_append_rid(&user_info_dc->sids[PRIMARY_GROUP_SID_INDEX], DOMAIN_RID_USERS);
+
+ user_info_dc->sids[2] = global_sid_Builtin_Administrators;
+
+ user_info_dc->sids[3] = *domain_sid;
+ sid_append_rid(&user_info_dc->sids[3], DOMAIN_RID_ADMINS);
+ user_info_dc->sids[4] = *domain_sid;
+ sid_append_rid(&user_info_dc->sids[4], DOMAIN_RID_ENTERPRISE_ADMINS);
+ user_info_dc->sids[5] = *domain_sid;
+ sid_append_rid(&user_info_dc->sids[5], DOMAIN_RID_POLICY_ADMINS);
+ user_info_dc->sids[6] = *domain_sid;
+ sid_append_rid(&user_info_dc->sids[6], DOMAIN_RID_SCHEMA_ADMINS);
+
+ /* What should the session key be?*/
+ user_info_dc->user_session_key = data_blob_talloc(user_info_dc, NULL, 16);
+ NT_STATUS_HAVE_NO_MEMORY(user_info_dc->user_session_key.data);
+
+ user_info_dc->lm_session_key = data_blob_talloc(user_info_dc, NULL, 16);
+ NT_STATUS_HAVE_NO_MEMORY(user_info_dc->lm_session_key.data);
+
+ data_blob_clear(&user_info_dc->user_session_key);
+ data_blob_clear(&user_info_dc->lm_session_key);
+
+ user_info_dc->info = info = talloc_zero(user_info_dc, struct auth_user_info);
+ NT_STATUS_HAVE_NO_MEMORY(user_info_dc->info);
+
+ info->account_name = talloc_strdup(info, "Administrator");
+ NT_STATUS_HAVE_NO_MEMORY(info->account_name);
+
+ info->domain_name = talloc_strdup(info, domain_name);
+ NT_STATUS_HAVE_NO_MEMORY(info->domain_name);
+
+ info->full_name = talloc_strdup(info, "Administrator");
+ NT_STATUS_HAVE_NO_MEMORY(info->full_name);
+
+ info->logon_script = talloc_strdup(info, "");
+ NT_STATUS_HAVE_NO_MEMORY(info->logon_script);
+
+ info->profile_path = talloc_strdup(info, "");
+ NT_STATUS_HAVE_NO_MEMORY(info->profile_path);
+
+ info->home_directory = talloc_strdup(info, "");
+ NT_STATUS_HAVE_NO_MEMORY(info->home_directory);
+
+ info->home_drive = talloc_strdup(info, "");
+ NT_STATUS_HAVE_NO_MEMORY(info->home_drive);
+
+ info->logon_server = talloc_strdup(info, netbios_name);
+ NT_STATUS_HAVE_NO_MEMORY(info->logon_server);
+
+ info->last_logon = 0;
+ info->last_logoff = 0;
+ info->acct_expiry = 0;
+ info->last_password_change = 0;
+ info->allow_password_change = 0;
+ info->force_password_change = 0;
+
+ info->logon_count = 0;
+ info->bad_password_count = 0;
+
+ info->acct_flags = ACB_NORMAL;
+
+ info->authenticated = true;
+
+ *_user_info_dc = user_info_dc;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS auth_domain_admin_session_info(TALLOC_CTX *parent_ctx,
+ struct loadparm_context *lp_ctx,
+ struct dom_sid *domain_sid,
+ struct auth_session_info **session_info)
+{
+ NTSTATUS nt_status;
+ struct auth_user_info_dc *user_info_dc = NULL;
+ TALLOC_CTX *mem_ctx = talloc_new(parent_ctx);
+
+ NT_STATUS_HAVE_NO_MEMORY(mem_ctx);
+
+ nt_status = auth_domain_admin_user_info_dc(mem_ctx, lpcfg_netbios_name(lp_ctx),
+ lpcfg_workgroup(lp_ctx), domain_sid,
+ &user_info_dc);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(mem_ctx);
+ return nt_status;
+ }
+
+ nt_status = auth_generate_session_info(mem_ctx, NULL, NULL, user_info_dc,
+ AUTH_SESSION_INFO_SIMPLE_PRIVILEGES|AUTH_SESSION_INFO_AUTHENTICATED|AUTH_SESSION_INFO_DEFAULT_GROUPS,
+ session_info);
+ /* There is already a reference between the sesion_info and user_info_dc */
+ if (NT_STATUS_IS_OK(nt_status)) {
+ talloc_steal(parent_ctx, *session_info);
+ }
+ talloc_free(mem_ctx);
+ return nt_status;
+}
+
+_PUBLIC_ struct auth_session_info *admin_session(TALLOC_CTX *mem_ctx, struct loadparm_context *lp_ctx, struct dom_sid *domain_sid)
+{
+ NTSTATUS nt_status;
+ struct auth_session_info *session_info = NULL;
+ nt_status = auth_domain_admin_session_info(mem_ctx,
+ lp_ctx,
+ domain_sid,
+ &session_info);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return NULL;
+ }
+ return session_info;
+}
+
+_PUBLIC_ NTSTATUS auth_anonymous_session_info(TALLOC_CTX *parent_ctx,
+ struct loadparm_context *lp_ctx,
+ struct auth_session_info **_session_info)
+{
+ NTSTATUS nt_status;
+ struct auth_user_info_dc *user_info_dc = NULL;
+ struct auth_session_info *session_info = NULL;
+ TALLOC_CTX *mem_ctx = talloc_new(parent_ctx);
+ bool ok;
+
+ nt_status = auth_anonymous_user_info_dc(mem_ctx,
+ lpcfg_netbios_name(lp_ctx),
+ &user_info_dc);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(mem_ctx);
+ return nt_status;
+ }
+
+ /* references the user_info_dc into the session_info */
+ nt_status = auth_generate_session_info(parent_ctx, NULL, NULL, user_info_dc, AUTH_SESSION_INFO_SIMPLE_PRIVILEGES, &session_info);
+ talloc_free(mem_ctx);
+
+ NT_STATUS_NOT_OK_RETURN(nt_status);
+
+ session_info->credentials = cli_credentials_init(session_info);
+ if (!session_info->credentials) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ok = cli_credentials_set_conf(session_info->credentials, lp_ctx);
+ if (!ok) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ cli_credentials_set_anonymous(session_info->credentials);
+
+ *_session_info = session_info;
+
+ return NT_STATUS_OK;
+}
+
+_PUBLIC_ NTSTATUS auth_anonymous_user_info_dc(TALLOC_CTX *mem_ctx,
+ const char *netbios_name,
+ struct auth_user_info_dc **_user_info_dc)
+{
+ struct auth_user_info_dc *user_info_dc;
+ struct auth_user_info *info;
+ user_info_dc = talloc_zero(mem_ctx, struct auth_user_info_dc);
+ NT_STATUS_HAVE_NO_MEMORY(user_info_dc);
+
+ /* This returns a pointer to a struct dom_sid, which is the
+ * same as a 1 element list of struct dom_sid */
+ user_info_dc->num_sids = 1;
+ user_info_dc->sids = dom_sid_dup(user_info_dc, &global_sid_Anonymous);
+ NT_STATUS_HAVE_NO_MEMORY(user_info_dc->sids);
+
+ /* annoying, but the Anonymous really does have a session key... */
+ user_info_dc->user_session_key = data_blob_talloc(user_info_dc, NULL, 16);
+ NT_STATUS_HAVE_NO_MEMORY(user_info_dc->user_session_key.data);
+
+ user_info_dc->lm_session_key = data_blob_talloc(user_info_dc, NULL, 16);
+ NT_STATUS_HAVE_NO_MEMORY(user_info_dc->lm_session_key.data);
+
+ /* and it is all zeros! */
+ data_blob_clear(&user_info_dc->user_session_key);
+ data_blob_clear(&user_info_dc->lm_session_key);
+
+ user_info_dc->info = info = talloc_zero(user_info_dc, struct auth_user_info);
+ NT_STATUS_HAVE_NO_MEMORY(user_info_dc->info);
+
+ info->account_name = talloc_strdup(info, "ANONYMOUS LOGON");
+ NT_STATUS_HAVE_NO_MEMORY(info->account_name);
+
+ info->domain_name = talloc_strdup(info, "NT AUTHORITY");
+ NT_STATUS_HAVE_NO_MEMORY(info->domain_name);
+
+ info->full_name = talloc_strdup(info, "Anonymous Logon");
+ NT_STATUS_HAVE_NO_MEMORY(info->full_name);
+
+ info->logon_script = talloc_strdup(info, "");
+ NT_STATUS_HAVE_NO_MEMORY(info->logon_script);
+
+ info->profile_path = talloc_strdup(info, "");
+ NT_STATUS_HAVE_NO_MEMORY(info->profile_path);
+
+ info->home_directory = talloc_strdup(info, "");
+ NT_STATUS_HAVE_NO_MEMORY(info->home_directory);
+
+ info->home_drive = talloc_strdup(info, "");
+ NT_STATUS_HAVE_NO_MEMORY(info->home_drive);
+
+ info->logon_server = talloc_strdup(info, netbios_name);
+ NT_STATUS_HAVE_NO_MEMORY(info->logon_server);
+
+ info->last_logon = 0;
+ info->last_logoff = 0;
+ info->acct_expiry = 0;
+ info->last_password_change = 0;
+ info->allow_password_change = 0;
+ info->force_password_change = 0;
+
+ info->logon_count = 0;
+ info->bad_password_count = 0;
+
+ info->acct_flags = ACB_NORMAL;
+
+ info->authenticated = false;
+
+ *_user_info_dc = user_info_dc;
+
+ return NT_STATUS_OK;
+}
+
diff --git a/source4/auth/tests/heimdal_unwrap_des.c b/source4/auth/tests/heimdal_unwrap_des.c
new file mode 100644
index 0000000..fbfe778
--- /dev/null
+++ b/source4/auth/tests/heimdal_unwrap_des.c
@@ -0,0 +1,1244 @@
+/*
+ * Unit tests for third_party/heimdal/lib/gssapi/krb5/unwrap.c
+ *
+ * Copyright (C) Catalyst.NET Ltd 2022
+ *
+ * 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/>.
+ *
+ */
+
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * This allows test applications to use custom definitions of C standard
+ * library functions and types.
+ *
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+
+#include <cmocka.h>
+
+#include "includes.h"
+#include "replace.h"
+
+#include "../../../third_party/heimdal/lib/gssapi/gssapi/gssapi.h"
+#include "gsskrb5_locl.h"
+
+/******************************************************************************
+ * Helper functions
+ ******************************************************************************/
+
+const uint8_t *valid_range_begin;
+const uint8_t *valid_range_end;
+const uint8_t *invalid_range_end;
+
+/*
+ * 'array_len' is the size of the passed in array. 'buffer_len' is the size to
+ * report in the resulting buffer.
+ */
+static const gss_buffer_desc get_input_buffer(TALLOC_CTX *mem_ctx,
+ const uint8_t array[],
+ const size_t array_len,
+ const size_t buffer_len)
+{
+ gss_buffer_desc buf;
+
+ /* Add some padding to catch invalid memory accesses. */
+ const size_t padding = 0x100;
+ const size_t padded_len = array_len + padding;
+
+ uint8_t *data = talloc_size(mem_ctx, padded_len);
+ assert_non_null(data);
+
+ memcpy(data, array, array_len);
+ memset(data + array_len, 0, padding);
+
+ assert_in_range(buffer_len, 0, array_len);
+
+ buf.value = data;
+ buf.length = buffer_len;
+
+ valid_range_begin = buf.value;
+ valid_range_end = valid_range_begin + buf.length;
+ invalid_range_end = valid_range_begin + padded_len;
+
+ return buf;
+}
+
+static void assert_mem_in_valid_range(const uint8_t *ptr, const size_t len)
+{
+ /* Ensure we've set up the range pointers properly. */
+ assert_non_null(valid_range_begin);
+ assert_non_null(valid_range_end);
+ assert_non_null(invalid_range_end);
+
+ /*
+ * Ensure the length isn't excessively large (a symptom of integer
+ * underflow).
+ */
+ assert_in_range(len, 0, 0x1000);
+
+ /* Ensure the memory is in our valid range. */
+ assert_in_range(ptr, valid_range_begin, valid_range_end);
+ assert_in_range(ptr + len, valid_range_begin, valid_range_end);
+}
+
+/*
+ * This function takes a pointer to volatile to allow it to be called from the
+ * ct_memcmp() wrapper.
+ */
+static void assert_mem_outside_invalid_range(const volatile uint8_t *ptr,
+ const size_t len)
+{
+ const LargestIntegralType _valid_range_end
+ = cast_ptr_to_largest_integral_type(valid_range_end);
+ const LargestIntegralType _invalid_range_end
+ = cast_ptr_to_largest_integral_type(invalid_range_end);
+ const LargestIntegralType _ptr = cast_ptr_to_largest_integral_type(ptr);
+ const LargestIntegralType _len = cast_to_largest_integral_type(len);
+
+ /* Ensure we've set up the range pointers properly. */
+ assert_non_null(valid_range_begin);
+ assert_non_null(valid_range_end);
+ assert_non_null(invalid_range_end);
+
+ /*
+ * Ensure the length isn't excessively large (a symptom of integer
+ * underflow).
+ */
+ assert_in_range(len, 0, 0x1000);
+
+ /* Ensure the memory is outside the invalid range. */
+ if (_ptr < _invalid_range_end && _ptr + _len > _valid_range_end) {
+ fail();
+ }
+}
+
+/*****************************************************************************
+ * wrapped functions
+ *****************************************************************************/
+
+krb5_keyblock dummy_key;
+
+krb5_error_code __wrap_krb5_auth_con_getlocalsubkey(krb5_context context,
+ krb5_auth_context auth_context,
+ krb5_keyblock **keyblock);
+krb5_error_code __wrap_krb5_auth_con_getlocalsubkey(krb5_context context,
+ krb5_auth_context auth_context,
+ krb5_keyblock **keyblock)
+{
+ *keyblock = &dummy_key;
+ return 0;
+}
+
+void __wrap_krb5_free_keyblock(krb5_context context,
+ krb5_keyblock *keyblock);
+void __wrap_krb5_free_keyblock(krb5_context context,
+ krb5_keyblock *keyblock)
+{
+ assert_ptr_equal(&dummy_key, keyblock);
+}
+
+struct krb5_crypto_data dummy_crypto;
+
+krb5_error_code __wrap_krb5_crypto_init(krb5_context context,
+ const krb5_keyblock *key,
+ krb5_enctype etype,
+ krb5_crypto *crypto);
+krb5_error_code __wrap_krb5_crypto_init(krb5_context context,
+ const krb5_keyblock *key,
+ krb5_enctype etype,
+ krb5_crypto *crypto)
+{
+ static const LargestIntegralType etypes[] = {ETYPE_DES3_CBC_NONE, 0};
+
+ assert_ptr_equal(&dummy_key, key);
+ assert_in_set(etype, etypes, ARRAY_SIZE(etypes));
+
+ *crypto = &dummy_crypto;
+
+ return 0;
+}
+
+krb5_error_code __wrap_krb5_decrypt(krb5_context context,
+ krb5_crypto crypto,
+ unsigned usage,
+ void *data,
+ size_t len,
+ krb5_data *result);
+krb5_error_code __wrap_krb5_decrypt(krb5_context context,
+ krb5_crypto crypto,
+ unsigned usage,
+ void *data,
+ size_t len,
+ krb5_data *result)
+{
+ assert_ptr_equal(&dummy_crypto, crypto);
+ assert_int_equal(KRB5_KU_USAGE_SEAL, usage);
+
+ assert_mem_in_valid_range(data, len);
+
+ check_expected(len);
+ check_expected_ptr(data);
+
+ result->data = malloc(len);
+ assert_non_null(result->data);
+ result->length = len;
+
+ memcpy(result->data, data, len);
+
+ return 0;
+}
+
+krb5_error_code __wrap_krb5_decrypt_ivec(krb5_context context,
+ krb5_crypto crypto,
+ unsigned usage,
+ void *data,
+ size_t len,
+ krb5_data *result,
+ void *ivec);
+krb5_error_code __wrap_krb5_decrypt_ivec(krb5_context context,
+ krb5_crypto crypto,
+ unsigned usage,
+ void *data,
+ size_t len,
+ krb5_data *result,
+ void *ivec)
+{
+ assert_ptr_equal(&dummy_crypto, crypto);
+ assert_int_equal(KRB5_KU_USAGE_SEQ, usage);
+
+ assert_mem_in_valid_range(data, len);
+
+ assert_int_equal(8, len);
+ check_expected_ptr(data);
+ check_expected_ptr(ivec);
+
+ result->data = malloc(len);
+ assert_non_null(result->data);
+ result->length = len;
+
+ memcpy(result->data, data, len);
+
+ return 0;
+}
+
+krb5_error_code __wrap_krb5_verify_checksum(krb5_context context,
+ krb5_crypto crypto,
+ krb5_key_usage usage,
+ void *data,
+ size_t len,
+ Checksum *cksum);
+krb5_error_code __wrap_krb5_verify_checksum(krb5_context context,
+ krb5_crypto crypto,
+ krb5_key_usage usage,
+ void *data,
+ size_t len,
+ Checksum *cksum)
+{
+ assert_ptr_equal(&dummy_crypto, crypto);
+ assert_int_equal(KRB5_KU_USAGE_SIGN, usage);
+
+ assert_mem_in_valid_range(data, len);
+
+ check_expected(len);
+ check_expected_ptr(data);
+
+ assert_non_null(cksum);
+ assert_int_equal(CKSUMTYPE_HMAC_SHA1_DES3, cksum->cksumtype);
+ assert_int_equal(20, cksum->checksum.length);
+ check_expected_ptr(cksum->checksum.data);
+
+ return 0;
+}
+
+krb5_error_code __wrap_krb5_crypto_destroy(krb5_context context,
+ krb5_crypto crypto);
+krb5_error_code __wrap_krb5_crypto_destroy(krb5_context context,
+ krb5_crypto crypto)
+{
+ assert_ptr_equal(&dummy_crypto, crypto);
+
+ return 0;
+}
+
+
+int __wrap_der_get_length(const unsigned char *p,
+ size_t len,
+ size_t *val,
+ size_t *size);
+int __real_der_get_length(const unsigned char *p,
+ size_t len,
+ size_t *val,
+ size_t *size);
+int __wrap_der_get_length(const unsigned char *p,
+ size_t len,
+ size_t *val,
+ size_t *size)
+{
+ assert_mem_in_valid_range(p, len);
+
+ return __real_der_get_length(p, len, val, size);
+}
+
+int __wrap_ct_memcmp(const volatile void * volatile p1,
+ const volatile void * volatile p2,
+ size_t len);
+int __real_ct_memcmp(const volatile void * volatile p1,
+ const volatile void * volatile p2,
+ size_t len);
+int __wrap_ct_memcmp(const volatile void * volatile p1,
+ const volatile void * volatile p2,
+ size_t len)
+{
+ assert_mem_outside_invalid_range(p1, len);
+ assert_mem_outside_invalid_range(p2, len);
+
+ return __real_ct_memcmp(p1, p2, len);
+}
+
+void *__wrap_malloc(size_t size);
+void *__real_malloc(size_t size);
+void *__wrap_malloc(size_t size)
+{
+ /*
+ * Ensure the length isn't excessively large (a symptom of integer
+ * underflow).
+ */
+ assert_in_range(size, 0, 0x10000);
+
+ return __real_malloc(size);
+}
+
+/*****************************************************************************
+ * Mock implementations
+ *****************************************************************************/
+
+/*
+ * Set the globals used by the mocked functions to a known and consistent state
+ *
+ */
+static void init_mock_results(TALLOC_CTX *mem_ctx)
+{
+ dummy_key.keytype = KRB5_ENCTYPE_DES3_CBC_MD5;
+ dummy_key.keyvalue.data = NULL;
+ dummy_key.keyvalue.length = 0;
+
+ dummy_crypto = (struct krb5_crypto_data) {0};
+
+ valid_range_begin = NULL;
+ valid_range_end = NULL;
+ invalid_range_end = NULL;
+}
+
+/*****************************************************************************
+ * Unit test set up and tear down
+ *****************************************************************************/
+
+struct context {
+ gss_ctx_id_t context_handle;
+};
+
+static int setup(void **state) {
+ struct context *ctx = NULL;
+ krb5_context context = NULL;
+ OM_uint32 major_status;
+ OM_uint32 minor_status;
+ krb5_error_code code;
+
+ ctx = talloc_zero(NULL, struct context);
+ assert_non_null(ctx);
+
+ init_mock_results(ctx);
+
+ code = _gsskrb5_init(&context);
+ assert_int_equal(0, code);
+
+ major_status = _gsskrb5_create_ctx(&minor_status,
+ &ctx->context_handle,
+ context,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ ACCEPTOR_START);
+ assert_int_equal(GSS_S_COMPLETE, major_status);
+
+ *state = ctx;
+ return 0;
+}
+
+static int teardown(void **state) {
+ struct context *ctx = *state;
+ OM_uint32 major_status;
+ OM_uint32 minor_status;
+
+ major_status = _gsskrb5_delete_sec_context(&minor_status,
+ &ctx->context_handle,
+ GSS_C_NO_BUFFER);
+ assert_int_equal(GSS_S_COMPLETE, major_status);
+
+ TALLOC_FREE(ctx);
+ return 0;
+}
+
+/*****************************************************************************
+ * _gsskrb5_unwrap unit tests
+ *****************************************************************************/
+
+static void test_unwrap_dce_style_missing_payload(void **state) {
+ struct context *ctx = *state;
+ OM_uint32 major_status;
+ OM_uint32 minor_status;
+ gsskrb5_ctx gss_ctx;
+ gss_buffer_desc input = {0};
+ gss_buffer_desc output = {0};
+ int conf_state;
+ gss_qop_t qop_state;
+
+ /* See RFC 1964 for token format. */
+ static const uint8_t data[] = {
+ 0x60, /* ASN.1 Application tag */
+ 0x37, /* total length */
+ 0x06, /* OBJECT IDENTIFIER */
+ 0x09, /* mech length */
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, /* GSS KRB5 mech */
+ 0x02, 0x01, /* TOK_ID */
+ 0x04, 0x00, /* SGN_ALG (HMAC SHA1 DES3-KD) */
+ 0xff, 0xff, /* SEAL_ALG (none) */
+ 0xff, 0xff, /* Filler */
+ 0xa0, 0xa1, 0xa2, 0xa3, /* encrypted sequence number */
+ 0x00, 0x00, 0x00, 0x00, /* sequence number direction (remote) */
+ /* checksum */
+ 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+ 0xa9, 0xaa, 0xab, 0xac, 0xad,
+ 0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+ 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+ };
+
+ input = get_input_buffer(ctx, data, sizeof(data), 22);
+
+ gss_ctx = (gsskrb5_ctx) ctx->context_handle;
+ gss_ctx->flags |= GSS_C_DCE_STYLE;
+
+ major_status = _gsskrb5_unwrap(&minor_status,
+ ctx->context_handle,
+ &input,
+ &output,
+ &conf_state,
+ &qop_state);
+ assert_int_equal(GSS_S_BAD_MECH, major_status);
+}
+
+static void test_unwrap_dce_style_valid(void **state) {
+ struct context *ctx = *state;
+ OM_uint32 major_status;
+ OM_uint32 minor_status;
+ gsskrb5_ctx gss_ctx;
+ gss_buffer_desc input = {0};
+ gss_buffer_desc output = {0};
+ int conf_state;
+ gss_qop_t qop_state;
+
+ /* See RFC 1964 for token format. */
+ static const uint8_t data[] = {
+ 0x60, /* ASN.1 Application tag */
+ 0x37, /* total length */
+ 0x06, /* OBJECT IDENTIFIER */
+ 0x09, /* mech length */
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, /* GSS KRB5 mech */
+ 0x02, 0x01, /* TOK_ID */
+ 0x04, 0x00, /* SGN_ALG (HMAC SHA1 DES3-KD) */
+ 0xff, 0xff, /* SEAL_ALG (none) */
+ 0xff, 0xff, /* Filler */
+ 0xa0, 0xa1, 0xa2, 0xa3, /* encrypted sequence number */
+ 0x00, 0x00, 0x00, 0x00, /* sequence number direction (remote) */
+ /* checksum */
+ 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+ 0xa9, 0xaa, 0xab, 0xac, 0xad,
+ 0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+ 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+ /* unused */
+ 0xb8, 0xb9, 0xba, 0xbb,
+ 0xbc, 0xbd, 0xbe,
+ 0x00, /* padding byte */
+ };
+
+ input = get_input_buffer(ctx, data, sizeof(data), 57);
+
+ gss_ctx = (gsskrb5_ctx) ctx->context_handle;
+ gss_ctx->flags |= GSS_C_DCE_STYLE;
+
+ expect_value(__wrap_krb5_decrypt_ivec, data, (uint8_t *)input.value + 21);
+ expect_memory(__wrap_krb5_decrypt_ivec, ivec,
+ (uint8_t *)input.value + 29, DES_CBLOCK_LEN);
+
+ expect_value(__wrap_krb5_verify_checksum, len, 16);
+ expect_value(__wrap_krb5_verify_checksum, data, (uint8_t *)input.value + 41);
+ expect_memory(__wrap_krb5_verify_checksum, cksum->checksum.data,
+ (uint8_t *)input.value + 29, 20);
+
+ major_status = _gsskrb5_unwrap(&minor_status,
+ ctx->context_handle,
+ &input,
+ &output,
+ &conf_state,
+ &qop_state);
+ assert_int_equal(GSS_S_COMPLETE, major_status);
+
+ assert_int_equal(0, conf_state);
+ assert_int_equal(GSS_C_QOP_DEFAULT, qop_state);
+
+ assert_int_equal(output.length, 0);
+
+ major_status = gss_release_buffer(&minor_status, &output);
+ assert_int_equal(GSS_S_COMPLETE, major_status);
+}
+
+static void test_unwrap_dce_style_with_seal_missing_payload(void **state) {
+ struct context *ctx = *state;
+ OM_uint32 major_status;
+ OM_uint32 minor_status;
+ gsskrb5_ctx gss_ctx;
+ gss_buffer_desc input = {0};
+ gss_buffer_desc output = {0};
+ int conf_state;
+ gss_qop_t qop_state;
+
+ /* See RFC 1964 for token format. */
+ static const uint8_t data[] = {
+ 0x60, /* ASN.1 Application tag */
+ 0x37, /* total length */
+ 0x06, /* OBJECT IDENTIFIER */
+ 0x09, /* mech length */
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, /* GSS KRB5 mech */
+ 0x02, 0x01, /* TOK_ID */
+ 0x04, 0x00, /* SGN_ALG (HMAC SHA1 DES3-KD) */
+ 0x02, 0x00, /* SEAL_ALG (DES3-KD) */
+ 0xff, 0xff, /* Filler */
+ 0xa0, 0xa1, 0xa2, 0xa3, /* encrypted sequence number */
+ 0x00, 0x00, 0x00, 0x00, /* sequence number direction (remote) */
+ /* checksum */
+ 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+ 0xa9, 0xaa, 0xab, 0xac, 0xad,
+ 0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+ 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+ };
+
+ input = get_input_buffer(ctx, data, sizeof(data), 22);
+
+ gss_ctx = (gsskrb5_ctx) ctx->context_handle;
+ gss_ctx->flags |= GSS_C_DCE_STYLE;
+
+ major_status = _gsskrb5_unwrap(&minor_status,
+ ctx->context_handle,
+ &input,
+ &output,
+ &conf_state,
+ &qop_state);
+ assert_int_equal(GSS_S_BAD_MECH, major_status);
+}
+
+static void test_unwrap_dce_style_with_seal_valid(void **state) {
+ struct context *ctx = *state;
+ OM_uint32 major_status;
+ OM_uint32 minor_status;
+ gsskrb5_ctx gss_ctx;
+ gss_buffer_desc input = {0};
+ gss_buffer_desc output = {0};
+ int conf_state;
+ gss_qop_t qop_state;
+
+ /* See RFC 1964 for token format. */
+ static const uint8_t data[] = {
+ 0x60, /* ASN.1 Application tag */
+ 0x37, /* total length */
+ 0x06, /* OBJECT IDENTIFIER */
+ 0x09, /* mech length */
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, /* GSS KRB5 mech */
+ 0x02, 0x01, /* TOK_ID */
+ 0x04, 0x00, /* SGN_ALG (HMAC SHA1 DES3-KD) */
+ 0x02, 0x00, /* SEAL_ALG (DES3-KD) */
+ 0xff, 0xff, /* Filler */
+ 0xa0, 0xa1, 0xa2, 0xa3, /* encrypted sequence number */
+ 0x00, 0x00, 0x00, 0x00, /* sequence number direction (remote) */
+ /* checksum */
+ 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+ 0xa9, 0xaa, 0xab, 0xac, 0xad,
+ 0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+ 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+ /* unused */
+ 0xb8, 0xb9, 0xba, 0xbb,
+ 0xbc, 0xbd, 0xbe,
+ 0x00, /* padding byte */
+ };
+
+ input = get_input_buffer(ctx, data, sizeof(data), 57);
+
+ gss_ctx = (gsskrb5_ctx) ctx->context_handle;
+ gss_ctx->flags |= GSS_C_DCE_STYLE;
+
+ expect_value(__wrap_krb5_decrypt, len, 8);
+ expect_value(__wrap_krb5_decrypt, data, (uint8_t *)input.value + 49);
+
+ expect_value(__wrap_krb5_decrypt_ivec, data, (uint8_t *)input.value + 21);
+ expect_memory(__wrap_krb5_decrypt_ivec, ivec,
+ (uint8_t *)input.value + 29, DES_CBLOCK_LEN);
+
+ expect_value(__wrap_krb5_verify_checksum, len, 16);
+ expect_value(__wrap_krb5_verify_checksum, data, (uint8_t *)input.value + 41);
+ expect_memory(__wrap_krb5_verify_checksum, cksum->checksum.data,
+ (uint8_t *)input.value + 29, 20);
+
+ major_status = _gsskrb5_unwrap(&minor_status,
+ ctx->context_handle,
+ &input,
+ &output,
+ &conf_state,
+ &qop_state);
+ assert_int_equal(GSS_S_COMPLETE, major_status);
+
+ assert_int_equal(1, conf_state);
+ assert_int_equal(GSS_C_QOP_DEFAULT, qop_state);
+
+ assert_int_equal(output.length, 0);
+
+ major_status = gss_release_buffer(&minor_status, &output);
+ assert_int_equal(GSS_S_COMPLETE, major_status);
+}
+
+static void test_unwrap_missing_8_bytes(void **state) {
+ struct context *ctx = *state;
+ OM_uint32 major_status;
+ OM_uint32 minor_status;
+ gss_buffer_desc input = {0};
+ gss_buffer_desc output = {0};
+ int conf_state;
+ gss_qop_t qop_state;
+
+ /* See RFC 1964 for token format. */
+ static const uint8_t data[] = {
+ 0x60, /* ASN.1 Application tag */
+ 0x2f, /* total length */
+ 0x06, /* OBJECT IDENTIFIER */
+ 0x09, /* mech length */
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, /* GSS KRB5 mech */
+ 0x02, 0x01, /* TOK_ID */
+ 0x04, 0x00, /* SGN_ALG (HMAC SHA1 DES3-KD) */
+ 0xff, 0xff, /* SEAL_ALG (none) */
+ 0xff, 0xff, /* Filler */
+ 0xa0, 0xa1, 0xa2, 0xa3, /* encrypted sequence number */
+ 0x00, 0x00, 0x00, 0x00, /* sequence number direction (remote) */
+ /* checksum */
+ 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+ 0xa9, 0xaa, 0xab, 0xac, 0xad,
+ 0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+ 0xb3, 0xb4, 0xb5, 0xb6, 0x00, /* padding byte */
+ };
+
+ input = get_input_buffer(ctx, data, sizeof(data), 49);
+
+ /*
+ * A fixed unwrap_des3() should fail before these wrappers are called,
+ * but we want the wrappers to have access to any required values in the
+ * event that they are called. Specifying WILL_RETURN_ONCE avoids a test
+ * failure if these values remain unused.
+ */
+ expect_value_count(__wrap_krb5_decrypt_ivec, data,
+ (uint8_t *)input.value + 21,
+ WILL_RETURN_ONCE);
+ expect_memory_count(__wrap_krb5_decrypt_ivec, ivec,
+ (uint8_t *)input.value + 29, DES_CBLOCK_LEN,
+ WILL_RETURN_ONCE);
+
+ expect_value_count(__wrap_krb5_verify_checksum, len, 8, WILL_RETURN_ONCE);
+ expect_value_count(__wrap_krb5_verify_checksum, data,
+ (uint8_t *)input.value + 41,
+ WILL_RETURN_ONCE);
+ expect_memory_count(__wrap_krb5_verify_checksum, cksum->checksum.data,
+ (uint8_t *)input.value + 29, 20,
+ WILL_RETURN_ONCE);
+
+ major_status = _gsskrb5_unwrap(&minor_status,
+ ctx->context_handle,
+ &input,
+ &output,
+ &conf_state,
+ &qop_state);
+ assert_int_equal(GSS_S_BAD_MECH, major_status);
+}
+
+static void test_unwrap_missing_payload(void **state) {
+ struct context *ctx = *state;
+ OM_uint32 major_status;
+ OM_uint32 minor_status;
+ gss_buffer_desc input = {0};
+ gss_buffer_desc output = {0};
+ int conf_state;
+ gss_qop_t qop_state;
+
+ /* See RFC 1964 for token format. */
+ static const uint8_t data[] = {
+ 0x60, /* ASN.1 Application tag */
+ 0x14, /* total length */
+ 0x06, /* OBJECT IDENTIFIER */
+ 0x09, /* mech length */
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, /* GSS KRB5 mech */
+ 0x02, 0x01, /* TOK_ID */
+ 0x04, 0x00, /* SGN_ALG (HMAC SHA1 DES3-KD) */
+ 0xff, 0xff, /* SEAL_ALG (none) */
+ 0xff, 0xff, /* Filler */
+ 0x00, 0xa1, 0xa2, 0xa3, /* padding byte / encrypted sequence number */
+ 0x00, 0x00, 0x00, 0x00, /* sequence number direction (remote) */
+ /* checksum */
+ 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+ 0xa9, 0xaa, 0xab, 0xac, 0xad,
+ 0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+ 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+ };
+
+ input = get_input_buffer(ctx, data, sizeof(data), 22);
+
+ major_status = _gsskrb5_unwrap(&minor_status,
+ ctx->context_handle,
+ &input,
+ &output,
+ &conf_state,
+ &qop_state);
+ assert_int_equal(GSS_S_BAD_MECH, major_status);
+}
+
+static void test_unwrap_truncated_header_0(void **state) {
+ struct context *ctx = *state;
+ OM_uint32 major_status;
+ OM_uint32 minor_status;
+ gss_buffer_desc input = {0};
+ gss_buffer_desc output = {0};
+ int conf_state;
+ gss_qop_t qop_state;
+
+ /* See RFC 1964 for token format. */
+ static const uint8_t data[] = {
+ 0x60, /* ASN.1 Application tag */
+ 0x00, /* total length */
+ 0x06, /* OBJECT IDENTIFIER */
+ };
+
+ input = get_input_buffer(ctx, data, sizeof(data), 2);
+
+ major_status = _gsskrb5_unwrap(&minor_status,
+ ctx->context_handle,
+ &input,
+ &output,
+ &conf_state,
+ &qop_state);
+ assert_int_equal(GSS_S_DEFECTIVE_TOKEN, major_status);
+}
+
+static void test_unwrap_truncated_header_1(void **state) {
+ struct context *ctx = *state;
+ OM_uint32 major_status;
+ OM_uint32 minor_status;
+ gss_buffer_desc input = {0};
+ gss_buffer_desc output = {0};
+ int conf_state;
+ gss_qop_t qop_state;
+
+ /* See RFC 1964 for token format. */
+ static const uint8_t data[] = {
+ 0x60, /* ASN.1 Application tag */
+ 0x02, /* total length */
+ 0x06, /* OBJECT IDENTIFIER */
+ 0x09, /* mech length */
+ 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, /* GSS KRB5 mech */
+ };
+
+ input = get_input_buffer(ctx, data, sizeof(data), 4);
+
+ major_status = _gsskrb5_unwrap(&minor_status,
+ ctx->context_handle,
+ &input,
+ &output,
+ &conf_state,
+ &qop_state);
+ assert_int_equal(GSS_S_BAD_MECH, major_status);
+}
+
+static void test_unwrap_valid(void **state) {
+ struct context *ctx = *state;
+ OM_uint32 major_status;
+ OM_uint32 minor_status;
+ gss_buffer_desc input = {0};
+ gss_buffer_desc output = {0};
+ int conf_state;
+ gss_qop_t qop_state;
+
+ /* See RFC 1964 for token format. */
+ static const uint8_t data[] = {
+ 0x60, /* ASN.1 Application tag */
+ 0x37, /* total length */
+ 0x06, /* OBJECT IDENTIFIER */
+ 0x09, /* mech length */
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, /* GSS KRB5 mech */
+ 0x02, 0x01, /* TOK_ID */
+ 0x04, 0x00, /* SGN_ALG (HMAC SHA1 DES3-KD) */
+ 0xff, 0xff, /* SEAL_ALG (none) */
+ 0xff, 0xff, /* Filler */
+ 0xa0, 0xa1, 0xa2, 0xa3, /* encrypted sequence number */
+ 0x00, 0x00, 0x00, 0x00, /* sequence number direction (remote) */
+ /* checksum */
+ 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+ 0xa9, 0xaa, 0xab, 0xac, 0xad,
+ 0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+ 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+ /* unused */
+ 0xb8, 0xb9, 0xba, 0xbb,
+ 0xbc, 0xbd, 0xbe,
+ 0x00, /* padding byte */
+ };
+
+ input = get_input_buffer(ctx, data, sizeof(data), 57);
+
+ expect_value(__wrap_krb5_decrypt_ivec, data, (uint8_t *)input.value + 21);
+ expect_memory(__wrap_krb5_decrypt_ivec, ivec,
+ (uint8_t *)input.value + 29, DES_CBLOCK_LEN);
+
+ expect_value(__wrap_krb5_verify_checksum, len, 16);
+ expect_value(__wrap_krb5_verify_checksum, data, (uint8_t *)input.value + 41);
+ expect_memory(__wrap_krb5_verify_checksum, cksum->checksum.data,
+ (uint8_t *)input.value + 29, 20);
+
+ major_status = _gsskrb5_unwrap(&minor_status,
+ ctx->context_handle,
+ &input,
+ &output,
+ &conf_state,
+ &qop_state);
+ assert_int_equal(GSS_S_COMPLETE, major_status);
+
+ assert_int_equal(0, conf_state);
+ assert_int_equal(GSS_C_QOP_DEFAULT, qop_state);
+
+ assert_int_equal(output.length, 0);
+
+ major_status = gss_release_buffer(&minor_status, &output);
+ assert_int_equal(GSS_S_COMPLETE, major_status);
+}
+
+static void test_unwrap_with_padding_truncated_0(void **state) {
+ struct context *ctx = *state;
+ OM_uint32 major_status;
+ OM_uint32 minor_status;
+ gss_buffer_desc input = {0};
+ gss_buffer_desc output = {0};
+ int conf_state;
+ gss_qop_t qop_state;
+
+ /* See RFC 1964 for token format. */
+ static const uint8_t data[] = {
+ 0x60, /* ASN.1 Application tag */
+ 0x37, /* total length */
+ 0x06, /* OBJECT IDENTIFIER */
+ 0x09, /* mech length */
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, /* GSS KRB5 mech */
+ 0x02, 0x01, /* TOK_ID */
+ 0x04, 0x00, /* SGN_ALG (HMAC SHA1 DES3-KD) */
+ 0xff, 0xff, /* SEAL_ALG (none) */
+ 0xff, 0xff, /* Filler */
+ 0xa0, 0xa1, 0xa2, 0xa3, /* encrypted sequence number */
+ 0x00, 0x00, 0x00, 0x00, /* sequence number direction (remote) */
+ /* checksum */
+ 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+ 0xa9, 0xaa, 0xab, 0xac, 0xad,
+ 0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+ 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+ /* unused */
+ 0xb8, 0xb9, 0xba, 0xbb,
+ 0x04, 0x04, 0x04, 0x04, /* padding bytes */
+ };
+
+ input = get_input_buffer(ctx, data, sizeof(data), 57);
+
+ /*
+ * A fixed unwrap_des3() should fail before these wrappers are called,
+ * but we want the wrappers to have access to any required values in the
+ * event that they are called. Specifying WILL_RETURN_ONCE avoids a test
+ * failure if these values remain unused.
+ */
+ expect_value_count(__wrap_krb5_decrypt_ivec, data,
+ (uint8_t *)input.value + 21,
+ WILL_RETURN_ONCE);
+ expect_memory_count(__wrap_krb5_decrypt_ivec, ivec,
+ (uint8_t *)input.value + 29, DES_CBLOCK_LEN,
+ WILL_RETURN_ONCE);
+
+ expect_value_count(__wrap_krb5_verify_checksum, len, 16, WILL_RETURN_ONCE);
+ expect_value_count(__wrap_krb5_verify_checksum, data,
+ (uint8_t *)input.value + 41,
+ WILL_RETURN_ONCE);
+ expect_memory_count(__wrap_krb5_verify_checksum, cksum->checksum.data,
+ (uint8_t *)input.value + 29, 20,
+ WILL_RETURN_ONCE);
+
+ major_status = _gsskrb5_unwrap(&minor_status,
+ ctx->context_handle,
+ &input,
+ &output,
+ &conf_state,
+ &qop_state);
+ assert_int_equal(GSS_S_BAD_MECH, major_status);
+}
+
+static void test_unwrap_with_padding_truncated_1(void **state) {
+ struct context *ctx = *state;
+ OM_uint32 major_status;
+ OM_uint32 minor_status;
+ gss_buffer_desc input = {0};
+ gss_buffer_desc output = {0};
+ int conf_state;
+ gss_qop_t qop_state;
+
+ /* See RFC 1964 for token format. */
+ static const uint8_t data[] = {
+ 0x60, /* ASN.1 Application tag */
+ 0x37, /* total length */
+ 0x06, /* OBJECT IDENTIFIER */
+ 0x09, /* mech length */
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, /* GSS KRB5 mech */
+ 0x02, 0x01, /* TOK_ID */
+ 0x04, 0x00, /* SGN_ALG (HMAC SHA1 DES3-KD) */
+ 0xff, 0xff, /* SEAL_ALG (none) */
+ 0xff, 0xff, /* Filler */
+ 0x00, 0xa1, 0xa2, 0xa3, /* padding byte / encrypted sequence number */
+ 0x00, 0x00, 0x00, 0x00, /* sequence number direction (remote) */
+ /* checksum */
+ 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+ 0xa9, 0xaa, 0xab, 0xac, 0xad,
+ 0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+ 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+ /* padding bytes */
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+ };
+
+ input = get_input_buffer(ctx, data, sizeof(data), 57);
+
+ /*
+ * A fixed unwrap_des3() should fail before these wrappers are called,
+ * but we want the wrappers to have access to any required values in the
+ * event that they are called. Specifying WILL_RETURN_ONCE avoids a test
+ * failure if these values remain unused.
+ */
+ expect_value_count(__wrap_krb5_decrypt_ivec, data,
+ (uint8_t *)input.value + 21,
+ WILL_RETURN_ONCE);
+ expect_memory_count(__wrap_krb5_decrypt_ivec, ivec,
+ (uint8_t *)input.value + 29, DES_CBLOCK_LEN,
+ WILL_RETURN_ONCE);
+
+ expect_value_count(__wrap_krb5_verify_checksum, len, 16, WILL_RETURN_ONCE);
+ expect_value_count(__wrap_krb5_verify_checksum, data,
+ (uint8_t *)input.value + 41,
+ WILL_RETURN_ONCE);
+ expect_memory_count(__wrap_krb5_verify_checksum, cksum->checksum.data,
+ (uint8_t *)input.value + 29, 20,
+ WILL_RETURN_ONCE);
+
+ major_status = _gsskrb5_unwrap(&minor_status,
+ ctx->context_handle,
+ &input,
+ &output,
+ &conf_state,
+ &qop_state);
+ assert_int_equal(GSS_S_BAD_MECH, major_status);
+}
+
+static void test_unwrap_with_padding_valid(void **state) {
+ struct context *ctx = *state;
+ OM_uint32 major_status;
+ OM_uint32 minor_status;
+ gss_buffer_desc input = {0};
+ gss_buffer_desc output = {0};
+ int conf_state;
+ gss_qop_t qop_state;
+
+ /* See RFC 1964 for token format. */
+ static const uint8_t data[] = {
+ 0x60, /* ASN.1 Application tag */
+ 0x3f, /* total length */
+ 0x06, /* OBJECT IDENTIFIER */
+ 0x09, /* mech length */
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, /* GSS KRB5 mech */
+ 0x02, 0x01, /* TOK_ID */
+ 0x04, 0x00, /* SGN_ALG (HMAC SHA1 DES3-KD) */
+ 0xff, 0xff, /* SEAL_ALG (none) */
+ 0xff, 0xff, /* Filler */
+ 0xa0, 0xa1, 0xa2, 0xa3, /* encrypted sequence number */
+ 0x00, 0x00, 0x00, 0x00, /* sequence number direction (remote) */
+ /* checksum */
+ 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+ 0xa9, 0xaa, 0xab, 0xac, 0xad,
+ 0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+ 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+ /* unused */
+ 0xb8, 0xb9, 0xba, 0xbb,
+ 0xbc, 0xbd, 0xbe, 0xbf,
+ /* padding bytes */
+ 0x08, 0x08, 0x08, 0x08,
+ 0x08, 0x08, 0x08, 0x08,
+ };
+
+ input = get_input_buffer(ctx, data, sizeof(data), 65);
+
+ expect_value(__wrap_krb5_decrypt_ivec, data, (uint8_t *)input.value + 21);
+ expect_memory(__wrap_krb5_decrypt_ivec, ivec,
+ (uint8_t *)input.value + 29, DES_CBLOCK_LEN);
+
+ expect_value(__wrap_krb5_verify_checksum, len, 24);
+ expect_value(__wrap_krb5_verify_checksum, data, (uint8_t *)input.value + 41);
+ expect_memory(__wrap_krb5_verify_checksum, cksum->checksum.data,
+ (uint8_t *)input.value + 29, 20);
+
+ major_status = _gsskrb5_unwrap(&minor_status,
+ ctx->context_handle,
+ &input,
+ &output,
+ &conf_state,
+ &qop_state);
+ assert_int_equal(GSS_S_COMPLETE, major_status);
+
+ assert_int_equal(0, conf_state);
+ assert_int_equal(GSS_C_QOP_DEFAULT, qop_state);
+
+ assert_int_equal(output.length, 0);
+
+ major_status = gss_release_buffer(&minor_status, &output);
+ assert_int_equal(GSS_S_COMPLETE, major_status);
+}
+
+static void test_unwrap_with_seal_empty_token_valid(void **state) {
+ struct context *ctx = *state;
+ OM_uint32 major_status;
+ OM_uint32 minor_status;
+ gss_buffer_desc input = {0};
+ gss_buffer_desc output = {0};
+ int conf_state;
+ gss_qop_t qop_state;
+
+ /* See RFC 1964 for token format. */
+ static const uint8_t data[] = {
+ 0x60, /* ASN.1 Application tag */
+ 0x37, /* total length */
+ 0x06, /* OBJECT IDENTIFIER */
+ 0x09, /* mech length */
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, /* GSS KRB5 mech */
+ 0x02, 0x01, /* TOK_ID */
+ 0x04, 0x00, /* SGN_ALG (HMAC SHA1 DES3-KD) */
+ 0x02, 0x00, /* SEAL_ALG (DES3-KD) */
+ 0xff, 0xff, /* Filler */
+ 0xa0, 0xa1, 0xa2, 0xa3, /* encrypted sequence number */
+ 0x00, 0x00, 0x00, 0x00, /* sequence number direction (remote) */
+ /* checksum */
+ 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+ 0xa9, 0xaa, 0xab, 0xac, 0xad,
+ 0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+ 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+ /* unused */
+ 0xb8, 0xb9, 0xba, 0xbb,
+ 0xbc, 0xbd, 0xbe,
+ 0x00, /* padding byte */
+ };
+
+ input = get_input_buffer(ctx, data, sizeof(data), 57);
+
+ expect_value(__wrap_krb5_decrypt, len, 8);
+ expect_value(__wrap_krb5_decrypt, data, (uint8_t *)input.value + 49);
+
+ expect_value(__wrap_krb5_decrypt_ivec, data, (uint8_t *)input.value + 21);
+ expect_memory(__wrap_krb5_decrypt_ivec, ivec,
+ (uint8_t *)input.value + 29, DES_CBLOCK_LEN);
+
+ expect_value(__wrap_krb5_verify_checksum, len, 16);
+ expect_value(__wrap_krb5_verify_checksum, data, (uint8_t *)input.value + 41);
+ expect_memory(__wrap_krb5_verify_checksum, cksum->checksum.data,
+ (uint8_t *)input.value + 29, 20);
+
+ major_status = _gsskrb5_unwrap(&minor_status,
+ ctx->context_handle,
+ &input,
+ &output,
+ &conf_state,
+ &qop_state);
+ assert_int_equal(GSS_S_COMPLETE, major_status);
+
+ assert_int_equal(1, conf_state);
+ assert_int_equal(GSS_C_QOP_DEFAULT, qop_state);
+
+ assert_int_equal(output.length, 0);
+
+ major_status = gss_release_buffer(&minor_status, &output);
+ assert_int_equal(GSS_S_COMPLETE, major_status);
+}
+
+static void test_unwrap_with_seal_missing_payload(void **state) {
+ struct context *ctx = *state;
+ OM_uint32 major_status;
+ OM_uint32 minor_status;
+ gss_buffer_desc input = {0};
+ gss_buffer_desc output = {0};
+ int conf_state;
+ gss_qop_t qop_state;
+
+ /* See RFC 1964 for token format. */
+ static const uint8_t data[] = {
+ 0x60, /* ASN.1 Application tag */
+ 0x14, /* total length */
+ 0x06, /* OBJECT IDENTIFIER */
+ 0x09, /* mech length */
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, /* GSS KRB5 mech */
+ 0x02, 0x01, /* TOK_ID */
+ 0x04, 0x00, /* SGN_ALG (HMAC SHA1 DES3-KD) */
+ 0x02, 0x00, /* SEAL_ALG (DES3-KD) */
+ 0xff, 0xff, /* Filler */
+ 0xa0, 0xa1, 0xa2, 0xa3, /* encrypted sequence number */
+ 0x00, 0x00, 0x00, 0x00, /* sequence number direction (remote) */
+ /* checksum */
+ 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+ 0xa9, 0xaa, 0xab, 0xac, 0xad,
+ 0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+ 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+ };
+
+ input = get_input_buffer(ctx, data, sizeof(data), 22);
+
+ major_status = _gsskrb5_unwrap(&minor_status,
+ ctx->context_handle,
+ &input,
+ &output,
+ &conf_state,
+ &qop_state);
+ assert_int_equal(GSS_S_BAD_MECH, major_status);
+}
+
+static void test_unwrap_with_seal_valid(void **state) {
+ struct context *ctx = *state;
+ OM_uint32 major_status;
+ OM_uint32 minor_status;
+ gss_buffer_desc input = {0};
+ gss_buffer_desc output = {0};
+ int conf_state;
+ gss_qop_t qop_state;
+
+ /* See RFC 1964 for token format. */
+ static const uint8_t data[] = {
+ 0x60, /* ASN.1 Application tag */
+ 0x3e, /* total length */
+ 0x06, /* OBJECT IDENTIFIER */
+ 0x09, /* mech length */
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, /* GSS KRB5 mech */
+ 0x02, 0x01, /* TOK_ID */
+ 0x04, 0x00, /* SGN_ALG (HMAC SHA1 DES3-KD) */
+ 0x02, 0x00, /* SEAL_ALG (DES3-KD) */
+ 0xff, 0xff, /* Filler */
+ 0xa0, 0xa1, 0xa2, 0xa3, /* encrypted sequence number */
+ 0x00, 0x00, 0x00, 0x00, /* sequence number direction (remote) */
+ /* checksum */
+ 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+ 0xa9, 0xaa, 0xab, 0xac, 0xad,
+ 0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+ 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+ /* unused */
+ 0xb8, 0xb9, 0xba, 0xbb,
+ 0xbc, 0xbd, 0xbe, 0xbf,
+ 0xc0, 0xc1, 0xc2, 0xc3,
+ 0xc4, 0xc5,
+ 0x00, /* padding byte */
+ };
+
+ input = get_input_buffer(ctx, data, sizeof(data), 64);
+
+ expect_value(__wrap_krb5_decrypt, len, 15);
+ expect_value(__wrap_krb5_decrypt, data, (uint8_t *)input.value + 49);
+
+ expect_value(__wrap_krb5_decrypt_ivec, data, (uint8_t *)input.value + 21);
+ expect_memory(__wrap_krb5_decrypt_ivec, ivec,
+ (uint8_t *)input.value + 29, DES_CBLOCK_LEN);
+
+ expect_value(__wrap_krb5_verify_checksum, len, 23);
+ expect_value(__wrap_krb5_verify_checksum, data, (uint8_t *)input.value + 41);
+ expect_memory(__wrap_krb5_verify_checksum, cksum->checksum.data,
+ (uint8_t *)input.value + 29, 20);
+
+ major_status = _gsskrb5_unwrap(&minor_status,
+ ctx->context_handle,
+ &input,
+ &output,
+ &conf_state,
+ &qop_state);
+ assert_int_equal(GSS_S_COMPLETE, major_status);
+
+ assert_int_equal(1, conf_state);
+ assert_int_equal(GSS_C_QOP_DEFAULT, qop_state);
+
+ assert_int_equal(output.length, 7);
+ assert_memory_equal((uint8_t *)input.value + 57, output.value, output.length);
+
+ major_status = gss_release_buffer(&minor_status, &output);
+ assert_int_equal(GSS_S_COMPLETE, major_status);
+}
+
+int main(int argc, const char **argv)
+{
+ static const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(
+ test_unwrap_dce_style_missing_payload, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_unwrap_dce_style_valid, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_unwrap_dce_style_with_seal_missing_payload, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_unwrap_dce_style_with_seal_valid, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_unwrap_missing_8_bytes, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_unwrap_missing_payload, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_unwrap_truncated_header_0, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_unwrap_truncated_header_1, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_unwrap_valid, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_unwrap_with_padding_truncated_0, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_unwrap_with_padding_truncated_1, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_unwrap_with_padding_valid, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_unwrap_with_seal_empty_token_valid, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_unwrap_with_seal_missing_payload, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_unwrap_with_seal_valid, setup, teardown),
+ };
+
+ cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/source4/auth/tests/kerberos.c b/source4/auth/tests/kerberos.c
new file mode 100644
index 0000000..d9be356
--- /dev/null
+++ b/source4/auth/tests/kerberos.c
@@ -0,0 +1,123 @@
+#include <time.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <stdint.h>
+#include <cmocka.h>
+
+#include "includes.h"
+#include "system/kerberos.h"
+#include "auth/kerberos/kerberos.h"
+#include "auth/credentials/credentials.h"
+#include "auth/credentials/credentials_krb5.h"
+#include "auth/kerberos/kerberos_credentials.h"
+#include "auth/kerberos/kerberos_util.h"
+
+static void internal_obsolete_keytab_test(int num_principals, int num_kvnos,
+ krb5_kvno kvno, const char *kt_name)
+{
+ krb5_context krb5_ctx;
+ krb5_keytab keytab;
+ krb5_keytab_entry kt_entry;
+ krb5_kt_cursor cursor;
+ krb5_error_code code;
+
+ int i,j;
+ char princ_name[] = "user0";
+ char expect_princ_name[] = "user0@samba.example.com";
+ bool found_previous;
+ const char *error_str;
+
+ TALLOC_CTX *tmp_ctx = talloc_new(NULL);
+ krb5_principal *principals = talloc_zero_array(tmp_ctx,
+ krb5_principal,
+ num_principals);
+ krb5_init_context(&krb5_ctx);
+ krb5_kt_resolve(krb5_ctx, kt_name, &keytab);
+ ZERO_STRUCT(kt_entry);
+
+ for(i=0; i<num_principals; i++) {
+ princ_name[4] = (char)i+48;
+ smb_krb5_make_principal(krb5_ctx, &(principals[i]),
+ "samba.example.com", princ_name, NULL);
+ kt_entry.principal = principals[i];
+ for (j=0; j<num_kvnos; j++) {
+ kt_entry.vno = j+1;
+ krb5_kt_add_entry(krb5_ctx, keytab, &kt_entry);
+ }
+ }
+
+ code = krb5_kt_start_seq_get(krb5_ctx, keytab, &cursor);
+ assert_int_equal(code, 0);
+#ifdef SAMBA4_USES_HEIMDAL
+ for (i=0; i<num_principals; i++) {
+ expect_princ_name[4] = (char)i+48;
+ for (j=0; j<num_kvnos; j++) {
+ char *unparsed_name;
+ code = krb5_kt_next_entry(krb5_ctx, keytab,
+ &kt_entry, &cursor);
+ assert_int_equal(code, 0);
+ assert_int_equal(kt_entry.vno, j+1);
+#else
+ /* MIT - For MEMORY type keytabs, krb5_kt_add_entry() adds an
+ * entry to the beginning of the keytab table, not the end */
+ for (i=num_principals-1; i>=0; i--) {
+ expect_princ_name[4] = (char)i+48;
+ for (j=num_kvnos; j>0; j--) {
+ char *unparsed_name;
+ code = krb5_kt_next_entry(krb5_ctx, keytab,
+ &kt_entry, &cursor);
+ assert_int_equal(code, 0);
+ assert_int_equal(kt_entry.vno, j);
+#endif
+ krb5_unparse_name(krb5_ctx, kt_entry.principal,
+ &unparsed_name);
+ assert_string_equal(expect_princ_name, unparsed_name);
+ }
+ }
+
+ smb_krb5_remove_obsolete_keytab_entries(tmp_ctx, krb5_ctx, keytab,
+ num_principals, principals,
+ kvno, &found_previous,
+ &error_str);
+
+ code = krb5_kt_start_seq_get(krb5_ctx, keytab, &cursor);
+ assert_int_equal(code, 0);
+#ifdef SAMBA4_USES_HEIMDAL
+ for (i=0; i<num_principals; i++) {
+#else /* MIT - reverse iterate through entries */
+ for (i=num_principals-1; i>=0; i--) {
+#endif
+ char *unparsed_name;
+ expect_princ_name[4] = (char)i+48;
+ code = krb5_kt_next_entry(krb5_ctx, keytab, &kt_entry, &cursor);
+ assert_int_equal(code, 0);
+ assert_int_equal(kt_entry.vno, kvno-1);
+ krb5_unparse_name(krb5_ctx, kt_entry.principal, &unparsed_name);
+ assert_string_equal(expect_princ_name, unparsed_name);
+ }
+ code = krb5_kt_next_entry(krb5_ctx, keytab, &kt_entry, &cursor);
+ assert_int_not_equal(code, 0);
+}
+
+static void test_krb5_remove_obsolete_keytab_entries_many(void **state)
+{
+ internal_obsolete_keytab_test(5, 4, (krb5_kvno)5, "MEMORY:LOL2");
+}
+
+static void test_krb5_remove_obsolete_keytab_entries_one(void **state)
+{
+ internal_obsolete_keytab_test(1, 2, (krb5_kvno)3, "MEMORY:LOL");
+}
+
+int main(int argc, const char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_krb5_remove_obsolete_keytab_entries_one),
+ cmocka_unit_test(test_krb5_remove_obsolete_keytab_entries_many),
+ };
+
+ cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/source4/auth/tests/sam.c b/source4/auth/tests/sam.c
new file mode 100644
index 0000000..e1e2c69
--- /dev/null
+++ b/source4/auth/tests/sam.c
@@ -0,0 +1,2746 @@
+/*
+ * Unit tests for source4/auth/sam.c
+ *
+ * Copyright (C) Catalyst.NET Ltd 2021
+ *
+ * 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/>.
+ *
+ */
+
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * This allows test applications to use custom definitions of C standard
+ * library functions and types.
+ *
+ */
+
+#include <time.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <stdint.h>
+#include <cmocka.h>
+
+#include "includes.h"
+#include "auth/sam.c"
+#include "ldb.h"
+#include "ntstatus.h"
+#include "librpc/gen_ndr/ndr_security.h"
+
+/*****************************************************************************
+ * wrapped functions
+ *
+ *****************************************************************************/
+int __wrap_samdb_msg_add_int64(
+ struct ldb_context *sam_ldb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message *msg,
+ const char *attr_name,
+ int64_t v);
+int __real_samdb_msg_add_int64(
+ struct ldb_context *sam_ldb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message *msg,
+ const char *attr_name,
+ int64_t v);
+int __wrap_samdb_msg_add_int64(
+ struct ldb_context *sam_ldb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message *msg,
+ const char *attr_name,
+ int64_t v)
+{
+
+ int ret;
+ ret = (int)mock();
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return __real_samdb_msg_add_int64(sam_ldb, mem_ctx, msg, attr_name, v);
+}
+/*****************************************************************************
+ * Mock implementations
+ *****************************************************************************/
+
+static int check_dn(const LargestIntegralType left_value,
+ const LargestIntegralType right_value)
+{
+ /*
+ * We have to cast away const so we can get the linearized form with
+ * ldb_dn_get_extended_linearized().
+ */
+ struct ldb_dn *left_dn = (void *)left_value;
+ struct ldb_dn *right_dn = (void *)right_value;
+ char *left_dn_string = NULL;
+ char *right_dn_string = NULL;
+ bool ok;
+
+ if (left_dn == NULL && right_dn == NULL) {
+ return true;
+ }
+
+ if (left_dn != NULL) {
+ left_dn_string = ldb_dn_get_extended_linearized(NULL, left_dn, 1);
+ assert_non_null(left_dn_string);
+ }
+
+ if (right_dn != NULL) {
+ right_dn_string = ldb_dn_get_extended_linearized(NULL, right_dn, 1);
+ assert_non_null(right_dn_string);
+ }
+
+ if (left_dn_string == NULL || right_dn_string == NULL) {
+ ok = false;
+ print_error("\"%s\" != \"%s\"\n",
+ left_dn_string != NULL ? left_dn_string : "<NULL>",
+ right_dn_string != NULL ? right_dn_string : "<NULL>");
+ } else {
+ ok = (strcmp(left_dn_string, right_dn_string) == 0);
+ if (!ok) {
+ print_error("\"%s\" != \"%s\"\n",
+ left_dn_string,
+ right_dn_string);
+ }
+
+ }
+
+ TALLOC_FREE(right_dn_string);
+ TALLOC_FREE(left_dn_string);
+
+ return ok;
+}
+
+int __wrap_dsdb_search_dn(struct ldb_context *ldb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_result **_result,
+ struct ldb_dn *basedn,
+ const char * const *attrs,
+ uint32_t dsdb_flags);
+int __wrap_dsdb_search_dn(struct ldb_context *ldb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_result **_result,
+ struct ldb_dn *basedn,
+ const char * const *attrs,
+ uint32_t dsdb_flags)
+{
+ check_expected(basedn);
+
+ *_result = talloc_steal(mem_ctx, mock_ptr_type(struct ldb_result *));
+
+ return mock();
+}
+
+int ldb_transaction_start_ret = LDB_SUCCESS;
+bool in_transaction = false;
+int ldb_transaction_start(struct ldb_context *ldb) {
+ assert_false(in_transaction);
+ if (ldb_transaction_start_ret == LDB_SUCCESS) {
+ in_transaction = true;
+ }
+ return ldb_transaction_start_ret;
+}
+
+int ldb_transaction_cancel_ret = LDB_SUCCESS;
+bool transaction_cancelled = false;
+int ldb_transaction_cancel(struct ldb_context *ldb) {
+ assert_true(in_transaction);
+ if (ldb_transaction_cancel_ret == LDB_SUCCESS) {
+ in_transaction = false;
+ transaction_cancelled = true;
+ }
+ return ldb_transaction_cancel_ret;
+}
+
+int ldb_transaction_commit_ret = LDB_SUCCESS;
+bool transaction_committed = false;
+int ldb_transaction_commit(struct ldb_context *ldb) {
+ assert_true(in_transaction);
+ if (ldb_transaction_commit_ret == LDB_SUCCESS) {
+ in_transaction = false;
+ transaction_committed = true;
+ }
+ return ldb_transaction_commit_ret;
+}
+
+NTSTATUS dsdb_update_bad_pwd_count_ret = NT_STATUS_OK;
+struct ldb_message *dsdb_update_bad_pwd_count_res = NULL;
+NTSTATUS dsdb_update_bad_pwd_count(TALLOC_CTX *mem_ctx,
+ struct ldb_context *sam_ctx,
+ struct ldb_message *user_msg,
+ struct ldb_message *domain_msg,
+ struct ldb_message *pso_msg,
+ struct ldb_message **_mod_msg) {
+
+ *_mod_msg = talloc_move(mem_ctx, &dsdb_update_bad_pwd_count_res);
+ return dsdb_update_bad_pwd_count_ret;
+}
+
+int ldb_build_mod_req_ret = LDB_SUCCESS;
+struct ldb_request *ldb_build_mod_req_res = NULL;
+int ldb_build_mod_req(struct ldb_request **ret_req,
+ struct ldb_context *ldb,
+ TALLOC_CTX *mem_ctx,
+ const struct ldb_message *message,
+ struct ldb_control **controls,
+ void *context,
+ ldb_request_callback_t callback,
+ struct ldb_request *parent)
+{
+ *ret_req = talloc_move(mem_ctx, &ldb_build_mod_req_res);
+ return ldb_build_mod_req_ret;
+}
+
+int ldb_request_add_control_ret = LDB_SUCCESS;
+int ldb_request_add_control(struct ldb_request *req,
+ const char *oid,
+ bool critical,
+ void *data)
+{
+ return ldb_request_add_control_ret;
+}
+
+int ldb_request_ret = LDB_SUCCESS;
+int ldb_request(struct ldb_context *ldb,
+ struct ldb_request *req)
+{
+ return ldb_request_ret;
+}
+
+int ldb_wait_ret = LDB_SUCCESS;
+int ldb_wait(struct ldb_handle *handle,
+ enum ldb_wait_type type)
+{
+ return ldb_wait_ret;
+}
+bool ldb_msg_new_fail = false;
+struct ldb_message *ldb_msg_new(TALLOC_CTX *mem_ctx)
+{
+ if (ldb_msg_new_fail) {
+ return NULL;
+ } else {
+ return talloc_zero(mem_ctx, struct ldb_message);
+ }
+}
+
+int samdb_rodc_ret = LDB_SUCCESS;
+bool samdb_rodc_res = false;
+
+int samdb_rodc(
+ struct ldb_context *sam_ctx,
+ bool *am_rodc)
+{
+
+ *am_rodc = samdb_rodc_res;
+ return samdb_rodc_ret;
+}
+
+struct loadparm_context *ldb_get_opaque_ret = NULL;
+void *ldb_get_opaque(struct ldb_context *ldb, const char *name)
+{
+ return ldb_get_opaque_ret;
+}
+
+struct db_context {};
+struct db_context *cluster_db_tmp_open_ret = NULL;
+struct db_context *cluster_db_tmp_open(
+ TALLOC_CTX *mem_ctx,
+ struct loadparm_context *lp_ctx,
+ const char *dbbase,
+ int flags)
+{
+ return cluster_db_tmp_open_ret;
+}
+
+NTSTATUS dbwrap_store_ret = NT_STATUS_OK;
+NTSTATUS dbwrap_store(struct db_context *db, TDB_DATA key,
+ TDB_DATA data, int flags)
+{
+ return dbwrap_store_ret;
+}
+bool dbwrap_exists_ret = true;
+
+bool dbwrap_exists(struct db_context *db, TDB_DATA key)
+{
+ return dbwrap_exists_ret;
+}
+
+NTSTATUS dbwrap_delete_ret = NT_STATUS_OK;
+NTSTATUS dbwrap_delete(struct db_context *db, TDB_DATA key)
+{
+ return dbwrap_delete_ret;
+}
+
+/*
+ * Set the globals used by the mocked functions to a known and consistent state
+ *
+ */
+static void init_mock_results(TALLOC_CTX *mem_ctx)
+{
+ ldb_transaction_start_ret = LDB_SUCCESS;
+ in_transaction = false;
+
+ ldb_transaction_cancel_ret = LDB_SUCCESS;
+ transaction_cancelled = false;
+
+ ldb_transaction_commit_ret = LDB_SUCCESS;
+ transaction_committed = false;
+
+ dsdb_update_bad_pwd_count_ret = NT_STATUS_OK;
+ dsdb_update_bad_pwd_count_res = NULL;
+
+ ldb_build_mod_req_ret = LDB_SUCCESS;
+ ldb_build_mod_req_res = NULL;
+
+ ldb_request_add_control_ret = LDB_SUCCESS;
+ ldb_request_ret = LDB_SUCCESS;
+ ldb_wait_ret = LDB_SUCCESS;
+
+ ldb_msg_new_fail = false;
+
+ samdb_rodc_ret = LDB_SUCCESS;
+ samdb_rodc_res = false;
+
+ ldb_get_opaque_ret = loadparm_init(mem_ctx);
+
+ cluster_db_tmp_open_ret = talloc_zero(mem_ctx, struct db_context);
+
+ dbwrap_store_ret = NT_STATUS_OK;
+
+ dbwrap_exists_ret = true;
+
+ dbwrap_delete_ret = NT_STATUS_OK;
+
+}
+
+/*****************************************************************************
+ * Unit test set up and tear down
+ *****************************************************************************/
+struct context {
+};
+
+static int setup(void **state) {
+ struct context *ctx = talloc_zero(NULL, struct context);
+ init_mock_results(ctx);
+
+ *state = ctx;
+ return 0;
+}
+
+static int teardown(void **state) {
+ struct context *ctx = *state;
+ TALLOC_FREE(ctx);
+ return 0;
+}
+
+/******************************************************************************
+ * Helper functions
+ ******************************************************************************/
+
+/*
+ * Build the "Original" user details record, i.e. the user being
+ * authenticated
+ */
+static struct ldb_message *create_message(TALLOC_CTX *ctx)
+{
+
+ int ret;
+ struct timeval tv_now = timeval_current();
+ NTTIME now = timeval_to_nttime(&tv_now);
+
+ struct ldb_message *msg = ldb_msg_new(ctx);
+
+ assert_non_null(msg);
+ ret = samdb_msg_add_int(ctx, msg, msg, "badPwdCount", 10);
+ assert_int_equal(LDB_SUCCESS, ret);
+ ret = __real_samdb_msg_add_int64(ctx, msg, msg, "badPasswordTime", now);
+ assert_int_equal(LDB_SUCCESS, ret);
+ ret = __real_samdb_msg_add_int64(ctx, msg, msg, "lockoutTime", now);
+ assert_int_equal(LDB_SUCCESS, ret);
+ return msg;
+}
+
+/*
+ * Add a binary objectSID from string form to the supplied message
+ *
+ *
+ */
+static void add_sid(
+ struct ldb_message *msg,
+ const char *sid_str)
+{
+ struct ldb_val v;
+ enum ndr_err_code ndr_err;
+ struct dom_sid *sid = NULL;
+
+ sid = talloc_zero(msg, struct dom_sid);
+ assert_non_null(sid);
+ assert_true(string_to_sid(sid, sid_str));
+ ndr_err = ndr_push_struct_blob(
+ &v, msg, sid, (ndr_push_flags_fn_t)ndr_push_dom_sid);
+ assert_true(NDR_ERR_CODE_IS_SUCCESS(ndr_err));
+ assert_int_equal(0, ldb_msg_add_value(msg, "objectSID", &v, NULL));
+}
+
+/*
+ * Build an ldb_result, for the re-reading of a user record
+ *
+ * if account_control < 0 then the msDS-User-Account-Control-Computed
+ * element is not included
+ * otherwise it is set to the value passed in account_control.
+ *
+ */
+static struct ldb_result *build_reread_result(
+ struct ldb_context *ldb,
+ TALLOC_CTX *ctx,
+ int account_control)
+{
+ struct ldb_message *msg = NULL;
+ int ret;
+
+ struct ldb_result *res = talloc_zero(ctx, struct ldb_result);
+
+ assert_non_null(res);
+ res->count = 1;
+ res->msgs = talloc_array(res, struct ldb_message *, 1);
+
+ msg = create_message(res);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000");
+ if (account_control >= 0) {
+ ret = samdb_msg_add_int(
+ ldb,
+ msg,
+ msg,
+ "msDS-User-Account-Control-Computed",
+ account_control);
+ assert_int_equal(LDB_SUCCESS, ret);
+ }
+
+ res->msgs[0] = msg;
+ return res;
+}
+
+/*
+ * Build a mock domain pso ldb_result
+ */
+static struct ldb_result *build_domain_pso_result(
+ struct ldb_context *ldb,
+ TALLOC_CTX *ctx)
+{
+ struct ldb_message *msg = NULL;
+ struct ldb_result *res = talloc_zero(ctx, struct ldb_result);
+
+ assert_non_null(res);
+ res->count = 1;
+ res->msgs = talloc_array(res, struct ldb_message *, 1);
+ assert_non_null(res->msgs);
+ msg = talloc_zero(res, struct ldb_message);
+ assert_non_null(msg);
+ res->msgs[0] = msg;
+ return res;
+}
+
+/*****************************************************************************
+ * authsam_reread_user_logon_data unit tests
+ *****************************************************************************/
+/*
+ * authsam_reread_user_logon_data unable to re-read the user record.
+ *
+ */
+static void test_reread_read_failure(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_message *cur = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000");
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ before = talloc_total_size(ctx);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, msg->dn);
+ will_return(__wrap_dsdb_search_dn, NULL);
+ will_return(__wrap_dsdb_search_dn, LDB_ERR_NO_SUCH_OBJECT);
+
+ status = authsam_reread_user_logon_data(ldb, ctx, msg, &cur);
+ assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR));
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * authsam_reread_user_logon_data account control flags missing from
+ * re-read data
+ *
+ */
+static void test_reread_missing_account_control(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_message *cur = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000");
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ before = talloc_total_size(ctx);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, msg->dn);
+ will_return(__wrap_dsdb_search_dn, build_reread_result(ldb, ctx, -1));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ status = authsam_reread_user_logon_data(ldb, ctx, msg, &cur);
+ assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR));
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * authsam_reread_user_logon_data account locked
+ * re-read data
+ *
+ */
+static void test_reread_account_locked(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_message *cur = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000");
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ before = talloc_total_size(ctx);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, msg->dn);
+ will_return(__wrap_dsdb_search_dn, build_reread_result(ldb, ctx, UF_LOCKOUT));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ status = authsam_reread_user_logon_data(ldb, ctx, msg, &cur);
+ assert_true(NT_STATUS_EQUAL(status, NT_STATUS_ACCOUNT_LOCKED_OUT));
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * authsam_reread_user_logon_data account is not locked
+ * re-read data
+ *
+ */
+static void test_reread_account_not_locked(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_message *cur = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ size_t result_size = 0;
+ NTSTATUS status;
+ struct ldb_result *res = NULL;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000");
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, msg->dn);
+ /*
+ * authsam_reread_user_logon_data returns the ldb_message portion
+ * of the ldb_result created by build_reread_result.
+ * So the tests for memory leaks will need to adjust for that
+ */
+ res = build_reread_result(ldb, ctx, 0);
+ will_return(__wrap_dsdb_search_dn, res);
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ result_size = talloc_total_size(res) -
+ talloc_total_size(res->msgs[0]);
+ before = talloc_total_size(ctx) - result_size;
+
+ status = authsam_reread_user_logon_data(ldb, ctx, msg, &cur);
+ assert_true(NT_STATUS_IS_OK(status));
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+
+/*****************************************************************************
+ * authsam_update_bad_pwd_count unit tests
+ *****************************************************************************/
+
+/*
+ * authsam_update_bad_pwd_account
+ *
+ * Unable to read the domain_dn record
+ *
+ */
+static void test_update_bad_domain_dn_search_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *domain_dn = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ domain_dn = ldb_dn_new(ctx, ldb, "CN=Domain");
+ assert_non_null(domain_dn);
+
+ msg = talloc_zero(ctx, struct ldb_message);
+ assert_non_null(msg);
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, domain_dn);
+ will_return(__wrap_dsdb_search_dn, NULL);
+ will_return(__wrap_dsdb_search_dn, LDB_ERR_NO_SUCH_OBJECT);
+
+ before = talloc_total_size(ctx);
+
+ status = authsam_update_bad_pwd_count(ldb, msg, domain_dn);
+ assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_DB_CORRUPTION));
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * authsam_update_bad_pwd_account
+ *
+ * authsam_get_user_pso failure
+ *
+ */
+static void test_update_bad_get_pso_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *domain_dn = NULL;
+ struct ldb_dn *pso_dn = NULL;
+ const char *pso_dn_str = "CN=PSO";
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+ int ret;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ domain_dn = ldb_dn_new(ctx, ldb, "CN=Domain");
+ assert_non_null(domain_dn);
+
+ pso_dn = ldb_dn_new(ctx, ldb, pso_dn_str);
+ assert_non_null(pso_dn);
+
+ msg = talloc_zero(ctx, struct ldb_message);
+ assert_non_null(msg);
+ ret = ldb_msg_add_string(msg, "msDS-ResultantPSO", pso_dn_str);
+ assert_int_equal(LDB_SUCCESS, ret);
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ before = talloc_total_size(ctx);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, domain_dn);
+ will_return(__wrap_dsdb_search_dn, build_domain_pso_result(ldb, ctx));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, pso_dn);
+ will_return(__wrap_dsdb_search_dn, NULL);
+ will_return(__wrap_dsdb_search_dn, LDB_ERR_NO_SUCH_OBJECT);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, msg->dn);
+ will_return(__wrap_dsdb_search_dn, build_reread_result(ldb, ctx, 0));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ status = authsam_update_bad_pwd_count(ldb, msg, domain_dn);
+ assert_true(NT_STATUS_IS_OK(status));
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+
+/*
+ * authsam_update_bad_pwd_account
+ *
+ * start_transaction failure
+ *
+ */
+static void test_update_bad_start_txn_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *domain_dn = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ domain_dn = ldb_dn_new(ctx, ldb, "CN=Domain");
+ assert_non_null(domain_dn);
+
+ msg = talloc_zero(ctx, struct ldb_message);
+ assert_non_null(msg);
+
+ before = talloc_total_size(ctx);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, domain_dn);
+ will_return(__wrap_dsdb_search_dn, build_domain_pso_result(ldb, ctx));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ ldb_transaction_start_ret = LDB_ERR_OPERATIONS_ERROR;
+
+ status = authsam_update_bad_pwd_count(ldb, msg, domain_dn);
+ assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR));
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * authsam_update_bad_pwd_account
+ *
+ * User details re-read failed
+ *
+ */
+static void test_update_bad_reread_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *domain_dn = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ domain_dn = ldb_dn_new(ctx, ldb, "CN=Domain");
+ assert_non_null(domain_dn);
+
+ msg = talloc_zero(ctx, struct ldb_message);
+ assert_non_null(msg);
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ before = talloc_total_size(ctx);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, domain_dn);
+ will_return(__wrap_dsdb_search_dn, build_domain_pso_result(ldb, ctx));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, msg->dn);
+ will_return(__wrap_dsdb_search_dn, NULL);
+ will_return(__wrap_dsdb_search_dn, LDB_ERR_NO_SUCH_OBJECT);
+
+ status = authsam_update_bad_pwd_count(ldb, msg, domain_dn);
+ assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR));
+ assert_true(transaction_cancelled);
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * authsam_update_bad_pwd_account
+ *
+ * User details re-read reported locked out.
+ *
+ */
+static void test_update_bad_reread_locked_out(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *domain_dn = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ domain_dn = ldb_dn_new(ctx, ldb, "CN=Domain");
+ assert_non_null(domain_dn);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000");
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ before = talloc_total_size(ctx);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, domain_dn);
+ will_return(__wrap_dsdb_search_dn, build_domain_pso_result(ldb, ctx));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, msg->dn);
+ will_return(__wrap_dsdb_search_dn, build_reread_result(ldb, ctx, UF_LOCKOUT));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ status = authsam_update_bad_pwd_count(ldb, msg, domain_dn);
+ assert_true(NT_STATUS_EQUAL(status, NT_STATUS_ACCOUNT_LOCKED_OUT));
+ assert_false(transaction_cancelled);
+ assert_true(transaction_committed);
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * authsam_update_bad_pwd_account
+ *
+ * Transaction cancel failure
+ */
+static void test_update_bad_txn_cancel_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *domain_dn = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ domain_dn = ldb_dn_new(ctx, ldb, "CN=Domain");
+ assert_non_null(domain_dn);
+
+ msg = talloc_zero(ctx, struct ldb_message);
+ assert_non_null(msg);
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ before = talloc_total_size(ctx);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, domain_dn);
+ will_return(__wrap_dsdb_search_dn, build_domain_pso_result(ldb, ctx));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, msg->dn);
+ will_return(__wrap_dsdb_search_dn, NULL);
+ will_return(__wrap_dsdb_search_dn, LDB_ERR_NO_SUCH_OBJECT);
+
+ ldb_transaction_cancel_ret = LDB_ERR_OPERATIONS_ERROR;
+
+ status = authsam_update_bad_pwd_count(ldb, msg, domain_dn);
+ assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR));
+ assert_true(in_transaction);
+ assert_false(transaction_cancelled);
+ assert_false(transaction_committed);
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * The following tests all expect the same setup, that is a normal
+ * good user object and empty domain object.
+ *
+ * returns the talloc size after result array setup for leak tests
+ */
+static size_t setup_bad_password_search_results(TALLOC_CTX *ctx,
+ struct ldb_context *ldb,
+ struct ldb_dn *domain_dn,
+ struct ldb_dn *user_dn)
+{
+ size_t before = 0;
+
+ before = talloc_total_size(ctx);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, domain_dn);
+ will_return(__wrap_dsdb_search_dn, build_domain_pso_result(ldb, ctx));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, user_dn);
+ will_return(__wrap_dsdb_search_dn, build_reread_result(ldb, ctx, 0));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ return before;
+}
+
+
+/*
+ * authsam_update_bad_pwd_account
+ *
+ * dsdb_update_bad_pwd_count failure
+ *
+ */
+static void test_update_bad_update_count_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *domain_dn = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ domain_dn = ldb_dn_new(ctx, ldb, "CN=Domain");
+ assert_non_null(domain_dn);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000");
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ before = setup_bad_password_search_results(ctx, ldb,
+ domain_dn,
+ msg->dn);
+
+ dsdb_update_bad_pwd_count_ret = NT_STATUS_INTERNAL_ERROR;
+
+ status = authsam_update_bad_pwd_count(ldb, msg, domain_dn);
+ assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR));
+ assert_true(transaction_cancelled);
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * authsam_update_bad_pwd_account
+ *
+ * No need to update the bad password stats
+ *
+ */
+static void test_update_bad_no_update_required(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *domain_dn = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ domain_dn = ldb_dn_new(ctx, ldb, "CN=Domain");
+ assert_non_null(domain_dn);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000");
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ before = setup_bad_password_search_results(ctx, ldb,
+ domain_dn,
+ msg->dn);
+
+ status = authsam_update_bad_pwd_count(ldb, msg, domain_dn);
+ assert_true(NT_STATUS_IS_OK(status));
+ assert_true(transaction_committed);
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * authsam_update_bad_pwd_account
+ *
+ * Transaction commit failure
+ *
+ */
+static void test_update_bad_commit_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *domain_dn = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ domain_dn = ldb_dn_new(ctx, ldb, "CN=Domain");
+ assert_non_null(domain_dn);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000");
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ before = setup_bad_password_search_results(ctx, ldb,
+ domain_dn,
+ msg->dn);
+
+ ldb_transaction_commit_ret = LDB_ERR_OPERATIONS_ERROR;
+
+ status = authsam_update_bad_pwd_count(ldb, msg, domain_dn);
+ assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR));
+ assert_true(in_transaction);
+ assert_false(transaction_cancelled);
+ assert_false(transaction_committed);
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * authsam_update_bad_pwd_account
+ *
+ * ldb_build_mod_req failed building the user update details
+ *
+ */
+static void test_update_bad_build_mod_request_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *domain_dn = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ domain_dn = ldb_dn_new(ctx, ldb, "CN=Domain");
+ assert_non_null(domain_dn);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000");
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ before = setup_bad_password_search_results(ctx, ldb,
+ domain_dn,
+ msg->dn);
+
+ dsdb_update_bad_pwd_count_res = talloc_zero(ctx, struct ldb_message);
+ ldb_build_mod_req_ret = LDB_ERR_OPERATIONS_ERROR;
+
+ status = authsam_update_bad_pwd_count(ldb, msg, domain_dn);
+ assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR));
+ assert_true(transaction_cancelled);
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * authsam_update_bad_pwd_account
+ *
+ * ldb_request_add_control failed to add DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE
+ * to the user update record.
+ *
+ */
+static void test_update_bad_add_control_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *domain_dn = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ domain_dn = ldb_dn_new(ctx, ldb, "CN=Domain");
+ assert_non_null(domain_dn);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000");
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ before = setup_bad_password_search_results(ctx, ldb,
+ domain_dn,
+ msg->dn);
+
+ dsdb_update_bad_pwd_count_res = talloc_zero(ctx, struct ldb_message);
+ ldb_build_mod_req_res = talloc_zero(ctx, struct ldb_request);
+ ldb_request_add_control_ret = LDB_ERR_OPERATIONS_ERROR;
+
+ status = authsam_update_bad_pwd_count(ldb, msg, domain_dn);
+ assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR));
+ assert_true(transaction_cancelled);
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * authsam_update_bad_pwd_account
+ *
+ * call to ldb_request failed
+ *
+ */
+static void test_update_bad_ldb_request_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *domain_dn = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ domain_dn = ldb_dn_new(ctx, ldb, "CN=Domain");
+ assert_non_null(domain_dn);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000");
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ before = setup_bad_password_search_results(ctx, ldb,
+ domain_dn,
+ msg->dn);
+
+ dsdb_update_bad_pwd_count_res = talloc_zero(ctx, struct ldb_message);
+ ldb_build_mod_req_res = talloc_zero(ctx, struct ldb_request);
+ ldb_request_ret = LDB_ERR_OPERATIONS_ERROR;
+
+ status = authsam_update_bad_pwd_count(ldb, msg, domain_dn);
+ assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR));
+ assert_true(transaction_cancelled);
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * authsam_update_bad_pwd_account
+ *
+ * call to ldb_wait failed
+ *
+ */
+static void test_update_bad_ldb_wait_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *domain_dn = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ domain_dn = ldb_dn_new(ctx, ldb, "CN=Domain");
+ assert_non_null(domain_dn);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000");
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ before = setup_bad_password_search_results(ctx, ldb,
+ domain_dn,
+ msg->dn);
+
+ dsdb_update_bad_pwd_count_res = talloc_zero(ctx, struct ldb_message);
+ ldb_build_mod_req_res = talloc_zero(ctx, struct ldb_request);
+ ldb_wait_ret = LDB_ERR_OPERATIONS_ERROR;
+
+ status = authsam_update_bad_pwd_count(ldb, msg, domain_dn);
+ assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR));
+ assert_true(transaction_cancelled);
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*****************************************************************************
+ * authsam_logon_success_accounting unit tests
+ *****************************************************************************/
+/*
+ * authsam_logon_success_accounting
+ *
+ * start_transaction failure
+ *
+ */
+static void test_success_accounting_start_txn_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *domain_dn = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ domain_dn = ldb_dn_new(ctx, ldb, "CN=Domain");
+ assert_non_null(domain_dn);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000");
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ before = talloc_total_size(ctx);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, domain_dn);
+ will_return(__wrap_dsdb_search_dn, build_domain_pso_result(ldb, ctx));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ ldb_transaction_start_ret = LDB_ERR_OPERATIONS_ERROR;
+
+ status = authsam_logon_success_accounting(
+ ldb, msg, domain_dn, true, NULL, NULL);
+ assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR));
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * authsam_logon_success_accounting
+ *
+ * User details re-read failed
+ *
+ */
+static void test_success_accounting_reread_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *domain_dn = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ domain_dn = ldb_dn_new(ctx, ldb, "CN=Domain");
+ assert_non_null(domain_dn);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000");
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ before = talloc_total_size(ctx);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, domain_dn);
+ will_return(__wrap_dsdb_search_dn, build_domain_pso_result(ldb, ctx));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, msg->dn);
+ will_return(__wrap_dsdb_search_dn, NULL);
+ will_return(__wrap_dsdb_search_dn, LDB_ERR_NO_SUCH_OBJECT);
+
+ status = authsam_logon_success_accounting(
+ ldb, msg, domain_dn, true, NULL, NULL);
+ assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR));
+ assert_true(transaction_cancelled);
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * authsam_logon_success_accounting
+ *
+ * ldb_msg_new failed
+ *
+ */
+static void test_success_accounting_ldb_msg_new_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *domain_dn = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ domain_dn = ldb_dn_new(ctx, ldb, "CN=Domain");
+ assert_non_null(domain_dn);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000");
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ before = talloc_total_size(ctx);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, domain_dn);
+ will_return(__wrap_dsdb_search_dn, build_domain_pso_result(ldb, ctx));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, msg->dn);
+ will_return(__wrap_dsdb_search_dn, build_reread_result(ldb, ctx, 0));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ ldb_msg_new_fail = true;
+
+ status = authsam_logon_success_accounting(
+ ldb, msg, domain_dn, true, NULL, NULL);
+ assert_true(NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY));
+ assert_true(transaction_cancelled);
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * authsam_logon_success_accounting
+ *
+ * samdb_rodc failed
+ *
+ */
+static void test_success_accounting_samdb_rodc_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *domain_dn = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ domain_dn = ldb_dn_new(ctx, ldb, "CN=Domain");
+ assert_non_null(domain_dn);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000");
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ before = talloc_total_size(ctx);
+
+ samdb_rodc_ret = LDB_ERR_OPERATIONS_ERROR;
+
+ status = authsam_logon_success_accounting(
+ ldb, msg, domain_dn, true, NULL, NULL);
+ assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR));
+ assert_false(in_transaction);
+ assert_false(transaction_cancelled);
+ assert_false(transaction_committed);
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * authsam_logon_success_accounting
+ *
+ * authsam_update_lastlogon_timestamp failed
+ *
+ */
+static void test_success_accounting_update_lastlogon_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *domain_dn = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ domain_dn = ldb_dn_new(ctx, ldb, "CN=Domain");
+ assert_non_null(domain_dn);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000");
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ ldb_build_mod_req_res = talloc_zero(ctx, struct ldb_request);
+
+ before = talloc_total_size(ctx);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, domain_dn);
+ will_return(__wrap_dsdb_search_dn, build_domain_pso_result(ldb, ctx));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, msg->dn);
+ will_return(__wrap_dsdb_search_dn, build_reread_result(ldb, ctx, 0));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ will_return(__wrap_samdb_msg_add_int64, LDB_ERR_OPERATIONS_ERROR);
+
+ status = authsam_logon_success_accounting(
+ ldb, msg, domain_dn, true, NULL, NULL);
+ assert_true(NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY));
+ assert_true(transaction_cancelled);
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * authsam_logon_success_accounting
+ *
+ * ldb_build_mod_req failed
+ *
+ */
+static void test_success_accounting_build_mod_req_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *domain_dn = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ domain_dn = ldb_dn_new(ctx, ldb, "CN=Domain");
+ assert_non_null(domain_dn);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000");
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ before = talloc_total_size(ctx);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, domain_dn);
+ will_return(__wrap_dsdb_search_dn, build_domain_pso_result(ldb, ctx));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, msg->dn);
+ will_return(__wrap_dsdb_search_dn, build_reread_result(ldb, ctx, 0));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ ldb_build_mod_req_ret = LDB_ERR_OPERATIONS_ERROR;
+
+ will_return(__wrap_samdb_msg_add_int64, LDB_SUCCESS);
+ will_return(__wrap_samdb_msg_add_int64, LDB_SUCCESS);
+
+ status = authsam_logon_success_accounting(
+ ldb, msg, domain_dn, true, NULL, NULL);
+ assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR));
+ assert_true(transaction_cancelled);
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * authsam_logon_success_accounting
+ *
+ * ldb_request_add_control failed
+ *
+ */
+static void test_success_accounting_add_control_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *domain_dn = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ domain_dn = ldb_dn_new(ctx, ldb, "CN=Domain");
+ assert_non_null(domain_dn);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000");
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ before = talloc_total_size(ctx);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, domain_dn);
+ will_return(__wrap_dsdb_search_dn, build_domain_pso_result(ldb, ctx));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, msg->dn);
+ will_return(__wrap_dsdb_search_dn, build_reread_result(ldb, ctx, 0));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ ldb_build_mod_req_res = talloc_zero(ldb, struct ldb_request);
+ ldb_request_add_control_ret = LDB_ERR_OPERATIONS_ERROR;
+
+ will_return(__wrap_samdb_msg_add_int64, LDB_SUCCESS);
+ will_return(__wrap_samdb_msg_add_int64, LDB_SUCCESS);
+
+ status = authsam_logon_success_accounting(
+ ldb, msg, domain_dn, true, NULL, NULL);
+ assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR));
+ assert_true(transaction_cancelled);
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * authsam_logon_success_accounting
+ *
+ * ldb_request failed
+ *
+ */
+static void test_success_accounting_ldb_request_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *domain_dn = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ domain_dn = ldb_dn_new(ctx, ldb, "CN=Domain");
+ assert_non_null(domain_dn);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000");
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ before = talloc_total_size(ctx);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, domain_dn);
+ will_return(__wrap_dsdb_search_dn, build_domain_pso_result(ldb, ctx));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, msg->dn);
+ will_return(__wrap_dsdb_search_dn, build_reread_result(ldb, ctx, 0));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ ldb_build_mod_req_res = talloc_zero(ldb, struct ldb_request);
+ ldb_request_ret = LDB_ERR_OPERATIONS_ERROR;
+
+ will_return(__wrap_samdb_msg_add_int64, LDB_SUCCESS);
+ will_return(__wrap_samdb_msg_add_int64, LDB_SUCCESS);
+
+ status = authsam_logon_success_accounting(
+ ldb, msg, domain_dn, true, NULL, NULL);
+ assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR));
+ assert_true(transaction_cancelled);
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * authsam_logon_success_accounting
+ *
+ * ldb_wait failed
+ *
+ */
+static void test_success_accounting_ldb_wait_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *domain_dn = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ domain_dn = ldb_dn_new(ctx, ldb, "CN=Domain");
+ assert_non_null(domain_dn);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000");
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ before = talloc_total_size(ctx);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, domain_dn);
+ will_return(__wrap_dsdb_search_dn, build_domain_pso_result(ldb, ctx));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, msg->dn);
+ will_return(__wrap_dsdb_search_dn, build_reread_result(ldb, ctx, 0));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ ldb_build_mod_req_res = talloc_zero(ldb, struct ldb_request);
+ ldb_wait_ret = LDB_ERR_OPERATIONS_ERROR;
+
+ will_return(__wrap_samdb_msg_add_int64, LDB_SUCCESS);
+ will_return(__wrap_samdb_msg_add_int64, LDB_SUCCESS);
+
+ status = authsam_logon_success_accounting(
+ ldb, msg, domain_dn, true, NULL, NULL);
+ assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR));
+ assert_true(transaction_cancelled);
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * authsam_logon_success_accounting
+ *
+ * ldb_transaction_commit failed
+ *
+ */
+static void test_success_accounting_commit_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *domain_dn = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ domain_dn = ldb_dn_new(ctx, ldb, "CN=Domain");
+ assert_non_null(domain_dn);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000");
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ before = talloc_total_size(ctx);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, domain_dn);
+ will_return(__wrap_dsdb_search_dn, build_domain_pso_result(ldb, ctx));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, msg->dn);
+ will_return(__wrap_dsdb_search_dn, build_reread_result(ldb, ctx, 0));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ ldb_build_mod_req_res = talloc_zero(ldb, struct ldb_request);
+ ldb_transaction_commit_ret = LDB_ERR_OPERATIONS_ERROR;
+
+ will_return(__wrap_samdb_msg_add_int64, LDB_SUCCESS);
+ will_return(__wrap_samdb_msg_add_int64, LDB_SUCCESS);
+
+ status = authsam_logon_success_accounting(
+ ldb, msg, domain_dn, true, NULL, NULL);
+ assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR));
+ assert_true(in_transaction);
+ assert_false(transaction_cancelled);
+ assert_false(transaction_committed);
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * authsam_logon_success_accounting
+ *
+ * ldb_wait failed and then ldb_transaction_cancel failed
+ *
+ */
+static void test_success_accounting_rollback_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *domain_dn = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ domain_dn = ldb_dn_new(ctx, ldb, "CN=Domain");
+ assert_non_null(domain_dn);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000");
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ before = talloc_total_size(ctx);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, domain_dn);
+ will_return(__wrap_dsdb_search_dn, build_domain_pso_result(ldb, ctx));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, msg->dn);
+ will_return(__wrap_dsdb_search_dn, build_reread_result(ldb, ctx, 0));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ ldb_build_mod_req_res = talloc_zero(ldb, struct ldb_request);
+ ldb_wait_ret = LDB_ERR_OPERATIONS_ERROR;
+ ldb_transaction_cancel_ret = LDB_ERR_OPERATIONS_ERROR;
+
+ will_return(__wrap_samdb_msg_add_int64, LDB_SUCCESS);
+ will_return(__wrap_samdb_msg_add_int64, LDB_SUCCESS);
+
+ status = authsam_logon_success_accounting(
+ ldb, msg, domain_dn, true, NULL, NULL);
+ assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR));
+ assert_true(in_transaction);
+ assert_false(transaction_cancelled);
+ assert_false(transaction_committed);
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * authsam_logon_success_accounting
+ *
+ * The bad password indicator is set, but the account is not locked out.
+ *
+ */
+static void test_success_accounting_spurious_bad_pwd_indicator(void **state) {
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *domain_dn = NULL;
+ TALLOC_CTX *ctx = NULL;
+ size_t before = 0;
+ size_t after = 0;
+ NTSTATUS status;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ domain_dn = ldb_dn_new(ctx, ldb, "CN=Domain");
+ assert_non_null(domain_dn);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1000");
+
+ msg->dn = ldb_dn_new(ctx, ldb, "CN=User");
+ assert_non_null(msg->dn);
+
+ before = talloc_total_size(ctx);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, domain_dn);
+ will_return(__wrap_dsdb_search_dn, build_domain_pso_result(ldb, ctx));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ expect_check(__wrap_dsdb_search_dn, basedn, check_dn, msg->dn);
+ will_return(__wrap_dsdb_search_dn, build_reread_result(ldb, ctx, 0));
+ will_return(__wrap_dsdb_search_dn, LDB_SUCCESS);
+
+ will_return_count(__wrap_samdb_msg_add_int64, LDB_SUCCESS, 2);
+
+ /*
+ * Set the bad password indicator.
+ */
+ status = authsam_set_bad_password_indicator(ldb, ctx, msg);
+ assert_true(NT_STATUS_EQUAL(NT_STATUS_OK, status));
+
+ ldb_build_mod_req_res = talloc_zero(ctx, struct ldb_request);
+
+ status = authsam_logon_success_accounting(
+ ldb, msg, domain_dn, true, NULL, NULL);
+ assert_true(NT_STATUS_EQUAL(status, NT_STATUS_OK));
+ assert_false(in_transaction);
+ assert_false(transaction_cancelled);
+ assert_true(transaction_committed);
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * get_bad_password_db
+ *
+ * ldb_get_opaque failure.
+ */
+static void test_get_bad_password_get_opaque_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ TALLOC_CTX *ctx = NULL;
+ struct db_context *db = NULL;
+ size_t before = 0;
+ size_t after = 0;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ /*
+ * clear the mock ldb_get_opaque return value, so that we get a null
+ * response.
+ */
+ TALLOC_FREE(ldb_get_opaque_ret);
+
+ before = talloc_total_size(ctx);
+
+ db = authsam_get_bad_password_db(ctx, ldb);
+ assert_null(db);
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * get_bad_password_db
+ *
+ * cluster_db_tmp_open failure.
+ */
+static void test_get_bad_password_db_open_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ TALLOC_CTX *ctx = NULL;
+ struct db_context *db = NULL;
+ size_t before = 0;
+ size_t after = 0;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ /*
+ * Clear the mock cluster_db_tmp_open return value so that
+ * it returns NULL
+ */
+ TALLOC_FREE(cluster_db_tmp_open_ret);
+ before = talloc_total_size(ctx);
+
+ db = authsam_get_bad_password_db(ctx, ldb);
+ assert_null(db);
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * set_bad_password_indicator
+ *
+ * set_bad_password_indicator failure.
+ */
+static void test_set_bad_password_indicator_get_db_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ TALLOC_CTX *ctx = NULL;
+ NTSTATUS status;
+ size_t before = 0;
+ size_t after = 0;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ /*
+ * Clear the mock cluster_db_tmp_open return value so that
+ * it returns NULL
+ */
+ TALLOC_FREE(cluster_db_tmp_open_ret);
+ before = talloc_total_size(ctx);
+
+ status = authsam_set_bad_password_indicator(ldb, ctx, NULL);
+ assert_true(NT_STATUS_EQUAL(NT_STATUS_INTERNAL_ERROR, status));
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * set_bad_password_indicator
+ *
+ * get_object_sid_as_tdb_data failure.
+ */
+static void test_set_bad_password_indicator_get_object_sid_failed(
+ void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ TALLOC_CTX *ctx = NULL;
+ NTSTATUS status;
+ size_t before = 0;
+ size_t after = 0;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ /*
+ * The created message does not contain an objectSid, so
+ * get_object_sid_as_tdb_data will fail.
+ */
+ msg = create_message(ctx);
+
+ before = talloc_total_size(ctx);
+
+ status = authsam_set_bad_password_indicator(ldb, ctx, msg);
+ assert_true(NT_STATUS_EQUAL(NT_STATUS_INTERNAL_ERROR, status));
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * set_bad_password_indicator
+ *
+ * dbwrap_store failure.
+ */
+static void test_set_bad_password_indicator_dbwrap_store_failed(
+ void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ TALLOC_CTX *ctx = NULL;
+ NTSTATUS status;
+ size_t before = 0;
+ size_t after = 0;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1010");
+
+ dbwrap_store_ret = NT_STATUS_INTERNAL_DB_CORRUPTION;
+
+ before = talloc_total_size(ctx);
+
+ status = authsam_set_bad_password_indicator(ldb, ctx, msg);
+ assert_true(NT_STATUS_EQUAL(NT_STATUS_INTERNAL_DB_CORRUPTION, status));
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * check_bad_password_indicator
+ *
+ * set_bad_password_indicator failure.
+ */
+static void test_check_bad_password_indicator_get_db_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ TALLOC_CTX *ctx = NULL;
+ NTSTATUS status;
+ size_t before = 0;
+ size_t after = 0;
+ bool exists = false;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ /*
+ * Clear the mock cluster_db_tmp_open return value so that
+ * it returns NULL
+ */
+ TALLOC_FREE(cluster_db_tmp_open_ret);
+ before = talloc_total_size(ctx);
+
+ status = authsam_check_bad_password_indicator(ldb, ctx, &exists, NULL);
+ assert_true(NT_STATUS_EQUAL(NT_STATUS_INTERNAL_ERROR, status));
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * check_bad_password_indicator
+ *
+ * get_object_sid_as_tdb_data failure.
+ */
+static void test_check_bad_password_indicator_get_object_sid_failed(
+ void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ TALLOC_CTX *ctx = NULL;
+ NTSTATUS status;
+ size_t before = 0;
+ size_t after = 0;
+ bool exists = false;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ /*
+ * The created message does not contain an objectSid, so
+ * get_object_sid_as_tdb_data will fail.
+ */
+ msg = create_message(ctx);
+
+ before = talloc_total_size(ctx);
+
+ status = authsam_check_bad_password_indicator(ldb, ctx, &exists, msg);
+ assert_true(NT_STATUS_EQUAL(NT_STATUS_INTERNAL_ERROR, status));
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * clear_bad_password_indicator
+ *
+ * set_bad_password_indicator failure.
+ */
+static void test_clear_bad_password_indicator_get_db_failed(void **state) {
+ struct ldb_context *ldb = NULL;
+ TALLOC_CTX *ctx = NULL;
+ NTSTATUS status;
+ size_t before = 0;
+ size_t after = 0;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ /*
+ * Clear the mock cluster_db_tmp_open return value so that
+ * it returns NULL
+ */
+ TALLOC_FREE(cluster_db_tmp_open_ret);
+ before = talloc_total_size(ctx);
+
+ status = authsam_clear_bad_password_indicator(ldb, ctx, NULL);
+ assert_true(NT_STATUS_EQUAL(NT_STATUS_INTERNAL_ERROR, status));
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * clear_bad_password_indicator
+ *
+ * get_object_sid_as_tdb_data failure.
+ */
+static void test_clear_bad_password_indicator_get_object_sid_failed(
+ void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ TALLOC_CTX *ctx = NULL;
+ NTSTATUS status;
+ size_t before = 0;
+ size_t after = 0;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ /*
+ * The created message does not contain an objectSid, so
+ * get_object_sid_as_tdb_data will fail.
+ */
+ msg = create_message(ctx);
+
+ before = talloc_total_size(ctx);
+
+ status = authsam_clear_bad_password_indicator(ldb, ctx, msg);
+ assert_true(NT_STATUS_EQUAL(NT_STATUS_INTERNAL_ERROR, status));
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * clear_bad_password_indicator
+ *
+ * dbwrap_delete failure.
+ */
+static void test_clear_bad_password_indicator_dbwrap_store_failed(
+ void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ TALLOC_CTX *ctx = NULL;
+ NTSTATUS status;
+ size_t before = 0;
+ size_t after = 0;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1010");
+
+ dbwrap_delete_ret = NT_STATUS_INTERNAL_DB_CORRUPTION;
+
+ before = talloc_total_size(ctx);
+
+ status = authsam_clear_bad_password_indicator(ldb, ctx, msg);
+ assert_true(NT_STATUS_EQUAL(NT_STATUS_INTERNAL_DB_CORRUPTION, status));
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * clear_bad_password_indicator
+ *
+ * dbwrap_delete returns NT_STATUS_NOT_FOUND.
+ */
+static void test_clear_bad_pwd_indicator_dbwrap_store_not_found(
+ void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+ TALLOC_CTX *ctx = NULL;
+ NTSTATUS status;
+ size_t before = 0;
+ size_t after = 0;
+
+ ctx = talloc_new(*state);
+ assert_non_null(ctx);
+
+ ldb = ldb_init(ctx, NULL);
+ assert_non_null(ldb);
+
+ msg = create_message(ctx);
+ add_sid(msg, "S-1-5-21-2470180966-3899876309-2637894779-1010");
+
+ dbwrap_delete_ret = NT_STATUS_NOT_FOUND;
+
+ before = talloc_total_size(ctx);
+
+ status = authsam_clear_bad_password_indicator(ldb, ctx, msg);
+ assert_true(NT_STATUS_IS_OK(status));
+
+ /*
+ * Check that all allocated memory was freed
+ */
+ after = talloc_total_size(ctx);
+ assert_int_equal(before, after);
+
+ /*
+ * Clean up
+ */
+ TALLOC_FREE(ctx);
+}
+
+int main(int argc, const char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(
+ test_reread_read_failure, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_reread_missing_account_control, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_reread_account_locked, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_reread_account_not_locked, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_update_bad_domain_dn_search_failed,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_update_bad_get_pso_failed, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_update_bad_start_txn_failed, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_update_bad_reread_failed, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_update_bad_reread_locked_out, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_update_bad_update_count_failed, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_update_bad_no_update_required, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_update_bad_build_mod_request_failed,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_update_bad_add_control_failed, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_update_bad_ldb_request_failed, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_update_bad_ldb_wait_failed, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_update_bad_txn_cancel_failed, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_update_bad_commit_failed, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_success_accounting_start_txn_failed,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_success_accounting_reread_failed,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_success_accounting_ldb_msg_new_failed,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_success_accounting_samdb_rodc_failed,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_success_accounting_update_lastlogon_failed,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_success_accounting_build_mod_req_failed,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_success_accounting_add_control_failed,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_success_accounting_ldb_request_failed,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_success_accounting_ldb_wait_failed,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_success_accounting_commit_failed,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_success_accounting_rollback_failed,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_success_accounting_spurious_bad_pwd_indicator,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_get_bad_password_get_opaque_failed,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_get_bad_password_db_open_failed,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_set_bad_password_indicator_get_db_failed,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_set_bad_password_indicator_get_object_sid_failed,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_set_bad_password_indicator_dbwrap_store_failed,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_check_bad_password_indicator_get_db_failed,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_check_bad_password_indicator_get_object_sid_failed,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_clear_bad_password_indicator_get_db_failed,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_clear_bad_password_indicator_get_object_sid_failed,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_clear_bad_password_indicator_dbwrap_store_failed,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_clear_bad_pwd_indicator_dbwrap_store_not_found,
+ setup,
+ teardown),
+ };
+
+ cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/source4/auth/unix_token.c b/source4/auth/unix_token.c
new file mode 100644
index 0000000..b3396b8
--- /dev/null
+++ b/source4/auth/unix_token.c
@@ -0,0 +1,228 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Deal with unix elements in the security token
+
+ Copyright (C) Andrew Tridgell 2004
+ Copyright (C) Andrew Bartlett 2011
+
+ 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 "libcli/wbclient/wbclient.h"
+#include "param/param.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+/*
+ form a security_unix_token from the current security_token
+*/
+NTSTATUS security_token_to_unix_token(TALLOC_CTX *mem_ctx,
+ struct security_token *token,
+ struct security_unix_token **sec)
+{
+ uint32_t s, g;
+ NTSTATUS status;
+ struct id_map *ids;
+ bool match;
+
+ match = security_token_is_system(token);
+ if (match) {
+ /*
+ * SYSTEM user uid and gid is 0
+ */
+
+ *sec = talloc_zero(mem_ctx, struct security_unix_token);
+ if (*sec == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+ }
+
+ /* we can't do unix security without a user and group */
+ if (token->num_sids < 2) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ *sec = talloc_zero(mem_ctx, struct security_unix_token);
+ if (*sec == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ids = talloc_zero_array(mem_ctx, struct id_map, token->num_sids);
+ NT_STATUS_HAVE_NO_MEMORY(ids);
+
+ for (s=0; s < token->num_sids; s++) {
+ ids[s].sid = &token->sids[s];
+ ids[s].status = ID_UNKNOWN;
+ }
+
+ status = wbc_sids_to_xids(ids, token->num_sids);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ g = token->num_sids;
+ if (ids[0].xid.type != ID_TYPE_BOTH) {
+ g--;
+ }
+ (*sec)->ngroups = g;
+ (*sec)->groups = talloc_array(*sec, gid_t, (*sec)->ngroups);
+ NT_STATUS_HAVE_NO_MEMORY((*sec)->groups);
+
+ g=0;
+ if (ids[0].xid.type == ID_TYPE_BOTH) {
+ (*sec)->uid = ids[0].xid.id;
+ (*sec)->groups[g] = ids[0].xid.id;
+ g++;
+ } else if (ids[0].xid.type == ID_TYPE_UID) {
+ (*sec)->uid = ids[0].xid.id;
+ } else {
+ struct dom_sid_buf buf;
+ DEBUG(0, ("Unable to convert first SID (%s) in user token to a UID. Conversion was returned as type %d, full token:\n",
+ dom_sid_str_buf(ids[0].sid, &buf),
+ (int)ids[0].xid.type));
+ security_token_debug(DBGC_AUTH, 0, token);
+ return NT_STATUS_INVALID_SID;
+ }
+
+ if (ids[1].xid.type == ID_TYPE_BOTH ||
+ ids[1].xid.type == ID_TYPE_GID) {
+ (*sec)->gid = ids[1].xid.id;
+ (*sec)->groups[g] = ids[1].xid.id;
+ g++;
+ } else {
+ struct dom_sid_buf buf;
+ DEBUG(0, ("Unable to convert second SID (%s) in user token to a GID. Conversion was returned as type %d, full token:\n",
+ dom_sid_str_buf(ids[1].sid, &buf),
+ (int)ids[1].xid.type));
+ security_token_debug(DBGC_AUTH, 0, token);
+ return NT_STATUS_INVALID_SID;
+ }
+
+ for (s=2; s < token->num_sids; s++) {
+ if (ids[s].xid.type == ID_TYPE_BOTH ||
+ ids[s].xid.type == ID_TYPE_GID) {
+ (*sec)->groups[g] = ids[s].xid.id;
+ g++;
+ } else {
+ struct dom_sid_buf buf;
+ DEBUG(0, ("Unable to convert SID (%s) at index %u in user token to a GID. Conversion was returned as type %d, full token:\n",
+ dom_sid_str_buf(ids[s].sid, &buf),
+ (unsigned int)s, (int)ids[s].xid.type));
+ security_token_debug(DBGC_AUTH, 0, token);
+ return NT_STATUS_INVALID_SID;
+ }
+ }
+
+ DEBUG(5, ("Successfully converted security token to a unix token:"));
+ security_token_debug(0, 5, token);
+ TALLOC_FREE(ids);
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * Fill in the unix_info elements in a struct session_info
+ */
+NTSTATUS fill_unix_info(struct loadparm_context *lp_ctx,
+ const char *original_user_name,
+ struct auth_session_info *session_info)
+{
+ session_info->unix_info = talloc_zero(session_info,
+ struct auth_user_info_unix);
+ NT_STATUS_HAVE_NO_MEMORY(session_info->unix_info);
+
+ session_info->unix_info->unix_name =
+ talloc_asprintf(session_info->unix_info,
+ "%s%s%s", session_info->info->domain_name,
+ lpcfg_winbind_separator(lp_ctx),
+ session_info->info->account_name);
+ NT_STATUS_HAVE_NO_MEMORY(session_info->unix_info->unix_name);
+
+ if (original_user_name == NULL) {
+ original_user_name = session_info->unix_info->unix_name;
+ }
+
+ session_info->unix_info->sanitized_username =
+ talloc_alpha_strcpy(session_info->unix_info,
+ original_user_name,
+ ". _-$");
+ NT_STATUS_HAVE_NO_MEMORY(session_info->unix_info->sanitized_username);
+
+ return NT_STATUS_OK;
+}
+
+/*
+ Fill in the auth_user_info_unix and auth_unix_token elements in a struct session_info
+*/
+NTSTATUS auth_session_info_fill_unix(struct loadparm_context *lp_ctx,
+ const char *original_user_name,
+ struct auth_session_info *session_info)
+{
+ NTSTATUS status = NT_STATUS_OK;
+
+ status = security_token_to_unix_token(session_info,
+ session_info->security_token,
+ &session_info->unix_token);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = fill_unix_info(lp_ctx,
+ original_user_name,
+ session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * Set the given auth_user_info_unix and auth_unix_token elements in a
+ * struct session_info, similar auth_session_info_fill_unix().
+ * Receives the uid and gid for the unix token as parameters and does
+ * not query the unix token from winbind (via security_token_to_unix_token()).
+ * This is useful to fill a user session info manually if winbind is not
+ * available.
+ */
+NTSTATUS auth_session_info_set_unix(struct loadparm_context *lp_ctx,
+ const char *original_user_name,
+ int uid,
+ int gid,
+ struct auth_session_info *session_info)
+{
+ NTSTATUS status;
+
+ session_info->unix_token = talloc_zero(session_info,
+ struct security_unix_token);
+ if (session_info->unix_token == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ session_info->unix_token->uid = uid;
+ session_info->unix_token->gid = gid;
+
+ status = fill_unix_info(lp_ctx,
+ original_user_name,
+ session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source4/auth/wscript_build b/source4/auth/wscript_build
new file mode 100644
index 0000000..9ea763f
--- /dev/null
+++ b/source4/auth/wscript_build
@@ -0,0 +1,94 @@
+#!/usr/bin/env python
+
+bld.RECURSE('gensec')
+bld.RECURSE('kerberos')
+bld.RECURSE('ntlm')
+
+bld.SAMBA_SUBSYSTEM('auth_session',
+ source='session.c',
+ autoproto='session_proto.h',
+ public_deps='samba-credentials',
+ public_headers='session.h',
+ header_path='samba',
+ deps='samdb auth4_sam'
+ )
+
+bld.SAMBA_LIBRARY('auth_unix_token',
+ source='unix_token.c',
+ autoproto='unix_token_proto.h',
+ public_deps='LIBWBCLIENT_OLD',
+ private_library=True,
+ )
+
+
+bld.SAMBA_SUBSYSTEM('samba_server_gensec',
+ source='samba_server_gensec.c',
+ public_deps='samba-credentials gensec auth4'
+ )
+
+
+bld.SAMBA_SUBSYSTEM('auth_system_session',
+ source='system_session.c',
+ autoproto='system_session_proto.h',
+ public_deps='samba-credentials',
+ deps='auth_session',
+ )
+
+
+bld.SAMBA_SUBSYSTEM('auth4_sam',
+ source='sam.c',
+ autoproto='auth_sam.h',
+ public_deps='samdb samba-security ldb tevent',
+ deps=''
+ )
+
+bld.SAMBA_BINARY('test_kerberos',
+ source='tests/kerberos.c',
+ deps='cmocka authkrb5 krb5samba com_err CREDENTIALS_KRB5',
+ local_include=False,
+ for_selftest=True
+ )
+
+bld.SAMBA_BINARY('test_heimdal_gensec_unwrap_des',
+ source='tests/heimdal_unwrap_des.c',
+ deps='cmocka talloc gssapi-subsystem',
+ local_include=False,
+ for_selftest=True,
+ enabled=(bld.CONFIG_SET('SAMBA4_USES_HEIMDAL') and
+ not bld.CONFIG_SET('USING_SYSTEM_GSSAPI')),
+ ldflags='''
+ -Wl,--wrap,ct_memcmp
+ -Wl,--wrap,der_get_length
+ -Wl,--wrap,krb5_auth_con_getlocalsubkey
+ -Wl,--wrap,krb5_crypto_destroy
+ -Wl,--wrap,krb5_crypto_init
+ -Wl,--wrap,krb5_decrypt
+ -Wl,--wrap,krb5_decrypt_ivec
+ -Wl,--wrap,krb5_free_keyblock
+ -Wl,--wrap,krb5_verify_checksum
+ -Wl,--wrap,malloc
+ '''
+)
+
+bld.SAMBA_BINARY('test_auth_sam',
+ source='tests/sam.c',
+ deps='cmocka samdb samba-security ldb tevent',
+ local_include=False,
+ for_selftest=True,
+ ldflags='''
+ -Wl,--wrap,dsdb_search_dn
+ -Wl,--wrap,samdb_msg_add_int64
+ '''
+ )
+
+pytalloc_util = bld.pyembed_libname('pytalloc-util')
+pyparam_util = bld.pyembed_libname('pyparam_util')
+pyldb_util = bld.pyembed_libname('pyldb-util')
+pycredentials = 'pycredentials'
+bld.SAMBA_PYTHON('pyauth',
+ source='pyauth.c',
+ public_deps='auth_system_session',
+ deps='samdb %s %s %s %s auth4' % (pytalloc_util, pyparam_util, pyldb_util, pycredentials),
+ realname='samba/auth.so'
+ )
+
diff --git a/source4/auth/wscript_configure b/source4/auth/wscript_configure
new file mode 100644
index 0000000..d25cc0b
--- /dev/null
+++ b/source4/auth/wscript_configure
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+
+conf.CHECK_HEADERS('security/pam_appl.h')
+conf.CHECK_FUNCS_IN('pam_start', 'pam', checklibc=True)