diff options
Diffstat (limited to '')
46 files changed, 21408 insertions, 0 deletions
diff --git a/source4/auth/auth.h b/source4/auth/auth.h new file mode 100644 index 0000000..1ea4f11 --- /dev/null +++ b/source4/auth/auth.h @@ -0,0 +1,215 @@ +/* + 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, + const struct authn_audit_info **client_audit_info, + const struct authn_audit_info **server_audit_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, + const 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 authsam_shallow_copy_user_info_dc(TALLOC_CTX *mem_ctx, + const struct auth_user_info_dc *user_info_dc_in, + struct auth_user_info_dc **user_info_dc_out); +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..cc0491f --- /dev/null +++ b/source4/auth/gensec/gensec_gssapi.c @@ -0,0 +1,1731 @@ +/* + 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, ("Acquiring 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 lifetime 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) { + gss_release_buffer(&min_stat, &output_token); + 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 cryptographic 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) { + gss_release_buffer(&min_stat, &output_token); + 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 'session 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 elsewhere - 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..5b4fd09 --- /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\n")); + return NT_STATUS_INVALID_PARAMETER; + } + if (strcmp(hostname, "localhost") == 0) { + DEBUG(2, ("krb5 to 'localhost' does not make sense\n")); + 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: Acquiring 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 omitted) */ + 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..cc7c83c --- /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; + size_t 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..97ced8f --- /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 "lib/replace/system/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..2885eb0 --- /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='talloc authkrb5', + 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..cb8f0a9 --- /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 implemented 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 substitution of names, such as might happen when + a user requests a ticket for a NetBIOS domain name, but gets back + a ticket for the corresponding 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 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 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 theoretical 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 protocols, 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 explicit 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 interface 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 +consistency 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..9df3a13 --- /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 implemented 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 substitution of names, such as might + happen when a user requests a ticket for a NetBIOS domain + name, but gets back a ticket for the corresponding 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 interface 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- + attempts), 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 consistency + 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..5b13f56 --- /dev/null +++ b/source4/auth/kerberos/kerberos.h @@ -0,0 +1,89 @@ +/* + 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/auth.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 + +krb5_error_code smb_krb5_princ_component(krb5_context context, + krb5_const_principal principal, + int i, + krb5_data *data); + +/* 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..9aeeb38 --- /dev/null +++ b/source4/auth/kerberos/kerberos_credentials.h @@ -0,0 +1,38 @@ +/* + 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 loadparm_context *lp_ctx, + 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..c33dc2f --- /dev/null +++ b/source4/auth/kerberos/kerberos_pac.c @@ -0,0 +1,539 @@ +/* + 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!\n")); + return EINVAL; + } + if (!srv_checksum) { + DEBUG(2, ("Invalid PAC constructed for signing, no SRV checksum present!\n")); + 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 deterministic 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, + AUTH_INCLUDE_RESOURCE_GROUPS, + &sam3, NULL); + 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_const_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_const_pac pac, + krb5_context context, + struct auth_user_info_dc **user_info_dc, + const enum auth_group_inclusion group_inclusion, + struct PAC_SIGNATURE_DATA *pac_srv_sig, + struct PAC_SIGNATURE_DATA *pac_kdc_sig, + struct PAC_DOMAIN_GROUP_MEMBERSHIP **resource_groups) +{ + 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; + const struct PAC_DOMAIN_GROUP_MEMBERSHIP *resource_groups_in = NULL; + struct PAC_DOMAIN_GROUP_MEMBERSHIP *resource_groups_out = NULL; + + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + + if (!tmp_ctx) { + return ENOMEM; + } + + if (pac == NULL) { + talloc_free(tmp_ctx); + return EINVAL; + } + + 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 system structures */ + nt_status = make_user_info_dc_pac(tmp_ctx, + info.logon_info.info, + upn_dns_info, + group_inclusion, + &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 server 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; + } + + /* + * If we have resource groups and the caller wants them returned, we + * oblige. + */ + resource_groups_in = &info.logon_info.info->resource_groups; + if (resource_groups != NULL && resource_groups_in->groups.count != 0) { + resource_groups_out = talloc(tmp_ctx, struct PAC_DOMAIN_GROUP_MEMBERSHIP); + if (resource_groups_out == NULL) { + talloc_free(tmp_ctx); + return ENOMEM; + } + + *resource_groups_out = (struct PAC_DOMAIN_GROUP_MEMBERSHIP) { + .domain_sid = talloc_steal(resource_groups_out, resource_groups_in->domain_sid), + .groups = { + .count = resource_groups_in->groups.count, + .rids = talloc_steal(resource_groups_out, resource_groups_in->groups.rids), + }, + }; + } + + *user_info_dc = talloc_steal(mem_ctx, user_info_dc_out); + if (resource_groups_out != NULL) { + *resource_groups = talloc_steal(mem_ctx, resource_groups_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, + AUTH_INCLUDE_RESOURCE_GROUPS, + pac_srv_sig, + pac_kdc_sig, + NULL); + 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..ba8d822 --- /dev/null +++ b/source4/auth/kerberos/kerberos_util.c @@ -0,0 +1,721 @@ +/* + 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; + } + + /* + * Start with talloc(), talloc_reference() and only then call + * krb5_parse_name(). If any of them fails, the cleanup code is simpler. + */ + mem_ctx = talloc(parent_ctx, struct principal_container); + if (!mem_ctx) { + (*error_string) = error_message(ENOMEM); + return ENOMEM; + } + + mem_ctx->smb_krb5_context = talloc_reference(mem_ctx, + smb_krb5_context); + if (mem_ctx->smb_krb5_context == NULL) { + (*error_string) = error_message(ENOMEM); + talloc_free(mem_ctx); + return ENOMEM; + } + + 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); + talloc_free(mem_ctx); + return ret; + } + + /* This song-and-dance effectively puts the principal + * into talloc, so we can't lose it. */ + 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 loadparm_context *lp_ctx, + 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; + struct cli_credentials *fast_creds; + + 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 + + fast_creds = cli_credentials_get_krb5_fast_armor_credentials(credentials); + + if (fast_creds != NULL) { +#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_FAST_CCACHE + struct ccache_container *fast_ccc = NULL; + const char *fast_error_string = NULL; + ret = cli_credentials_get_ccache(fast_creds, event_ctx, lp_ctx, &fast_ccc, &fast_error_string); + if (ret != 0) { + (*error_string) = talloc_asprintf(credentials, + "Obtaining the Kerberos FAST armor credentials failed: %s\n", + fast_error_string); + return ret; + } + krb5_get_init_creds_opt_set_fast_ccache(smb_krb5_context->krb5_context, + krb_options, + fast_ccc->ccache); +#else + *error_string = talloc_strdup(credentials, + "Using Kerberos FAST " + "armor credentials not possible " + "with this Kerberos library. " + "Modern MIT or Samba's embedded " + "Heimdal required"); + return EINVAL; +#endif + } + +#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_FAST_FLAGS + { + bool require_fast; + /* + * This ensures that if FAST was required, but no armor + * credentials cache was specified, we proceed with (eg) + * anonymous PKINIT + */ + require_fast = cli_credentials_get_krb5_require_fast_armor(credentials); + if (require_fast) { + krb5_get_init_creds_opt_set_fast_flags(smb_krb5_context->krb5_context, + krb_options, + KRB5_FAST_REQUIRED); + } + } +#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); + krb5_get_init_creds_opt_free(smb_krb5_context->krb5_context, krb_options); + 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"; + 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; + } 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, + lp_ctx, + 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; + + /* + * Start with talloc(), talloc_reference() and only then call + * krb5_kt_resolve(). If any of them fails, the cleanup code is simpler. + */ + *ktc = talloc(mem_ctx, struct keytab_container); + if (!*ktc) { + return ENOMEM; + } + + (*ktc)->smb_krb5_context = talloc_reference(*ktc, smb_krb5_context); + if ((*ktc)->smb_krb5_context == NULL) { + TALLOC_FREE(*ktc); + return ENOMEM; + } + + 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))); + TALLOC_FREE(*ktc); + return ret; + } + } + + (*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..e8114af --- /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 */ + + /* This is equivalent 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..8eaa150 --- /dev/null +++ b/source4/auth/kerberos/srv_keytab.c @@ -0,0 +1,407 @@ +/* + 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 = NULL; + krb5_error_code ret; + bool found_previous = false; + TALLOC_CTX *tmp_ctx = NULL; + 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"); + ret = ENOMEM; + goto done; + } + + upper_realm = strupper_talloc(tmp_ctx, realm); + if (upper_realm == NULL) { + *perror_string = talloc_strdup(parent_ctx, + "Cannot allocate memory to upper case realm"); + ret = ENOMEM; + goto done; + } + + 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..a26cfaf --- /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', + 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..3ad18bd --- /dev/null +++ b/source4/auth/ntlm/auth.c @@ -0,0 +1,869 @@ +/* + 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 generation + * 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; + const struct authn_audit_info *client_audit_info; + const struct authn_audit_info *server_audit_info; + 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, + &state->client_audit_info, + &state->server_audit_info, + &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, + state->client_audit_info, + state->server_audit_info); + 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[PRIMARY_USER_SID_INDEX].sid, + state->client_audit_info, + state->server_audit_info); + + /* + * Release our handle to state->user_info_dc. + * state->{client,server}_audit_info, if non-NULL, becomes the new + * parent. + */ + *user_info_dc = talloc_reparent(state, mem_ctx, state->user_info_dc); + state->user_info_dc = NULL; + + 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 dependency 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->user_flags & NETLOGON_GUEST)) { + 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 have been caught 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); + + /* + * FIXME: To correctly create the security token, we also need to get the + * claims info, device info, and device claims info from the PAC. For now, + * we support claims only in the KDC. + */ + 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->user_flags & NETLOGON_GUEST)) { + 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..328347a --- /dev/null +++ b/source4/auth/ntlm/auth_anonymous.c @@ -0,0 +1,166 @@ +/* + Unix SMB/CIFS implementation. + + Anonymous Authentication + + 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, + const struct authn_audit_info **client_audit_info, + const struct authn_audit_info **server_audit_info, + bool *authoritative) +{ + struct anonymous_check_password_state *state = tevent_req_data( + req, struct anonymous_check_password_state); + NTSTATUS status; + + *client_audit_info = NULL; + *server_audit_info = NULL; + + 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..89db15d --- /dev/null +++ b/source4/auth/ntlm/auth_developer.c @@ -0,0 +1,224 @@ +/* + 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 obscure 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 = talloc(user_info_dc, struct auth_SidAttr); + NT_STATUS_HAVE_NO_MEMORY(user_info_dc->sids); + + user_info_dc->sids->sid = global_sid_Anonymous; + user_info_dc->sids->attrs = SE_GROUP_DEFAULT_FLAGS; + + /* 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->user_flags = 0; + + *_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, + const struct authn_audit_info **client_audit_info, + const struct authn_audit_info **server_audit_info, + bool *authoritative) +{ + struct name_to_ntstatus_check_password_state *state = tevent_req_data( + req, struct name_to_ntstatus_check_password_state); + NTSTATUS status; + + *authoritative = state->authoritative; + *client_audit_info = NULL; + *server_audit_info = NULL; + + 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 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..d12045d --- /dev/null +++ b/source4/auth/ntlm/auth_sam.c @@ -0,0 +1,1417 @@ +/* + 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/samdb/ldb_modules/util.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/authn_policy_util.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\n", + 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, + lpcfg_ntlm_auth(auth_context->lp_ctx), + 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!\n", + 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 */ + ¤t_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 update 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_check_netlogon_trust(TALLOC_CTX *mem_ctx, + struct ldb_context *sam_ctx, + struct loadparm_context *lp_ctx, + const struct auth_usersupplied_info *user_info, + const struct auth_user_info_dc *user_info_dc, + struct authn_audit_info **server_audit_info_out) +{ + TALLOC_CTX *tmp_ctx = NULL; + + static const char *authn_policy_silo_attrs[] = { + "msDS-AssignedAuthNPolicy", + "msDS-AssignedAuthNPolicySilo", + "objectClass", /* used to determine which set of policy + * attributes apply. */ + NULL, + }; + + const struct authn_server_policy *authn_server_policy = NULL; + + struct dom_sid_buf netlogon_trust_sid_buf; + const char *netlogon_trust_sid_str = NULL; + struct ldb_dn *netlogon_trust_dn = NULL; + struct ldb_message *netlogon_trust_msg = NULL; + + int ret; + + /* Have we established a secure channel? */ + if (user_info->netlogon_trust_account.secure_channel_type == SEC_CHAN_NULL) { + return NT_STATUS_OK; + } + + if (!authn_policy_silos_and_policies_in_effect(sam_ctx)) { + return NT_STATUS_OK; + } + + /* + * We have established a secure channel, and we should have the machine + * account’s SID. + */ + SMB_ASSERT(user_info->netlogon_trust_account.sid != NULL); + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + netlogon_trust_sid_str = dom_sid_str_buf(user_info->netlogon_trust_account.sid, + &netlogon_trust_sid_buf); + + netlogon_trust_dn = ldb_dn_new_fmt(tmp_ctx, sam_ctx, + "<SID=%s>", + netlogon_trust_sid_str); + if (netlogon_trust_dn == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + /* + * Look up the machine account to see if it has an applicable + * authentication policy. + */ + ret = dsdb_search_one(sam_ctx, + tmp_ctx, + &netlogon_trust_msg, + netlogon_trust_dn, + LDB_SCOPE_BASE, + authn_policy_silo_attrs, + 0, + NULL); + if (ret) { + talloc_free(tmp_ctx); + return dsdb_ldb_err_to_ntstatus(ret); + } + + ret = authn_policy_server(sam_ctx, + tmp_ctx, + netlogon_trust_msg, + &authn_server_policy); + if (ret) { + talloc_free(tmp_ctx); + return NT_STATUS_INTERNAL_ERROR; + } + + if (authn_server_policy != NULL) { + struct authn_audit_info *server_audit_info = NULL; + NTSTATUS status; + + /* + * An authentication policy applies to the machine + * account. Carry out the access check. + */ + status = authn_policy_authenticate_to_service(tmp_ctx, + sam_ctx, + lp_ctx, + AUTHN_POLICY_AUTH_TYPE_NTLM, + user_info_dc, + NULL /* device_info */, + /* + * It seems that claims go ignored for + * SamLogon (see SamLogonTests — + * test_samlogon_allowed_to_computer_silo). + */ + (struct auth_claims) {}, + authn_server_policy, + (struct authn_policy_flags) {}, + &server_audit_info); + if (server_audit_info != NULL) { + *server_audit_info_out = talloc_move(mem_ctx, &server_audit_info); + } + if (!NT_STATUS_IS_OK(status)) { + talloc_free(tmp_ctx); + return status; + } + } + + return NT_STATUS_OK; +} + +static NTSTATUS authsam_authenticate(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, + const struct auth_user_info_dc *user_info_dc, + DATA_BLOB *user_sess_key, DATA_BLOB *lm_sess_key, + struct authn_audit_info **client_audit_info_out, + struct authn_audit_info **server_audit_info_out, + bool *authoritative) +{ + NTSTATUS nt_status; + int ret; + 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; + const struct authn_ntlm_client_policy *authn_client_policy = 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; + } + } + + /* See whether an authentication policy applies to the client. */ + ret = authn_policy_ntlm_client(auth_context->sam_ctx, + tmp_ctx, + msg, + &authn_client_policy); + if (ret) { + TALLOC_FREE(tmp_ctx); + return NT_STATUS_INTERNAL_ERROR; + } + + nt_status = authn_policy_ntlm_apply_device_restriction(mem_ctx, + authn_client_policy, + client_audit_info_out); + if (!NT_STATUS_IS_OK(nt_status)) { + /* + * As we didn’t get far enough to check the server policy, only + * the client policy will be referenced in the authentication + * log message. + */ + TALLOC_FREE(tmp_ctx); + return nt_status; + } + + 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_check_netlogon_trust(mem_ctx, + auth_context->sam_ctx, + auth_context->lp_ctx, + user_info, + user_info_dc, + server_audit_info_out); + 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, + struct authn_audit_info **client_audit_info_out, + struct authn_audit_info **server_audit_info_out, + 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; + struct auth_user_info_dc *reparented = NULL; + struct authn_audit_info *client_audit_info = NULL; + struct authn_audit_info *server_audit_info = 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, + domain_dn, + msg, + user_info, + *user_info_dc, + &user_sess_key, + &lm_sess_key, + &client_audit_info, + &server_audit_info, + authoritative); + if (client_audit_info != NULL) { + *client_audit_info_out = talloc_move(mem_ctx, &client_audit_info); + } + if (server_audit_info != NULL) { + *server_audit_info_out = talloc_move(mem_ctx, &server_audit_info); + } + 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; + } + } + + /* + * Release our handle to *user_info_dc. {client,server}_audit_info_out, + * if non-NULL, becomes the new parent. + */ + reparented = talloc_reparent(tmp_ctx, mem_ctx, *user_info_dc); + if (reparented == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_INTERNAL_ERROR; + } + + talloc_free(tmp_ctx); + + return NT_STATUS_OK; +} + +struct authsam_check_password_state { + struct auth_user_info_dc *user_info_dc; + struct authn_audit_info *client_audit_info; + struct authn_audit_info *server_audit_info; + 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->client_audit_info, + &state->server_audit_info, + &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, + const struct authn_audit_info **client_audit_info, + const struct authn_audit_info **server_audit_info, + bool *authoritative) +{ + struct authsam_check_password_state *state = tevent_req_data( + req, struct authsam_check_password_state); + NTSTATUS status; + + *authoritative = state->authoritative; + + *client_audit_info = talloc_reparent(state, mem_ctx, state->client_audit_info); + state->client_audit_info = NULL; + + *server_audit_info = talloc_reparent(state, mem_ctx, state->server_audit_info); + state->server_audit_info = NULL; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + /* + * Release our handle to state->user_info_dc. + * {client,server}_audit_info, if non-NULL, becomes the new parent. + */ + *interim_info = talloc_reparent(state, mem_ctx, state->user_info_dc); + state->user_info_dc = NULL; + + 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..605ed9e --- /dev/null +++ b/source4/auth/ntlm/auth_simple.c @@ -0,0 +1,221 @@ +/* + 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, + NULL /* client_audit_info */, + NULL /* server_audit_info */); + } + 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->user_flags & NETLOGON_GUEST)) { + 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, + NULL /* client_audit_info */, + NULL /* server_audit_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..2b1cc51 --- /dev/null +++ b/source4/auth/ntlm/auth_winbind.c @@ -0,0 +1,326 @@ +/* + Unix SMB/CIFS implementation. + + Winbind authentication mechanism + + 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, + const struct authn_audit_info **client_audit_info, + const struct authn_audit_info **server_audit_info, + 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; + *client_audit_info = NULL; + *server_audit_info = NULL; + + 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..0698f6b --- /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 authn_policy_util', + 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..498b52e --- /dev/null +++ b/source4/auth/pyauth.c @@ -0,0 +1,507 @@ +/* + 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 "lib/replace/system/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 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 *const *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 = (const char * const *)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..33956c1 --- /dev/null +++ b/source4/auth/sam.c @@ -0,0 +1,1900 @@ +/* + 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" + +#define AUTHN_POLICY_ATTRS \ + /* Required for authentication policies / silos */ \ + "msDS-AssignedAuthNPolicy", \ + "msDS-AssignedAuthNPolicySilo" + +const char *krbtgt_attrs[] = { + /* + * Authentication policies will not be enforced on the TGS + * account. Don’t include the relevant attributes in the account search. + */ + KRBTGT_ATTRS, NULL +}; + +const char *server_attrs[] = { + KRBTGT_ATTRS, + AUTHN_POLICY_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 calculations to use. + */ + "msDS-ResultantPSO", + + KRBTGT_ATTRS, + AUTHN_POLICY_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. + */ + talloc_asprintf_addbuf(&filter, + "(!(groupType:"LDB_OID_COMPARATOR_AND":=%u))", + GROUP_TYPE_BUILTIN_LOCAL_GROUP); + if (filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + /* + * Only include security groups. + */ + talloc_asprintf_addbuf(&filter, + "(groupType:"LDB_OID_COMPARATOR_AND":=%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, + const struct ldb_message *msg, + DATA_BLOB user_sess_key, + DATA_BLOB lm_sess_key, + struct auth_user_info_dc **_user_info_dc) +{ + NTSTATUS status; + int ret; + 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_str = NULL; + DATA_BLOB primary_group_blob; + struct ldb_dn *primary_group_dn = NULL; + struct ldb_message *primary_group_msg = NULL; + unsigned primary_group_type; + /* SID structures for the expanded group memberships */ + struct auth_SidAttr *sids = NULL; + uint32_t num_sids = 0; + unsigned int i; + struct dom_sid *domain_sid; + TALLOC_CTX *tmp_ctx; + struct ldb_message_element *el; + static const char * const group_type_attrs[] = { "groupType", NULL }; + + if (msg == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + 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; + } + + /* + * We'll typically store three SIDs: the SID of the user, the SID of the + * primary group, and a copy of the latter if it's not a resource + * group. Allocate enough memory for these three SIDs. + */ + sids = talloc_zero_array(user_info_dc, struct auth_SidAttr, 3); + 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].sid = *account_sid; + sids[PRIMARY_USER_SID_INDEX].attrs = SE_GROUP_DEFAULT_FLAGS; + sids[PRIMARY_GROUP_SID_INDEX].sid = *domain_sid; + sid_append_rid(&sids[PRIMARY_GROUP_SID_INDEX].sid, ldb_msg_find_attr_as_uint(msg, "primaryGroupID", ~0)); + sids[PRIMARY_GROUP_SID_INDEX].attrs = SE_GROUP_DEFAULT_FLAGS; + + /* + * 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_str = talloc_asprintf( + tmp_ctx, + "<SID=%s>", + dom_sid_str_buf(&sids[PRIMARY_GROUP_SID_INDEX].sid, &buf)); + if (primary_group_dn_str == NULL) { + TALLOC_FREE(user_info_dc); + return NT_STATUS_NO_MEMORY; + } + + /* Get the DN of the primary group. */ + primary_group_dn = ldb_dn_new(tmp_ctx, sam_ctx, primary_group_dn_str); + if (primary_group_dn == NULL) { + TALLOC_FREE(user_info_dc); + return NT_STATUS_NO_MEMORY; + } + + /* + * Do a search for the primary group, for the purpose of checking + * whether it's a resource group. + */ + ret = dsdb_search_one(sam_ctx, tmp_ctx, + &primary_group_msg, + primary_group_dn, + LDB_SCOPE_BASE, + group_type_attrs, + 0, + NULL); + if (ret != LDB_SUCCESS) { + talloc_free(user_info_dc); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + /* Check the type of the primary group. */ + primary_group_type = ldb_msg_find_attr_as_uint(primary_group_msg, "groupType", 0); + if (primary_group_type & GROUP_TYPE_RESOURCE_GROUP) { + /* + * If it's a resource group, we might as well indicate that in + * its attributes. At any rate, the primary group's attributes + * are unlikely to be used in the code, as there's nowhere to + * store them. + */ + sids[PRIMARY_GROUP_SID_INDEX].attrs |= SE_GROUP_RESOURCE; + } else { + /* + * The primary group is not a resource group. Make a copy of its + * SID to ensure it is added to the Base SIDs in the PAC. + */ + sids[REMAINING_SIDS_INDEX] = sids[PRIMARY_GROUP_SID_INDEX]; + ++num_sids; + } + + primary_group_blob = data_blob_string_const(primary_group_dn_str); + + /* 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); + if (user_info_dc->info == NULL) { + talloc_free(user_info_dc); + return NT_STATUS_NO_MEMORY; + } + + 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 auth_SidAttr, + 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].sid = global_sid_Enterprise_DCs; + user_info_dc->sids[user_info_dc->num_sids].attrs = SE_GROUP_DEFAULT_FLAGS; + 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 auth_SidAttr, + 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].sid = *domain_sid; + sid_append_rid(&user_info_dc->sids[user_info_dc->num_sids].sid, + DOMAIN_RID_ENTERPRISE_READONLY_DCS); + user_info_dc->sids[user_info_dc->num_sids].attrs = SE_GROUP_DEFAULT_FLAGS; + user_info_dc->num_sids++; + } + + info->user_flags = 0; + + 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)) { + 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].sid; + 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)) { + talloc_free(filter); + return status; + } + } + + talloc_free(filter); + return NT_STATUS_OK; +} + +/* + * Make a shallow copy of a talloc-allocated user_info_dc structure, holding a + * reference to each of the original fields. + */ +NTSTATUS authsam_shallow_copy_user_info_dc(TALLOC_CTX *mem_ctx, + const struct auth_user_info_dc *user_info_dc_in, + struct auth_user_info_dc **user_info_dc_out) +{ + struct auth_user_info_dc *user_info_dc = NULL; + NTSTATUS status = NT_STATUS_OK; + + if (user_info_dc_in == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (user_info_dc_out == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + user_info_dc = talloc_zero(mem_ctx, struct auth_user_info_dc); + if (user_info_dc == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + *user_info_dc = *user_info_dc_in; + + if (user_info_dc->info != NULL) { + if (talloc_reference(user_info_dc, user_info_dc->info) == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + } + + if (user_info_dc->user_session_key.data != NULL) { + if (talloc_reference(user_info_dc, user_info_dc->user_session_key.data) == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + } + + if (user_info_dc->lm_session_key.data != NULL) { + if (talloc_reference(user_info_dc, user_info_dc->lm_session_key.data) == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + } + + if (user_info_dc->sids != NULL) { + /* + * Because we want to modify the SIDs in the user_info_dc + * structure, adding various well-known SIDs such as Asserted + * Identity or Claims Valid, make a copy of the SID array to + * guard against modification of the original. + * + * It’s better not to make a reference, because anything that + * tries to call talloc_realloc() on the original or the copy + * will fail when called for any referenced talloc context. + */ + user_info_dc->sids = talloc_memdup(user_info_dc, + user_info_dc->sids, + talloc_get_size(user_info_dc->sids)); + if (user_info_dc->sids == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + } + + *user_info_dc_out = user_info_dc; + user_info_dc = NULL; + +out: + talloc_free(user_info_dc); + return status; +} + +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)) { + talloc_free(tmp_ctx); + 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))); + talloc_free(tmp_ctx); + return NT_STATUS_NO_SUCH_USER; + } + + } else { + talloc_free(tmp_ctx); + 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, ¤t); + 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 every time, 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; + char *account_name_encoded = NULL; + + account_name_encoded = ldb_binary_encode_string(mem_ctx, account_name); + if (account_name_encoded == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* 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))", + account_name_encoded); + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + DEBUG(3,("authsam_search_account: 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)) { + TALLOC_FREE(mem_ctx); + 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, ¤t); + 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..b5d436d --- /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 + * (because 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..9c9d8c4 --- /dev/null +++ b/source4/auth/session.c @@ -0,0 +1,828 @@ +/* + 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/security/claims-conversions.h" +#include "libcli/auth/libcli_auth.h" +#include "librpc/gen_ndr/claims.h" +#include "librpc/gen_ndr/ndr_claims.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_security_token(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, /* Optional, if you don't want privileges */ + struct ldb_context *sam_ctx, /* Optional, if you don't want local groups */ + const struct auth_user_info_dc *user_info_dc, + const struct auth_user_info_dc *device_info_dc, + const struct auth_claims auth_claims, + uint32_t session_info_flags, + struct security_token **_security_token) +{ + struct security_token *security_token = NULL; + NTSTATUS nt_status; + uint32_t i; + uint32_t num_sids = 0; + uint32_t num_device_sids = 0; + const char *filter = NULL; + struct auth_SidAttr *sids = NULL; + struct auth_SidAttr *device_sids = NULL; + + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + sids = talloc_array(tmp_ctx, struct auth_SidAttr, 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 auth_SidAttr, num_sids + 2); + if (sids == NULL) { + TALLOC_FREE(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + sid_copy(&sids[num_sids].sid, &global_sid_World); + sids[num_sids].attrs = SE_GROUP_DEFAULT_FLAGS; + num_sids++; + + sid_copy(&sids[num_sids].sid, &global_sid_Network); + sids[num_sids].attrs = SE_GROUP_DEFAULT_FLAGS; + num_sids++; + } + + if (session_info_flags & AUTH_SESSION_INFO_AUTHENTICATED) { + sids = talloc_realloc(tmp_ctx, sids, struct auth_SidAttr, num_sids + 1); + if (sids == NULL) { + TALLOC_FREE(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + sid_copy(&sids[num_sids].sid, &global_sid_Authenticated_Users); + sids[num_sids].attrs = SE_GROUP_DEFAULT_FLAGS; + num_sids++; + } + + if (session_info_flags & AUTH_SESSION_INFO_NTLM) { + sids = talloc_realloc(tmp_ctx, sids, struct auth_SidAttr, 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].sid)) { + TALLOC_FREE(tmp_ctx); + return NT_STATUS_INTERNAL_ERROR; + } + sids[num_sids].attrs = SE_GROUP_DEFAULT_FLAGS; + num_sids++; + } + + + if (num_sids > PRIMARY_USER_SID_INDEX && dom_sid_equal(&global_sid_Anonymous, &sids[PRIMARY_USER_SID_INDEX].sid)) { + /* Don't expand nested groups of system, anonymous etc*/ + } else if (num_sids > PRIMARY_USER_SID_INDEX && dom_sid_equal(&global_sid_System, &sids[PRIMARY_USER_SID_INDEX].sid)) { + /* Don't expand nested groups of system, anonymous etc*/ + } else if (sam_ctx != NULL) { + filter = talloc_asprintf(tmp_ctx, "(&(objectClass=group)(groupType:"LDB_OID_COMPARATOR_AND":=%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].sid, &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; + } + } + } + + if (device_info_dc != NULL) { + /* + * Make a copy of the device SIDs in case we need to add extra SIDs on + * the end. One can never have too much copying. + */ + num_device_sids = device_info_dc->num_sids; + device_sids = talloc_array(tmp_ctx, + struct auth_SidAttr, + num_device_sids); + if (device_sids == NULL) { + TALLOC_FREE(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + for (i = 0; i < num_device_sids; i++) { + device_sids[i] = device_info_dc->sids[i]; + } + + if (session_info_flags & AUTH_SESSION_INFO_DEVICE_DEFAULT_GROUPS) { + device_sids = talloc_realloc(tmp_ctx, + device_sids, + struct auth_SidAttr, + num_device_sids + 2); + if (device_sids == NULL) { + TALLOC_FREE(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + device_sids[num_device_sids++] = (struct auth_SidAttr) { + .sid = global_sid_World, + .attrs = SE_GROUP_DEFAULT_FLAGS, + }; + device_sids[num_device_sids++] = (struct auth_SidAttr) { + .sid = global_sid_Network, + .attrs = SE_GROUP_DEFAULT_FLAGS, + }; + } + + if (session_info_flags & AUTH_SESSION_INFO_DEVICE_AUTHENTICATED) { + device_sids = talloc_realloc(tmp_ctx, + device_sids, + struct auth_SidAttr, + num_device_sids + 1); + if (device_sids == NULL) { + TALLOC_FREE(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + device_sids[num_device_sids++] = (struct auth_SidAttr) { + .sid = global_sid_Authenticated_Users, + .attrs = SE_GROUP_DEFAULT_FLAGS, + }; + } + } + + nt_status = security_token_create(mem_ctx, + lp_ctx, + num_sids, + sids, + num_device_sids, + device_sids, + auth_claims, + session_info_flags, + &security_token); + if (!NT_STATUS_IS_OK(nt_status)) { + TALLOC_FREE(tmp_ctx); + return nt_status; + } + + talloc_steal(mem_ctx, security_token); + *_security_token = security_token; + talloc_free(tmp_ctx); + return NT_STATUS_OK; +} + +_PUBLIC_ NTSTATUS auth_generate_session_info(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, /* Optional, if you don't want privileges */ + struct ldb_context *sam_ctx, /* Optional, if you don't want local groups */ + const 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; + + 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); + if (session_info->info == NULL) { + TALLOC_FREE(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + 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 && user_info_dc->user_session_key.length) { + TALLOC_FREE(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + nt_status = auth_generate_security_token(session_info, + lp_ctx, + sam_ctx, + user_info_dc, + NULL /*device_info_dc */, + (struct auth_claims) {}, + 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 structure 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 arbitrary 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); +} + +NTSTATUS encode_claims_set(TALLOC_CTX *mem_ctx, + struct CLAIMS_SET *claims_set, + DATA_BLOB *claims_blob) +{ + TALLOC_CTX *tmp_ctx = NULL; + enum ndr_err_code ndr_err; + struct CLAIMS_SET_NDR *claims_set_info = NULL; + struct CLAIMS_SET_METADATA *metadata = NULL; + struct CLAIMS_SET_METADATA_NDR *metadata_ndr = NULL; + + if (claims_blob == NULL) { + return NT_STATUS_INVALID_PARAMETER_3; + } + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + metadata_ndr = talloc(tmp_ctx, struct CLAIMS_SET_METADATA_NDR); + if (metadata_ndr == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + metadata = talloc(metadata_ndr, struct CLAIMS_SET_METADATA); + if (metadata == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + claims_set_info = talloc(metadata, struct CLAIMS_SET_NDR); + if (claims_set_info == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + *metadata_ndr = (struct CLAIMS_SET_METADATA_NDR) { + .claims.metadata = metadata, + }; + + *metadata = (struct CLAIMS_SET_METADATA) { + .claims_set = claims_set_info, + .compression_format = CLAIMS_COMPRESSION_FORMAT_XPRESS_HUFF, + }; + + *claims_set_info = (struct CLAIMS_SET_NDR) { + .claims.claims = claims_set, + }; + + ndr_err = ndr_push_struct_blob(claims_blob, mem_ctx, metadata_ndr, + (ndr_push_flags_fn_t)ndr_push_CLAIMS_SET_METADATA_NDR); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err); + DBG_ERR("CLAIMS_SET_METADATA_NDR push failed: %s\n", + nt_errstr(nt_status)); + + talloc_free(tmp_ctx); + return nt_status; + } + + talloc_free(tmp_ctx); + return NT_STATUS_OK; +} + +/* + * Construct a ‘claims_data’ structure from a claims blob, such as is found in a + * PAC. + */ +NTSTATUS claims_data_from_encoded_claims_set(TALLOC_CTX *claims_data_ctx, + const DATA_BLOB *encoded_claims_set, + struct claims_data **out) +{ + struct claims_data *claims_data = NULL; + DATA_BLOB data = {}; + + if (out == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + *out = NULL; + + claims_data = talloc(claims_data_ctx, struct claims_data); + if (claims_data == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (encoded_claims_set != NULL) { + /* + * We make a copy of the data, for it might not be + * talloc‐allocated — we might have obtained it directly with + * krb5_pac_get_buffer(). + */ + data = data_blob_dup_talloc(claims_data, *encoded_claims_set); + if (data.length != encoded_claims_set->length) { + talloc_free(claims_data); + return NT_STATUS_NO_MEMORY; + } + } + + *claims_data = (struct claims_data) { + .encoded_claims_set = data, + .flags = CLAIMS_DATA_ENCODED_CLAIMS_PRESENT, + }; + + *out = claims_data; + + return NT_STATUS_OK; +} + +/* + * Construct a ‘claims_data’ structure from a talloc‐allocated claims set, such + * as we might build from searching the database. If this function returns + * successfully, it assumes ownership of the claims set. + */ +NTSTATUS claims_data_from_claims_set(TALLOC_CTX *claims_data_ctx, + struct CLAIMS_SET *claims_set, + struct claims_data **out) +{ + struct claims_data *claims_data = NULL; + + if (out == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + *out = NULL; + + claims_data = talloc(claims_data_ctx, struct claims_data); + if (claims_data == NULL) { + return NT_STATUS_NO_MEMORY; + } + *claims_data = (struct claims_data) { + .claims_set = talloc_steal(claims_data, claims_set), + .flags = CLAIMS_DATA_CLAIMS_PRESENT, + }; + + *out = claims_data; + + return NT_STATUS_OK; +} + +/* + * From a ‘claims_data’ structure, return an encoded claims blob that can be put + * into a PAC. + */ +NTSTATUS claims_data_encoded_claims_set(TALLOC_CTX *mem_ctx, + struct claims_data *claims_data, + DATA_BLOB *encoded_claims_set_out) +{ + uint8_t *data = NULL; + size_t len; + + if (encoded_claims_set_out == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + *encoded_claims_set_out = data_blob_null; + + if (claims_data == NULL) { + return NT_STATUS_OK; + } + + if (!(claims_data->flags & CLAIMS_DATA_ENCODED_CLAIMS_PRESENT)) { + NTSTATUS status; + + /* See whether we have a claims set that we can encode. */ + if (!(claims_data->flags & CLAIMS_DATA_CLAIMS_PRESENT)) { + return NT_STATUS_OK; + } + + status = encode_claims_set(claims_data, + claims_data->claims_set, + &claims_data->encoded_claims_set); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + claims_data->flags |= CLAIMS_DATA_ENCODED_CLAIMS_PRESENT; + } + + if (claims_data->encoded_claims_set.data != NULL) { + data = talloc_reference(mem_ctx, claims_data->encoded_claims_set.data); + if (data == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + len = claims_data->encoded_claims_set.length; + + *encoded_claims_set_out = data_blob_const(data, len); + return NT_STATUS_OK; +} + +/* + * From a ‘claims_data’ structure, return an array of security claims that can + * be put in a security token for access checks. + */ +NTSTATUS claims_data_security_claims(TALLOC_CTX *mem_ctx, + struct claims_data *claims_data, + struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 **security_claims_out, + uint32_t *n_security_claims_out) +{ + struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *security_claims = NULL; + uint32_t n_security_claims; + NTSTATUS status; + + if (security_claims_out == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (n_security_claims_out == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + *security_claims_out = NULL; + *n_security_claims_out = 0; + + if (claims_data == NULL) { + return NT_STATUS_OK; + } + + if (!(claims_data->flags & CLAIMS_DATA_SECURITY_CLAIMS_PRESENT)) { + struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *decoded_claims = NULL; + uint32_t n_decoded_claims = 0; + + /* See whether we have a claims set that we can convert. */ + if (!(claims_data->flags & CLAIMS_DATA_CLAIMS_PRESENT)) { + + /* + * See whether we have an encoded claims set that we can + * decode. + */ + if (!(claims_data->flags & CLAIMS_DATA_ENCODED_CLAIMS_PRESENT)) { + /* We don’t have anything. */ + return NT_STATUS_OK; + } + + /* Decode an existing claims set. */ + + if (claims_data->encoded_claims_set.length) { + TALLOC_CTX *tmp_ctx = NULL; + struct CLAIMS_SET_METADATA_NDR claims; + const struct CLAIMS_SET_METADATA *metadata = NULL; + enum ndr_err_code ndr_err; + + tmp_ctx = talloc_new(claims_data); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + ndr_err = ndr_pull_struct_blob(&claims_data->encoded_claims_set, + tmp_ctx, + &claims, + (ndr_pull_flags_fn_t)ndr_pull_CLAIMS_SET_METADATA_NDR); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DBG_ERR("Failed to parse encoded claims set: %s\n", + nt_errstr(status)); + talloc_free(tmp_ctx); + return status; + } + + metadata = claims.claims.metadata; + if (metadata != NULL) { + struct CLAIMS_SET_NDR *claims_set_ndr = metadata->claims_set; + if (claims_set_ndr != NULL) { + struct CLAIMS_SET **claims_set = &claims_set_ndr->claims.claims; + + claims_data->claims_set = talloc_move(claims_data, claims_set); + } + } + + talloc_free(tmp_ctx); + } + + claims_data->flags |= CLAIMS_DATA_CLAIMS_PRESENT; + } + + /* + * Convert the decoded claims set to the security attribute + * claims format. + */ + status = token_claims_to_claims_v1(claims_data, + claims_data->claims_set, + &decoded_claims, + &n_decoded_claims); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + claims_data->security_claims = decoded_claims; + claims_data->n_security_claims = n_decoded_claims; + + claims_data->flags |= CLAIMS_DATA_SECURITY_CLAIMS_PRESENT; + } + + if (claims_data->security_claims != NULL) { + security_claims = talloc_reference(mem_ctx, claims_data->security_claims); + if (security_claims == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + n_security_claims = claims_data->n_security_claims; + + *security_claims_out = security_claims; + *n_security_claims_out = n_security_claims; + + return NT_STATUS_OK; +} diff --git a/source4/auth/session.h b/source4/auth/session.h new file mode 100644 index 0000000..3258c80 --- /dev/null +++ b/source4/auth/session.h @@ -0,0 +1,152 @@ +/* + 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 "lib/util/data_blob.h" +#include "librpc/gen_ndr/security.h" +#include "libcli/util/werror.h" +#include "lib/util/time.h" +#include "librpc/gen_ndr/netlogon.h" +#include "librpc/gen_ndr/auth.h" + +struct loadparm_context; +struct tevent_context; +struct ldb_context; +struct ldb_dn; +/* Create a security token for a session SYSTEM (the most + * trusted/privileged account), including the local machine account as + * the off-host credentials */ +struct auth_session_info *system_session(struct loadparm_context *lp_ctx) ; + +enum claims_data_present { + CLAIMS_DATA_ENCODED_CLAIMS_PRESENT = 0x01, + CLAIMS_DATA_CLAIMS_PRESENT = 0x02, + CLAIMS_DATA_SECURITY_CLAIMS_PRESENT = 0x04, +}; + +struct claims_data { + DATA_BLOB encoded_claims_set; + struct CLAIMS_SET *claims_set; + /* + * These security claims are here treated as only a product — the result + * of conversion from another format — and ought not to be treated as + * authoritative. + */ + struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *security_claims; + uint32_t n_security_claims; + enum claims_data_present flags; +}; + +struct auth_claims { + struct claims_data *user_claims; + struct claims_data *device_claims; +}; + +NTSTATUS auth_anonymous_user_info_dc(TALLOC_CTX *mem_ctx, + const char *netbios_name, + struct auth_user_info_dc **interim_info); +NTSTATUS auth_generate_security_token(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, /* Optional, if you don't want privileges */ + struct ldb_context *sam_ctx, /* Optional, if you don't want local groups */ + const struct auth_user_info_dc *user_info_dc, + const struct auth_user_info_dc *device_info_dc, + const struct auth_claims auth_claims, + uint32_t session_info_flags, + struct security_token **_security_token); +NTSTATUS auth_generate_session_info(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, /* Optional, if you don't want privileges */ + struct ldb_context *sam_ctx, /* Optional, if you don't want local groups */ + const struct auth_user_info_dc *user_info_dc, + 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 arbitrary 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); + +NTSTATUS encode_claims_set(TALLOC_CTX *mem_ctx, + struct CLAIMS_SET *claims_set, + DATA_BLOB *claims_blob); + +/* + * Construct a ‘claims_data’ structure from a claims blob, such as is found in a + * PAC. + */ +NTSTATUS claims_data_from_encoded_claims_set(TALLOC_CTX *claims_data_ctx, + const DATA_BLOB *encoded_claims_set, + struct claims_data **out); + +/* + * Construct a ‘claims_data’ structure from a talloc‐allocated claims set, such + * as we might build from searching the database. If this function returns + * successfully, it assumes ownership of the claims set. + */ +NTSTATUS claims_data_from_claims_set(TALLOC_CTX *claims_data_ctx, + struct CLAIMS_SET *claims_set, + struct claims_data **out); + +/* + * From a ‘claims_data’ structure, return an encoded claims blob that can be put + * into a PAC. + */ +NTSTATUS claims_data_encoded_claims_set(TALLOC_CTX *mem_ctx, + struct claims_data *claims_data, + DATA_BLOB *encoded_claims_set_out); + +/* + * From a ‘claims_data’ structure, return an array of security claims that can + * be put in a security token for access checks. + */ +NTSTATUS claims_data_security_claims(TALLOC_CTX *mem_ctx, + struct claims_data *claims_data, + struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 **security_claims_out, + uint32_t *n_security_claims_out); + +#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..31a4517 --- /dev/null +++ b/source4/auth/system_session.c @@ -0,0 +1,577 @@ +/* + 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, + lp_ctx, + NULL /* sam_ctx */, + 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) { + talloc_free(session_info); + return NT_STATUS_NO_MEMORY; + } + + ok = cli_credentials_set_conf(session_info->credentials, lp_ctx); + if (!ok) { + talloc_free(session_info); + 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 = talloc(user_info_dc, struct auth_SidAttr); + if (user_info_dc->sids == NULL) { + talloc_free(user_info_dc); + return NT_STATUS_NO_MEMORY; + } + + user_info_dc->sids->sid = global_sid_System; + user_info_dc->sids->attrs = SE_GROUP_DEFAULT_FLAGS; + + /* 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); + 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, NULL, 16); + if (user_info_dc->lm_session_key.data == NULL) { + talloc_free(user_info_dc); + return NT_STATUS_NO_MEMORY; + } + + 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); + if (user_info_dc->info == NULL) { + talloc_free(user_info_dc); + return NT_STATUS_NO_MEMORY; + }; + + info->account_name = talloc_strdup(info, "SYSTEM"); + if (info->account_name == NULL) { + talloc_free(user_info_dc); + return NT_STATUS_NO_MEMORY; + }; + + info->domain_name = talloc_strdup(info, "NT AUTHORITY"); + if (info->domain_name == NULL) { + talloc_free(user_info_dc); + return NT_STATUS_NO_MEMORY; + }; + + info->full_name = talloc_strdup(info, "System"); + if (info->full_name == NULL) { + talloc_free(user_info_dc); + return NT_STATUS_NO_MEMORY; + }; + + info->logon_script = talloc_strdup(info, ""); + if (info->logon_script == NULL) { + talloc_free(user_info_dc); + return NT_STATUS_NO_MEMORY; + }; + + info->profile_path = talloc_strdup(info, ""); + if (info->profile_path == NULL) { + talloc_free(user_info_dc); + return NT_STATUS_NO_MEMORY; + }; + + info->home_directory = talloc_strdup(info, ""); + if (info->home_directory == NULL) { + talloc_free(user_info_dc); + return NT_STATUS_NO_MEMORY; + }; + + info->home_drive = talloc_strdup(info, ""); + 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 = 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->user_flags = 0; + + *_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 = 8; + user_info_dc->sids = talloc_array(user_info_dc, struct auth_SidAttr, user_info_dc->num_sids); + + user_info_dc->sids[PRIMARY_USER_SID_INDEX].sid = *domain_sid; + sid_append_rid(&user_info_dc->sids[PRIMARY_USER_SID_INDEX].sid, DOMAIN_RID_ADMINISTRATOR); + user_info_dc->sids[PRIMARY_USER_SID_INDEX].attrs = SE_GROUP_DEFAULT_FLAGS; + + user_info_dc->sids[PRIMARY_GROUP_SID_INDEX].sid = *domain_sid; + sid_append_rid(&user_info_dc->sids[PRIMARY_GROUP_SID_INDEX].sid, DOMAIN_RID_USERS); + user_info_dc->sids[PRIMARY_GROUP_SID_INDEX].attrs = SE_GROUP_DEFAULT_FLAGS; + + /* Add the primary group again. */ + user_info_dc->sids[2] = user_info_dc->sids[PRIMARY_GROUP_SID_INDEX]; + + user_info_dc->sids[3].sid = global_sid_Builtin_Administrators; + user_info_dc->sids[3].attrs = SE_GROUP_DEFAULT_FLAGS; + + user_info_dc->sids[4].sid = *domain_sid; + sid_append_rid(&user_info_dc->sids[4].sid, DOMAIN_RID_ADMINS); + user_info_dc->sids[4].attrs = SE_GROUP_DEFAULT_FLAGS; + user_info_dc->sids[5].sid = *domain_sid; + sid_append_rid(&user_info_dc->sids[5].sid, DOMAIN_RID_ENTERPRISE_ADMINS); + user_info_dc->sids[5].attrs = SE_GROUP_DEFAULT_FLAGS; + user_info_dc->sids[6].sid = *domain_sid; + sid_append_rid(&user_info_dc->sids[6].sid, DOMAIN_RID_POLICY_ADMINS); + user_info_dc->sids[6].attrs = SE_GROUP_DEFAULT_FLAGS; + user_info_dc->sids[7].sid = *domain_sid; + sid_append_rid(&user_info_dc->sids[7].sid, DOMAIN_RID_SCHEMA_ADMINS); + user_info_dc->sids[7].attrs = SE_GROUP_DEFAULT_FLAGS; + + /* What should the session key be?*/ + user_info_dc->user_session_key = data_blob_talloc(user_info_dc, NULL, 16); + 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, NULL, 16); + if (user_info_dc->lm_session_key.data == NULL) { + talloc_free(user_info_dc); + return NT_STATUS_NO_MEMORY; + }; + + 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); + if (user_info_dc->info == NULL) { + talloc_free(user_info_dc); + return NT_STATUS_NO_MEMORY; + }; + + info->account_name = talloc_strdup(info, "Administrator"); + if (info->account_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; + }; + + info->full_name = talloc_strdup(info, "Administrator"); + if (info->full_name == NULL) { + talloc_free(user_info_dc); + return NT_STATUS_NO_MEMORY; + }; + + info->logon_script = talloc_strdup(info, ""); + if (info->logon_script == NULL) { + talloc_free(user_info_dc); + return NT_STATUS_NO_MEMORY; + }; + + info->profile_path = talloc_strdup(info, ""); + if (info->profile_path == NULL) { + talloc_free(user_info_dc); + return NT_STATUS_NO_MEMORY; + }; + + info->home_directory = talloc_strdup(info, ""); + if (info->home_directory == NULL) { + talloc_free(user_info_dc); + return NT_STATUS_NO_MEMORY; + }; + + info->home_drive = talloc_strdup(info, ""); + 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 = 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->user_flags = 0; + + *_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, + lp_ctx, + NULL /* sam_ctx */, + 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 session_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; + + if (mem_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + 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, + lp_ctx, + NULL /* sam_ctx */, + 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) { + talloc_free(session_info); + return NT_STATUS_NO_MEMORY; + } + + ok = cli_credentials_set_conf(session_info->credentials, lp_ctx); + if (!ok) { + talloc_free(session_info); + 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 = talloc(user_info_dc, struct auth_SidAttr); + if (user_info_dc->sids == NULL) { + talloc_free(user_info_dc); + return NT_STATUS_NO_MEMORY; + }; + + user_info_dc->sids->sid = global_sid_Anonymous; + user_info_dc->sids->attrs = SE_GROUP_DEFAULT_FLAGS; + + /* annoying, but the Anonymous really does have a session key... */ + user_info_dc->user_session_key = data_blob_talloc(user_info_dc, NULL, 16); + 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, NULL, 16); + if (user_info_dc->lm_session_key.data == NULL) { + talloc_free(user_info_dc); + return NT_STATUS_NO_MEMORY; + }; + + /* 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); + if (user_info_dc->info == NULL) { + talloc_free(user_info_dc); + return NT_STATUS_NO_MEMORY; + }; + + info->account_name = talloc_strdup(info, "ANONYMOUS LOGON"); + if (info->account_name == NULL) { + talloc_free(user_info_dc); + return NT_STATUS_NO_MEMORY; + }; + + info->domain_name = talloc_strdup(info, "NT AUTHORITY"); + if (info->domain_name == NULL) { + talloc_free(user_info_dc); + return NT_STATUS_NO_MEMORY; + }; + + info->full_name = talloc_strdup(info, "Anonymous Logon"); + if (info->full_name == NULL) { + talloc_free(user_info_dc); + return NT_STATUS_NO_MEMORY; + }; + + info->logon_script = talloc_strdup(info, ""); + if (info->logon_script == NULL) { + talloc_free(user_info_dc); + return NT_STATUS_NO_MEMORY; + }; + + info->profile_path = talloc_strdup(info, ""); + if (info->profile_path == NULL) { + talloc_free(user_info_dc); + return NT_STATUS_NO_MEMORY; + }; + + info->home_directory = talloc_strdup(info, ""); + if (info->home_directory == NULL) { + talloc_free(user_info_dc); + return NT_STATUS_NO_MEMORY; + }; + + info->home_drive = talloc_strdup(info, ""); + 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 = 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; + + /* The user is not authenticated. */ + info->user_flags = NETLOGON_GUEST; + + *_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..a2bc4a5 --- /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 "libcli/util/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..97b8292 --- /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 < PRIMARY_SIDS_COUNT) { + 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[PRIMARY_USER_SID_INDEX].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[PRIMARY_USER_SID_INDEX].xid.type == ID_TYPE_BOTH) { + (*sec)->uid = ids[0].xid.id; + (*sec)->groups[g] = ids[0].xid.id; + g++; + } else if (ids[PRIMARY_USER_SID_INDEX].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[PRIMARY_USER_SID_INDEX].sid, &buf), + (int)ids[PRIMARY_USER_SID_INDEX].xid.type)); + security_token_debug(DBGC_AUTH, 0, token); + return NT_STATUS_INVALID_SID; + } + + if (ids[PRIMARY_GROUP_SID_INDEX].xid.type == ID_TYPE_BOTH || + ids[PRIMARY_GROUP_SID_INDEX].xid.type == ID_TYPE_GID) { + (*sec)->gid = ids[PRIMARY_GROUP_SID_INDEX].xid.id; + (*sec)->groups[g] = ids[PRIMARY_GROUP_SID_INDEX].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[PRIMARY_GROUP_SID_INDEX].sid, &buf), + (int)ids[PRIMARY_GROUP_SID_INDEX].xid.type)); + security_token_debug(DBGC_AUTH, 0, token); + return NT_STATUS_INVALID_SID; + } + + for (s=REMAINING_SIDS_INDEX; 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..57bb9f7 --- /dev/null +++ b/source4/auth/wscript_build @@ -0,0 +1,96 @@ +#!/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_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 + ''' + ) + +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 + ''' +) + +pytalloc_util = bld.pyembed_libname('pytalloc-util') +pyparam_util = bld.pyembed_libname('pyparam_util') +pyldb_util = bld.pyembed_libname('pyldb-util') +pycredentials = 'pycredentials' +libpython = bld.pyembed_libname('LIBPYTHON') + +bld.SAMBA_PYTHON('pyauth', + source='pyauth.c', + public_deps='auth_system_session', + deps=f'samdb {pytalloc_util} {pyparam_util} {pyldb_util} {pycredentials} {libpython} auth4', + 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) |