diff options
Diffstat (limited to 'auth/ntlmssp')
-rw-r--r-- | auth/ntlmssp/gensec_ntlmssp.c | 246 | ||||
-rw-r--r-- | auth/ntlmssp/gensec_ntlmssp_server.c | 245 | ||||
-rw-r--r-- | auth/ntlmssp/ntlmssp.c | 408 | ||||
-rw-r--r-- | auth/ntlmssp/ntlmssp.h | 151 | ||||
-rw-r--r-- | auth/ntlmssp/ntlmssp_client.c | 1020 | ||||
-rw-r--r-- | auth/ntlmssp/ntlmssp_ndr.c | 134 | ||||
-rw-r--r-- | auth/ntlmssp/ntlmssp_ndr.h | 38 | ||||
-rw-r--r-- | auth/ntlmssp/ntlmssp_private.h | 192 | ||||
-rw-r--r-- | auth/ntlmssp/ntlmssp_server.c | 1160 | ||||
-rw-r--r-- | auth/ntlmssp/ntlmssp_sign.c | 893 | ||||
-rw-r--r-- | auth/ntlmssp/ntlmssp_util.c | 220 | ||||
-rw-r--r-- | auth/ntlmssp/wscript_build | 27 |
12 files changed, 4734 insertions, 0 deletions
diff --git a/auth/ntlmssp/gensec_ntlmssp.c b/auth/ntlmssp/gensec_ntlmssp.c new file mode 100644 index 0000000..329d8eb --- /dev/null +++ b/auth/ntlmssp/gensec_ntlmssp.c @@ -0,0 +1,246 @@ +/* + * Unix SMB/CIFS implementation. + * Version 3.0 + * NTLMSSP Signing routines + * Copyright (C) Luke Kenneth Casson Leighton 1996-2001 + * Copyright (C) Andrew Bartlett <abartlet@samba.org> 2003-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/ntlmssp/ntlmssp.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" +#include "auth/ntlmssp/ntlmssp_private.h" + +NTSTATUS gensec_ntlmssp_magic(struct gensec_security *gensec_security, + const DATA_BLOB *first_packet) +{ + if (ntlmssp_blob_matches_magic(first_packet)) { + return NT_STATUS_OK; + } else { + return NT_STATUS_INVALID_PARAMETER; + } +} + +/** + * Return the NTLMSSP master session key + * + * @param ntlmssp_state NTLMSSP State + */ + +NTSTATUS gensec_ntlmssp_session_key(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + DATA_BLOB *session_key) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + struct ntlmssp_state *ntlmssp_state = gensec_ntlmssp->ntlmssp_state; + + if (ntlmssp_state->expected_state != NTLMSSP_DONE) { + return NT_STATUS_NO_USER_SESSION_KEY; + } + + if (!ntlmssp_state->session_key.data) { + return NT_STATUS_NO_USER_SESSION_KEY; + } + *session_key = data_blob_talloc(mem_ctx, ntlmssp_state->session_key.data, ntlmssp_state->session_key.length); + if (!session_key->data) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +bool gensec_ntlmssp_have_feature(struct gensec_security *gensec_security, + uint32_t feature) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + struct ntlmssp_state *ntlmssp_state = gensec_ntlmssp->ntlmssp_state; + + if (feature & GENSEC_FEATURE_SIGN) { + if (!ntlmssp_state->session_key.length) { + return false; + } + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SIGN) { + return true; + } + } + if (feature & GENSEC_FEATURE_SEAL) { + if (!ntlmssp_state->session_key.length) { + return false; + } + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SEAL) { + return true; + } + } + if (feature & GENSEC_FEATURE_SESSION_KEY) { + if (ntlmssp_state->session_key.length) { + return true; + } + } + if (feature & GENSEC_FEATURE_DCE_STYLE) { + return true; + } + if (feature & GENSEC_FEATURE_ASYNC_REPLIES) { + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + return true; + } + } + if (feature & GENSEC_FEATURE_SIGN_PKT_HEADER) { + return true; + } + if (feature & GENSEC_FEATURE_NEW_SPNEGO) { + if (!ntlmssp_state->session_key.length) { + return false; + } + if (!(ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SIGN)) { + return false; + } + return ntlmssp_state->new_spnego; + } + + return false; +} + +NTSTATUS gensec_ntlmssp_start(struct gensec_security *gensec_security) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp; + + gensec_ntlmssp = talloc_zero(gensec_security, + struct gensec_ntlmssp_context); + if (!gensec_ntlmssp) { + return NT_STATUS_NO_MEMORY; + } + + gensec_security->private_data = gensec_ntlmssp; + return NT_STATUS_OK; +} + +NTSTATUS gensec_ntlmssp_sign_packet(struct gensec_security *gensec_security, + TALLOC_CTX *sig_mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + NTSTATUS nt_status; + + nt_status = ntlmssp_sign_packet(gensec_ntlmssp->ntlmssp_state, + sig_mem_ctx, + data, length, + whole_pdu, pdu_length, + sig); + + return nt_status; +} + +NTSTATUS gensec_ntlmssp_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_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + NTSTATUS nt_status; + + nt_status = ntlmssp_check_packet(gensec_ntlmssp->ntlmssp_state, + data, length, + whole_pdu, pdu_length, + sig); + + return nt_status; +} + +NTSTATUS gensec_ntlmssp_seal_packet(struct gensec_security *gensec_security, + TALLOC_CTX *sig_mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + NTSTATUS nt_status; + + nt_status = ntlmssp_seal_packet(gensec_ntlmssp->ntlmssp_state, + sig_mem_ctx, + data, length, + whole_pdu, pdu_length, + sig); + + return nt_status; +} + +/* + wrappers for the ntlmssp_*() functions +*/ +NTSTATUS gensec_ntlmssp_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_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + NTSTATUS nt_status; + + nt_status = ntlmssp_unseal_packet(gensec_ntlmssp->ntlmssp_state, + data, length, + whole_pdu, pdu_length, + sig); + + return nt_status; +} + +size_t gensec_ntlmssp_sig_size(struct gensec_security *gensec_security, size_t data_size) +{ + return NTLMSSP_SIG_SIZE; +} + +NTSTATUS gensec_ntlmssp_wrap(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + + return ntlmssp_wrap(gensec_ntlmssp->ntlmssp_state, + out_mem_ctx, + in, out); +} + + +NTSTATUS gensec_ntlmssp_unwrap(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + + return ntlmssp_unwrap(gensec_ntlmssp->ntlmssp_state, + out_mem_ctx, + in, out); +} diff --git a/auth/ntlmssp/gensec_ntlmssp_server.c b/auth/ntlmssp/gensec_ntlmssp_server.c new file mode 100644 index 0000000..ab92f4d --- /dev/null +++ b/auth/ntlmssp/gensec_ntlmssp_server.c @@ -0,0 +1,245 @@ +/* + Unix SMB/Netbios implementation. + Version 3.0 + handle NLTMSSP, client server side parsing + + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2005 + 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 "system/network.h" +#include "lib/tsocket/tsocket.h" +#include "auth/ntlmssp/ntlmssp.h" +#include "../librpc/gen_ndr/ndr_ntlmssp.h" +#include "auth/ntlmssp/ntlmssp_ndr.h" +#include "auth/ntlmssp/ntlmssp_private.h" +#include "../libcli/auth/libcli_auth.h" +#include "../lib/crypto/crypto.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" +#include "auth/common_auth.h" +#include "param/param.h" +#include "param/loadparm.h" +#include "libds/common/roles.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +/** + * Return the credentials of a logged on user, including session keys + * etc. + * + * Only valid after a successful authentication + * + * May only be called once per authentication. + * + */ + +NTSTATUS gensec_ntlmssp_session_info(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + struct auth_session_info **session_info) +{ + NTSTATUS nt_status; + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + uint32_t session_info_flags = 0; + + if (gensec_security->want_features & GENSEC_FEATURE_UNIX_TOKEN) { + session_info_flags |= AUTH_SESSION_INFO_UNIX_TOKEN; + } + + session_info_flags |= AUTH_SESSION_INFO_DEFAULT_GROUPS; + session_info_flags |= AUTH_SESSION_INFO_NTLM; + + if (gensec_security->auth_context && gensec_security->auth_context->generate_session_info) { + nt_status = gensec_security->auth_context->generate_session_info(gensec_security->auth_context, mem_ctx, + gensec_ntlmssp->server_returned_info, + gensec_ntlmssp->ntlmssp_state->user, + session_info_flags, + session_info); + } else { + DEBUG(0, ("Cannot generate a session_info without the auth_context\n")); + return NT_STATUS_INTERNAL_ERROR; + } + + NT_STATUS_NOT_OK_RETURN(nt_status); + + nt_status = gensec_ntlmssp_session_key(gensec_security, *session_info, + &(*session_info)->session_key); + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_USER_SESSION_KEY)) { + (*session_info)->session_key = data_blob_null; + nt_status = NT_STATUS_OK; + } + + return nt_status; +} + +/** + * Start NTLMSSP on the server side + * + */ +NTSTATUS gensec_ntlmssp_server_start(struct gensec_security *gensec_security) +{ + NTSTATUS nt_status; + struct ntlmssp_state *ntlmssp_state; + struct gensec_ntlmssp_context *gensec_ntlmssp; + const char *netbios_name; + const char *netbios_domain; + const char *dns_name; + const char *dns_domain; + enum server_role role; + + role = lpcfg_server_role(gensec_security->settings->lp_ctx); + + nt_status = gensec_ntlmssp_start(gensec_security); + NT_STATUS_NOT_OK_RETURN(nt_status); + + gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + + ntlmssp_state = talloc_zero(gensec_ntlmssp, + struct ntlmssp_state); + if (!ntlmssp_state) { + return NT_STATUS_NO_MEMORY; + } + gensec_ntlmssp->ntlmssp_state = ntlmssp_state; + + ntlmssp_state->role = NTLMSSP_SERVER; + + ntlmssp_state->expected_state = NTLMSSP_NEGOTIATE; + + ntlmssp_state->allow_lm_response = + lpcfg_lanman_auth(gensec_security->settings->lp_ctx); + + if (ntlmssp_state->allow_lm_response && + gensec_setting_bool(gensec_security->settings, + "ntlmssp_server", "allow_lm_key", false)) + { + ntlmssp_state->allow_lm_key = true; + } + + ntlmssp_state->force_old_spnego = false; + + if (gensec_setting_bool(gensec_security->settings, "ntlmssp_server", "force_old_spnego", false)) { + /* + * For testing Windows 2000 mode + */ + ntlmssp_state->force_old_spnego = true; + } + + ntlmssp_state->neg_flags = + NTLMSSP_NEGOTIATE_NTLM | NTLMSSP_NEGOTIATE_VERSION; + + if (gensec_setting_bool(gensec_security->settings, "ntlmssp_server", "128bit", true)) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_128; + } + + if (gensec_setting_bool(gensec_security->settings, "ntlmssp_server", "56bit", true)) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_56; + } + + if (gensec_setting_bool(gensec_security->settings, "ntlmssp_server", "keyexchange", true)) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_KEY_EXCH; + } + + if (gensec_setting_bool(gensec_security->settings, "ntlmssp_server", "alwayssign", true)) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN; + } + + if (gensec_setting_bool(gensec_security->settings, "ntlmssp_server", "ntlm2", true)) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_NTLM2; + } + + if (ntlmssp_state->allow_lm_key) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_LM_KEY; + } + + /* + * We always allow NTLMSSP_NEGOTIATE_SIGN and NTLMSSP_NEGOTIATE_SEAL. + * + * These will be removed if the client doesn't want them. + */ + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_SIGN; + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_SEAL; + + + if (role == ROLE_STANDALONE) { + ntlmssp_state->server.is_standalone = true; + } else { + ntlmssp_state->server.is_standalone = false; + } + + if (gensec_security->settings->server_netbios_name) { + netbios_name = gensec_security->settings->server_netbios_name; + } else { + netbios_name = lpcfg_netbios_name(gensec_security->settings->lp_ctx); + } + + if (gensec_security->settings->server_netbios_domain) { + netbios_domain = gensec_security->settings->server_netbios_domain; + } else { + netbios_domain = lpcfg_workgroup(gensec_security->settings->lp_ctx); + } + + if (gensec_security->settings->server_dns_name) { + dns_name = gensec_security->settings->server_dns_name; + } else { + const char *dnsdomain = lpcfg_dnsdomain(gensec_security->settings->lp_ctx); + char *lower_netbiosname; + + lower_netbiosname = strlower_talloc(ntlmssp_state, netbios_name); + NT_STATUS_HAVE_NO_MEMORY(lower_netbiosname); + + /* Find out the DNS host name */ + if (dnsdomain && dnsdomain[0] != '\0') { + dns_name = talloc_asprintf(ntlmssp_state, "%s.%s", + lower_netbiosname, + dnsdomain); + talloc_free(lower_netbiosname); + NT_STATUS_HAVE_NO_MEMORY(dns_name); + } else { + dns_name = lower_netbiosname; + } + } + + if (gensec_security->settings->server_dns_domain) { + dns_domain = gensec_security->settings->server_dns_domain; + } else { + dns_domain = lpcfg_dnsdomain(gensec_security->settings->lp_ctx); + } + + ntlmssp_state->server.netbios_name = talloc_strdup(ntlmssp_state, netbios_name); + NT_STATUS_HAVE_NO_MEMORY(ntlmssp_state->server.netbios_name); + + ntlmssp_state->server.netbios_domain = talloc_strdup(ntlmssp_state, netbios_domain); + NT_STATUS_HAVE_NO_MEMORY(ntlmssp_state->server.netbios_domain); + + ntlmssp_state->server.dns_name = talloc_strdup(ntlmssp_state, dns_name); + NT_STATUS_HAVE_NO_MEMORY(ntlmssp_state->server.dns_name); + + ntlmssp_state->server.dns_domain = talloc_strdup(ntlmssp_state, dns_domain); + NT_STATUS_HAVE_NO_MEMORY(ntlmssp_state->server.dns_domain); + + ntlmssp_state->neg_flags |= ntlmssp_state->required_flags; + ntlmssp_state->conf_flags = ntlmssp_state->neg_flags; + + return NT_STATUS_OK; +} + diff --git a/auth/ntlmssp/ntlmssp.c b/auth/ntlmssp/ntlmssp.c new file mode 100644 index 0000000..745f262 --- /dev/null +++ b/auth/ntlmssp/ntlmssp.c @@ -0,0 +1,408 @@ +/* + Unix SMB/Netbios implementation. + Version 3.0 + handle NLTMSSP, client server side parsing + + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2005 + 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/>. +*/ + +struct auth_session_info; + +#include "includes.h" +#include <tevent.h> +#include "lib/util/tevent_ntstatus.h" +#include "auth/ntlmssp/ntlmssp.h" +#include "auth/ntlmssp/ntlmssp_private.h" +#include "../libcli/auth/libcli_auth.h" +#include "librpc/gen_ndr/ndr_dcerpc.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +/** + * Callbacks for NTLMSSP - for both client and server operating modes + * + */ + +static const struct ntlmssp_callbacks { + enum ntlmssp_role role; + enum ntlmssp_message_type command; + NTSTATUS (*sync_fn)(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB in, DATA_BLOB *out); + struct tevent_req *(*send_fn)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_security, + const DATA_BLOB in); + NTSTATUS (*recv_fn)(struct tevent_req *req, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out); +} ntlmssp_callbacks[] = { + { + .role = NTLMSSP_CLIENT, + .command = NTLMSSP_INITIAL, + .sync_fn = ntlmssp_client_initial, + },{ + .role = NTLMSSP_CLIENT, + .command = NTLMSSP_NEGOTIATE, + .sync_fn = gensec_ntlmssp_resume_ccache, + },{ + .role = NTLMSSP_SERVER, + .command = NTLMSSP_NEGOTIATE, + .sync_fn = gensec_ntlmssp_server_negotiate, + },{ + .role = NTLMSSP_CLIENT, + .command = NTLMSSP_CHALLENGE, + .sync_fn = ntlmssp_client_challenge, + },{ + .role = NTLMSSP_SERVER, + .command = NTLMSSP_AUTH, + .send_fn = ntlmssp_server_auth_send, + .recv_fn = ntlmssp_server_auth_recv, + } +}; + + +static NTSTATUS gensec_ntlmssp_update_find(struct gensec_security *gensec_security, + struct gensec_ntlmssp_context *gensec_ntlmssp, + const DATA_BLOB input, uint32_t *idx) +{ + uint32_t ntlmssp_command; + uint32_t i; + + if (gensec_ntlmssp->ntlmssp_state->expected_state == NTLMSSP_DONE) { + /* We are strict here because other modules, which we + * don't fully control (such as GSSAPI) are also + * strict, but are tested less often */ + + DEBUG(1, ("Called NTLMSSP after state machine was 'done'\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!input.length) { + switch (gensec_ntlmssp->ntlmssp_state->role) { + case NTLMSSP_CLIENT: + if (gensec_ntlmssp->ntlmssp_state->resume_ccache) { + /* + * make sure gensec_ntlmssp_resume_ccache() + * will be called + */ + ntlmssp_command = NTLMSSP_NEGOTIATE; + break; + } + + ntlmssp_command = NTLMSSP_INITIAL; + break; + case NTLMSSP_SERVER: + if (gensec_security->want_features & GENSEC_FEATURE_DATAGRAM_MODE) { + /* 'datagram' mode - no neg packet */ + ntlmssp_command = NTLMSSP_NEGOTIATE; + } else { + /* This is normal in SPNEGO mech negotiation fallback */ + DEBUG(2, ("Failed to parse NTLMSSP packet: zero length\n")); + return NT_STATUS_INVALID_PARAMETER; + } + break; + default: + DEBUG(1, ("NTLMSSP state has invalid role %d\n", + gensec_ntlmssp->ntlmssp_state->role)); + return NT_STATUS_INVALID_PARAMETER; + } + } else { + if (!msrpc_parse(gensec_ntlmssp->ntlmssp_state, + &input, "Cd", + "NTLMSSP", + &ntlmssp_command)) { + DEBUG(1, ("Failed to parse NTLMSSP packet, could not extract NTLMSSP command\n")); + dump_data(2, input.data, input.length); + return NT_STATUS_INVALID_PARAMETER; + } + } + + if (ntlmssp_command != gensec_ntlmssp->ntlmssp_state->expected_state) { + DEBUG(2, ("got NTLMSSP command %u, expected %u\n", ntlmssp_command, + gensec_ntlmssp->ntlmssp_state->expected_state)); + return NT_STATUS_INVALID_PARAMETER; + } + + for (i=0; i < ARRAY_SIZE(ntlmssp_callbacks); i++) { + if (ntlmssp_callbacks[i].role == gensec_ntlmssp->ntlmssp_state->role && + ntlmssp_callbacks[i].command == ntlmssp_command) { + *idx = i; + return NT_STATUS_OK; + } + } + + DEBUG(1, ("failed to find NTLMSSP callback for NTLMSSP mode %u, command %u\n", + gensec_ntlmssp->ntlmssp_state->role, ntlmssp_command)); + + return NT_STATUS_INVALID_PARAMETER; +} + +struct gensec_ntlmssp_update_state { + const struct ntlmssp_callbacks *cb; + NTSTATUS status; + DATA_BLOB out; +}; + +static void gensec_ntlmssp_update_done(struct tevent_req *subreq); + +static struct tevent_req *gensec_ntlmssp_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_security, + const DATA_BLOB in) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + struct tevent_req *req = NULL; + struct gensec_ntlmssp_update_state *state = NULL; + NTSTATUS status; + uint32_t i = 0; + + req = tevent_req_create(mem_ctx, &state, + struct gensec_ntlmssp_update_state); + if (req == NULL) { + return NULL; + } + + status = gensec_ntlmssp_update_find(gensec_security, + gensec_ntlmssp, + in, &i); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + if (ntlmssp_callbacks[i].send_fn != NULL) { + struct tevent_req *subreq = NULL; + + state->cb = &ntlmssp_callbacks[i]; + + subreq = state->cb->send_fn(state, ev, + gensec_security, + in); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, + gensec_ntlmssp_update_done, + req); + return req; + } + + status = ntlmssp_callbacks[i].sync_fn(gensec_security, + state, + 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 void gensec_ntlmssp_update_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct gensec_ntlmssp_update_state *state = + tevent_req_data(req, + struct gensec_ntlmssp_update_state); + NTSTATUS status; + + status = state->cb->recv_fn(subreq, state, &state->out); + TALLOC_FREE(subreq); + if (GENSEC_UPDATE_IS_NTERROR(status)) { + tevent_req_nterror(req, status); + return; + } + + state->status = status; + tevent_req_done(req); +} + +static NTSTATUS gensec_ntlmssp_update_recv(struct tevent_req *req, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + struct gensec_ntlmssp_update_state *state = + tevent_req_data(req, + struct gensec_ntlmssp_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_ntlmssp_may_reset_crypto(struct gensec_security *gensec_security, + bool full_reset) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + struct ntlmssp_state *ntlmssp_state = gensec_ntlmssp->ntlmssp_state; + NTSTATUS status; + bool reset_seqnums = full_reset; + + if (!gensec_ntlmssp_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + return NT_STATUS_OK; + } + + status = ntlmssp_sign_reset(ntlmssp_state, reset_seqnums); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Could not reset NTLMSSP signing/sealing system (error was: %s)\n", + nt_errstr(status))); + return status; + } + + return NT_STATUS_OK; +} + +static const char *gensec_ntlmssp_final_auth_type(struct gensec_security *gensec_security) +{ + return GENSEC_FINAL_AUTH_TYPE_NTLMSSP; +} + +static const char *gensec_ntlmssp_oids[] = { + GENSEC_OID_NTLMSSP, + NULL +}; + +static const struct gensec_security_ops gensec_ntlmssp_security_ops = { + .name = "ntlmssp", + .sasl_name = GENSEC_SASL_NAME_NTLMSSP, /* "NTLM" */ + .auth_type = DCERPC_AUTH_TYPE_NTLMSSP, + .weak_crypto = true, + .oid = gensec_ntlmssp_oids, + .client_start = gensec_ntlmssp_client_start, + .server_start = gensec_ntlmssp_server_start, + .magic = gensec_ntlmssp_magic, + .update_send = gensec_ntlmssp_update_send, + .update_recv = gensec_ntlmssp_update_recv, + .may_reset_crypto= gensec_ntlmssp_may_reset_crypto, + .sig_size = gensec_ntlmssp_sig_size, + .sign_packet = gensec_ntlmssp_sign_packet, + .check_packet = gensec_ntlmssp_check_packet, + .seal_packet = gensec_ntlmssp_seal_packet, + .unseal_packet = gensec_ntlmssp_unseal_packet, + .wrap = gensec_ntlmssp_wrap, + .unwrap = gensec_ntlmssp_unwrap, + .session_key = gensec_ntlmssp_session_key, + .session_info = gensec_ntlmssp_session_info, + .have_feature = gensec_ntlmssp_have_feature, + .final_auth_type = gensec_ntlmssp_final_auth_type, + .enabled = true, + .priority = GENSEC_NTLMSSP +}; + +static const struct gensec_security_ops gensec_ntlmssp_resume_ccache_ops = { + .name = "ntlmssp_resume_ccache", + .weak_crypto = true, + .client_start = gensec_ntlmssp_resume_ccache_start, + .update_send = gensec_ntlmssp_update_send, + .update_recv = gensec_ntlmssp_update_recv, + .session_key = gensec_ntlmssp_session_key, + .have_feature = gensec_ntlmssp_have_feature, + .enabled = true, + .priority = GENSEC_NTLMSSP +}; + +_PUBLIC_ NTSTATUS gensec_ntlmssp_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret; + + ret = gensec_register(ctx, &gensec_ntlmssp_security_ops); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register '%s' gensec backend!\n", + gensec_ntlmssp_security_ops.name)); + return ret; + } + + ret = gensec_register(ctx, &gensec_ntlmssp_resume_ccache_ops); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register '%s' gensec backend!\n", + gensec_ntlmssp_resume_ccache_ops.name)); + return ret; + } + + return ret; +} + +static struct gensec_security *gensec_find_child_by_ops(struct gensec_security *gensec_security, + const struct gensec_security_ops *ops) +{ + struct gensec_security *current = gensec_security; + + while (current != NULL) { + if (current->ops == ops) { + return current; + } + + current = current->child_security; + } + + return NULL; +} + +uint32_t gensec_ntlmssp_neg_flags(struct gensec_security *gensec_security) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp; + + gensec_security = gensec_find_child_by_ops(gensec_security, + &gensec_ntlmssp_security_ops); + if (gensec_security == NULL) { + return 0; + } + + gensec_ntlmssp = talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + return gensec_ntlmssp->ntlmssp_state->neg_flags; +} + +const char *gensec_ntlmssp_server_domain(struct gensec_security *gensec_security) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp; + + gensec_security = gensec_find_child_by_ops(gensec_security, + &gensec_ntlmssp_security_ops); + if (gensec_security == NULL) { + return NULL; + } + + gensec_ntlmssp = talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + return gensec_ntlmssp->ntlmssp_state->server.netbios_domain; +} diff --git a/auth/ntlmssp/ntlmssp.h b/auth/ntlmssp/ntlmssp.h new file mode 100644 index 0000000..658d3fa --- /dev/null +++ b/auth/ntlmssp/ntlmssp.h @@ -0,0 +1,151 @@ +/* + Unix SMB/CIFS implementation. + SMB parameters and setup + Copyright (C) Andrew Tridgell 1992-1997 + Copyright (C) Luke Kenneth Casson Leighton 1996-1997 + Copyright (C) Paul Ashton 1997 + 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 "../librpc/gen_ndr/ntlmssp.h" + +struct auth_context; +struct auth_serversupplied_info; +struct tsocket_address; +struct auth_user_info_dc; +struct gensec_security; +struct ntlmssp_state; + +/* NTLMSSP mode */ +enum ntlmssp_role +{ + NTLMSSP_SERVER, + NTLMSSP_CLIENT +}; + +/* NTLMSSP message types */ +enum ntlmssp_message_type +{ + NTLMSSP_INITIAL = 0 /* samba internal state */, + NTLMSSP_NEGOTIATE = 1, + NTLMSSP_CHALLENGE = 2, + NTLMSSP_AUTH = 3, + NTLMSSP_UNKNOWN = 4, + NTLMSSP_DONE = 5 /* samba final state */ +}; + +#define NTLMSSP_FEATURE_SESSION_KEY 0x00000001 +#define NTLMSSP_FEATURE_SIGN 0x00000002 +#define NTLMSSP_FEATURE_SEAL 0x00000004 +#define NTLMSSP_FEATURE_CCACHE 0x00000008 + +union ntlmssp_crypt_state; + +struct ntlmssp_state +{ + enum ntlmssp_role role; + uint32_t expected_state; + + bool unicode; + bool use_ntlmv2; + bool use_ccache; + bool resume_ccache; + bool use_nt_response; /* Set to 'False' to debug what happens when the NT response is omited */ + bool allow_lm_response;/* The LM_RESPONSE code is not very secure... */ + bool allow_lm_key; /* The LM_KEY code is not very secure... */ + + const char *user; + const char *domain; + uint8_t *nt_hash; + uint8_t *lm_hash; + + DATA_BLOB negotiate_blob; + DATA_BLOB challenge_blob; + bool new_spnego; + bool force_old_spnego; + + struct { + const char *netbios_name; + const char *netbios_domain; + struct AV_PAIR_LIST av_pair_list; + } client; + + struct { + bool is_standalone; + const char *netbios_name; + const char *netbios_domain; + const char *dns_name; + const char *dns_domain; + NTTIME challenge_endtime; + struct AV_PAIR_LIST av_pair_list; + } server; + + DATA_BLOB internal_chal; /* Random challenge as supplied to the client for NTLM authentication */ + + DATA_BLOB chal; /* Random challenge as input into the actual NTLM (or NTLM2) authentication */ + DATA_BLOB lm_resp; + DATA_BLOB nt_resp; + DATA_BLOB session_key; + + uint32_t conf_flags; + uint32_t required_flags; + uint32_t neg_flags; /* the current state of negotiation with the NTLMSSP partner */ + + bool force_wrap_seal; + + union ntlmssp_crypt_state *crypt; +}; + +/* The following definitions come from libcli/auth/ntlmssp_sign.c */ + +NTSTATUS ntlmssp_sign_packet(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *sig_mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig); +NTSTATUS ntlmssp_check_packet(struct ntlmssp_state *ntlmssp_state, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) ; +NTSTATUS ntlmssp_seal_packet(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *sig_mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig); +NTSTATUS ntlmssp_unseal_packet(struct ntlmssp_state *ntlmssp_state, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig); +NTSTATUS ntlmssp_wrap(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out); +NTSTATUS ntlmssp_unwrap(struct ntlmssp_state *ntlmssp_stae, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out); +NTSTATUS ntlmssp_sign_reset(struct ntlmssp_state *ntlmssp_state, + bool reset_seqnums); +NTSTATUS ntlmssp_sign_init(struct ntlmssp_state *ntlmssp_state); + +bool ntlmssp_blob_matches_magic(const DATA_BLOB *blob); + +/* The following definitions come from auth/ntlmssp/gensec_ntlmssp.c */ + +NTSTATUS gensec_ntlmssp_init(TALLOC_CTX *ctx); + +uint32_t gensec_ntlmssp_neg_flags(struct gensec_security *gensec_security); +const char *gensec_ntlmssp_server_domain(struct gensec_security *gensec_security); diff --git a/auth/ntlmssp/ntlmssp_client.c b/auth/ntlmssp/ntlmssp_client.c new file mode 100644 index 0000000..af1642d --- /dev/null +++ b/auth/ntlmssp/ntlmssp_client.c @@ -0,0 +1,1020 @@ +/* + Unix SMB/Netbios implementation. + Version 3.0 + handle NLTMSSP, client server side parsing + + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2005 + 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/>. +*/ + +struct auth_session_info; + +#include "includes.h" +#include "auth/ntlmssp/ntlmssp.h" +#include "../libcli/auth/libcli_auth.h" +#include "auth/credentials/credentials.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" +#include "param/param.h" +#include "auth/ntlmssp/ntlmssp_private.h" +#include "../librpc/gen_ndr/ndr_ntlmssp.h" +#include "../auth/ntlmssp/ntlmssp_ndr.h" +#include "../nsswitch/libwbclient/wbclient.h" + +#include "lib/crypto/gnutls_helpers.h" +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +/********************************************************************* + Client side NTLMSSP +*********************************************************************/ + +/** + * Next state function for the Initial packet + * + * @param ntlmssp_state NTLMSSP State + * @param out_mem_ctx The DATA_BLOB *out will be allocated on this context + * @param in A NULL data blob (input ignored) + * @param out The initial negotiate request to the server, as an talloc()ed DATA_BLOB, on out_mem_ctx + * @return Errors or NT_STATUS_OK. + */ + +NTSTATUS ntlmssp_client_initial(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB in, DATA_BLOB *out) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + struct ntlmssp_state *ntlmssp_state = gensec_ntlmssp->ntlmssp_state; + NTSTATUS status; + const DATA_BLOB version_blob = ntlmssp_version_blob(); + + /* generate the ntlmssp negotiate packet */ + status = msrpc_gen(out_mem_ctx, + out, "CddAAb", + "NTLMSSP", + NTLMSSP_NEGOTIATE, + ntlmssp_state->neg_flags, + "", /* domain */ + "", /* workstation */ + version_blob.data, version_blob.length); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("ntlmssp_client_initial: failed to generate " + "ntlmssp negotiate packet\n")); + return status; + } + + if (DEBUGLEVEL >= 10) { + struct NEGOTIATE_MESSAGE *negotiate = talloc( + ntlmssp_state, struct NEGOTIATE_MESSAGE); + if (negotiate != NULL) { + status = ntlmssp_pull_NEGOTIATE_MESSAGE( + out, negotiate, negotiate); + if (NT_STATUS_IS_OK(status)) { + NDR_PRINT_DEBUG(NEGOTIATE_MESSAGE, + negotiate); + } + TALLOC_FREE(negotiate); + } + } + + ntlmssp_state->negotiate_blob = data_blob_dup_talloc(ntlmssp_state, + *out); + if (ntlmssp_state->negotiate_blob.length != out->length) { + return NT_STATUS_NO_MEMORY; + } + + ntlmssp_state->expected_state = NTLMSSP_CHALLENGE; + + return NT_STATUS_MORE_PROCESSING_REQUIRED; +} + +NTSTATUS gensec_ntlmssp_resume_ccache(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB in, DATA_BLOB *out) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + struct ntlmssp_state *ntlmssp_state = gensec_ntlmssp->ntlmssp_state; + uint32_t neg_flags = 0; + uint32_t ntlmssp_command; + NTSTATUS status; + bool ok; + + *out = data_blob_null; + + if (in.length == 0) { + /* + * This is compat code for older callers + * which were missing the "initial_blob"/"negotiate_blob". + * + * That means we can't calculate the NTLMSSP_MIC + * field correctly and need to force the + * old_spnego behaviour. + */ + DEBUG(10, ("%s: in.length==%u force_old_spnego!\n", + __func__, (unsigned int)in.length)); + ntlmssp_state->force_old_spnego = true; + ntlmssp_state->neg_flags |= ntlmssp_state->required_flags; + ntlmssp_state->required_flags = 0; + ntlmssp_state->expected_state = NTLMSSP_CHALLENGE; + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } + + /* parse the NTLMSSP packet */ + + if (in.length > UINT16_MAX) { + DEBUG(1, ("%s: reject large request of length %u\n", + __func__, (unsigned int)in.length)); + return NT_STATUS_INVALID_PARAMETER; + } + + ok = msrpc_parse(ntlmssp_state, &in, "Cdd", + "NTLMSSP", + &ntlmssp_command, + &neg_flags); + if (!ok) { + DEBUG(1, ("%s: failed to parse NTLMSSP Negotiate of length %u\n", + __func__, (unsigned int)in.length)); + dump_data(2, in.data, in.length); + return NT_STATUS_INVALID_PARAMETER; + } + + if (ntlmssp_command != NTLMSSP_NEGOTIATE) { + DEBUG(1, ("%s: no NTLMSSP Negotiate message (length %u)\n", + __func__, (unsigned int)in.length)); + dump_data(2, in.data, in.length); + return NT_STATUS_INVALID_PARAMETER; + } + + ntlmssp_state->neg_flags = neg_flags; + DEBUG(3, ("Imported Negotiate flags:\n")); + debug_ntlmssp_flags(neg_flags); + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_UNICODE) { + ntlmssp_state->unicode = true; + } else { + ntlmssp_state->unicode = false; + } + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SIGN) { + gensec_security->want_features |= GENSEC_FEATURE_SIGN; + } + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SEAL) { + gensec_security->want_features |= GENSEC_FEATURE_SEAL; + } + + ntlmssp_state->conf_flags = ntlmssp_state->neg_flags; + ntlmssp_state->required_flags = 0; + + if (DEBUGLEVEL >= 10) { + struct NEGOTIATE_MESSAGE *negotiate = talloc( + ntlmssp_state, struct NEGOTIATE_MESSAGE); + if (negotiate != NULL) { + status = ntlmssp_pull_NEGOTIATE_MESSAGE( + &in, negotiate, negotiate); + if (NT_STATUS_IS_OK(status)) { + NDR_PRINT_DEBUG(NEGOTIATE_MESSAGE, + negotiate); + } + TALLOC_FREE(negotiate); + } + } + + ntlmssp_state->negotiate_blob = data_blob_dup_talloc(ntlmssp_state, + in); + if (ntlmssp_state->negotiate_blob.length != in.length) { + return NT_STATUS_NO_MEMORY; + } + + ntlmssp_state->expected_state = NTLMSSP_CHALLENGE; + + return NT_STATUS_MORE_PROCESSING_REQUIRED; +} + +/** + * Next state function for the Challenge Packet. Generate an auth packet. + * + * @param gensec_security GENSEC state + * @param out_mem_ctx Memory context for *out + * @param in The server challnege, as a DATA_BLOB. reply.data must be NULL + * @param out The next request (auth packet) to the server, as an allocated DATA_BLOB, on the out_mem_ctx context + * @return Errors or NT_STATUS_OK. + */ + +NTSTATUS ntlmssp_client_challenge(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + struct ntlmssp_state *ntlmssp_state = gensec_ntlmssp->ntlmssp_state; + uint32_t chal_flags, ntlmssp_command, unkn1 = 0, unkn2 = 0; + DATA_BLOB server_domain_blob; + DATA_BLOB challenge_blob; + DATA_BLOB target_info = data_blob(NULL, 0); + char *server_domain; + const char *chal_parse_string; + const char *chal_parse_string_short = NULL; + const char *auth_gen_string; + DATA_BLOB lm_response = data_blob(NULL, 0); + DATA_BLOB nt_response = data_blob(NULL, 0); + DATA_BLOB session_key = data_blob(NULL, 0); + DATA_BLOB lm_session_key = data_blob(NULL, 0); + DATA_BLOB encrypted_session_key = data_blob(NULL, 0); + NTSTATUS nt_status; + int flags = 0; + const char *user = NULL, *domain = NULL, *workstation = NULL; + bool is_anonymous = false; + const DATA_BLOB version_blob = ntlmssp_version_blob(); + const NTTIME *server_timestamp = NULL; + uint8_t mic_buffer[NTLMSSP_MIC_SIZE] = { 0, }; + DATA_BLOB mic_blob = data_blob_const(mic_buffer, sizeof(mic_buffer)); + gnutls_hmac_hd_t hmac_hnd = NULL; + int rc; + + TALLOC_CTX *mem_ctx = talloc_new(out_mem_ctx); + if (!mem_ctx) { + return NT_STATUS_NO_MEMORY; + } + + if (!msrpc_parse(mem_ctx, + &in, "CdBd", + "NTLMSSP", + &ntlmssp_command, + &server_domain_blob, + &chal_flags)) { + DEBUG(1, ("Failed to parse the NTLMSSP Challenge: (#1)\n")); + dump_data(2, in.data, in.length); + talloc_free(mem_ctx); + + return NT_STATUS_INVALID_PARAMETER; + } + + data_blob_free(&server_domain_blob); + + DEBUG(3, ("Got challenge flags:\n")); + debug_ntlmssp_flags(chal_flags); + + nt_status = ntlmssp_handle_neg_flags(ntlmssp_state, + chal_flags, "challenge"); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + if (ntlmssp_state->unicode) { + if (chal_flags & NTLMSSP_NEGOTIATE_TARGET_INFO) { + chal_parse_string = "CdUdbddB"; + } else { + chal_parse_string = "CdUdbdd"; + chal_parse_string_short = "CdUdb"; + } + auth_gen_string = "CdBBUUUBdbb"; + } else { + if (chal_flags & NTLMSSP_NEGOTIATE_TARGET_INFO) { + chal_parse_string = "CdAdbddB"; + } else { + chal_parse_string = "CdAdbdd"; + chal_parse_string_short = "CdAdb"; + } + + auth_gen_string = "CdBBAAABdbb"; + } + + if (!msrpc_parse(mem_ctx, + &in, chal_parse_string, + "NTLMSSP", + &ntlmssp_command, + &server_domain, + &chal_flags, + &challenge_blob, 8, + &unkn1, &unkn2, + &target_info)) { + + bool ok = false; + + DEBUG(1, ("Failed to parse the NTLMSSP Challenge: (#2)\n")); + + if (chal_parse_string_short != NULL) { + /* + * In the case where NTLMSSP_NEGOTIATE_TARGET_INFO + * is not used, some NTLMSSP servers don't return + * the unused unkn1 and unkn2 fields. + * See bug: + * https://bugzilla.samba.org/show_bug.cgi?id=10016 + * for packet traces. + * Try and parse again without them. + */ + ok = msrpc_parse(mem_ctx, + &in, chal_parse_string_short, + "NTLMSSP", + &ntlmssp_command, + &server_domain, + &chal_flags, + &challenge_blob, 8); + if (!ok) { + DEBUG(1, ("Failed to short parse " + "the NTLMSSP Challenge: (#2)\n")); + } + } + + if (!ok) { + dump_data(2, in.data, in.length); + talloc_free(mem_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + } + + if (DEBUGLEVEL >= 10) { + struct CHALLENGE_MESSAGE *challenge = + talloc(ntlmssp_state, struct CHALLENGE_MESSAGE); + if (challenge != NULL) { + NTSTATUS status; + challenge->NegotiateFlags = chal_flags; + status = ntlmssp_pull_CHALLENGE_MESSAGE( + &in, challenge, challenge); + if (NT_STATUS_IS_OK(status)) { + NDR_PRINT_DEBUG(CHALLENGE_MESSAGE, + challenge); + } + TALLOC_FREE(challenge); + } + } + + if (chal_flags & NTLMSSP_TARGET_TYPE_SERVER) { + ntlmssp_state->server.is_standalone = true; + } else { + ntlmssp_state->server.is_standalone = false; + } + /* TODO: parse struct_blob and fill in the rest */ + ntlmssp_state->server.netbios_name = ""; + ntlmssp_state->server.netbios_domain = talloc_move(ntlmssp_state, &server_domain); + ntlmssp_state->server.dns_name = ""; + ntlmssp_state->server.dns_domain = ""; + + if (challenge_blob.length != 8) { + talloc_free(mem_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + is_anonymous = cli_credentials_is_anonymous(gensec_security->credentials); + cli_credentials_get_ntlm_username_domain(gensec_security->credentials, mem_ctx, + &user, &domain); + + workstation = cli_credentials_get_workstation(gensec_security->credentials); + + if (user == NULL) { + DEBUG(10, ("User is NULL, returning INVALID_PARAMETER\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (domain == NULL) { + DEBUG(10, ("Domain is NULL, returning INVALID_PARAMETER\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (workstation == NULL) { + DEBUG(10, ("Workstation is NULL, returning INVALID_PARAMETER\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (is_anonymous) { + ntlmssp_state->neg_flags |= NTLMSSP_ANONYMOUS; + /* + * don't use the ccache for anonymous auth + */ + ntlmssp_state->use_ccache = false; + } + if (ntlmssp_state->use_ccache) { + struct samr_Password *nt_hash = NULL; + + /* + * If we have a password given we don't + * use the ccache + */ + nt_hash = cli_credentials_get_nt_hash(gensec_security->credentials, + mem_ctx); + if (nt_hash != NULL) { + ZERO_STRUCTP(nt_hash); + TALLOC_FREE(nt_hash); + ntlmssp_state->use_ccache = false; + } + } + + if (ntlmssp_state->use_ccache) { + struct wbcCredentialCacheParams params; + struct wbcCredentialCacheInfo *info = NULL; + struct wbcAuthErrorInfo *error = NULL; + struct wbcNamedBlob auth_blobs[2]; + const struct wbcBlob *wbc_auth_blob = NULL; + const struct wbcBlob *wbc_session_key = NULL; + wbcErr wbc_status; + size_t i; + bool new_spnego = false; + + params.account_name = user; + params.domain_name = domain; + params.level = WBC_CREDENTIAL_CACHE_LEVEL_NTLMSSP; + + auth_blobs[0].name = "challenge_blob"; + auth_blobs[0].flags = 0; + auth_blobs[0].blob.data = in.data; + auth_blobs[0].blob.length = in.length; + auth_blobs[1].name = "negotiate_blob"; + auth_blobs[1].flags = 0; + auth_blobs[1].blob.data = ntlmssp_state->negotiate_blob.data; + auth_blobs[1].blob.length = ntlmssp_state->negotiate_blob.length; + params.num_blobs = ARRAY_SIZE(auth_blobs); + params.blobs = auth_blobs; + + wbc_status = wbcCredentialCache(¶ms, &info, &error); + wbcFreeMemory(error); + if (!WBC_ERROR_IS_OK(wbc_status)) { + return NT_STATUS_WRONG_CREDENTIAL_HANDLE; + } + + for (i=0; i<info->num_blobs; i++) { + if (strequal(info->blobs[i].name, "auth_blob")) { + wbc_auth_blob = &info->blobs[i].blob; + } + if (strequal(info->blobs[i].name, "session_key")) { + wbc_session_key = &info->blobs[i].blob; + } + if (strequal(info->blobs[i].name, "new_spnego")) { + new_spnego = true; + } + } + if ((wbc_auth_blob == NULL) || (wbc_session_key == NULL)) { + wbcFreeMemory(info); + return NT_STATUS_WRONG_CREDENTIAL_HANDLE; + } + + session_key = data_blob_talloc(mem_ctx, + wbc_session_key->data, + wbc_session_key->length); + if (session_key.length != wbc_session_key->length) { + wbcFreeMemory(info); + return NT_STATUS_NO_MEMORY; + } + *out = data_blob_talloc(mem_ctx, + wbc_auth_blob->data, + wbc_auth_blob->length); + if (out->length != wbc_auth_blob->length) { + wbcFreeMemory(info); + return NT_STATUS_NO_MEMORY; + } + ntlmssp_state->new_spnego = new_spnego; + + wbcFreeMemory(info); + goto done; + } + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + flags |= CLI_CRED_NTLM2; + } + if (ntlmssp_state->use_ntlmv2) { + flags |= CLI_CRED_NTLMv2_AUTH; + } + if (ntlmssp_state->use_nt_response) { + flags |= CLI_CRED_NTLM_AUTH; + } + if (ntlmssp_state->allow_lm_response) { + flags |= CLI_CRED_LANMAN_AUTH; + } + + if (target_info.length != 0 && !is_anonymous) { + struct AV_PAIR *pairs = NULL; + uint32_t count = 0; + enum ndr_err_code err; + struct AV_PAIR *timestamp = NULL; + struct AV_PAIR *eol = NULL; + uint32_t i = 0; + const char *service = NULL; + const char *hostname = NULL; + + err = ndr_pull_struct_blob(&target_info, + ntlmssp_state, + &ntlmssp_state->server.av_pair_list, + (ndr_pull_flags_fn_t)ndr_pull_AV_PAIR_LIST); + if (!NDR_ERR_CODE_IS_SUCCESS(err)) { + return ndr_map_error2ntstatus(err); + } + + count = ntlmssp_state->server.av_pair_list.count; + /* + * We need room for Flags, SingleHost, + * ChannelBindings and Target + */ + pairs = talloc_zero_array(ntlmssp_state, struct AV_PAIR, + count + 4); + if (pairs == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i = 0; i < count; i++) { + pairs[i] = ntlmssp_state->server.av_pair_list.pair[i]; + } + + ntlmssp_state->client.av_pair_list.count = count; + ntlmssp_state->client.av_pair_list.pair = pairs; + + eol = ndr_ntlmssp_find_av(&ntlmssp_state->client.av_pair_list, + MsvAvEOL); + if (eol == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + timestamp = ndr_ntlmssp_find_av(&ntlmssp_state->client.av_pair_list, + MsvAvTimestamp); + if (timestamp != NULL) { + uint32_t sign_features = + GENSEC_FEATURE_SESSION_KEY | + GENSEC_FEATURE_SIGN | + GENSEC_FEATURE_SEAL; + + server_timestamp = ×tamp->Value.AvTimestamp; + + if (ntlmssp_state->force_old_spnego) { + sign_features = 0; + } + + if (gensec_security->want_features & sign_features) { + struct AV_PAIR *av_flags = NULL; + + av_flags = ndr_ntlmssp_find_av(&ntlmssp_state->client.av_pair_list, + MsvAvFlags); + if (av_flags == NULL) { + av_flags = eol; + eol++; + count++; + *eol = *av_flags; + av_flags->AvId = MsvAvFlags; + av_flags->Value.AvFlags = 0; + } + + av_flags->Value.AvFlags |= NTLMSSP_AVFLAG_MIC_IN_AUTHENTICATE_MESSAGE; + ntlmssp_state->new_spnego = true; + } + } + + { + struct AV_PAIR *SingleHost = NULL; + + SingleHost = eol; + eol++; + count++; + *eol = *SingleHost; + + /* + * This is not really used, but we want to + * add some more random bytes and match + * Windows. + */ + SingleHost->AvId = MsvAvSingleHost; + SingleHost->Value.AvSingleHost.token_info.Flags = 0; + SingleHost->Value.AvSingleHost.token_info.TokenIL = 0; + generate_random_buffer(SingleHost->Value.AvSingleHost.token_info.MachineId, + sizeof(SingleHost->Value.AvSingleHost.token_info.MachineId)); + SingleHost->Value.AvSingleHost.remaining = data_blob_null; + } + + { + struct AV_PAIR *ChannelBindings = NULL; + + ChannelBindings = eol; + eol++; + count++; + *eol = *ChannelBindings; + + /* + * gensec doesn't support channel bindings yet, + * but we want to match Windows on the wire + */ + ChannelBindings->AvId = MsvChannelBindings; + memset(ChannelBindings->Value.ChannelBindings, 0, + sizeof(ChannelBindings->Value.ChannelBindings)); + } + + service = gensec_get_target_service(gensec_security); + hostname = gensec_get_target_hostname(gensec_security); + if (service != NULL && hostname != NULL) { + struct AV_PAIR *target = NULL; + + target = eol; + eol++; + count++; + *eol = *target; + + target->AvId = MsvAvTargetName; + target->Value.AvTargetName = talloc_asprintf(pairs, "%s/%s", + service, + hostname); + if (target->Value.AvTargetName == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + ntlmssp_state->client.av_pair_list.count = count; + ntlmssp_state->client.av_pair_list.pair = pairs; + + err = ndr_push_struct_blob(&target_info, + ntlmssp_state, + &ntlmssp_state->client.av_pair_list, + (ndr_push_flags_fn_t)ndr_push_AV_PAIR_LIST); + if (!NDR_ERR_CODE_IS_SUCCESS(err)) { + return NT_STATUS_NO_MEMORY; + } + } + + nt_status = cli_credentials_get_ntlm_response(gensec_security->credentials, mem_ctx, + &flags, challenge_blob, + server_timestamp, target_info, + &lm_response, &nt_response, + &lm_session_key, &session_key); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + if (!(flags & CLI_CRED_LANMAN_AUTH)) { + /* LM Key is still possible, just silly, so we do not + * allow it. Fortunetly all LM crypto is off by + * default and we require command line options to end + * up here */ + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + } + + if (!(flags & CLI_CRED_NTLM2)) { + /* NTLM2 is incompatible... */ + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_NTLM2; + } + + if ((ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_LM_KEY) + && ntlmssp_state->allow_lm_key && lm_session_key.length == 16) { + DATA_BLOB new_session_key = data_blob_talloc(mem_ctx, NULL, 16); + if (lm_response.length == 24) { + nt_status = SMBsesskeygen_lm_sess_key(lm_session_key.data, + lm_response.data, + new_session_key.data); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + } else { + static const uint8_t zeros[24]; + nt_status = SMBsesskeygen_lm_sess_key(lm_session_key.data, + zeros, + new_session_key.data); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + } + session_key = new_session_key; + dump_data_pw("LM session key\n", session_key.data, session_key.length); + } + + + /* Key exchange encryptes a new client-generated session key with + the password-derived key */ + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) { + /* Make up a new session key */ + uint8_t client_session_key[16]; + gnutls_cipher_hd_t cipher_hnd; + gnutls_datum_t enc_session_key = { + .data = session_key.data, + .size = session_key.length, + }; + + generate_random_buffer(client_session_key, sizeof(client_session_key)); + + /* Encrypt the new session key with the old one */ + encrypted_session_key = data_blob_talloc(ntlmssp_state, + client_session_key, sizeof(client_session_key)); + dump_data_pw("KEY_EXCH session key:\n", encrypted_session_key.data, encrypted_session_key.length); + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &enc_session_key, + NULL); + if (rc < 0) { + nt_status = gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + ZERO_ARRAY(client_session_key); + goto done; + } + rc = gnutls_cipher_encrypt(cipher_hnd, + encrypted_session_key.data, + encrypted_session_key.length); + gnutls_cipher_deinit(cipher_hnd); + if (rc < 0) { + nt_status = gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + ZERO_ARRAY(client_session_key); + goto done; + } + + dump_data_pw("KEY_EXCH session key (enc):\n", encrypted_session_key.data, encrypted_session_key.length); + + /* Mark the new session key as the 'real' session key */ + session_key = data_blob_talloc(mem_ctx, client_session_key, sizeof(client_session_key)); + ZERO_ARRAY(client_session_key); + } + + /* this generates the actual auth packet */ + nt_status = msrpc_gen(mem_ctx, + out, auth_gen_string, + "NTLMSSP", + NTLMSSP_AUTH, + lm_response.data, lm_response.length, + nt_response.data, nt_response.length, + domain, + user, + workstation, + encrypted_session_key.data, encrypted_session_key.length, + ntlmssp_state->neg_flags, + version_blob.data, version_blob.length, + mic_blob.data, mic_blob.length); + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(mem_ctx); + return nt_status; + } + + if (DEBUGLEVEL >= 10) { + struct AUTHENTICATE_MESSAGE *authenticate = + talloc(ntlmssp_state, struct AUTHENTICATE_MESSAGE); + if (authenticate != NULL) { + NTSTATUS status; + authenticate->NegotiateFlags = ntlmssp_state->neg_flags; + status = ntlmssp_pull_AUTHENTICATE_MESSAGE( + out, authenticate, authenticate); + if (NT_STATUS_IS_OK(status)) { + NDR_PRINT_DEBUG(AUTHENTICATE_MESSAGE, + authenticate); + } + TALLOC_FREE(authenticate); + } + } + + /* + * We always include the MIC, even without: + * av_flags->Value.AvFlags |= NTLMSSP_AVFLAG_MIC_IN_AUTHENTICATE_MESSAGE; + * ntlmssp_state->new_spnego = true; + * + * This matches a Windows client. + */ + rc = gnutls_hmac_init(&hmac_hnd, + GNUTLS_MAC_MD5, + session_key.data, + MIN(session_key.length, 64)); + if (rc < 0) { + nt_status = gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + goto done; + } + + rc = gnutls_hmac(hmac_hnd, + ntlmssp_state->negotiate_blob.data, + ntlmssp_state->negotiate_blob.length); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + nt_status = gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + goto done; + } + rc = gnutls_hmac(hmac_hnd, in.data, in.length); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + nt_status = gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + goto done; + } + rc = gnutls_hmac(hmac_hnd, out->data, out->length); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + nt_status = gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + goto done; + } + + gnutls_hmac_deinit(hmac_hnd, mic_buffer); + + memcpy(out->data + NTLMSSP_MIC_OFFSET, mic_buffer, NTLMSSP_MIC_SIZE); + ZERO_ARRAY(mic_buffer); + + nt_status = NT_STATUS_OK; +done: + ZERO_ARRAY_LEN(ntlmssp_state->negotiate_blob.data, + ntlmssp_state->negotiate_blob.length); + data_blob_free(&ntlmssp_state->negotiate_blob); + + ntlmssp_state->session_key = session_key; + talloc_steal(ntlmssp_state, session_key.data); + + DEBUG(3, ("NTLMSSP: Set final flags:\n")); + debug_ntlmssp_flags(ntlmssp_state->neg_flags); + + talloc_steal(out_mem_ctx, out->data); + + ntlmssp_state->expected_state = NTLMSSP_DONE; + + if (gensec_ntlmssp_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + nt_status = ntlmssp_sign_init(ntlmssp_state); + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(1, ("Could not setup NTLMSSP signing/sealing system (error was: %s)\n", + nt_errstr(nt_status))); + talloc_free(mem_ctx); + return nt_status; + } + } + + talloc_free(mem_ctx); + return nt_status; +} + +NTSTATUS gensec_ntlmssp_client_start(struct gensec_security *gensec_security) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp; + struct ntlmssp_state *ntlmssp_state; + NTSTATUS nt_status; + + nt_status = gensec_ntlmssp_start(gensec_security); + NT_STATUS_NOT_OK_RETURN(nt_status); + + gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + + ntlmssp_state = talloc_zero(gensec_ntlmssp, + struct ntlmssp_state); + if (!ntlmssp_state) { + return NT_STATUS_NO_MEMORY; + } + + gensec_ntlmssp->ntlmssp_state = ntlmssp_state; + + ntlmssp_state = gensec_ntlmssp->ntlmssp_state; + + ntlmssp_state->role = NTLMSSP_CLIENT; + + ntlmssp_state->client.netbios_domain = lpcfg_workgroup(gensec_security->settings->lp_ctx); + ntlmssp_state->client.netbios_name = cli_credentials_get_workstation(gensec_security->credentials); + + ntlmssp_state->unicode = gensec_setting_bool(gensec_security->settings, "ntlmssp_client", "unicode", true); + + ntlmssp_state->use_nt_response = \ + gensec_setting_bool(gensec_security->settings, + "ntlmssp_client", + "send_nt_response", + true); + + ntlmssp_state->allow_lm_response = lpcfg_client_lanman_auth(gensec_security->settings->lp_ctx); + + ntlmssp_state->allow_lm_key = (ntlmssp_state->allow_lm_response + && (gensec_setting_bool(gensec_security->settings, "ntlmssp_client", "allow_lm_key", false) + || gensec_setting_bool(gensec_security->settings, "ntlmssp_client", "lm_key", false))); + + ntlmssp_state->use_ntlmv2 = lpcfg_client_ntlmv2_auth(gensec_security->settings->lp_ctx); + + ntlmssp_state->force_old_spnego = gensec_setting_bool(gensec_security->settings, + "ntlmssp_client", "force_old_spnego", false); + + ntlmssp_state->expected_state = NTLMSSP_INITIAL; + + ntlmssp_state->neg_flags = + NTLMSSP_NEGOTIATE_NTLM | + NTLMSSP_NEGOTIATE_VERSION | + NTLMSSP_REQUEST_TARGET; + + if (ntlmssp_state->unicode) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_UNICODE; + } else { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_OEM; + } + + if (gensec_setting_bool(gensec_security->settings, "ntlmssp_client", "128bit", true)) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_128; + } + + if (gensec_setting_bool(gensec_security->settings, "ntlmssp_client", "56bit", false)) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_56; + } + + if (gensec_setting_bool(gensec_security->settings, "ntlmssp_client", "lm_key", false)) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_LM_KEY; + } + + if (gensec_setting_bool(gensec_security->settings, "ntlmssp_client", "keyexchange", true)) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_KEY_EXCH; + } + + if (gensec_setting_bool(gensec_security->settings, "ntlmssp_client", "alwayssign", true)) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN; + } + + if (gensec_setting_bool(gensec_security->settings, "ntlmssp_client", "ntlm2", true)) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_NTLM2; + } else { + /* apparently we can't do ntlmv2 if we don't do ntlm2 */ + ntlmssp_state->use_ntlmv2 = false; + } + + if (ntlmssp_state->use_ntlmv2) { + ntlmssp_state->required_flags |= NTLMSSP_NEGOTIATE_NTLM2; + ntlmssp_state->allow_lm_response = false; + ntlmssp_state->allow_lm_key = false; + } + + if (ntlmssp_state->allow_lm_key) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_LM_KEY; + } + + if (gensec_security->want_features & GENSEC_FEATURE_SESSION_KEY) { + /* + * We need to set this to allow a later SetPassword + * via the SAMR pipe to succeed. Strange.... We could + * also add NTLMSSP_NEGOTIATE_SEAL here. JRA. + * + * Without this, Windows will not create the master key + * that it thinks is only used for NTLMSSP signing and + * sealing. (It is actually pulled out and used directly) + * + * We don't require this here as some servers (e.g. NetAPP) + * doesn't support this. + */ + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_SIGN; + } + if (gensec_security->want_features & GENSEC_FEATURE_SIGN) { + ntlmssp_state->required_flags |= NTLMSSP_NEGOTIATE_SIGN; + + if (gensec_security->want_features & GENSEC_FEATURE_LDAP_STYLE) { + /* + * We need to handle NTLMSSP_NEGOTIATE_SIGN as + * NTLMSSP_NEGOTIATE_SEAL if GENSEC_FEATURE_LDAP_STYLE + * is requested. + */ + ntlmssp_state->force_wrap_seal = true; + } + } + if (ntlmssp_state->force_wrap_seal) { + bool ret; + + /* + * We want also work against old Samba servers + * which didn't had GENSEC_FEATURE_LDAP_STYLE + * we negotiate SEAL too. We may remove this + * in a few years. As all servers should have + * GENSEC_FEATURE_LDAP_STYLE by then. + */ + ret = gensec_setting_bool(gensec_security->settings, + "ntlmssp_client", + "ldap_style_send_seal", + true); + if (ret) { + ntlmssp_state->required_flags |= NTLMSSP_NEGOTIATE_SEAL; + } + } + if (gensec_security->want_features & GENSEC_FEATURE_SEAL) { + ntlmssp_state->required_flags |= NTLMSSP_NEGOTIATE_SIGN; + ntlmssp_state->required_flags |= NTLMSSP_NEGOTIATE_SEAL; + } + if (gensec_security->want_features & GENSEC_FEATURE_NTLM_CCACHE) { + ntlmssp_state->use_ccache = true; + } + + ntlmssp_state->neg_flags |= ntlmssp_state->required_flags; + ntlmssp_state->conf_flags = ntlmssp_state->neg_flags; + + return NT_STATUS_OK; +} + +NTSTATUS gensec_ntlmssp_resume_ccache_start(struct gensec_security *gensec_security) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = NULL; + NTSTATUS status; + + status = gensec_ntlmssp_client_start(gensec_security); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + gensec_ntlmssp = talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + gensec_ntlmssp->ntlmssp_state->use_ccache = false; + gensec_ntlmssp->ntlmssp_state->resume_ccache = true; + gensec_ntlmssp->ntlmssp_state->expected_state = NTLMSSP_NEGOTIATE; + + return NT_STATUS_OK; +} diff --git a/auth/ntlmssp/ntlmssp_ndr.c b/auth/ntlmssp/ntlmssp_ndr.c new file mode 100644 index 0000000..ea5d6f0 --- /dev/null +++ b/auth/ntlmssp/ntlmssp_ndr.c @@ -0,0 +1,134 @@ +/* + Unix SMB/Netbios implementation. + NTLMSSP ndr functions + + Copyright (C) Guenther Deschner 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 "../librpc/gen_ndr/ndr_ntlmssp.h" +#include "ntlmssp_ndr.h" + +#define NTLMSSP_PULL_MESSAGE(type, blob, mem_ctx, r) \ +do { \ + enum ndr_err_code __ndr_err; \ + ZERO_STRUCTP(r); /* in order to deal with unset neg flags */\ + __ndr_err = ndr_pull_struct_blob(blob, mem_ctx, r, \ + (ndr_pull_flags_fn_t)ndr_pull_ ##type); \ + if (!NDR_ERR_CODE_IS_SUCCESS(__ndr_err)) { \ + return ndr_map_error2ntstatus(__ndr_err); \ + } \ + if (!mem_equal_const_time(r->Signature, "NTLMSSP\0", 8)) { \ + return NT_STATUS_INVALID_PARAMETER; \ + } \ + return NT_STATUS_OK; \ +} while(0); + +#define NTLMSSP_PUSH_MESSAGE(type, blob, mem_ctx, r) \ +do { \ + enum ndr_err_code __ndr_err; \ + __ndr_err = ndr_push_struct_blob(blob, mem_ctx, r, \ + (ndr_push_flags_fn_t)ndr_push_ ##type); \ + if (!NDR_ERR_CODE_IS_SUCCESS(__ndr_err)) { \ + return ndr_map_error2ntstatus(__ndr_err); \ + } \ + return NT_STATUS_OK; \ +} while(0); + + +/** + * Pull NTLMSSP NEGOTIATE_MESSAGE struct from a blob + * @param blob The plain packet blob + * @param mem_ctx A talloc context + * @param r Pointer to a NTLMSSP NEGOTIATE_MESSAGE structure + */ + +NTSTATUS ntlmssp_pull_NEGOTIATE_MESSAGE(const DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + struct NEGOTIATE_MESSAGE *r) +{ + NTLMSSP_PULL_MESSAGE(NEGOTIATE_MESSAGE, blob, mem_ctx, r); +} + +/** + * Pull NTLMSSP CHALLENGE_MESSAGE struct from a blob + * @param blob The plain packet blob + * @param mem_ctx A talloc context + * @param r Pointer to a NTLMSSP CHALLENGE_MESSAGE structure + */ + +NTSTATUS ntlmssp_pull_CHALLENGE_MESSAGE(const DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + struct CHALLENGE_MESSAGE *r) +{ + NTLMSSP_PULL_MESSAGE(CHALLENGE_MESSAGE, blob, mem_ctx, r); +} + +/** + * Pull NTLMSSP AUTHENTICATE_MESSAGE struct from a blob + * @param blob The plain packet blob + * @param mem_ctx A talloc context + * @param r Pointer to a NTLMSSP AUTHENTICATE_MESSAGE structure + */ + +NTSTATUS ntlmssp_pull_AUTHENTICATE_MESSAGE(const DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + struct AUTHENTICATE_MESSAGE *r) +{ + NTLMSSP_PULL_MESSAGE(AUTHENTICATE_MESSAGE, blob, mem_ctx, r); +} + +/** + * Push NTLMSSP NEGOTIATE_MESSAGE struct into a blob + * @param blob The plain packet blob + * @param mem_ctx A talloc context + * @param r Pointer to a NTLMSSP NEGOTIATE_MESSAGE structure + */ + +NTSTATUS ntlmssp_push_NEGOTIATE_MESSAGE(DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + const struct NEGOTIATE_MESSAGE *r) +{ + NTLMSSP_PUSH_MESSAGE(NEGOTIATE_MESSAGE, blob, mem_ctx, r); +} + +/** + * Push NTLMSSP CHALLENGE_MESSAGE struct into a blob + * @param blob The plain packet blob + * @param mem_ctx A talloc context + * @param r Pointer to a NTLMSSP CHALLENGE_MESSAGE structure + */ + +NTSTATUS ntlmssp_push_CHALLENGE_MESSAGE(DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + const struct CHALLENGE_MESSAGE *r) +{ + NTLMSSP_PUSH_MESSAGE(CHALLENGE_MESSAGE, blob, mem_ctx, r); +} + +/** + * Push NTLMSSP AUTHENTICATE_MESSAGE struct into a blob + * @param blob The plain packet blob + * @param mem_ctx A talloc context + * @param r Pointer to a NTLMSSP AUTHENTICATE_MESSAGE structure + */ + +NTSTATUS ntlmssp_push_AUTHENTICATE_MESSAGE(DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + const struct AUTHENTICATE_MESSAGE *r) +{ + NTLMSSP_PUSH_MESSAGE(AUTHENTICATE_MESSAGE, blob, mem_ctx, r); +} diff --git a/auth/ntlmssp/ntlmssp_ndr.h b/auth/ntlmssp/ntlmssp_ndr.h new file mode 100644 index 0000000..e619231 --- /dev/null +++ b/auth/ntlmssp/ntlmssp_ndr.h @@ -0,0 +1,38 @@ +/* + Unix SMB/Netbios implementation. + NTLMSSP ndr functions + + Copyright (C) Guenther Deschner 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/>. +*/ + +NTSTATUS ntlmssp_pull_NEGOTIATE_MESSAGE(const DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + struct NEGOTIATE_MESSAGE *r); +NTSTATUS ntlmssp_pull_CHALLENGE_MESSAGE(const DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + struct CHALLENGE_MESSAGE *r); +NTSTATUS ntlmssp_pull_AUTHENTICATE_MESSAGE(const DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + struct AUTHENTICATE_MESSAGE *r); +NTSTATUS ntlmssp_push_NEGOTIATE_MESSAGE(DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + const struct NEGOTIATE_MESSAGE *r); +NTSTATUS ntlmssp_push_CHALLENGE_MESSAGE(DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + const struct CHALLENGE_MESSAGE *r); +NTSTATUS ntlmssp_push_AUTHENTICATE_MESSAGE(DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + const struct AUTHENTICATE_MESSAGE *r); diff --git a/auth/ntlmssp/ntlmssp_private.h b/auth/ntlmssp/ntlmssp_private.h new file mode 100644 index 0000000..4d84e33 --- /dev/null +++ b/auth/ntlmssp/ntlmssp_private.h @@ -0,0 +1,192 @@ +/* + * Unix SMB/CIFS implementation. + * Version 3.0 + * NTLMSSP Signing routines + * Copyright (C) Andrew Bartlett 2003-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/>. + */ + +/* For structures internal to the NTLMSSP implementation that should not be exposed */ + +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +struct auth_session_info; + +struct ntlmssp_crypt_direction { + uint32_t seq_num; + uint8_t sign_key[16]; + gnutls_cipher_hd_t seal_state; +}; + +union ntlmssp_crypt_state { + /* NTLM */ + struct ntlmssp_crypt_direction ntlm; + + /* NTLM2 */ + struct { + struct ntlmssp_crypt_direction sending; + struct ntlmssp_crypt_direction receiving; + } ntlm2; +}; + +struct gensec_ntlmssp_context { + /* For GENSEC users */ + void *server_returned_info; + + /* used by both client and server implementation */ + struct ntlmssp_state *ntlmssp_state; +}; + +/* The following definitions come from auth/ntlmssp_util.c */ + +void debug_ntlmssp_flags(uint32_t neg_flags); +NTSTATUS ntlmssp_handle_neg_flags(struct ntlmssp_state *ntlmssp_state, + uint32_t neg_flags, const char *name); +const DATA_BLOB ntlmssp_version_blob(void); + +/* The following definitions come from auth/ntlmssp_server.c */ + +const char *ntlmssp_target_name(struct ntlmssp_state *ntlmssp_state, + uint32_t neg_flags, uint32_t *chal_flags); +NTSTATUS ntlmssp_server_negotiate(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out); +NTSTATUS ntlmssp_server_auth(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB request, DATA_BLOB *reply); +/* The following definitions come from auth/ntlmssp/ntlmssp_client.c */ + + +/** + * Next state function for the Initial packet + * + * @param ntlmssp_state NTLMSSP State + * @param out_mem_ctx The DATA_BLOB *out will be allocated on this context + * @param in A NULL data blob (input ignored) + * @param out The initial negotiate request to the server, as an talloc()ed DATA_BLOB, on out_mem_ctx + * @return Errors or NT_STATUS_OK. + */ +NTSTATUS ntlmssp_client_initial(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB in, DATA_BLOB *out) ; + +NTSTATUS gensec_ntlmssp_resume_ccache(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB in, DATA_BLOB *out); + +/** + * Next state function for the Challenge Packet. Generate an auth packet. + * + * @param gensec_security GENSEC state + * @param out_mem_ctx Memory context for *out + * @param in The server challnege, as a DATA_BLOB. reply.data must be NULL + * @param out The next request (auth packet) to the server, as an allocated DATA_BLOB, on the out_mem_ctx context + * @return Errors or NT_STATUS_OK. + */ +NTSTATUS ntlmssp_client_challenge(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out) ; +NTSTATUS gensec_ntlmssp_client_start(struct gensec_security *gensec_security); +NTSTATUS gensec_ntlmssp_resume_ccache_start(struct gensec_security *gensec_security); + +/* The following definitions come from auth/ntlmssp/gensec_ntlmssp_server.c */ + + +/** + * Next state function for the Negotiate packet (GENSEC wrapper) + * + * @param gensec_security GENSEC state + * @param out_mem_ctx Memory context for *out + * @param in The request, as a DATA_BLOB. reply.data must be NULL + * @param out The reply, as an allocated DATA_BLOB, caller to free. + * @return Errors or MORE_PROCESSING_REQUIRED if (normal) a reply is required. + */ +NTSTATUS gensec_ntlmssp_server_negotiate(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB request, DATA_BLOB *reply); + +struct tevent_req *ntlmssp_server_auth_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_security, + const DATA_BLOB in); +NTSTATUS ntlmssp_server_auth_recv(struct tevent_req *req, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out); + + +/** + * Start NTLMSSP on the server side + * + */ +NTSTATUS gensec_ntlmssp_server_start(struct gensec_security *gensec_security); + +/** + * Return the credentials of a logged on user, including session keys + * etc. + * + * Only valid after a successful authentication + * + * May only be called once per authentication. + * + */ +NTSTATUS gensec_ntlmssp_session_info(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + struct auth_session_info **session_info) ; + +/* The following definitions come from auth/ntlmssp/gensec_ntlmssp.c */ + +NTSTATUS gensec_ntlmssp_sign_packet(struct gensec_security *gensec_security, + TALLOC_CTX *sig_mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig); +NTSTATUS gensec_ntlmssp_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); +NTSTATUS gensec_ntlmssp_seal_packet(struct gensec_security *gensec_security, + TALLOC_CTX *sig_mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig); +NTSTATUS gensec_ntlmssp_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); +size_t gensec_ntlmssp_sig_size(struct gensec_security *gensec_security, size_t data_size) ; +NTSTATUS gensec_ntlmssp_wrap(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out); +NTSTATUS gensec_ntlmssp_unwrap(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out); + +/** + * Return the NTLMSSP master session key + * + * @param ntlmssp_state NTLMSSP State + */ +NTSTATUS gensec_ntlmssp_magic(struct gensec_security *gensec_security, + const DATA_BLOB *first_packet); +bool gensec_ntlmssp_have_feature(struct gensec_security *gensec_security, + uint32_t feature); +NTSTATUS gensec_ntlmssp_session_key(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + DATA_BLOB *session_key); +NTSTATUS gensec_ntlmssp_start(struct gensec_security *gensec_security); + diff --git a/auth/ntlmssp/ntlmssp_server.c b/auth/ntlmssp/ntlmssp_server.c new file mode 100644 index 0000000..6a27db1 --- /dev/null +++ b/auth/ntlmssp/ntlmssp_server.c @@ -0,0 +1,1160 @@ +/* + Unix SMB/Netbios implementation. + Version 3.0 + handle NTLMSSP, server side + + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Andrew Bartlett 2001-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 <tevent.h> +#include "lib/util/tevent_ntstatus.h" +#include "lib/util/time_basic.h" +#include "auth/ntlmssp/ntlmssp.h" +#include "auth/ntlmssp/ntlmssp_private.h" +#include "../librpc/gen_ndr/ndr_ntlmssp.h" +#include "auth/ntlmssp/ntlmssp_ndr.h" +#include "../libcli/auth/libcli_auth.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" +#include "auth/common_auth.h" +#include "param/param.h" +#include "param/loadparm.h" +#include "libcli/security/session.h" + +#include "lib/crypto/gnutls_helpers.h" +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +/** + * Determine correct target name flags for reply, given server role + * and negotiated flags + * + * @param ntlmssp_state NTLMSSP State + * @param neg_flags The flags from the packet + * @param chal_flags The flags to be set in the reply packet + * @return The 'target name' string. + */ + +const char *ntlmssp_target_name(struct ntlmssp_state *ntlmssp_state, + uint32_t neg_flags, uint32_t *chal_flags) +{ + if (neg_flags & NTLMSSP_REQUEST_TARGET) { + *chal_flags |= NTLMSSP_NEGOTIATE_TARGET_INFO; + *chal_flags |= NTLMSSP_REQUEST_TARGET; + if (ntlmssp_state->server.is_standalone) { + *chal_flags |= NTLMSSP_TARGET_TYPE_SERVER; + return ntlmssp_state->server.netbios_name; + } else { + *chal_flags |= NTLMSSP_TARGET_TYPE_DOMAIN; + return ntlmssp_state->server.netbios_domain; + }; + } else { + return ""; + } +} + +/** + * Next state function for the NTLMSSP Negotiate packet + * + * @param gensec_security GENSEC state + * @param out_mem_ctx Memory context for *out + * @param in The request, as a DATA_BLOB. reply.data must be NULL + * @param out The reply, as an allocated DATA_BLOB, caller to free. + * @return Errors or MORE_PROCESSING_REQUIRED if (normal) a reply is required. + */ + +NTSTATUS gensec_ntlmssp_server_negotiate(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB request, DATA_BLOB *reply) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + struct ntlmssp_state *ntlmssp_state = gensec_ntlmssp->ntlmssp_state; + struct auth4_context *auth_context = gensec_security->auth_context; + DATA_BLOB struct_blob; + uint32_t neg_flags = 0; + uint32_t ntlmssp_command, chal_flags; + uint8_t cryptkey[8]; + const char *target_name; + NTSTATUS status; + struct timeval tv_now = timeval_current(); + /* + * See [MS-NLMP] + * + * Windows NT 4.0, windows_2000: use 30 minutes, + * Windows XP, Windows Server 2003, Windows Vista, + * Windows Server 2008, Windows 7, and Windows Server 2008 R2 + * use 36 hours. + * + * Newer systems doesn't check this, likely because the + * connectionless NTLMSSP is no longer supported. + * + * As we expect the AUTHENTICATION_MESSAGE to arrive + * directly after the NEGOTIATE_MESSAGE (typically less than + * as 1 second later). We use a hard timeout of 30 Minutes. + * + * We don't look at AUTHENTICATE_MESSAGE.NtChallengeResponse.TimeStamp + * instead we just remember our own time. + */ + uint32_t max_lifetime = 30 * 60; + struct timeval tv_end = timeval_add(&tv_now, max_lifetime, 0); + + /* parse the NTLMSSP packet */ +#if 0 + file_save("ntlmssp_negotiate.dat", request.data, request.length); +#endif + + if (request.length) { + if (request.length > UINT16_MAX) { + DEBUG(1, ("ntlmssp_server_negotiate: reject large request of length %u\n", + (unsigned int)request.length)); + return NT_STATUS_INVALID_PARAMETER; + } + + if ((request.length < 16) || !msrpc_parse(ntlmssp_state, &request, "Cdd", + "NTLMSSP", + &ntlmssp_command, + &neg_flags)) { + DEBUG(1, ("ntlmssp_server_negotiate: failed to parse NTLMSSP Negotiate of length %u\n", + (unsigned int)request.length)); + dump_data(2, request.data, request.length); + return NT_STATUS_INVALID_PARAMETER; + } + debug_ntlmssp_flags(neg_flags); + + if (DEBUGLEVEL >= 10) { + struct NEGOTIATE_MESSAGE *negotiate = talloc( + ntlmssp_state, struct NEGOTIATE_MESSAGE); + if (negotiate != NULL) { + status = ntlmssp_pull_NEGOTIATE_MESSAGE( + &request, negotiate, negotiate); + if (NT_STATUS_IS_OK(status)) { + NDR_PRINT_DEBUG(NEGOTIATE_MESSAGE, + negotiate); + } + TALLOC_FREE(negotiate); + } + } + } + + status = ntlmssp_handle_neg_flags(ntlmssp_state, neg_flags, "negotiate"); + if (!NT_STATUS_IS_OK(status)){ + return status; + } + + /* Ask our caller what challenge they would like in the packet */ + if (auth_context->get_ntlm_challenge) { + status = auth_context->get_ntlm_challenge(auth_context, cryptkey); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("gensec_ntlmssp_server_negotiate: failed to get challenge: %s\n", + nt_errstr(status))); + return status; + } + } else { + DEBUG(1, ("gensec_ntlmssp_server_negotiate: backend doesn't give a challenge\n")); + return NT_STATUS_NOT_IMPLEMENTED; + } + + /* The flags we send back are not just the negotiated flags, + * they are also 'what is in this packet'. Therfore, we + * operate on 'chal_flags' from here on + */ + + chal_flags = ntlmssp_state->neg_flags; + ntlmssp_state->server.challenge_endtime = timeval_to_nttime(&tv_end); + + /* get the right name to fill in as 'target' */ + target_name = ntlmssp_target_name(ntlmssp_state, + neg_flags, &chal_flags); + if (target_name == NULL) + return NT_STATUS_INVALID_PARAMETER; + + ntlmssp_state->chal = data_blob_talloc(ntlmssp_state, cryptkey, 8); + ntlmssp_state->internal_chal = data_blob_talloc(ntlmssp_state, + cryptkey, 8); + + /* This creates the 'blob' of names that appears at the end of the packet */ + if (chal_flags & NTLMSSP_NEGOTIATE_TARGET_INFO) { + enum ndr_err_code err; + struct AV_PAIR *pairs = NULL; + uint32_t count = 5; + + pairs = talloc_zero_array(ntlmssp_state, struct AV_PAIR, count + 1); + if (pairs == NULL) { + return NT_STATUS_NO_MEMORY; + } + + pairs[0].AvId = MsvAvNbDomainName; + pairs[0].Value.AvNbDomainName = target_name; + + pairs[1].AvId = MsvAvNbComputerName; + pairs[1].Value.AvNbComputerName = ntlmssp_state->server.netbios_name; + + pairs[2].AvId = MsvAvDnsDomainName; + pairs[2].Value.AvDnsDomainName = ntlmssp_state->server.dns_domain; + + pairs[3].AvId = MsvAvDnsComputerName; + pairs[3].Value.AvDnsComputerName= ntlmssp_state->server.dns_name; + + if (!ntlmssp_state->force_old_spnego) { + pairs[4].AvId = MsvAvTimestamp; + pairs[4].Value.AvTimestamp = + timeval_to_nttime(&tv_now); + count += 1; + + pairs[5].AvId = MsvAvEOL; + } else { + pairs[4].AvId = MsvAvEOL; + } + + ntlmssp_state->server.av_pair_list.count = count; + ntlmssp_state->server.av_pair_list.pair = pairs; + + err = ndr_push_struct_blob(&struct_blob, + ntlmssp_state, + &ntlmssp_state->server.av_pair_list, + (ndr_push_flags_fn_t)ndr_push_AV_PAIR_LIST); + if (!NDR_ERR_CODE_IS_SUCCESS(err)) { + return NT_STATUS_NO_MEMORY; + } + } else { + struct_blob = data_blob_null; + } + + { + /* Marshal the packet in the right format, be it unicode or ASCII */ + const char *gen_string; + const DATA_BLOB version_blob = ntlmssp_version_blob(); + + if (ntlmssp_state->unicode) { + gen_string = "CdUdbddBb"; + } else { + gen_string = "CdAdbddBb"; + } + + status = msrpc_gen(out_mem_ctx, reply, gen_string, + "NTLMSSP", + NTLMSSP_CHALLENGE, + target_name, + chal_flags, + cryptkey, 8, + 0, 0, + struct_blob.data, struct_blob.length, + version_blob.data, version_blob.length); + + if (!NT_STATUS_IS_OK(status)) { + data_blob_free(&struct_blob); + return status; + } + + if (DEBUGLEVEL >= 10) { + struct CHALLENGE_MESSAGE *challenge = talloc( + ntlmssp_state, struct CHALLENGE_MESSAGE); + if (challenge != NULL) { + challenge->NegotiateFlags = chal_flags; + status = ntlmssp_pull_CHALLENGE_MESSAGE( + reply, challenge, challenge); + if (NT_STATUS_IS_OK(status)) { + NDR_PRINT_DEBUG(CHALLENGE_MESSAGE, + challenge); + } + TALLOC_FREE(challenge); + } + } + } + + data_blob_free(&struct_blob); + + ntlmssp_state->negotiate_blob = data_blob_dup_talloc(ntlmssp_state, + request); + if (ntlmssp_state->negotiate_blob.length != request.length) { + return NT_STATUS_NO_MEMORY; + } + + ntlmssp_state->challenge_blob = data_blob_dup_talloc(ntlmssp_state, + *reply); + if (ntlmssp_state->challenge_blob.length != reply->length) { + return NT_STATUS_NO_MEMORY; + } + + ntlmssp_state->expected_state = NTLMSSP_AUTH; + + return NT_STATUS_MORE_PROCESSING_REQUIRED; +} + +struct ntlmssp_server_auth_state { + struct gensec_security *gensec_security; + struct gensec_ntlmssp_context *gensec_ntlmssp; + DATA_BLOB in; + struct auth_usersupplied_info *user_info; + DATA_BLOB user_session_key; + DATA_BLOB lm_session_key; + /* internal variables used by KEY_EXCH (client-supplied user session key */ + DATA_BLOB encrypted_session_key; + bool doing_ntlm2; + /* internal variables used by NTLM2 */ + uint8_t session_nonce[16]; +}; + +static NTSTATUS ntlmssp_server_preauth(struct gensec_security *gensec_security, + struct gensec_ntlmssp_context *gensec_ntlmssp, + struct ntlmssp_server_auth_state *state, + const DATA_BLOB request); +static void ntlmssp_server_auth_done(struct tevent_req *subreq); +static NTSTATUS ntlmssp_server_postauth(struct gensec_security *gensec_security, + struct gensec_ntlmssp_context *gensec_ntlmssp, + struct ntlmssp_server_auth_state *state, + DATA_BLOB request); + +struct tevent_req *ntlmssp_server_auth_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_security, + const DATA_BLOB in) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + struct auth4_context *auth_context = gensec_security->auth_context; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct ntlmssp_server_auth_state *state = NULL; + NTSTATUS status; + + req = tevent_req_create(mem_ctx, &state, + struct ntlmssp_server_auth_state); + if (req == NULL) { + return NULL; + } + state->gensec_security = gensec_security; + state->gensec_ntlmssp = gensec_ntlmssp; + state->in = in; + + status = ntlmssp_server_preauth(gensec_security, + gensec_ntlmssp, + state, in); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + subreq = auth_context->check_ntlm_password_send( + state, ev, auth_context, state->user_info); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, ntlmssp_server_auth_done, req); + return req; +} + +/** + * Next state function for the Authenticate packet + * + * @param ntlmssp_state NTLMSSP State + * @param request The request, as a DATA_BLOB + * @return Errors or NT_STATUS_OK. + */ + +static NTSTATUS ntlmssp_server_preauth(struct gensec_security *gensec_security, + struct gensec_ntlmssp_context *gensec_ntlmssp, + struct ntlmssp_server_auth_state *state, + const DATA_BLOB request) +{ + struct ntlmssp_state *ntlmssp_state = gensec_ntlmssp->ntlmssp_state; + struct auth4_context *auth_context = gensec_security->auth_context; + struct auth_usersupplied_info *user_info = NULL; + uint32_t ntlmssp_command, auth_flags; + NTSTATUS nt_status; + const unsigned int version_len = 8; + DATA_BLOB version_blob = data_blob_null; + const unsigned int mic_len = NTLMSSP_MIC_SIZE; + DATA_BLOB mic_blob = data_blob_null; + const char *parse_string; + bool ok; + struct timeval endtime; + bool expired = false; + +#if 0 + file_save("ntlmssp_auth.dat", request.data, request.length); +#endif + + if (ntlmssp_state->unicode) { + parse_string = "CdBBUUUBdbb"; + } else { + parse_string = "CdBBAAABdbb"; + } + + /* zero these out */ + data_blob_free(&ntlmssp_state->session_key); + data_blob_free(&ntlmssp_state->lm_resp); + data_blob_free(&ntlmssp_state->nt_resp); + + ntlmssp_state->user = NULL; + ntlmssp_state->domain = NULL; + ntlmssp_state->client.netbios_name = NULL; + + /* now the NTLMSSP encoded auth hashes */ + ok = msrpc_parse(ntlmssp_state, &request, parse_string, + "NTLMSSP", + &ntlmssp_command, + &ntlmssp_state->lm_resp, + &ntlmssp_state->nt_resp, + &ntlmssp_state->domain, + &ntlmssp_state->user, + &ntlmssp_state->client.netbios_name, + &state->encrypted_session_key, + &auth_flags, + &version_blob, version_len, + &mic_blob, mic_len); + if (!ok) { + DEBUG(10, ("ntlmssp_server_auth: failed to parse NTLMSSP (nonfatal):\n")); + dump_data(10, request.data, request.length); + + data_blob_free(&version_blob); + data_blob_free(&mic_blob); + + if (ntlmssp_state->unicode) { + parse_string = "CdBBUUUBd"; + } else { + parse_string = "CdBBAAABd"; + } + + ok = msrpc_parse(ntlmssp_state, &request, parse_string, + "NTLMSSP", + &ntlmssp_command, + &ntlmssp_state->lm_resp, + &ntlmssp_state->nt_resp, + &ntlmssp_state->domain, + &ntlmssp_state->user, + &ntlmssp_state->client.netbios_name, + &state->encrypted_session_key, + &auth_flags); + } + + if (!ok) { + DEBUG(10, ("ntlmssp_server_auth: failed to parse NTLMSSP (nonfatal):\n")); + dump_data(10, request.data, request.length); + + /* zero this out */ + data_blob_free(&state->encrypted_session_key); + auth_flags = 0; + + /* Try again with a shorter string (Win9X truncates this packet) */ + if (ntlmssp_state->unicode) { + parse_string = "CdBBUUU"; + } else { + parse_string = "CdBBAAA"; + } + + /* now the NTLMSSP encoded auth hashes */ + if (!msrpc_parse(ntlmssp_state, &request, parse_string, + "NTLMSSP", + &ntlmssp_command, + &ntlmssp_state->lm_resp, + &ntlmssp_state->nt_resp, + &ntlmssp_state->domain, + &ntlmssp_state->user, + &ntlmssp_state->client.netbios_name)) { + DEBUG(1, ("ntlmssp_server_auth: failed to parse NTLMSSP (tried both formats):\n")); + dump_data(2, request.data, request.length); + + return NT_STATUS_INVALID_PARAMETER; + } + } + + talloc_steal(state, state->encrypted_session_key.data); + + if (auth_flags != 0) { + nt_status = ntlmssp_handle_neg_flags(ntlmssp_state, + auth_flags, + "authenticate"); + if (!NT_STATUS_IS_OK(nt_status)){ + return nt_status; + } + } + + if (DEBUGLEVEL >= 10) { + struct AUTHENTICATE_MESSAGE *authenticate = talloc( + ntlmssp_state, struct AUTHENTICATE_MESSAGE); + if (authenticate != NULL) { + NTSTATUS status; + authenticate->NegotiateFlags = auth_flags; + status = ntlmssp_pull_AUTHENTICATE_MESSAGE( + &request, authenticate, authenticate); + if (NT_STATUS_IS_OK(status)) { + NDR_PRINT_DEBUG(AUTHENTICATE_MESSAGE, + authenticate); + } + TALLOC_FREE(authenticate); + } + } + + DEBUG(3,("Got user=[%s] domain=[%s] workstation=[%s] len1=%lu len2=%lu\n", + ntlmssp_state->user, ntlmssp_state->domain, + ntlmssp_state->client.netbios_name, + (unsigned long)ntlmssp_state->lm_resp.length, + (unsigned long)ntlmssp_state->nt_resp.length)); + +#if 0 + file_save("nthash1.dat", &ntlmssp_state->nt_resp.data, &ntlmssp_state->nt_resp.length); + file_save("lmhash1.dat", &ntlmssp_state->lm_resp.data, &ntlmssp_state->lm_resp.length); +#endif + + if (ntlmssp_state->nt_resp.length > 24) { + struct NTLMv2_RESPONSE v2_resp; + enum ndr_err_code err; + uint32_t i = 0; + uint32_t count = 0; + const struct AV_PAIR *flags = NULL; + const struct AV_PAIR *eol = NULL; + uint32_t av_flags = 0; + + err = ndr_pull_struct_blob(&ntlmssp_state->nt_resp, + ntlmssp_state, + &v2_resp, + (ndr_pull_flags_fn_t)ndr_pull_NTLMv2_RESPONSE); + if (!NDR_ERR_CODE_IS_SUCCESS(err)) { + nt_status = ndr_map_error2ntstatus(err); + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_BUFFER_TOO_SMALL)) { + /* + * Note that invalid blobs should result in + * INVALID_PARAMETER, as demonstrated by + * smb2.session.ntlmssp_bug14932 + */ + nt_status = NT_STATUS_INVALID_PARAMETER; + } + DEBUG(1,("%s: failed to parse NTLMv2_RESPONSE of length %zu for " + "user=[%s] domain=[%s] workstation=[%s] - %s %s\n", + __func__, ntlmssp_state->nt_resp.length, + ntlmssp_state->user, ntlmssp_state->domain, + ntlmssp_state->client.netbios_name, + ndr_errstr(err), nt_errstr(nt_status))); + return nt_status; + } + + if (DEBUGLVL(10)) { + NDR_PRINT_DEBUG(NTLMv2_RESPONSE, &v2_resp); + } + + eol = ndr_ntlmssp_find_av(&v2_resp.Challenge.AvPairs, + MsvAvEOL); + if (eol == NULL) { + DEBUG(1,("%s: missing MsvAvEOL for " + "user=[%s] domain=[%s] workstation=[%s]\n", + __func__, ntlmssp_state->user, ntlmssp_state->domain, + ntlmssp_state->client.netbios_name)); + return NT_STATUS_INVALID_PARAMETER; + } + + flags = ndr_ntlmssp_find_av(&v2_resp.Challenge.AvPairs, + MsvAvFlags); + if (flags != NULL) { + av_flags = flags->Value.AvFlags; + } + + if (av_flags & NTLMSSP_AVFLAG_MIC_IN_AUTHENTICATE_MESSAGE) { + if (mic_blob.length != NTLMSSP_MIC_SIZE) { + DEBUG(1,("%s: mic_blob.length[%u] for " + "user=[%s] domain=[%s] workstation=[%s]\n", + __func__, + (unsigned)mic_blob.length, + ntlmssp_state->user, + ntlmssp_state->domain, + ntlmssp_state->client.netbios_name)); + return NT_STATUS_INVALID_PARAMETER; + } + + if (request.length < + (NTLMSSP_MIC_OFFSET + NTLMSSP_MIC_SIZE)) + { + DEBUG(1,("%s: missing MIC " + "request.length[%u] for " + "user=[%s] domain=[%s] workstation=[%s]\n", + __func__, + (unsigned)request.length, + ntlmssp_state->user, + ntlmssp_state->domain, + ntlmssp_state->client.netbios_name)); + return NT_STATUS_INVALID_PARAMETER; + } + + ntlmssp_state->new_spnego = true; + } + + count = ntlmssp_state->server.av_pair_list.count; + if (v2_resp.Challenge.AvPairs.count < count) { + return NT_STATUS_INVALID_PARAMETER; + } + + for (i = 0; i < count; i++) { + const struct AV_PAIR *sp = + &ntlmssp_state->server.av_pair_list.pair[i]; + const struct AV_PAIR *cp = NULL; + + if (sp->AvId == MsvAvEOL) { + continue; + } + + cp = ndr_ntlmssp_find_av(&v2_resp.Challenge.AvPairs, + sp->AvId); + if (cp == NULL) { + DEBUG(1,("%s: AvId 0x%x missing for" + "user=[%s] domain=[%s] " + "workstation=[%s]\n", + __func__, + (unsigned)sp->AvId, + ntlmssp_state->user, + ntlmssp_state->domain, + ntlmssp_state->client.netbios_name)); + return NT_STATUS_INVALID_PARAMETER; + } + + switch (cp->AvId) { +#define CASE_STRING(v) case Msv ## v: do { \ + int cmp; \ + if (sp->Value.v == NULL) { \ + return NT_STATUS_INTERNAL_ERROR; \ + } \ + if (cp->Value.v == NULL) { \ + DEBUG(1,("%s: invalid %s " \ + "got[%s] expect[%s] for " \ + "user=[%s] domain=[%s] workstation=[%s]\n", \ + __func__, #v, \ + cp->Value.v, \ + sp->Value.v, \ + ntlmssp_state->user, \ + ntlmssp_state->domain, \ + ntlmssp_state->client.netbios_name)); \ + return NT_STATUS_INVALID_PARAMETER; \ + } \ + cmp = strcmp(cp->Value.v, sp->Value.v); \ + if (cmp != 0) { \ + DEBUG(1,("%s: invalid %s " \ + "got[%s] expect[%s] for " \ + "user=[%s] domain=[%s] workstation=[%s]\n", \ + __func__, #v, \ + cp->Value.v, \ + sp->Value.v, \ + ntlmssp_state->user, \ + ntlmssp_state->domain, \ + ntlmssp_state->client.netbios_name)); \ + return NT_STATUS_INVALID_PARAMETER; \ + } \ +} while(0); break + CASE_STRING(AvNbComputerName); + CASE_STRING(AvNbDomainName); + CASE_STRING(AvDnsComputerName); + CASE_STRING(AvDnsDomainName); + CASE_STRING(AvDnsTreeName); + case MsvAvTimestamp: + if (cp->Value.AvTimestamp != sp->Value.AvTimestamp) { + struct timeval ct; + struct timeval st; + struct timeval_buf tmp1; + struct timeval_buf tmp2; + + nttime_to_timeval(&ct, + cp->Value.AvTimestamp); + nttime_to_timeval(&st, + sp->Value.AvTimestamp); + + DEBUG(1,("%s: invalid AvTimestamp " + "got[%s] expect[%s] for " + "user=[%s] domain=[%s] " + "workstation=[%s]\n", + __func__, + timeval_str_buf(&ct, false, + true, &tmp1), + timeval_str_buf(&st, false, + true, &tmp2), + ntlmssp_state->user, + ntlmssp_state->domain, + ntlmssp_state->client.netbios_name)); + return NT_STATUS_INVALID_PARAMETER; + } + break; + default: + /* + * This can't happen as we control + * ntlmssp_state->server.av_pair_list + */ + return NT_STATUS_INTERNAL_ERROR; + } + } + } + + nttime_to_timeval(&endtime, ntlmssp_state->server.challenge_endtime); + expired = timeval_expired(&endtime); + if (expired) { + struct timeval_buf tmp; + DEBUG(1,("%s: challenge invalid (expired %s) for " + "user=[%s] domain=[%s] workstation=[%s]\n", + __func__, + timeval_str_buf(&endtime, false, true, &tmp), + ntlmssp_state->user, ntlmssp_state->domain, + ntlmssp_state->client.netbios_name)); + return NT_STATUS_INVALID_PARAMETER; + } + + /* NTLM2 uses a 'challenge' that is made of up both the server challenge, and a + client challenge + + However, the NTLM2 flag may still be set for the real NTLMv2 logins, be careful. + */ + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + if (ntlmssp_state->nt_resp.length == 24 && ntlmssp_state->lm_resp.length == 24) { + state->doing_ntlm2 = true; + + memcpy(state->session_nonce, ntlmssp_state->internal_chal.data, 8); + memcpy(&state->session_nonce[8], ntlmssp_state->lm_resp.data, 8); + + SMB_ASSERT(ntlmssp_state->internal_chal.data && ntlmssp_state->internal_chal.length == 8); + + /* LM response is no longer useful */ + data_blob_free(&ntlmssp_state->lm_resp); + + /* We changed the effective challenge - set it */ + if (auth_context->set_ntlm_challenge) { + uint8_t session_nonce_hash[16]; + int rc; + + rc = gnutls_hash_fast(GNUTLS_DIG_MD5, + state->session_nonce, + 16, + session_nonce_hash); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + + + nt_status = auth_context->set_ntlm_challenge(auth_context, + session_nonce_hash, + "NTLMSSP callback (NTLM2)"); + ZERO_ARRAY(session_nonce_hash); + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(1, ("gensec_ntlmssp_server_negotiate: failed to get challenge: %s\n", + nt_errstr(nt_status))); + return nt_status; + } + } else { + DEBUG(1, ("gensec_ntlmssp_server_negotiate: backend doesn't have facility for challenge to be set\n")); + + return NT_STATUS_NOT_IMPLEMENTED; + } + + /* LM Key is incompatible. */ + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + } + } + + user_info = talloc_zero(state, struct auth_usersupplied_info); + if (!user_info) { + return NT_STATUS_NO_MEMORY; + } + + user_info->logon_parameters = MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT | MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT; + user_info->flags = 0; + user_info->client.account_name = ntlmssp_state->user; + user_info->client.domain_name = ntlmssp_state->domain; + user_info->workstation_name = ntlmssp_state->client.netbios_name; + user_info->remote_host = gensec_get_remote_address(gensec_security); + user_info->local_host = gensec_get_local_address(gensec_security); + user_info->service_description + = gensec_get_target_service_description(gensec_security); + + /* + * This will just be the string "NTLMSSP" from + * gensec_ntlmssp_final_auth_type, but ensures it stays in sync + * with the same use in the authorization logging triggered by + * gensec_session_info() later + */ + user_info->auth_description = gensec_final_auth_type(gensec_security); + + user_info->password_state = AUTH_PASSWORD_RESPONSE; + user_info->password.response.lanman = ntlmssp_state->lm_resp; + user_info->password.response.nt = ntlmssp_state->nt_resp; + + state->user_info = user_info; + return NT_STATUS_OK; +} + +static void ntlmssp_server_auth_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct ntlmssp_server_auth_state *state = + tevent_req_data(req, + struct ntlmssp_server_auth_state); + struct gensec_security *gensec_security = state->gensec_security; + struct gensec_ntlmssp_context *gensec_ntlmssp = state->gensec_ntlmssp; + struct auth4_context *auth_context = gensec_security->auth_context; + uint8_t authoritative = 1; + NTSTATUS status; + + status = auth_context->check_ntlm_password_recv(subreq, + gensec_ntlmssp, + &authoritative, + &gensec_ntlmssp->server_returned_info, + &state->user_session_key, + &state->lm_session_key); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + DBG_INFO("Checking NTLMSSP password for %s\\%s failed: %s\n", + state->user_info->client.domain_name, + state->user_info->client.account_name, + nt_errstr(status)); + } + if (tevent_req_nterror(req, status)) { + return; + } + talloc_steal(state, state->user_session_key.data); + talloc_steal(state, state->lm_session_key.data); + + status = ntlmssp_server_postauth(state->gensec_security, + state->gensec_ntlmssp, + state, state->in); + if (tevent_req_nterror(req, status)) { + return; + } + + tevent_req_done(req); +} + +/** + * Next state function for the Authenticate packet + * (after authentication - figures out the session keys etc) + * + * @param ntlmssp_state NTLMSSP State + * @return Errors or NT_STATUS_OK. + */ + +static NTSTATUS ntlmssp_server_postauth(struct gensec_security *gensec_security, + struct gensec_ntlmssp_context *gensec_ntlmssp, + struct ntlmssp_server_auth_state *state, + DATA_BLOB request) +{ + struct ntlmssp_state *ntlmssp_state = gensec_ntlmssp->ntlmssp_state; + struct auth4_context *auth_context = gensec_security->auth_context; + DATA_BLOB user_session_key = state->user_session_key; + DATA_BLOB lm_session_key = state->lm_session_key; + NTSTATUS nt_status = NT_STATUS_OK; + DATA_BLOB session_key = data_blob(NULL, 0); + struct auth_session_info *session_info = NULL; + + TALLOC_FREE(state->user_info); + + if (lpcfg_map_to_guest(gensec_security->settings->lp_ctx) != NEVER_MAP_TO_GUEST + && auth_context->generate_session_info != NULL) + { + NTSTATUS tmp_status; + + /* + * We need to check if the auth is anonymous or mapped to guest + */ + tmp_status = auth_context->generate_session_info(auth_context, state, + gensec_ntlmssp->server_returned_info, + gensec_ntlmssp->ntlmssp_state->user, + AUTH_SESSION_INFO_SIMPLE_PRIVILEGES, + &session_info); + if (!NT_STATUS_IS_OK(tmp_status)) { + /* + * We don't care about failures, + * the worst result is that we try MIC checking + * for a map to guest authentication. + */ + TALLOC_FREE(session_info); + } + } + + if (session_info != NULL) { + if (security_session_user_level(session_info, NULL) < SECURITY_USER) { + /* + * Anonymous and GUEST are not secure anyway. + * avoid new_spnego and MIC checking. + */ + ntlmssp_state->new_spnego = false; + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_SIGN; + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_SEAL; + } + TALLOC_FREE(session_info); + } + + dump_data_pw("NT session key:\n", user_session_key.data, user_session_key.length); + dump_data_pw("LM first-8:\n", lm_session_key.data, lm_session_key.length); + + /* Handle the different session key derivation for NTLM2 */ + if (state->doing_ntlm2) { + if (user_session_key.data && user_session_key.length == 16) { + int rc; + + session_key = data_blob_talloc(ntlmssp_state, + NULL, 16); + + rc = gnutls_hmac_fast(GNUTLS_MAC_MD5, + user_session_key.data, + user_session_key.length, + state->session_nonce, + sizeof(state->session_nonce), + session_key.data); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + + DEBUG(10,("ntlmssp_server_auth: Created NTLM2 session key.\n")); + dump_data_pw("NTLM2 session key:\n", session_key.data, session_key.length); + + } else { + DEBUG(10,("ntlmssp_server_auth: Failed to create NTLM2 session key.\n")); + session_key = data_blob_null; + } + } else if ((ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_LM_KEY) + /* Ensure we can never get here on NTLMv2 */ + && (ntlmssp_state->nt_resp.length == 0 || ntlmssp_state->nt_resp.length == 24)) { + + if (lm_session_key.data && lm_session_key.length >= 8) { + if (ntlmssp_state->lm_resp.data && ntlmssp_state->lm_resp.length == 24) { + session_key = data_blob_talloc(ntlmssp_state, + NULL, 16); + if (session_key.data == NULL) { + return NT_STATUS_NO_MEMORY; + } + nt_status = SMBsesskeygen_lm_sess_key(lm_session_key.data, + ntlmssp_state->lm_resp.data, + session_key.data); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + DEBUG(10,("ntlmssp_server_auth: Created NTLM session key.\n")); + } else { + static const uint8_t zeros[24] = {0, }; + session_key = data_blob_talloc( + ntlmssp_state, NULL, 16); + if (session_key.data == NULL) { + return NT_STATUS_NO_MEMORY; + } + nt_status = SMBsesskeygen_lm_sess_key(zeros, zeros, + session_key.data); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + DEBUG(10,("ntlmssp_server_auth: Created NTLM session key.\n")); + } + dump_data_pw("LM session key:\n", session_key.data, + session_key.length); + } else { + /* LM Key not selected */ + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + + DEBUG(10,("ntlmssp_server_auth: Failed to create NTLM session key.\n")); + session_key = data_blob_null; + } + + } else if (user_session_key.data) { + session_key = user_session_key; + DEBUG(10,("ntlmssp_server_auth: Using unmodified nt session key.\n")); + dump_data_pw("unmodified session key:\n", session_key.data, session_key.length); + + /* LM Key not selected */ + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + + } else if (lm_session_key.data) { + /* Very weird to have LM key, but no user session key, but anyway.. */ + session_key = lm_session_key; + DEBUG(10,("ntlmssp_server_auth: Using unmodified lm session key.\n")); + dump_data_pw("unmodified session key:\n", session_key.data, session_key.length); + + /* LM Key not selected */ + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + + } else { + DEBUG(10,("ntlmssp_server_auth: Failed to create unmodified session key.\n")); + session_key = data_blob_null; + + /* LM Key not selected */ + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + } + + /* With KEY_EXCH, the client supplies the proposed session key, + but encrypts it with the long-term key */ + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) { + if (!state->encrypted_session_key.data + || state->encrypted_session_key.length != 16) { + DEBUG(1, ("Client-supplied KEY_EXCH session key was of invalid length (%u)!\n", + (unsigned)state->encrypted_session_key.length)); + return NT_STATUS_INVALID_PARAMETER; + } else if (!session_key.data || session_key.length != 16) { + DEBUG(5, ("server session key is invalid (len == %u), cannot do KEY_EXCH!\n", + (unsigned int)session_key.length)); + ntlmssp_state->session_key = session_key; + talloc_steal(ntlmssp_state, session_key.data); + } else { + gnutls_cipher_hd_t cipher_hnd; + gnutls_datum_t enc_session_key = { + .data = session_key.data, + .size = session_key.length, + }; + int rc; + + dump_data_pw("KEY_EXCH session key (enc):\n", + state->encrypted_session_key.data, + state->encrypted_session_key.length); + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &enc_session_key, + NULL); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + rc = gnutls_cipher_encrypt(cipher_hnd, + state->encrypted_session_key.data, + state->encrypted_session_key.length); + gnutls_cipher_deinit(cipher_hnd); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + + ntlmssp_state->session_key = data_blob_talloc(ntlmssp_state, + state->encrypted_session_key.data, + state->encrypted_session_key.length); + dump_data_pw("KEY_EXCH session key:\n", + state->encrypted_session_key.data, + state->encrypted_session_key.length); + } + } else { + ntlmssp_state->session_key = session_key; + talloc_steal(ntlmssp_state, session_key.data); + } + + if (ntlmssp_state->new_spnego) { + gnutls_hmac_hd_t hmac_hnd = NULL; + uint8_t mic_buffer[NTLMSSP_MIC_SIZE] = { 0, }; + bool cmp; + int rc; + + rc = gnutls_hmac_init(&hmac_hnd, + GNUTLS_MAC_MD5, + ntlmssp_state->session_key.data, + MIN(ntlmssp_state->session_key.length, 64)); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + rc = gnutls_hmac(hmac_hnd, + ntlmssp_state->negotiate_blob.data, + ntlmssp_state->negotiate_blob.length); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + rc = gnutls_hmac(hmac_hnd, + ntlmssp_state->challenge_blob.data, + ntlmssp_state->challenge_blob.length); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + + /* checked were we set ntlmssp_state->new_spnego */ + SMB_ASSERT(request.length > + (NTLMSSP_MIC_OFFSET + NTLMSSP_MIC_SIZE)); + + rc = gnutls_hmac(hmac_hnd, request.data, NTLMSSP_MIC_OFFSET); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + rc = gnutls_hmac(hmac_hnd, mic_buffer, NTLMSSP_MIC_SIZE); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + rc = gnutls_hmac(hmac_hnd, + request.data + (NTLMSSP_MIC_OFFSET + NTLMSSP_MIC_SIZE), + request.length - (NTLMSSP_MIC_OFFSET + NTLMSSP_MIC_SIZE)); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + gnutls_hmac_deinit(hmac_hnd, mic_buffer); + + cmp = mem_equal_const_time(request.data + NTLMSSP_MIC_OFFSET, + mic_buffer, NTLMSSP_MIC_SIZE); + if (!cmp) { + DEBUG(1,("%s: invalid NTLMSSP_MIC for " + "user=[%s] domain=[%s] workstation=[%s]\n", + __func__, + ntlmssp_state->user, + ntlmssp_state->domain, + ntlmssp_state->client.netbios_name)); + dump_data(11, request.data + NTLMSSP_MIC_OFFSET, + NTLMSSP_MIC_SIZE); + dump_data(11, mic_buffer, + NTLMSSP_MIC_SIZE); + } + + ZERO_ARRAY(mic_buffer); + + if (!cmp) { + return NT_STATUS_INVALID_PARAMETER; + } + } + + data_blob_free(&ntlmssp_state->negotiate_blob); + data_blob_free(&ntlmssp_state->challenge_blob); + + if (gensec_ntlmssp_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + if (gensec_security->want_features & GENSEC_FEATURE_LDAP_STYLE) { + /* + * We need to handle NTLMSSP_NEGOTIATE_SIGN as + * NTLMSSP_NEGOTIATE_SEAL if GENSEC_FEATURE_LDAP_STYLE + * is requested. + */ + ntlmssp_state->force_wrap_seal = true; + } + nt_status = ntlmssp_sign_init(ntlmssp_state); + } + + data_blob_clear_free(&ntlmssp_state->internal_chal); + data_blob_clear_free(&ntlmssp_state->chal); + data_blob_clear_free(&ntlmssp_state->lm_resp); + data_blob_clear_free(&ntlmssp_state->nt_resp); + + ntlmssp_state->expected_state = NTLMSSP_DONE; + + return nt_status; +} + +NTSTATUS ntlmssp_server_auth_recv(struct tevent_req *req, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + NTSTATUS status; + + *out = data_blob_null; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + tevent_req_received(req); + return NT_STATUS_OK; +} diff --git a/auth/ntlmssp/ntlmssp_sign.c b/auth/ntlmssp/ntlmssp_sign.c new file mode 100644 index 0000000..11e5930 --- /dev/null +++ b/auth/ntlmssp/ntlmssp_sign.c @@ -0,0 +1,893 @@ +/* + * Unix SMB/CIFS implementation. + * Version 3.0 + * NTLMSSP Signing routines + * Copyright (C) Andrew Bartlett 2003-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/ntlmssp/ntlmssp.h" +#include "../libcli/auth/libcli_auth.h" +#include "zlib.h" +#include "../auth/ntlmssp/ntlmssp_private.h" + +#include "lib/crypto/gnutls_helpers.h" +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +#define CLI_SIGN "session key to client-to-server signing key magic constant" +#define CLI_SEAL "session key to client-to-server sealing key magic constant" +#define SRV_SIGN "session key to server-to-client signing key magic constant" +#define SRV_SEAL "session key to server-to-client sealing key magic constant" + +/** + * Some notes on the NTLM2 code: + * + * NTLM2 is a AEAD system. This means that the data encrypted is not + * all the data that is signed. In DCE-RPC case, the headers of the + * DCE-RPC packets are also signed. This prevents some of the + * fun-and-games one might have by changing them. + * + */ + +static void dump_arc4_state(const char *description, + gnutls_cipher_hd_t *state) +{ + DBG_DEBUG("%s\n", description); +} + +static NTSTATUS calc_ntlmv2_key(uint8_t subkey[16], + DATA_BLOB session_key, + const char *constant) +{ + gnutls_hash_hd_t hash_hnd = NULL; + int rc; + + rc = gnutls_hash_init(&hash_hnd, GNUTLS_DIG_MD5); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + rc = gnutls_hash(hash_hnd, session_key.data, session_key.length); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + rc = gnutls_hash(hash_hnd, constant, strlen(constant) + 1); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + gnutls_hash_deinit(hash_hnd, subkey); + + return NT_STATUS_OK; +} + +enum ntlmssp_direction { + NTLMSSP_SEND, + NTLMSSP_RECEIVE +}; + +static NTSTATUS ntlmssp_make_packet_signature(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *sig_mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + enum ntlmssp_direction direction, + DATA_BLOB *sig, bool encrypt_sig) +{ + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + int rc; + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + gnutls_hmac_hd_t hmac_hnd = NULL; + uint8_t digest[16]; + uint8_t seq_num[4]; + + *sig = data_blob_talloc(sig_mem_ctx, NULL, NTLMSSP_SIG_SIZE); + if (!sig->data) { + return NT_STATUS_NO_MEMORY; + } + + switch (direction) { + case NTLMSSP_SEND: + DEBUG(100,("ntlmssp_make_packet_signature: SEND seq = %u, len = %u, pdu_len = %u\n", + ntlmssp_state->crypt->ntlm2.sending.seq_num, + (unsigned int)length, + (unsigned int)pdu_length)); + + SIVAL(seq_num, 0, ntlmssp_state->crypt->ntlm2.sending.seq_num); + ntlmssp_state->crypt->ntlm2.sending.seq_num++; + + rc = gnutls_hmac_init(&hmac_hnd, + GNUTLS_MAC_MD5, + ntlmssp_state->crypt->ntlm2.sending.sign_key, + 16); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + break; + case NTLMSSP_RECEIVE: + + DEBUG(100,("ntlmssp_make_packet_signature: RECV seq = %u, len = %u, pdu_len = %u\n", + ntlmssp_state->crypt->ntlm2.receiving.seq_num, + (unsigned int)length, + (unsigned int)pdu_length)); + + SIVAL(seq_num, 0, ntlmssp_state->crypt->ntlm2.receiving.seq_num); + ntlmssp_state->crypt->ntlm2.receiving.seq_num++; + + rc = gnutls_hmac_init(&hmac_hnd, + GNUTLS_MAC_MD5, + ntlmssp_state->crypt->ntlm2.receiving.sign_key, + 16); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + break; + } + + dump_data_pw("pdu data ", whole_pdu, pdu_length); + + rc = gnutls_hmac(hmac_hnd, seq_num, sizeof(seq_num)); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + rc = gnutls_hmac(hmac_hnd, whole_pdu, pdu_length); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + gnutls_hmac_deinit(hmac_hnd, digest); + + if (encrypt_sig && (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH)) { + switch (direction) { + case NTLMSSP_SEND: + rc = gnutls_cipher_encrypt(ntlmssp_state->crypt->ntlm2.sending.seal_state, + digest, + 8); + break; + case NTLMSSP_RECEIVE: + rc = gnutls_cipher_encrypt(ntlmssp_state->crypt->ntlm2.receiving.seal_state, + digest, + 8); + break; + } + if (rc < 0) { + DBG_ERR("gnutls_cipher_encrypt for NTLMv2 EXCH " + "%s packet signature failed: %s\n", + direction == NTLMSSP_SEND ? + "send" : "receive", + gnutls_strerror(rc)); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + } + + SIVAL(sig->data, 0, NTLMSSP_SIGN_VERSION); + memcpy(sig->data + 4, digest, 8); + ZERO_ARRAY(digest); + memcpy(sig->data + 12, seq_num, 4); + ZERO_ARRAY(seq_num); + + dump_data_pw("ntlmssp v2 sig ", sig->data, sig->length); + + } else { + uint32_t crc; + + crc = crc32(0, Z_NULL, 0); + crc = crc32(crc, data, length); + + status = msrpc_gen(sig_mem_ctx, + sig, "dddd", + NTLMSSP_SIGN_VERSION, 0, crc, + ntlmssp_state->crypt->ntlm.seq_num); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + ntlmssp_state->crypt->ntlm.seq_num++; + + dump_arc4_state("ntlmssp hash: \n", + &ntlmssp_state->crypt->ntlm.seal_state); + rc = gnutls_cipher_encrypt(ntlmssp_state->crypt->ntlm.seal_state, + sig->data + 4, + sig->length - 4); + if (rc < 0) { + DBG_ERR("gnutls_cipher_encrypt for NTLM packet " + "signature failed: %s\n", + gnutls_strerror(rc)); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + } + + return NT_STATUS_OK; +} + +NTSTATUS ntlmssp_sign_packet(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *sig_mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + NTSTATUS nt_status; + + if (!(ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SIGN)) { + DEBUG(3, ("NTLMSSP Signing not negotiated - cannot sign packet!\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!ntlmssp_state->session_key.length) { + DEBUG(3, ("NO session key, cannot check sign packet\n")); + return NT_STATUS_NO_USER_SESSION_KEY; + } + + nt_status = ntlmssp_make_packet_signature(ntlmssp_state, + sig_mem_ctx, + data, length, + whole_pdu, pdu_length, + NTLMSSP_SEND, sig, true); + + return nt_status; +} + +/** + * Check the signature of an incoming packet + * @note caller *must* check that the signature is the size it expects + * + */ + +NTSTATUS ntlmssp_check_packet(struct ntlmssp_state *ntlmssp_state, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) +{ + DATA_BLOB local_sig; + NTSTATUS nt_status; + TALLOC_CTX *tmp_ctx; + + if (!ntlmssp_state->session_key.length) { + DEBUG(3, ("NO session key, cannot check packet signature\n")); + return NT_STATUS_NO_USER_SESSION_KEY; + } + + if (sig->length < 8) { + DEBUG(0, ("NTLMSSP packet check failed due to short signature (%lu bytes)!\n", + (unsigned long)sig->length)); + } + + tmp_ctx = talloc_new(ntlmssp_state); + if (!tmp_ctx) { + return NT_STATUS_NO_MEMORY; + } + + nt_status = ntlmssp_make_packet_signature(ntlmssp_state, + tmp_ctx, + data, length, + whole_pdu, pdu_length, + NTLMSSP_RECEIVE, + &local_sig, true); + + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(0,("NTLMSSP packet sig creation failed with %s\n", + nt_errstr(nt_status))); + talloc_free(tmp_ctx); + return nt_status; + } + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + if (local_sig.length != sig->length || + !mem_equal_const_time(local_sig.data, sig->data, sig->length)) { + DEBUG(5, ("BAD SIG NTLM2: wanted signature of\n")); + dump_data(5, local_sig.data, local_sig.length); + + DEBUG(5, ("BAD SIG: got signature of\n")); + dump_data(5, sig->data, sig->length); + + DEBUG(0, ("NTLMSSP NTLM2 packet check failed due to invalid signature!\n")); + talloc_free(tmp_ctx); + return NT_STATUS_ACCESS_DENIED; + } + } else { + if (local_sig.length != sig->length || + !mem_equal_const_time(local_sig.data + 8, sig->data + 8, sig->length - 8)) { + DEBUG(5, ("BAD SIG NTLM1: wanted signature of\n")); + dump_data(5, local_sig.data, local_sig.length); + + DEBUG(5, ("BAD SIG: got signature of\n")); + dump_data(5, sig->data, sig->length); + + DEBUG(0, ("NTLMSSP NTLM1 packet check failed due to invalid signature!\n")); + talloc_free(tmp_ctx); + return NT_STATUS_ACCESS_DENIED; + } + } + dump_data_pw("checked ntlmssp signature\n", sig->data, sig->length); + DEBUG(10,("ntlmssp_check_packet: NTLMSSP signature OK !\n")); + + talloc_free(tmp_ctx); + return NT_STATUS_OK; +} + +/** + * Seal data with the NTLMSSP algorithm + * + */ + +NTSTATUS ntlmssp_seal_packet(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *sig_mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + int rc; + + if (!(ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SEAL)) { + DEBUG(3, ("NTLMSSP Sealing not negotiated - cannot seal packet!\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!(ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SIGN)) { + DEBUG(3, ("NTLMSSP Sealing not negotiated - cannot seal packet!\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!ntlmssp_state->session_key.length) { + DEBUG(3, ("NO session key, cannot seal packet\n")); + return NT_STATUS_NO_USER_SESSION_KEY; + } + + DEBUG(10,("ntlmssp_seal_data: seal\n")); + dump_data_pw("ntlmssp clear data\n", data, length); + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + NTSTATUS nt_status; + /* + * The order of these two operations matters - we + * must first seal the packet, then seal the + * sequence number - this is because the + * send_seal_hash is not constant, but is is rather + * updated with each iteration + */ + nt_status = ntlmssp_make_packet_signature(ntlmssp_state, + sig_mem_ctx, + data, length, + whole_pdu, pdu_length, + NTLMSSP_SEND, + sig, false); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + rc = gnutls_cipher_encrypt(ntlmssp_state->crypt->ntlm2.sending.seal_state, + data, + length); + if (rc < 0) { + DBG_ERR("gnutls_cipher_encrypt ntlmv2 sealing the data " + "failed: %s\n", + gnutls_strerror(rc)); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) { + rc = gnutls_cipher_encrypt(ntlmssp_state->crypt->ntlm2.sending.seal_state, + sig->data + 4, + 8); + if (rc < 0) { + DBG_ERR("gnutls_cipher_encrypt ntlmv2 sealing " + "the EXCH signature data failed: %s\n", + gnutls_strerror(rc)); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + } + } else { + NTSTATUS status; + uint32_t crc; + + crc = crc32(0, Z_NULL, 0); + crc = crc32(crc, data, length); + + status = msrpc_gen(sig_mem_ctx, + sig, "dddd", + NTLMSSP_SIGN_VERSION, 0, crc, + ntlmssp_state->crypt->ntlm.seq_num); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* + * The order of these two operations matters - we + * must first seal the packet, then seal the + * sequence number - this is because the ntlmv1_arc4_state + * is not constant, but is is rather updated with + * each iteration + */ + dump_arc4_state("ntlmv1 arc4 state:\n", + &ntlmssp_state->crypt->ntlm.seal_state); + rc = gnutls_cipher_encrypt(ntlmssp_state->crypt->ntlm.seal_state, + data, + length); + if (rc < 0) { + DBG_ERR("gnutls_cipher_encrypt ntlmv1 sealing data" + "failed: %s\n", + gnutls_strerror(rc)); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + + dump_arc4_state("ntlmv1 arc4 state:\n", + &ntlmssp_state->crypt->ntlm.seal_state); + + rc = gnutls_cipher_encrypt(ntlmssp_state->crypt->ntlm.seal_state, + sig->data + 4, + sig->length - 4); + if (rc < 0) { + DBG_ERR("gnutls_cipher_encrypt ntlmv1 sealing signing " + "data failed: %s\n", + gnutls_strerror(rc)); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + + ntlmssp_state->crypt->ntlm.seq_num++; + } + dump_data_pw("ntlmssp signature\n", sig->data, sig->length); + dump_data_pw("ntlmssp sealed data\n", data, length); + + return NT_STATUS_OK; +} + +/** + * Unseal data with the NTLMSSP algorithm + * + */ + +NTSTATUS ntlmssp_unseal_packet(struct ntlmssp_state *ntlmssp_state, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) +{ + NTSTATUS status; + int rc; + + if (!ntlmssp_state->session_key.length) { + DEBUG(3, ("NO session key, cannot unseal packet\n")); + return NT_STATUS_NO_USER_SESSION_KEY; + } + + DEBUG(10,("ntlmssp_unseal_packet: seal\n")); + dump_data_pw("ntlmssp sealed data\n", data, length); + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + /* First unseal the data. */ + rc = gnutls_cipher_decrypt(ntlmssp_state->crypt->ntlm2.receiving.seal_state, + data, + length); + if (rc < 0) { + DBG_ERR("gnutls_cipher_decrypt ntlmv2 unsealing the " + "data failed: %s\n", + gnutls_strerror(rc)); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + dump_data_pw("ntlmv2 clear data\n", data, length); + } else { + rc = gnutls_cipher_decrypt(ntlmssp_state->crypt->ntlm.seal_state, + data, + length); + if (rc < 0) { + DBG_ERR("gnutls_cipher_decrypt ntlmv1 unsealing the " + "data failed: %s\n", + gnutls_strerror(rc)); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + dump_data_pw("ntlmv1 clear data\n", data, length); + } + + status = ntlmssp_check_packet(ntlmssp_state, + data, length, + whole_pdu, pdu_length, + sig); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1,("NTLMSSP packet check for unseal failed due to invalid signature on %llu bytes of input:\n", + (unsigned long long)length)); + } + return status; +} + +NTSTATUS ntlmssp_wrap(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + NTSTATUS nt_status; + DATA_BLOB sig; + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SEAL) { + if (in->length + NTLMSSP_SIG_SIZE < in->length) { + return NT_STATUS_INVALID_PARAMETER; + } + + *out = data_blob_talloc(out_mem_ctx, NULL, in->length + NTLMSSP_SIG_SIZE); + if (!out->data) { + return NT_STATUS_NO_MEMORY; + } + memcpy(out->data + NTLMSSP_SIG_SIZE, in->data, in->length); + + nt_status = ntlmssp_seal_packet(ntlmssp_state, out_mem_ctx, + out->data + NTLMSSP_SIG_SIZE, + out->length - NTLMSSP_SIG_SIZE, + out->data + NTLMSSP_SIG_SIZE, + out->length - NTLMSSP_SIG_SIZE, + &sig); + + if (NT_STATUS_IS_OK(nt_status)) { + memcpy(out->data, sig.data, NTLMSSP_SIG_SIZE); + talloc_free(sig.data); + } + return nt_status; + + } else if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SIGN) { + if (in->length + NTLMSSP_SIG_SIZE < in->length) { + return NT_STATUS_INVALID_PARAMETER; + } + + *out = data_blob_talloc(out_mem_ctx, NULL, in->length + NTLMSSP_SIG_SIZE); + if (!out->data) { + return NT_STATUS_NO_MEMORY; + } + memcpy(out->data + NTLMSSP_SIG_SIZE, in->data, in->length); + + nt_status = ntlmssp_sign_packet(ntlmssp_state, out_mem_ctx, + out->data + NTLMSSP_SIG_SIZE, + out->length - NTLMSSP_SIG_SIZE, + out->data + NTLMSSP_SIG_SIZE, + out->length - NTLMSSP_SIG_SIZE, + &sig); + + if (NT_STATUS_IS_OK(nt_status)) { + memcpy(out->data, sig.data, NTLMSSP_SIG_SIZE); + talloc_free(sig.data); + } + return nt_status; + } else { + *out = data_blob_talloc(out_mem_ctx, in->data, in->length); + if (!out->data) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; + } +} + +NTSTATUS ntlmssp_unwrap(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + DATA_BLOB sig; + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SEAL) { + if (in->length < NTLMSSP_SIG_SIZE) { + return NT_STATUS_INVALID_PARAMETER; + } + sig.data = in->data; + sig.length = NTLMSSP_SIG_SIZE; + + *out = data_blob_talloc(out_mem_ctx, in->data + NTLMSSP_SIG_SIZE, in->length - NTLMSSP_SIG_SIZE); + + return ntlmssp_unseal_packet(ntlmssp_state, + out->data, out->length, + out->data, out->length, + &sig); + + } else if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SIGN) { + if (in->length < NTLMSSP_SIG_SIZE) { + return NT_STATUS_INVALID_PARAMETER; + } + sig.data = in->data; + sig.length = NTLMSSP_SIG_SIZE; + + *out = data_blob_talloc(out_mem_ctx, in->data + NTLMSSP_SIG_SIZE, in->length - NTLMSSP_SIG_SIZE); + + return ntlmssp_check_packet(ntlmssp_state, + out->data, out->length, + out->data, out->length, + &sig); + } else { + *out = data_blob_talloc(out_mem_ctx, in->data, in->length); + if (!out->data) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; + } +} + +/** + Initialise the state for NTLMSSP signing. +*/ +NTSTATUS ntlmssp_sign_reset(struct ntlmssp_state *ntlmssp_state, + bool reset_seqnums) +{ + int rc; + + DEBUG(3, ("NTLMSSP Sign/Seal - Initialising with flags:\n")); + debug_ntlmssp_flags(ntlmssp_state->neg_flags); + + if (ntlmssp_state->crypt == NULL) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + if (ntlmssp_state->force_wrap_seal && + (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SIGN)) + { + /* + * We need to handle NTLMSSP_NEGOTIATE_SIGN as + * NTLMSSP_NEGOTIATE_SEAL if GENSEC_FEATURE_LDAP_STYLE + * is requested. + * + * The negotiation of flags (and authentication) + * is completed when ntlmssp_sign_init() is called + * so we can safely pretent NTLMSSP_NEGOTIATE_SEAL + * was negotiated. + */ + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_SEAL; + } + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + DATA_BLOB weak_session_key = ntlmssp_state->session_key; + const char *send_sign_const; + const char *send_seal_const; + const char *recv_sign_const; + const char *recv_seal_const; + uint8_t send_seal_key[16] = {0}; + gnutls_datum_t send_seal_blob = { + .data = send_seal_key, + .size = sizeof(send_seal_key), + }; + uint8_t recv_seal_key[16] = {0}; + gnutls_datum_t recv_seal_blob = { + .data = recv_seal_key, + .size = sizeof(recv_seal_key), + }; + NTSTATUS status; + + switch (ntlmssp_state->role) { + case NTLMSSP_CLIENT: + send_sign_const = CLI_SIGN; + send_seal_const = CLI_SEAL; + recv_sign_const = SRV_SIGN; + recv_seal_const = SRV_SEAL; + break; + case NTLMSSP_SERVER: + send_sign_const = SRV_SIGN; + send_seal_const = SRV_SEAL; + recv_sign_const = CLI_SIGN; + recv_seal_const = CLI_SEAL; + break; + default: + return NT_STATUS_INTERNAL_ERROR; + } + + /* + * Weaken NTLMSSP keys to cope with down-level + * clients, servers and export restrictions. + * + * We probably should have some parameters to + * control this, once we get NTLM2 working. + * + * Key weakening was not performed on the master key + * for NTLM2, but must be done on the encryption subkeys only. + */ + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_128) { + /* nothing to do */ + } else if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_56) { + weak_session_key.length = 7; + } else { /* forty bits */ + weak_session_key.length = 5; + } + + dump_data_pw("NTLMSSP weakend master key:\n", + weak_session_key.data, + weak_session_key.length); + + /* SEND: sign key */ + status = calc_ntlmv2_key(ntlmssp_state->crypt->ntlm2.sending.sign_key, + ntlmssp_state->session_key, send_sign_const); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + dump_data_pw("NTLMSSP send sign key:\n", + ntlmssp_state->crypt->ntlm2.sending.sign_key, 16); + + /* SEND: seal ARCFOUR pad */ + status = calc_ntlmv2_key(send_seal_key, + weak_session_key, + send_seal_const); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + dump_data_pw("NTLMSSP send seal key:\n", + send_seal_key, + sizeof(send_seal_key)); + + if (ntlmssp_state->crypt->ntlm2.sending.seal_state != NULL) { + gnutls_cipher_deinit(ntlmssp_state->crypt->ntlm2.sending.seal_state); + } + rc = gnutls_cipher_init(&ntlmssp_state->crypt->ntlm2.sending.seal_state, + GNUTLS_CIPHER_ARCFOUR_128, + &send_seal_blob, + NULL); + if (rc < 0) { + DBG_ERR("gnutls_cipher_init failed: %s\n", + gnutls_strerror(rc)); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + + dump_arc4_state("NTLMSSP send seal arc4 state:\n", + &ntlmssp_state->crypt->ntlm2.sending.seal_state); + + /* SEND: seq num */ + if (reset_seqnums) { + ntlmssp_state->crypt->ntlm2.sending.seq_num = 0; + } + + /* RECV: sign key */ + status = calc_ntlmv2_key(ntlmssp_state->crypt->ntlm2.receiving.sign_key, + ntlmssp_state->session_key, recv_sign_const); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + dump_data_pw("NTLMSSP recv sign key:\n", + ntlmssp_state->crypt->ntlm2.receiving.sign_key, 16); + + /* RECV: seal ARCFOUR pad */ + status = calc_ntlmv2_key(recv_seal_key, + weak_session_key, + recv_seal_const); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + dump_data_pw("NTLMSSP recv seal key:\n", + recv_seal_key, + sizeof(recv_seal_key)); + + if (ntlmssp_state->crypt->ntlm2.receiving.seal_state != NULL) { + gnutls_cipher_deinit(ntlmssp_state->crypt->ntlm2.receiving.seal_state); + } + rc = gnutls_cipher_init(&ntlmssp_state->crypt->ntlm2.receiving.seal_state, + GNUTLS_CIPHER_ARCFOUR_128, + &recv_seal_blob, + NULL); + if (rc < 0) { + DBG_ERR("gnutls_cipher_init failed: %s\n", + gnutls_strerror(rc)); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + + dump_arc4_state("NTLMSSP recv seal arc4 state:\n", + &ntlmssp_state->crypt->ntlm2.receiving.seal_state); + + /* RECV: seq num */ + if (reset_seqnums) { + ntlmssp_state->crypt->ntlm2.receiving.seq_num = 0; + } + } else { + gnutls_datum_t seal_session_key = { + .data = ntlmssp_state->session_key.data, + .size = ntlmssp_state->session_key.length, + }; + bool do_weak = false; + + DEBUG(5, ("NTLMSSP Sign/Seal - using NTLM1\n")); + + /* + * Key weakening not performed on the master key for NTLM2 + * and does not occour for NTLM1. Therefore we only need + * to do this for the LM_KEY. + */ + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_LM_KEY) { + do_weak = true; + } + + /* + * Nothing to weaken. + * We certainly don't want to 'extend' the length... + */ + if (ntlmssp_state->session_key.length < 16) { + /* TODO: is this really correct? */ + do_weak = false; + } + + if (do_weak) { + uint8_t weak_session_key[8]; + + memcpy(weak_session_key, seal_session_key.data, 8); + seal_session_key = (gnutls_datum_t) { + .data = weak_session_key, + .size = sizeof(weak_session_key), + }; + + /* + * LM key doesn't support 128 bit crypto, so this is + * the best we can do. If you negotiate 128 bit, but + * not 56, you end up with 40 bit... + */ + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_56) { + weak_session_key[7] = 0xa0; + } else { /* forty bits */ + weak_session_key[5] = 0xe5; + weak_session_key[6] = 0x38; + weak_session_key[7] = 0xb0; + } + } + + if (ntlmssp_state->crypt->ntlm.seal_state != NULL) { + gnutls_cipher_deinit(ntlmssp_state->crypt->ntlm.seal_state); + } + rc = gnutls_cipher_init(&ntlmssp_state->crypt->ntlm.seal_state, + GNUTLS_CIPHER_ARCFOUR_128, + &seal_session_key, + NULL); + if (rc < 0) { + DBG_ERR("gnutls_cipher_init failed: %s\n", + gnutls_strerror(rc)); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + + dump_arc4_state("NTLMv1 arc4 state:\n", + &ntlmssp_state->crypt->ntlm.seal_state); + + if (reset_seqnums) { + ntlmssp_state->crypt->ntlm.seq_num = 0; + } + } + + return NT_STATUS_OK; +} + +static int ntlmssp_crypt_free_gnutls_cipher_state(union ntlmssp_crypt_state *c) +{ + if (c->ntlm2.sending.seal_state != NULL) { + gnutls_cipher_deinit(c->ntlm2.sending.seal_state); + c->ntlm2.sending.seal_state = NULL; + } + if (c->ntlm2.receiving.seal_state != NULL) { + gnutls_cipher_deinit(c->ntlm2.receiving.seal_state); + c->ntlm2.receiving.seal_state = NULL; + } + if (c->ntlm.seal_state != NULL) { + gnutls_cipher_deinit(c->ntlm.seal_state); + c->ntlm.seal_state = NULL; + } + + return 0; +} + +NTSTATUS ntlmssp_sign_init(struct ntlmssp_state *ntlmssp_state) +{ + if (ntlmssp_state->session_key.length < 8) { + DEBUG(3, ("NO session key, cannot initialise signing\n")); + return NT_STATUS_NO_USER_SESSION_KEY; + } + + ntlmssp_state->crypt = talloc_zero(ntlmssp_state, + union ntlmssp_crypt_state); + if (ntlmssp_state->crypt == NULL) { + return NT_STATUS_NO_MEMORY; + } + talloc_set_destructor(ntlmssp_state->crypt, + ntlmssp_crypt_free_gnutls_cipher_state); + + return ntlmssp_sign_reset(ntlmssp_state, true); +} diff --git a/auth/ntlmssp/ntlmssp_util.c b/auth/ntlmssp/ntlmssp_util.c new file mode 100644 index 0000000..6f3b474 --- /dev/null +++ b/auth/ntlmssp/ntlmssp_util.c @@ -0,0 +1,220 @@ +/* + Unix SMB/Netbios implementation. + Version 3.0 + handle NLTMSSP, server side + + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Andrew Bartlett 2001-2003 + Copyright (C) Andrew Bartlett 2005 (Updated from gensec). + + 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/ntlmssp/ntlmssp.h" +#include "../auth/ntlmssp/ntlmssp_private.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +static void debug_ntlmssp_flags_raw(int level, uint32_t flags) +{ +#define _PRINT_FLAG_LINE(v) do { \ + if (flags & (v)) { \ + DEBUGADD(level, (" " #v "\n")); \ + } \ +} while (0) + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_UNICODE); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_OEM); + _PRINT_FLAG_LINE(NTLMSSP_REQUEST_TARGET); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_SIGN); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_SEAL); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_DATAGRAM); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_LM_KEY); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_NETWARE); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_NTLM); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_NT_ONLY); + _PRINT_FLAG_LINE(NTLMSSP_ANONYMOUS); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_THIS_IS_LOCAL_CALL); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_ALWAYS_SIGN); + _PRINT_FLAG_LINE(NTLMSSP_TARGET_TYPE_DOMAIN); + _PRINT_FLAG_LINE(NTLMSSP_TARGET_TYPE_SERVER); + _PRINT_FLAG_LINE(NTLMSSP_TARGET_TYPE_SHARE); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_IDENTIFY); + _PRINT_FLAG_LINE(NTLMSSP_REQUEST_NON_NT_SESSION_KEY); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_TARGET_INFO); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_VERSION); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_128); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_KEY_EXCH); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_56); +} + +/** + * Print out the NTLMSSP flags for debugging + * @param neg_flags The flags from the packet + */ +void debug_ntlmssp_flags(uint32_t neg_flags) +{ + DEBUG(3,("Got NTLMSSP neg_flags=0x%08x\n", neg_flags)); + debug_ntlmssp_flags_raw(4, neg_flags); +} + +NTSTATUS ntlmssp_handle_neg_flags(struct ntlmssp_state *ntlmssp_state, + uint32_t flags, const char *name) +{ + uint32_t missing_flags = ntlmssp_state->required_flags; + + if (ntlmssp_state->use_ntlmv2) { + /* + * Using NTLMv2 as a client implies + * using NTLMSSP_NEGOTIATE_NTLM2 + * (NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY) + * + * Note that 'use_ntlmv2' is only set + * true in the client case. + * + * Even if the server has a bug and does not announce + * it, we need to assume it's present. + * + * Note that we also have the flag + * in ntlmssp_state->required_flags, + * see gensec_ntlmssp_client_start(). + * + * See bug #12862. + */ + flags |= NTLMSSP_NEGOTIATE_NTLM2; + } + + if (flags & NTLMSSP_NEGOTIATE_UNICODE) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_UNICODE; + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_OEM; + ntlmssp_state->unicode = true; + } else { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_UNICODE; + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_OEM; + ntlmssp_state->unicode = false; + } + + /* + * NTLMSSP_NEGOTIATE_NTLM2 (NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY) + * has priority over NTLMSSP_NEGOTIATE_LM_KEY + */ + if (!(flags & NTLMSSP_NEGOTIATE_NTLM2)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_NTLM2; + } + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + } + + if (!(flags & NTLMSSP_NEGOTIATE_LM_KEY)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + } + + if (!(flags & NTLMSSP_NEGOTIATE_ALWAYS_SIGN)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_ALWAYS_SIGN; + } + + if (!(flags & NTLMSSP_NEGOTIATE_128)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_128; + } + + if (!(flags & NTLMSSP_NEGOTIATE_56)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_56; + } + + if (!(flags & NTLMSSP_NEGOTIATE_KEY_EXCH)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_KEY_EXCH; + } + + if (!(flags & NTLMSSP_NEGOTIATE_SIGN)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_SIGN; + } + + if (!(flags & NTLMSSP_NEGOTIATE_SEAL)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_SEAL; + } + + if ((flags & NTLMSSP_REQUEST_TARGET)) { + ntlmssp_state->neg_flags |= NTLMSSP_REQUEST_TARGET; + } + + missing_flags &= ~ntlmssp_state->neg_flags; + if (missing_flags != 0) { + HRESULT hres = HRES_SEC_E_UNSUPPORTED_FUNCTION; + NTSTATUS status = NT_STATUS(HRES_ERROR_V(hres)); + DEBUG(1, ("%s: Got %s flags[0x%08x] " + "- possible downgrade detected! " + "missing_flags[0x%08x] - %s\n", + __func__, name, + (unsigned)flags, + (unsigned)missing_flags, + nt_errstr(status))); + debug_ntlmssp_flags_raw(1, missing_flags); + DEBUGADD(4, ("neg_flags[0x%08x]\n", + (unsigned)ntlmssp_state->neg_flags)); + debug_ntlmssp_flags_raw(4, ntlmssp_state->neg_flags); + return status; + } + + return NT_STATUS_OK; +} + +/* Does this blob looks like it could be NTLMSSP? */ +bool ntlmssp_blob_matches_magic(const DATA_BLOB *blob) +{ + if (blob->length > 8 && memcmp("NTLMSSP\0", blob->data, 8) == 0) { + return true; + } else { + return false; + } +} + +const DATA_BLOB ntlmssp_version_blob(void) +{ + /* + * This is a simplified version of + * + * enum ndr_err_code err; + * struct ntlmssp_VERSION vers; + * + * ZERO_STRUCT(vers); + * vers.ProductMajorVersion = NTLMSSP_WINDOWS_MAJOR_VERSION_6; + * vers.ProductMinorVersion = NTLMSSP_WINDOWS_MINOR_VERSION_1; + * vers.ProductBuild = 0; + * vers.NTLMRevisionCurrent = NTLMSSP_REVISION_W2K3; + * + * err = ndr_push_struct_blob(&version_blob, + * ntlmssp_state, + * &vers, + * (ndr_push_flags_fn_t)ndr_push_ntlmssp_VERSION); + * + * if (!NDR_ERR_CODE_IS_SUCCESS(err)) { + * data_blob_free(&struct_blob); + * return NT_STATUS_NO_MEMORY; + * } + */ + static const uint8_t version_buffer[8] = { + NTLMSSP_WINDOWS_MAJOR_VERSION_6, + NTLMSSP_WINDOWS_MINOR_VERSION_1, + 0x00, 0x00, /* product build */ + 0x00, 0x00, 0x00, /* reserved */ + NTLMSSP_REVISION_W2K3 + }; + + return data_blob_const(version_buffer, ARRAY_SIZE(version_buffer)); +} diff --git a/auth/ntlmssp/wscript_build b/auth/ntlmssp/wscript_build new file mode 100644 index 0000000..20836ef --- /dev/null +++ b/auth/ntlmssp/wscript_build @@ -0,0 +1,27 @@ +bld.SAMBA_SUBSYSTEM('NTLMSSP_COMMON', + source='''gensec_ntlmssp.c + ntlmssp.c + ntlmssp_util.c + ntlmssp_ndr.c + ntlmssp_client.c + ntlmssp_server.c + ntlmssp_sign.c + gensec_ntlmssp_server.c''', + deps=''' + samba-util + NDR_NTLMSSP + MSRPC_PARSE + NTLM_CHECK + samba-credentials + wbclient + z + GNUTLS_HELPERS + ''') + +bld.SAMBA_MODULE('gensec_ntlmssp', + source='''''', + subsystem='gensec', + init_function='gensec_ntlmssp_init', + deps='NTLMSSP_COMMON', + internal_module=True + ) |