summaryrefslogtreecommitdiffstats
path: root/source3/libsmb/cliconnect.c
diff options
context:
space:
mode:
Diffstat (limited to 'source3/libsmb/cliconnect.c')
-rw-r--r--source3/libsmb/cliconnect.c3991
1 files changed, 3991 insertions, 0 deletions
diff --git a/source3/libsmb/cliconnect.c b/source3/libsmb/cliconnect.c
new file mode 100644
index 0000000..3e60295
--- /dev/null
+++ b/source3/libsmb/cliconnect.c
@@ -0,0 +1,3991 @@
+/*
+ Unix SMB/CIFS implementation.
+ client connect/disconnect routines
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Andrew Bartlett 2001-2003
+ Copyright (C) Volker Lendecke 2011
+ Copyright (C) Jeremy Allison 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/libsmb.h"
+#include "libsmb/namequery.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "../libcli/auth/spnego.h"
+#include "smb_krb5.h"
+#include "auth/credentials/credentials.h"
+#include "auth/gensec/gensec.h"
+#include "auth/ntlmssp/ntlmssp.h"
+#include "auth_generic.h"
+#include "libads/kerberos_proto.h"
+#include "krb5_env.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "async_smb.h"
+#include "libsmb/nmblib.h"
+#include "librpc/ndr/libndr.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "../libcli/smb/smb_seal.h"
+#include "lib/param/param.h"
+#include "../libcli/smb/smb2_negotiate_context.h"
+
+#define STAR_SMBSERVER "*SMBSERVER"
+
+static char *cli_session_setup_get_account(TALLOC_CTX *mem_ctx,
+ const char *principal);
+
+struct cli_credentials *cli_session_creds_init(TALLOC_CTX *mem_ctx,
+ const char *username,
+ const char *domain,
+ const char *realm,
+ const char *password,
+ bool use_kerberos,
+ bool fallback_after_kerberos,
+ bool use_ccache,
+ bool password_is_nt_hash)
+{
+ struct loadparm_context *lp_ctx = NULL;
+ struct cli_credentials *creds = NULL;
+ const char *principal = NULL;
+ char *tmp = NULL;
+ char *p = NULL;
+ bool ok;
+
+ creds = cli_credentials_init(mem_ctx);
+ if (creds == NULL) {
+ return NULL;
+ }
+
+ lp_ctx = loadparm_init_s3(creds, loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ goto fail;
+ }
+ ok = cli_credentials_set_conf(creds, lp_ctx);
+ if (!ok) {
+ goto fail;
+ }
+
+ if (username == NULL) {
+ username = "";
+ }
+
+ if (strlen(username) == 0) {
+ if (password != NULL && strlen(password) == 0) {
+ /*
+ * some callers pass "" as no password
+ *
+ * gensec only handles NULL as no password.
+ */
+ password = NULL;
+ }
+ if (password == NULL) {
+ cli_credentials_set_anonymous(creds);
+ return creds;
+ }
+ }
+
+ tmp = talloc_strdup(creds, username);
+ if (tmp == NULL) {
+ goto fail;
+ }
+ username = tmp;
+
+ /* allow for workgroups as part of the username */
+ if ((p = strchr_m(tmp, '\\')) ||
+ (p = strchr_m(tmp, '/')) ||
+ (p = strchr_m(tmp, *lp_winbind_separator()))) {
+ *p = 0;
+ username = p + 1;
+ domain = tmp;
+ }
+
+ principal = username;
+ username = cli_session_setup_get_account(creds, principal);
+ if (username == NULL) {
+ goto fail;
+ }
+ ok = strequal(username, principal);
+ if (ok) {
+ /*
+ * Ok still the same, so it's not a principal
+ */
+ principal = NULL;
+ }
+
+ if (use_kerberos && fallback_after_kerberos) {
+ cli_credentials_set_kerberos_state(creds,
+ CRED_USE_KERBEROS_DESIRED,
+ CRED_SPECIFIED);
+ } else if (use_kerberos) {
+ cli_credentials_set_kerberos_state(creds,
+ CRED_USE_KERBEROS_REQUIRED,
+ CRED_SPECIFIED);
+ } else {
+ cli_credentials_set_kerberos_state(creds,
+ CRED_USE_KERBEROS_DISABLED,
+ CRED_SPECIFIED);
+ }
+
+ if (use_ccache) {
+ uint32_t features;
+
+ features = cli_credentials_get_gensec_features(creds);
+ features |= GENSEC_FEATURE_NTLM_CCACHE;
+ cli_credentials_set_gensec_features(creds,
+ features,
+ CRED_SPECIFIED);
+
+ if (password != NULL && strlen(password) == 0) {
+ /*
+ * some callers pass "" as no password
+ *
+ * GENSEC_FEATURE_NTLM_CCACHE only handles
+ * NULL as no password.
+ */
+ password = NULL;
+ }
+ }
+
+ ok = cli_credentials_set_username(creds,
+ username,
+ CRED_SPECIFIED);
+ if (!ok) {
+ goto fail;
+ }
+
+ if (domain != NULL) {
+ ok = cli_credentials_set_domain(creds,
+ domain,
+ CRED_SPECIFIED);
+ if (!ok) {
+ goto fail;
+ }
+ }
+
+ if (principal != NULL) {
+ ok = cli_credentials_set_principal(creds,
+ principal,
+ CRED_SPECIFIED);
+ if (!ok) {
+ goto fail;
+ }
+ }
+
+ if (realm != NULL) {
+ ok = cli_credentials_set_realm(creds,
+ realm,
+ CRED_SPECIFIED);
+ if (!ok) {
+ goto fail;
+ }
+ }
+
+ if (password != NULL && strlen(password) > 0) {
+ if (password_is_nt_hash) {
+ struct samr_Password nt_hash;
+ size_t converted;
+
+ converted = strhex_to_str((char *)nt_hash.hash,
+ sizeof(nt_hash.hash),
+ password,
+ strlen(password));
+ if (converted != sizeof(nt_hash.hash)) {
+ goto fail;
+ }
+
+ ok = cli_credentials_set_nt_hash(creds,
+ &nt_hash,
+ CRED_SPECIFIED);
+ if (!ok) {
+ goto fail;
+ }
+ } else {
+ ok = cli_credentials_set_password(creds,
+ password,
+ CRED_SPECIFIED);
+ if (!ok) {
+ goto fail;
+ }
+ }
+ }
+
+ return creds;
+fail:
+ TALLOC_FREE(creds);
+ return NULL;
+}
+
+NTSTATUS cli_session_creds_prepare_krb5(struct cli_state *cli,
+ struct cli_credentials *creds)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const char *user_principal = NULL;
+ const char *user_account = NULL;
+ const char *user_domain = NULL;
+ const char *pass = NULL;
+ char *canon_principal = NULL;
+ char *canon_realm = NULL;
+ const char *target_hostname = NULL;
+ enum credentials_use_kerberos krb5_state;
+ bool try_kerberos = false;
+ bool need_kinit = false;
+ bool auth_requested = true;
+ int ret;
+ bool ok;
+
+ target_hostname = smbXcli_conn_remote_name(cli->conn);
+
+ auth_requested = cli_credentials_authentication_requested(creds);
+ if (auth_requested) {
+ errno = 0;
+ user_principal = cli_credentials_get_principal(creds, frame);
+ if (errno != 0) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ user_account = cli_credentials_get_username(creds);
+ user_domain = cli_credentials_get_domain(creds);
+ pass = cli_credentials_get_password(creds);
+
+ krb5_state = cli_credentials_get_kerberos_state(creds);
+
+ if (krb5_state != CRED_USE_KERBEROS_DISABLED) {
+ try_kerberos = true;
+ }
+
+ if (user_principal == NULL) {
+ try_kerberos = false;
+ }
+
+ if (target_hostname == NULL) {
+ try_kerberos = false;
+ } else if (is_ipaddress(target_hostname)) {
+ try_kerberos = false;
+ } else if (strequal(target_hostname, "localhost")) {
+ try_kerberos = false;
+ } else if (strequal(target_hostname, STAR_SMBSERVER)) {
+ try_kerberos = false;
+ } else if (!auth_requested) {
+ try_kerberos = false;
+ }
+
+ if (krb5_state == CRED_USE_KERBEROS_REQUIRED && !try_kerberos) {
+ DEBUG(0, ("Kerberos auth with '%s' (%s\\%s) to access "
+ "'%s' not possible\n",
+ user_principal, user_domain, user_account,
+ target_hostname));
+ TALLOC_FREE(frame);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (pass == NULL || strlen(pass) == 0) {
+ need_kinit = false;
+ } else if (krb5_state == CRED_USE_KERBEROS_REQUIRED) {
+ need_kinit = try_kerberos;
+ } else {
+ need_kinit = try_kerberos;
+ }
+
+ if (!need_kinit) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ DBG_INFO("Doing kinit for %s to access %s\n",
+ user_principal, target_hostname);
+
+ /*
+ * TODO: This should be done within the gensec layer
+ * only if required!
+ */
+ setenv(KRB5_ENV_CCNAME, "MEMORY:cliconnect", 1);
+ ret = kerberos_kinit_password_ext(user_principal,
+ pass,
+ 0,
+ 0,
+ 0,
+ NULL,
+ false,
+ false,
+ 0,
+ frame,
+ &canon_principal,
+ &canon_realm,
+ NULL);
+ if (ret != 0) {
+ int dbglvl = DBGLVL_NOTICE;
+
+ if (krb5_state == CRED_USE_KERBEROS_REQUIRED) {
+ dbglvl = DBGLVL_ERR;
+ }
+
+ DEBUG(dbglvl, ("Kinit for %s to access %s failed: %s\n",
+ user_principal, target_hostname,
+ error_message(ret)));
+ if (krb5_state == CRED_USE_KERBEROS_REQUIRED) {
+ TALLOC_FREE(frame);
+ return krb5_to_nt_status(ret);
+ }
+
+ /*
+ * Ignore the error and hope that NTLM will work
+ */
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ ok = cli_credentials_set_principal(creds,
+ canon_principal,
+ CRED_SPECIFIED);
+ if (!ok) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ok = cli_credentials_set_realm(creds,
+ canon_realm,
+ CRED_SPECIFIED);
+ if (!ok) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DBG_DEBUG("Successfully authenticated as %s (%s) to access %s using "
+ "Kerberos\n",
+ user_principal,
+ canon_principal,
+ target_hostname);
+
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cli_state_update_after_sesssetup(struct cli_state *cli,
+ const char *native_os,
+ const char *native_lm,
+ const char *primary_domain)
+{
+#define _VALID_STR(p) ((p) != NULL && (p)[0] != '\0')
+
+ if (!_VALID_STR(cli->server_os) && _VALID_STR(native_os)) {
+ cli->server_os = talloc_strdup(cli, native_os);
+ if (cli->server_os == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (!_VALID_STR(cli->server_type) && _VALID_STR(native_lm)) {
+ cli->server_type = talloc_strdup(cli, native_lm);
+ if (cli->server_type == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (!_VALID_STR(cli->server_domain) && _VALID_STR(primary_domain)) {
+ cli->server_domain = talloc_strdup(cli, primary_domain);
+ if (cli->server_domain == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+#undef _VALID_STRING
+ return NT_STATUS_OK;
+}
+
+/********************************************************
+ Utility function to ensure we always return at least
+ a valid char * pointer to an empty string for the
+ cli->server_os, cli->server_type and cli->server_domain
+ strings.
+*******************************************************/
+
+static NTSTATUS smb_bytes_talloc_string(TALLOC_CTX *mem_ctx,
+ const uint8_t *hdr,
+ char **dest,
+ uint8_t *src,
+ size_t srclen,
+ ssize_t *destlen)
+{
+ *destlen = pull_string_talloc(mem_ctx,
+ (const char *)hdr,
+ SVAL(hdr, HDR_FLG2),
+ dest,
+ (char *)src,
+ srclen,
+ STR_TERMINATE);
+ if (*destlen == -1) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (*dest == NULL) {
+ *dest = talloc_strdup(mem_ctx, "");
+ if (*dest == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Work out suitable capabilities to offer the server.
+****************************************************************************/
+
+static uint32_t cli_session_setup_capabilities(struct cli_state *cli,
+ uint32_t sesssetup_capabilities)
+{
+ uint32_t client_capabilities = smb1cli_conn_capabilities(cli->conn);
+
+ /*
+ * We only send capabilities based on the mask for:
+ * - client only flags
+ * - flags used in both directions
+ *
+ * We do not echo the server only flags, except some legacy flags.
+ *
+ * SMB_CAP_LEGACY_CLIENT_MASK contains CAP_LARGE_READX and
+ * CAP_LARGE_WRITEX in order to allow us to do large reads
+ * against old Samba releases (<= 3.6.x).
+ */
+ client_capabilities &= (SMB_CAP_BOTH_MASK | SMB_CAP_LEGACY_CLIENT_MASK);
+
+ /*
+ * Session Setup specific flags CAP_DYNAMIC_REAUTH
+ * and CAP_EXTENDED_SECURITY are passed by the caller.
+ * We need that in order to do guest logins even if
+ * CAP_EXTENDED_SECURITY is negotiated.
+ */
+ client_capabilities &= ~(CAP_DYNAMIC_REAUTH|CAP_EXTENDED_SECURITY);
+ sesssetup_capabilities &= (CAP_DYNAMIC_REAUTH|CAP_EXTENDED_SECURITY);
+ client_capabilities |= sesssetup_capabilities;
+
+ return client_capabilities;
+}
+
+/****************************************************************************
+ Do a NT1 guest session setup.
+****************************************************************************/
+
+struct cli_session_setup_guest_state {
+ struct cli_state *cli;
+ uint16_t vwv[13];
+ struct iovec bytes;
+};
+
+static void cli_session_setup_guest_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_session_setup_guest_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ struct tevent_req **psmbreq)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_session_setup_guest_state *state;
+ uint16_t *vwv;
+ uint8_t *bytes;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_session_setup_guest_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+ vwv = state->vwv;
+
+ SCVAL(vwv+0, 0, 0xFF);
+ SCVAL(vwv+0, 1, 0);
+ SSVAL(vwv+1, 0, 0);
+ SSVAL(vwv+2, 0, CLI_BUFFER_SIZE);
+ SSVAL(vwv+3, 0, 2);
+ SSVAL(vwv+4, 0, cli_state_get_vc_num(cli));
+ SIVAL(vwv+5, 0, smb1cli_conn_server_session_key(cli->conn));
+ SSVAL(vwv+7, 0, 0);
+ SSVAL(vwv+8, 0, 0);
+ SSVAL(vwv+9, 0, 0);
+ SSVAL(vwv+10, 0, 0);
+ SIVAL(vwv+11, 0, cli_session_setup_capabilities(cli, 0));
+
+ bytes = talloc_array(state, uint8_t, 0);
+
+ bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn), "", 1, /* username */
+ NULL);
+ bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn), "", 1, /* workgroup */
+ NULL);
+ bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn), "Unix", 5, NULL);
+ bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn), "Samba", 6, NULL);
+
+ if (bytes == NULL) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ state->bytes.iov_base = (void *)bytes;
+ state->bytes.iov_len = talloc_get_size(bytes);
+
+ subreq = cli_smb_req_create(state, ev, cli, SMBsesssetupX, 0, 0, 13,
+ vwv, 1, &state->bytes);
+ if (subreq == NULL) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, cli_session_setup_guest_done, req);
+ *psmbreq = subreq;
+ return req;
+}
+
+struct tevent_req *cli_session_setup_guest_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli)
+{
+ struct tevent_req *req, *subreq;
+ NTSTATUS status;
+
+ req = cli_session_setup_guest_create(mem_ctx, ev, cli, &subreq);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ status = smb1cli_req_chain_submit(&subreq, 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ return req;
+}
+
+static void cli_session_setup_guest_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_session_setup_guest_state *state = tevent_req_data(
+ req, struct cli_session_setup_guest_state);
+ struct cli_state *cli = state->cli;
+ uint32_t num_bytes;
+ uint8_t *in;
+ uint8_t *inhdr;
+ uint8_t *bytes;
+ uint8_t *p;
+ NTSTATUS status;
+ ssize_t ret;
+ uint8_t wct;
+ uint16_t *vwv;
+
+ status = cli_smb_recv(subreq, state, &in, 3, &wct, &vwv,
+ &num_bytes, &bytes);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ inhdr = in + NBT_HDR_SIZE;
+ p = bytes;
+
+ cli_state_set_uid(state->cli, SVAL(inhdr, HDR_UID));
+ smb1cli_session_set_action(cli->smb1.session, SVAL(vwv+2, 0));
+
+ status = smb_bytes_talloc_string(cli,
+ inhdr,
+ &cli->server_os,
+ p,
+ bytes+num_bytes-p,
+ &ret);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ p += ret;
+
+ status = smb_bytes_talloc_string(cli,
+ inhdr,
+ &cli->server_type,
+ p,
+ bytes+num_bytes-p,
+ &ret);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ p += ret;
+
+ status = smb_bytes_talloc_string(cli,
+ inhdr,
+ &cli->server_domain,
+ p,
+ bytes+num_bytes-p,
+ &ret);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_session_setup_guest_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+/* The following is calculated from :
+ * (smb_size-4) = 35
+ * (smb_wcnt * 2) = 24 (smb_wcnt == 12 in cli_session_setup_blob_send() )
+ * (strlen("Unix") + 1 + strlen("Samba") + 1) * 2 = 22 (unicode strings at
+ * end of packet.
+ */
+
+#define BASE_SESSSETUP_BLOB_PACKET_SIZE (35 + 24 + 22)
+
+struct cli_sesssetup_blob_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ DATA_BLOB blob;
+ uint16_t max_blob_size;
+
+ DATA_BLOB this_blob;
+ struct iovec *recv_iov;
+
+ NTSTATUS status;
+ const uint8_t *inbuf;
+ DATA_BLOB ret_blob;
+
+ char *out_native_os;
+ char *out_native_lm;
+};
+
+static bool cli_sesssetup_blob_next(struct cli_sesssetup_blob_state *state,
+ struct tevent_req **psubreq);
+static void cli_sesssetup_blob_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_sesssetup_blob_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ DATA_BLOB blob)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_sesssetup_blob_state *state;
+ uint32_t usable_space;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_sesssetup_blob_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->blob = blob;
+ state->cli = cli;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ usable_space = UINT16_MAX;
+ } else {
+ usable_space = cli_state_available_size(cli,
+ BASE_SESSSETUP_BLOB_PACKET_SIZE);
+ }
+
+ if (usable_space == 0) {
+ DEBUG(1, ("cli_session_setup_blob: cli->max_xmit too small "
+ "(not possible to send %u bytes)\n",
+ BASE_SESSSETUP_BLOB_PACKET_SIZE + 1));
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ state->max_blob_size = MIN(usable_space, 0xFFFF);
+
+ if (!cli_sesssetup_blob_next(state, &subreq)) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_sesssetup_blob_done, req);
+ return req;
+}
+
+static bool cli_sesssetup_blob_next(struct cli_sesssetup_blob_state *state,
+ struct tevent_req **psubreq)
+{
+ struct tevent_req *subreq;
+ uint16_t thistime;
+
+ thistime = MIN(state->blob.length, state->max_blob_size);
+
+ state->this_blob.data = state->blob.data;
+ state->this_blob.length = thistime;
+
+ state->blob.data += thistime;
+ state->blob.length -= thistime;
+
+ if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+ subreq = smb2cli_session_setup_send(state, state->ev,
+ state->cli->conn,
+ state->cli->timeout,
+ state->cli->smb2.session,
+ 0, /* in_flags */
+ SMB2_CAP_DFS, /* in_capabilities */
+ 0, /* in_channel */
+ 0, /* in_previous_session_id */
+ &state->this_blob);
+ if (subreq == NULL) {
+ return false;
+ }
+ } else {
+ uint16_t in_buf_size = 0;
+ uint16_t in_mpx_max = 0;
+ uint16_t in_vc_num = 0;
+ uint32_t in_sess_key = 0;
+ uint32_t in_capabilities = 0;
+ const char *in_native_os = NULL;
+ const char *in_native_lm = NULL;
+
+ in_buf_size = CLI_BUFFER_SIZE;
+ in_mpx_max = smbXcli_conn_max_requests(state->cli->conn);
+ in_vc_num = cli_state_get_vc_num(state->cli);
+ in_sess_key = smb1cli_conn_server_session_key(state->cli->conn);
+ in_capabilities = cli_session_setup_capabilities(state->cli,
+ CAP_EXTENDED_SECURITY);
+ in_native_os = "Unix";
+ in_native_lm = "Samba";
+
+ /*
+ * For now we keep the same values as before,
+ * we may remove these in a separate commit later.
+ */
+ in_mpx_max = 2;
+ in_vc_num = 1;
+ in_sess_key = 0;
+
+ subreq = smb1cli_session_setup_ext_send(state, state->ev,
+ state->cli->conn,
+ state->cli->timeout,
+ state->cli->smb1.pid,
+ state->cli->smb1.session,
+ in_buf_size,
+ in_mpx_max,
+ in_vc_num,
+ in_sess_key,
+ state->this_blob,
+ in_capabilities,
+ in_native_os,
+ in_native_lm);
+ if (subreq == NULL) {
+ return false;
+ }
+ }
+ *psubreq = subreq;
+ return true;
+}
+
+static void cli_sesssetup_blob_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_sesssetup_blob_state *state = tevent_req_data(
+ req, struct cli_sesssetup_blob_state);
+ NTSTATUS status;
+
+ if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+ status = smb2cli_session_setup_recv(subreq, state,
+ &state->recv_iov,
+ &state->ret_blob);
+ } else {
+ status = smb1cli_session_setup_ext_recv(subreq, state,
+ &state->recv_iov,
+ &state->inbuf,
+ &state->ret_blob,
+ &state->out_native_os,
+ &state->out_native_lm);
+ }
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)
+ && !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ state->status = status;
+
+ status = cli_state_update_after_sesssetup(state->cli,
+ state->out_native_os,
+ state->out_native_lm,
+ NULL);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (state->blob.length != 0) {
+ /*
+ * More to send
+ */
+ if (!cli_sesssetup_blob_next(state, &subreq)) {
+ tevent_req_oom(req);
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_sesssetup_blob_done, req);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_sesssetup_blob_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *pblob,
+ const uint8_t **pinbuf,
+ struct iovec **precv_iov)
+{
+ struct cli_sesssetup_blob_state *state = tevent_req_data(
+ req, struct cli_sesssetup_blob_state);
+ NTSTATUS status;
+ struct iovec *recv_iov;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ TALLOC_FREE(state->cli->smb2.session);
+ cli_state_set_uid(state->cli, UID_FIELD_INVALID);
+ tevent_req_received(req);
+ return status;
+ }
+
+ recv_iov = talloc_move(mem_ctx, &state->recv_iov);
+ if (pblob != NULL) {
+ *pblob = state->ret_blob;
+ }
+ if (pinbuf != NULL) {
+ *pinbuf = state->inbuf;
+ }
+ if (precv_iov != NULL) {
+ *precv_iov = recv_iov;
+ }
+ /* could be NT_STATUS_MORE_PROCESSING_REQUIRED */
+ status = state->status;
+ tevent_req_received(req);
+ return status;
+}
+
+/****************************************************************************
+ Do a spnego/NTLMSSP encrypted session setup.
+****************************************************************************/
+
+struct cli_session_setup_gensec_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ struct auth_generic_state *auth_generic;
+ bool is_anonymous;
+ DATA_BLOB blob_in;
+ const uint8_t *inbuf;
+ struct iovec *recv_iov;
+ DATA_BLOB blob_out;
+ bool local_ready;
+ bool remote_ready;
+ DATA_BLOB session_key;
+};
+
+static int cli_session_setup_gensec_state_destructor(
+ struct cli_session_setup_gensec_state *state)
+{
+ TALLOC_FREE(state->auth_generic);
+ data_blob_clear_free(&state->session_key);
+ return 0;
+}
+
+static void cli_session_setup_gensec_local_next(struct tevent_req *req);
+static void cli_session_setup_gensec_local_done(struct tevent_req *subreq);
+static void cli_session_setup_gensec_remote_next(struct tevent_req *req);
+static void cli_session_setup_gensec_remote_done(struct tevent_req *subreq);
+static void cli_session_setup_gensec_ready(struct tevent_req *req);
+
+static struct tevent_req *cli_session_setup_gensec_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli,
+ struct cli_credentials *creds,
+ const char *target_service,
+ const char *target_hostname)
+{
+ struct tevent_req *req;
+ struct cli_session_setup_gensec_state *state;
+ NTSTATUS status;
+ const DATA_BLOB *b = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_session_setup_gensec_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+
+ talloc_set_destructor(
+ state, cli_session_setup_gensec_state_destructor);
+
+ status = auth_generic_client_prepare(state, &state->auth_generic);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ status = auth_generic_set_creds(state->auth_generic, creds);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ gensec_want_feature(state->auth_generic->gensec_security,
+ GENSEC_FEATURE_SESSION_KEY);
+
+ if (target_service != NULL) {
+ status = gensec_set_target_service(
+ state->auth_generic->gensec_security,
+ target_service);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ if (target_hostname != NULL) {
+ status = gensec_set_target_hostname(
+ state->auth_generic->gensec_security,
+ target_hostname);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ b = smbXcli_conn_server_gss_blob(cli->conn);
+ if (b != NULL) {
+ state->blob_in = *b;
+ }
+
+ state->is_anonymous = cli_credentials_is_anonymous(state->auth_generic->credentials);
+
+ status = auth_generic_client_start(state->auth_generic,
+ GENSEC_OID_SPNEGO);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ state->cli->smb2.session = smbXcli_session_create(cli,
+ cli->conn);
+ if (tevent_req_nomem(state->cli->smb2.session, req)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ cli_session_setup_gensec_local_next(req);
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void cli_session_setup_gensec_local_next(struct tevent_req *req)
+{
+ struct cli_session_setup_gensec_state *state =
+ tevent_req_data(req,
+ struct cli_session_setup_gensec_state);
+ struct tevent_req *subreq = NULL;
+
+ if (state->local_ready) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ subreq = gensec_update_send(state, state->ev,
+ state->auth_generic->gensec_security,
+ state->blob_in);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_session_setup_gensec_local_done, req);
+}
+
+static void cli_session_setup_gensec_local_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct cli_session_setup_gensec_state *state =
+ tevent_req_data(req,
+ struct cli_session_setup_gensec_state);
+ NTSTATUS status;
+
+ status = gensec_update_recv(subreq, state, &state->blob_out);
+ TALLOC_FREE(subreq);
+ state->blob_in = data_blob_null;
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED))
+ {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ state->local_ready = true;
+ }
+
+ if (state->local_ready && state->remote_ready) {
+ cli_session_setup_gensec_ready(req);
+ return;
+ }
+
+ cli_session_setup_gensec_remote_next(req);
+}
+
+static void cli_session_setup_gensec_remote_next(struct tevent_req *req)
+{
+ struct cli_session_setup_gensec_state *state =
+ tevent_req_data(req,
+ struct cli_session_setup_gensec_state);
+ struct tevent_req *subreq = NULL;
+
+ if (state->remote_ready) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ subreq = cli_sesssetup_blob_send(state, state->ev,
+ state->cli, state->blob_out);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ cli_session_setup_gensec_remote_done,
+ req);
+}
+
+static void cli_session_setup_gensec_remote_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct cli_session_setup_gensec_state *state =
+ tevent_req_data(req,
+ struct cli_session_setup_gensec_state);
+ NTSTATUS status;
+
+ state->inbuf = NULL;
+ TALLOC_FREE(state->recv_iov);
+
+ status = cli_sesssetup_blob_recv(subreq, state, &state->blob_in,
+ &state->inbuf, &state->recv_iov);
+ TALLOC_FREE(subreq);
+ data_blob_free(&state->blob_out);
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED))
+ {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ struct smbXcli_session *session = NULL;
+ bool is_guest = false;
+
+ if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+ session = state->cli->smb2.session;
+ } else {
+ session = state->cli->smb1.session;
+ }
+
+ is_guest = smbXcli_session_is_guest(session);
+ if (is_guest) {
+ /*
+ * We can't finish the gensec handshake, we don't
+ * have a negotiated session key.
+ *
+ * So just pretend we are completely done,
+ * we need to continue as anonymous from this point,
+ * as we can't get a session key.
+ *
+ * Note that smbXcli_session_is_guest()
+ * always returns false if we require signing.
+ */
+ state->blob_in = data_blob_null;
+ state->local_ready = true;
+ state->is_anonymous = true;
+ }
+
+ state->remote_ready = true;
+ }
+
+ if (state->local_ready && state->remote_ready) {
+ cli_session_setup_gensec_ready(req);
+ return;
+ }
+
+ cli_session_setup_gensec_local_next(req);
+}
+
+static void cli_session_dump_keys(TALLOC_CTX *mem_ctx,
+ struct smbXcli_session *session,
+ DATA_BLOB session_key)
+{
+ NTSTATUS status;
+ DATA_BLOB sig = data_blob_null;
+ DATA_BLOB app = data_blob_null;
+ DATA_BLOB enc = data_blob_null;
+ DATA_BLOB dec = data_blob_null;
+ uint64_t sid = smb2cli_session_current_id(session);
+
+ status = smb2cli_session_signing_key(session, mem_ctx, &sig);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ status = smbXcli_session_application_key(session, mem_ctx, &app);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ status = smb2cli_session_encryption_key(session, mem_ctx, &enc);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ status = smb2cli_session_decryption_key(session, mem_ctx, &dec);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ DEBUG(0, ("debug encryption: dumping generated session keys\n"));
+ DEBUGADD(0, ("Session Id "));
+ dump_data(0, (uint8_t*)&sid, sizeof(sid));
+ DEBUGADD(0, ("Session Key "));
+ dump_data(0, session_key.data, session_key.length);
+ DEBUGADD(0, ("Signing Key "));
+ dump_data(0, sig.data, sig.length);
+ DEBUGADD(0, ("App Key "));
+ dump_data(0, app.data, app.length);
+
+ /* In client code, ServerIn is the encryption key */
+
+ DEBUGADD(0, ("ServerIn Key "));
+ dump_data(0, enc.data, enc.length);
+ DEBUGADD(0, ("ServerOut Key "));
+ dump_data(0, dec.data, dec.length);
+
+out:
+ data_blob_clear_free(&sig);
+ data_blob_clear_free(&app);
+ data_blob_clear_free(&enc);
+ data_blob_clear_free(&dec);
+}
+
+static void cli_session_setup_gensec_ready(struct tevent_req *req)
+{
+ struct cli_session_setup_gensec_state *state =
+ tevent_req_data(req,
+ struct cli_session_setup_gensec_state);
+ const char *server_domain = NULL;
+ NTSTATUS status;
+
+ if (state->blob_in.length != 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ if (state->blob_out.length != 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ /*
+ * gensec_ntlmssp_server_domain() returns NULL
+ * if NTLMSSP is not used.
+ *
+ * We can remove this later
+ * and leave the server domain empty for SMB2 and above
+ * in future releases.
+ */
+ server_domain = gensec_ntlmssp_server_domain(
+ state->auth_generic->gensec_security);
+
+ if (state->cli->server_domain[0] == '\0' && server_domain != NULL) {
+ TALLOC_FREE(state->cli->server_domain);
+ state->cli->server_domain = talloc_strdup(state->cli,
+ server_domain);
+ if (state->cli->server_domain == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ }
+
+ if (state->is_anonymous) {
+ /*
+ * Windows server does not set the
+ * SMB2_SESSION_FLAG_IS_NULL flag.
+ *
+ * This fix makes sure we do not try
+ * to verify a signature on the final
+ * session setup response.
+ */
+ tevent_req_done(req);
+ return;
+ }
+
+ status = gensec_session_key(state->auth_generic->gensec_security,
+ state, &state->session_key);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+ struct smbXcli_session *session = state->cli->smb2.session;
+
+ status = smb2cli_session_set_session_key(session,
+ state->session_key,
+ state->recv_iov);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB3_00
+ && lp_debug_encryption())
+ {
+ cli_session_dump_keys(state, session, state->session_key);
+ }
+ } else {
+ struct smbXcli_session *session = state->cli->smb1.session;
+ bool active;
+
+ status = smb1cli_session_set_session_key(session,
+ state->session_key);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ active = smb1cli_conn_activate_signing(state->cli->conn,
+ state->session_key,
+ data_blob_null);
+ if (active) {
+ bool ok;
+
+ ok = smb1cli_conn_check_signing(state->cli->conn,
+ state->inbuf, 1);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+ }
+ }
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_session_setup_gensec_recv(struct tevent_req *req)
+{
+ struct cli_session_setup_gensec_state *state =
+ tevent_req_data(req,
+ struct cli_session_setup_gensec_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ cli_state_set_uid(state->cli, UID_FIELD_INVALID);
+ return status;
+ }
+ return NT_STATUS_OK;
+}
+
+static char *cli_session_setup_get_account(TALLOC_CTX *mem_ctx,
+ const char *principal)
+{
+ char *account, *p;
+
+ account = talloc_strdup(mem_ctx, principal);
+ if (account == NULL) {
+ return NULL;
+ }
+ p = strchr_m(account, '@');
+ if (p != NULL) {
+ *p = '\0';
+ }
+ return account;
+}
+
+/****************************************************************************
+ Do a spnego encrypted session setup.
+
+ user_domain: The shortname of the domain the user/machine is a member of.
+ dest_realm: The realm we're connecting to, if NULL we use our default realm.
+****************************************************************************/
+
+struct cli_session_setup_spnego_state {
+ ADS_STATUS result;
+};
+
+static void cli_session_setup_spnego_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_session_setup_spnego_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli,
+ struct cli_credentials *creds)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_session_setup_spnego_state *state;
+ const char *target_service = NULL;
+ const char *target_hostname = NULL;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_session_setup_spnego_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ target_service = "cifs";
+ target_hostname = smbXcli_conn_remote_name(cli->conn);
+
+ status = cli_session_creds_prepare_krb5(cli, creds);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ DBG_INFO("Connect to %s as %s using SPNEGO\n",
+ target_hostname,
+ cli_credentials_get_principal(creds, talloc_tos()));
+
+ subreq = cli_session_setup_gensec_send(state, ev, cli, creds,
+ target_service, target_hostname);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ subreq, cli_session_setup_spnego_done, req);
+ return req;
+}
+
+static void cli_session_setup_spnego_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = cli_session_setup_gensec_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static ADS_STATUS cli_session_setup_spnego_recv(struct tevent_req *req)
+{
+ struct cli_session_setup_spnego_state *state = tevent_req_data(
+ req, struct cli_session_setup_spnego_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ state->result = ADS_ERROR_NT(status);
+ }
+
+ return state->result;
+}
+
+struct cli_session_setup_creds_state {
+ struct cli_state *cli;
+ DATA_BLOB apassword_blob;
+ DATA_BLOB upassword_blob;
+ DATA_BLOB lm_session_key;
+ DATA_BLOB session_key;
+ char *out_native_os;
+ char *out_native_lm;
+ char *out_primary_domain;
+};
+
+static void cli_session_setup_creds_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state)
+{
+ struct cli_session_setup_creds_state *state = tevent_req_data(
+ req, struct cli_session_setup_creds_state);
+
+ if (req_state != TEVENT_REQ_RECEIVED) {
+ return;
+ }
+
+ /*
+ * We only call data_blob_clear() as
+ * some of the blobs point to the same memory.
+ *
+ * We let the talloc hierarchy free the memory.
+ */
+ data_blob_clear(&state->apassword_blob);
+ data_blob_clear(&state->upassword_blob);
+ data_blob_clear(&state->lm_session_key);
+ data_blob_clear(&state->session_key);
+ ZERO_STRUCTP(state);
+}
+
+static void cli_session_setup_creds_done_spnego(struct tevent_req *subreq);
+static void cli_session_setup_creds_done_nt1(struct tevent_req *subreq);
+static void cli_session_setup_creds_done_lm21(struct tevent_req *subreq);
+
+/****************************************************************************
+ Send a session setup. The username and workgroup is in UNIX character
+ format and must be converted to DOS codepage format before sending. If the
+ password is in plaintext, the same should be done.
+****************************************************************************/
+
+struct tevent_req *cli_session_setup_creds_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ struct cli_credentials *creds)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_session_setup_creds_state *state;
+ uint16_t sec_mode = smb1cli_conn_server_security_mode(cli->conn);
+ bool use_spnego = false;
+ int flags = 0;
+ const char *username = "";
+ const char *domain = "";
+ DATA_BLOB target_info = data_blob_null;
+ DATA_BLOB challenge = data_blob_null;
+ uint16_t in_buf_size = 0;
+ uint16_t in_mpx_max = 0;
+ uint16_t in_vc_num = 0;
+ uint32_t in_sess_key = 0;
+ const char *in_native_os = NULL;
+ const char *in_native_lm = NULL;
+ enum credentials_use_kerberos krb5_state =
+ cli_credentials_get_kerberos_state(creds);
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_session_setup_creds_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+
+ tevent_req_set_cleanup_fn(req, cli_session_setup_creds_cleanup);
+
+ /*
+ * Now work out what sort of session setup we are going to
+ * do. I have split this into separate functions to make the flow a bit
+ * easier to understand (tridge).
+ */
+ if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_NT1) {
+ use_spnego = false;
+ } else if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ use_spnego = true;
+ } else if (smb1cli_conn_capabilities(cli->conn) & CAP_EXTENDED_SECURITY) {
+ /*
+ * if the server supports extended security then use SPNEGO
+ * even for anonymous connections.
+ */
+ use_spnego = true;
+ } else {
+ use_spnego = false;
+ }
+
+ if (use_spnego) {
+ subreq = cli_session_setup_spnego_send(
+ state, ev, cli, creds);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_session_setup_creds_done_spnego,
+ req);
+ return req;
+ }
+
+ if (krb5_state == CRED_USE_KERBEROS_REQUIRED) {
+ DBG_WARNING("Kerberos authentication requested, but "
+ "the server does not support SPNEGO authentication\n");
+ tevent_req_nterror(req, NT_STATUS_NETWORK_CREDENTIAL_CONFLICT);
+ return tevent_req_post(req, ev);
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_LANMAN1) {
+ /*
+ * SessionSetupAndX was introduced by LANMAN 1.0. So we skip
+ * this step against older servers.
+ */
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ if (cli_credentials_is_anonymous(creds)) {
+ /*
+ * Do an anonymous session setup
+ */
+ goto non_spnego_creds_done;
+ }
+
+ if ((sec_mode & NEGOTIATE_SECURITY_USER_LEVEL) == 0) {
+ /*
+ * Do an anonymous session setup,
+ * the password is passed via the tree connect.
+ */
+ goto non_spnego_creds_done;
+ }
+
+ cli_credentials_get_ntlm_username_domain(creds, state,
+ &username,
+ &domain);
+ if (tevent_req_nomem(username, req)) {
+ return tevent_req_post(req, ev);
+ }
+ if (tevent_req_nomem(domain, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ DBG_INFO("Connect to %s as %s using NTLM\n", domain, username);
+
+ if ((sec_mode & NEGOTIATE_SECURITY_CHALLENGE_RESPONSE) == 0) {
+ bool use_unicode = smbXcli_conn_use_unicode(cli->conn);
+ uint8_t *bytes = NULL;
+ size_t bytes_len = 0;
+ const char *pw = cli_credentials_get_password(creds);
+ size_t pw_len = 0;
+
+ if (pw == NULL) {
+ pw = "";
+ }
+ pw_len = strlen(pw) + 1;
+
+ if (!lp_client_plaintext_auth()) {
+ DEBUG(1, ("Server requested PLAINTEXT password but "
+ "'client plaintext auth = no'\n"));
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return tevent_req_post(req, ev);
+ }
+
+ bytes = talloc_array(state, uint8_t, 0);
+ bytes = trans2_bytes_push_str(bytes, use_unicode,
+ pw, pw_len, &bytes_len);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (use_unicode) {
+ /*
+ * CAP_UNICODE, can only be negotiated by NT1.
+ */
+ state->upassword_blob = data_blob_const(bytes,
+ bytes_len);
+ } else {
+ state->apassword_blob = data_blob_const(bytes,
+ bytes_len);
+ }
+
+ goto non_spnego_creds_done;
+ }
+
+ challenge = data_blob_const(smb1cli_conn_server_challenge(cli->conn), 8);
+
+ if (smbXcli_conn_protocol(cli->conn) == PROTOCOL_NT1) {
+ if (lp_client_ntlmv2_auth() && lp_client_use_spnego()) {
+ /*
+ * Don't send an NTLMv2 response without NTLMSSP if we
+ * want to use spnego support.
+ */
+ DEBUG(1, ("Server does not support EXTENDED_SECURITY "
+ " but 'client use spnego = yes'"
+ " and 'client ntlmv2 auth = yes' is set\n"));
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return tevent_req_post(req, ev);
+ }
+
+ if (lp_client_ntlmv2_auth()) {
+ flags |= CLI_CRED_NTLMv2_AUTH;
+
+ /*
+ * note that the 'domain' here is a best
+ * guess - we don't know the server's domain
+ * at this point. Windows clients also don't
+ * use hostname...
+ */
+ target_info = NTLMv2_generate_names_blob(state,
+ NULL,
+ domain);
+ if (tevent_req_nomem(target_info.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+ } else {
+ flags |= CLI_CRED_NTLM_AUTH;
+ if (lp_client_lanman_auth()) {
+ flags |= CLI_CRED_LANMAN_AUTH;
+ }
+ }
+ } else {
+ if (!lp_client_lanman_auth()) {
+ DEBUG(1, ("Server requested user level LM password but "
+ "'client lanman auth = no' is set.\n"));
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return tevent_req_post(req, ev);
+ }
+
+ flags |= CLI_CRED_LANMAN_AUTH;
+ }
+
+ status = cli_credentials_get_ntlm_response(creds, state, &flags,
+ challenge, NULL,
+ target_info,
+ &state->apassword_blob,
+ &state->upassword_blob,
+ &state->lm_session_key,
+ &state->session_key);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+non_spnego_creds_done:
+
+ in_buf_size = CLI_BUFFER_SIZE;
+ in_mpx_max = smbXcli_conn_max_requests(cli->conn);
+ in_vc_num = cli_state_get_vc_num(cli);
+ in_sess_key = smb1cli_conn_server_session_key(cli->conn);
+ in_native_os = "Unix";
+ in_native_lm = "Samba";
+
+ if (smbXcli_conn_protocol(cli->conn) == PROTOCOL_NT1) {
+ uint32_t in_capabilities = 0;
+
+ in_capabilities = cli_session_setup_capabilities(cli, 0);
+
+ /*
+ * For now we keep the same values as before,
+ * we may remove these in a separate commit later.
+ */
+ in_mpx_max = 2;
+
+ subreq = smb1cli_session_setup_nt1_send(state, ev,
+ cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.session,
+ in_buf_size,
+ in_mpx_max,
+ in_vc_num,
+ in_sess_key,
+ username,
+ domain,
+ state->apassword_blob,
+ state->upassword_blob,
+ in_capabilities,
+ in_native_os,
+ in_native_lm);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_session_setup_creds_done_nt1,
+ req);
+ return req;
+ }
+
+ /*
+ * For now we keep the same values as before,
+ * we may remove these in a separate commit later.
+ */
+ in_mpx_max = 2;
+ in_vc_num = 1;
+
+ subreq = smb1cli_session_setup_lm21_send(state, ev,
+ cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.session,
+ in_buf_size,
+ in_mpx_max,
+ in_vc_num,
+ in_sess_key,
+ username,
+ domain,
+ state->apassword_blob,
+ in_native_os,
+ in_native_lm);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_session_setup_creds_done_lm21,
+ req);
+ return req;
+}
+
+static void cli_session_setup_creds_done_spnego(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ ADS_STATUS status;
+
+ status = cli_session_setup_spnego_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(3, ("SPNEGO login failed: %s\n", ads_errstr(status)));
+ tevent_req_nterror(req, ads_ntstatus(status));
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static void cli_session_setup_creds_done_nt1(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_session_setup_creds_state *state = tevent_req_data(
+ req, struct cli_session_setup_creds_state);
+ struct cli_state *cli = state->cli;
+ NTSTATUS status;
+ struct iovec *recv_iov = NULL;
+ const uint8_t *inbuf = NULL;
+ bool ok;
+
+ status = smb1cli_session_setup_nt1_recv(subreq, state,
+ &recv_iov,
+ &inbuf,
+ &state->out_native_os,
+ &state->out_native_lm,
+ &state->out_primary_domain);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("NT1 login failed: %s\n", nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ status = cli_state_update_after_sesssetup(state->cli,
+ state->out_native_os,
+ state->out_native_lm,
+ state->out_primary_domain);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ ok = smb1cli_conn_activate_signing(cli->conn,
+ state->session_key,
+ state->upassword_blob);
+ if (ok) {
+ ok = smb1cli_conn_check_signing(cli->conn, inbuf, 1);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+ }
+
+ if (state->session_key.data) {
+ struct smbXcli_session *session = cli->smb1.session;
+
+ status = smb1cli_session_set_session_key(session,
+ state->session_key);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ }
+
+ tevent_req_done(req);
+}
+
+static void cli_session_setup_creds_done_lm21(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_session_setup_creds_state *state = tevent_req_data(
+ req, struct cli_session_setup_creds_state);
+ NTSTATUS status;
+
+ status = smb1cli_session_setup_lm21_recv(subreq, state,
+ &state->out_native_os,
+ &state->out_native_lm);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("LM21 login failed: %s\n", nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ status = cli_state_update_after_sesssetup(state->cli,
+ state->out_native_os,
+ state->out_native_lm,
+ NULL);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_session_setup_creds_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_session_setup_creds(struct cli_state *cli,
+ struct cli_credentials *creds)
+{
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_session_setup_creds_send(ev, ev, cli, creds);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_session_setup_creds_recv(req);
+ fail:
+ TALLOC_FREE(ev);
+ return status;
+}
+
+NTSTATUS cli_session_setup_anon(struct cli_state *cli)
+{
+ NTSTATUS status;
+ struct cli_credentials *creds = NULL;
+
+ creds = cli_credentials_init_anon(cli);
+ if (creds == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = cli_session_setup_creds(cli, creds);
+ TALLOC_FREE(creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Send a uloggoff.
+*****************************************************************************/
+
+struct cli_ulogoff_state {
+ struct cli_state *cli;
+ uint16_t vwv[3];
+};
+
+static void cli_ulogoff_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_ulogoff_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_ulogoff_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_ulogoff_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+
+ SCVAL(state->vwv+0, 0, 0xFF);
+ SCVAL(state->vwv+1, 0, 0);
+ SSVAL(state->vwv+2, 0, 0);
+
+ subreq = cli_smb_send(state, ev, cli, SMBulogoffX, 0, 0, 2, state->vwv,
+ 0, NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_ulogoff_done, req);
+ return req;
+}
+
+static void cli_ulogoff_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_ulogoff_state *state = tevent_req_data(
+ req, struct cli_ulogoff_state);
+ NTSTATUS status;
+
+ status = cli_smb_recv(subreq, NULL, NULL, 0, NULL, NULL, NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ cli_state_set_uid(state->cli, UID_FIELD_INVALID);
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_ulogoff_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_ulogoff(struct cli_state *cli)
+{
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ status = smb2cli_logoff(cli->conn,
+ cli->timeout,
+ cli->smb2.session);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ smb2cli_session_set_id_and_flags(cli->smb2.session,
+ UINT64_MAX, 0);
+ return NT_STATUS_OK;
+ }
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_ulogoff_send(ev, ev, cli);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_ulogoff_recv(req);
+fail:
+ TALLOC_FREE(ev);
+ return status;
+}
+
+/****************************************************************************
+ Send a tconX.
+****************************************************************************/
+
+struct cli_tcon_andx_state {
+ struct cli_state *cli;
+ uint16_t vwv[4];
+ struct iovec bytes;
+};
+
+static void cli_tcon_andx_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_tcon_andx_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *share, const char *dev,
+ const char *pass, int passlen,
+ struct tevent_req **psmbreq)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_tcon_andx_state *state;
+ uint8_t p24[24];
+ uint16_t *vwv;
+ char *tmp = NULL;
+ uint8_t *bytes;
+ uint16_t sec_mode = smb1cli_conn_server_security_mode(cli->conn);
+ uint16_t tcon_flags = 0;
+
+ *psmbreq = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_tcon_andx_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+ vwv = state->vwv;
+
+ TALLOC_FREE(cli->smb1.tcon);
+ cli->smb1.tcon = smbXcli_tcon_create(cli);
+ if (tevent_req_nomem(cli->smb1.tcon, req)) {
+ return tevent_req_post(req, ev);
+ }
+ smb1cli_tcon_set_id(cli->smb1.tcon, UINT16_MAX);
+
+ cli->share = talloc_strdup(cli, share);
+ if (!cli->share) {
+ return NULL;
+ }
+
+ /* in user level security don't send a password now */
+ if (sec_mode & NEGOTIATE_SECURITY_USER_LEVEL) {
+ passlen = 1;
+ pass = "";
+ } else if (pass == NULL) {
+ DEBUG(1, ("Server not using user level security and no "
+ "password supplied.\n"));
+ goto access_denied;
+ }
+
+ if ((sec_mode & NEGOTIATE_SECURITY_CHALLENGE_RESPONSE) &&
+ *pass && passlen != 24) {
+ if (!lp_client_lanman_auth()) {
+ DEBUG(1, ("Server requested LANMAN password "
+ "(share-level security) but "
+ "'client lanman auth = no' or 'client ntlmv2 auth = yes'\n"));
+ goto access_denied;
+ }
+
+ /*
+ * Non-encrypted passwords - convert to DOS codepage before
+ * encryption.
+ */
+ SMBencrypt(pass, smb1cli_conn_server_challenge(cli->conn), p24);
+ passlen = 24;
+ pass = (const char *)p24;
+ } else {
+ if((sec_mode & (NEGOTIATE_SECURITY_USER_LEVEL
+ |NEGOTIATE_SECURITY_CHALLENGE_RESPONSE))
+ == 0) {
+ uint8_t *tmp_pass;
+
+ if (!lp_client_plaintext_auth() && (*pass)) {
+ DEBUG(1, ("Server requested PLAINTEXT "
+ "password but "
+ "'client plaintext auth = no' or 'client ntlmv2 auth = yes'\n"));
+ goto access_denied;
+ }
+
+ /*
+ * Non-encrypted passwords - convert to DOS codepage
+ * before using.
+ */
+ tmp_pass = talloc_array(talloc_tos(), uint8_t, 0);
+ if (tevent_req_nomem(tmp_pass, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tmp_pass = trans2_bytes_push_str(tmp_pass,
+ false, /* always DOS */
+ pass,
+ passlen,
+ NULL);
+ if (tevent_req_nomem(tmp_pass, req)) {
+ return tevent_req_post(req, ev);
+ }
+ pass = (const char *)tmp_pass;
+ passlen = talloc_get_size(tmp_pass);
+ }
+ }
+
+ tcon_flags |= TCONX_FLAG_EXTENDED_RESPONSE;
+ tcon_flags |= TCONX_FLAG_EXTENDED_SIGNATURES;
+
+ SCVAL(vwv+0, 0, 0xFF);
+ SCVAL(vwv+0, 1, 0);
+ SSVAL(vwv+1, 0, 0);
+ SSVAL(vwv+2, 0, tcon_flags);
+ SSVAL(vwv+3, 0, passlen);
+
+ if (passlen && pass) {
+ bytes = (uint8_t *)talloc_memdup(state, pass, passlen);
+ } else {
+ bytes = talloc_array(state, uint8_t, 0);
+ }
+
+ /*
+ * Add the sharename
+ */
+ tmp = talloc_asprintf_strupper_m(talloc_tos(), "\\\\%s\\%s",
+ smbXcli_conn_remote_name(cli->conn), share);
+ if (tmp == NULL) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+ bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn), tmp, strlen(tmp)+1,
+ NULL);
+ TALLOC_FREE(tmp);
+
+ /*
+ * Add the devicetype
+ */
+ tmp = talloc_strdup_upper(talloc_tos(), dev);
+ if (tmp == NULL) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+ bytes = smb_bytes_push_str(bytes, false, tmp, strlen(tmp)+1, NULL);
+ TALLOC_FREE(tmp);
+
+ if (bytes == NULL) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ state->bytes.iov_base = (void *)bytes;
+ state->bytes.iov_len = talloc_get_size(bytes);
+
+ subreq = cli_smb_req_create(state, ev, cli, SMBtconX, 0, 0, 4, vwv,
+ 1, &state->bytes);
+ if (subreq == NULL) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, cli_tcon_andx_done, req);
+ *psmbreq = subreq;
+ return req;
+
+ access_denied:
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return tevent_req_post(req, ev);
+}
+
+struct tevent_req *cli_tcon_andx_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *share, const char *dev,
+ const char *pass, int passlen)
+{
+ struct tevent_req *req, *subreq;
+ NTSTATUS status;
+
+ req = cli_tcon_andx_create(mem_ctx, ev, cli, share, dev, pass, passlen,
+ &subreq);
+ if (req == NULL) {
+ return NULL;
+ }
+ if (subreq == NULL) {
+ return req;
+ }
+ status = smb1cli_req_chain_submit(&subreq, 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ return req;
+}
+
+static void cli_tcon_andx_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_tcon_andx_state *state = tevent_req_data(
+ req, struct cli_tcon_andx_state);
+ struct cli_state *cli = state->cli;
+ uint8_t *in;
+ uint8_t *inhdr;
+ uint8_t wct;
+ uint16_t *vwv;
+ uint32_t num_bytes;
+ uint8_t *bytes;
+ NTSTATUS status;
+ uint16_t optional_support = 0;
+
+ status = cli_smb_recv(subreq, state, &in, 0, &wct, &vwv,
+ &num_bytes, &bytes);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ inhdr = in + NBT_HDR_SIZE;
+
+ if (num_bytes) {
+ if (pull_string_talloc(cli,
+ (const char *)inhdr,
+ SVAL(inhdr, HDR_FLG2),
+ &cli->dev,
+ bytes,
+ num_bytes,
+ STR_TERMINATE|STR_ASCII) == -1) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ } else {
+ cli->dev = talloc_strdup(cli, "");
+ if (cli->dev == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ }
+
+ if ((smbXcli_conn_protocol(cli->conn) >= PROTOCOL_NT1) && (num_bytes == 3)) {
+ /* almost certainly win95 - enable bug fixes */
+ cli->win95 = True;
+ }
+
+ /*
+ * Make sure that we have the optional support 16-bit field. WCT > 2.
+ * Avoids issues when connecting to Win9x boxes sharing files
+ */
+
+ if ((wct > 2) && (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_LANMAN2)) {
+ optional_support = SVAL(vwv+2, 0);
+ }
+
+ if (optional_support & SMB_EXTENDED_SIGNATURES) {
+ smb1cli_session_protect_session_key(cli->smb1.session);
+ }
+
+ smb1cli_tcon_set_values(state->cli->smb1.tcon,
+ SVAL(inhdr, HDR_TID),
+ optional_support,
+ 0, /* maximal_access */
+ 0, /* guest_maximal_access */
+ NULL, /* service */
+ NULL); /* fs_type */
+
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_tcon_andx_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_tcon_andx(struct cli_state *cli, const char *share,
+ const char *dev, const char *pass, int passlen)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+
+ req = cli_tcon_andx_send(frame, ev, cli, share, dev, pass, passlen);
+ if (req == NULL) {
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_tcon_andx_recv(req);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_tree_connect_state {
+ struct cli_state *cli;
+};
+
+static struct tevent_req *cli_raw_tcon_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli,
+ const char *service, const char *pass, const char *dev);
+static NTSTATUS cli_raw_tcon_recv(struct tevent_req *req,
+ uint16_t *max_xmit, uint16_t *tid);
+
+static void cli_tree_connect_smb2_done(struct tevent_req *subreq);
+static void cli_tree_connect_andx_done(struct tevent_req *subreq);
+static void cli_tree_connect_raw_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_tree_connect_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli,
+ const char *share, const char *dev, const char *pass)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_tree_connect_state *state;
+ int passlen;
+
+ if (pass == NULL) {
+ pass = "";
+ }
+ passlen = strlen(pass) + 1;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_tree_connect_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+
+ cli->share = talloc_strdup(cli, share);
+ if (tevent_req_nomem(cli->share, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ char *unc;
+
+ TALLOC_FREE(cli->smb2.tcon);
+ cli->smb2.tcon = smbXcli_tcon_create(cli);
+ if (tevent_req_nomem(cli->smb2.tcon, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ unc = talloc_asprintf(state, "\\\\%s\\%s",
+ smbXcli_conn_remote_name(cli->conn),
+ share);
+ if (tevent_req_nomem(unc, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = smb2cli_tcon_send(state, ev, cli->conn, cli->timeout,
+ cli->smb2.session, cli->smb2.tcon,
+ 0, /* flags */
+ unc);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_tree_connect_smb2_done,
+ req);
+ return req;
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_LANMAN1) {
+ subreq = cli_tcon_andx_send(state, ev, cli, share, dev,
+ pass, passlen);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_tree_connect_andx_done,
+ req);
+ return req;
+ }
+
+ subreq = cli_raw_tcon_send(state, ev, cli, share, pass, dev);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_tree_connect_raw_done, req);
+
+ return req;
+}
+
+static void cli_tree_connect_smb2_done(struct tevent_req *subreq)
+{
+ tevent_req_simple_finish_ntstatus(
+ subreq, smb2cli_tcon_recv(subreq));
+}
+
+static void cli_tree_connect_andx_done(struct tevent_req *subreq)
+{
+ tevent_req_simple_finish_ntstatus(
+ subreq, cli_tcon_andx_recv(subreq));
+}
+
+static void cli_tree_connect_raw_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_tree_connect_state *state = tevent_req_data(
+ req, struct cli_tree_connect_state);
+ NTSTATUS status;
+ uint16_t max_xmit = 0;
+ uint16_t tid = 0;
+
+ status = cli_raw_tcon_recv(subreq, &max_xmit, &tid);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ smb1cli_tcon_set_values(state->cli->smb1.tcon,
+ tid,
+ 0, /* optional_support */
+ 0, /* maximal_access */
+ 0, /* guest_maximal_access */
+ NULL, /* service */
+ NULL); /* fs_type */
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_tree_connect_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_tree_connect(struct cli_state *cli, const char *share,
+ const char *dev, const char *pass)
+{
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_tree_connect_send(ev, ev, cli, share, dev, pass);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_tree_connect_recv(req);
+fail:
+ TALLOC_FREE(ev);
+ return status;
+}
+
+NTSTATUS cli_tree_connect_creds(struct cli_state *cli,
+ const char *share, const char *dev,
+ struct cli_credentials *creds)
+{
+ const char *pw = NULL;
+
+ if (creds != NULL) {
+ pw = cli_credentials_get_password(creds);
+ }
+
+ return cli_tree_connect(cli, share, dev, pw);
+}
+
+/****************************************************************************
+ Send a tree disconnect.
+****************************************************************************/
+
+struct cli_tdis_state {
+ struct cli_state *cli;
+};
+
+static void cli_tdis_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_tdis_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_tdis_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_tdis_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+
+ subreq = cli_smb_send(state, ev, cli, SMBtdis, 0, 0, 0, NULL, 0, NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_tdis_done, req);
+ return req;
+}
+
+static void cli_tdis_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_tdis_state *state = tevent_req_data(
+ req, struct cli_tdis_state);
+ NTSTATUS status;
+
+ status = cli_smb_recv(subreq, NULL, NULL, 0, NULL, NULL, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ TALLOC_FREE(state->cli->smb1.tcon);
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_tdis_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_tdis(struct cli_state *cli)
+{
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ status = smb2cli_tdis(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon);
+ if (NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(cli->smb2.tcon);
+ }
+ return status;
+ }
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_tdis_send(ev, ev, cli);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_tdis_recv(req);
+fail:
+ TALLOC_FREE(ev);
+ return status;
+}
+
+struct cli_connect_sock_state {
+ const char **called_names;
+ const char **calling_names;
+ int *called_types;
+ int fd;
+ uint16_t port;
+};
+
+static void cli_connect_sock_done(struct tevent_req *subreq);
+
+/*
+ * Async only if we don't have to look up the name, i.e. "pss" is set with a
+ * nonzero address.
+ */
+
+static struct tevent_req *cli_connect_sock_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ const char *host, int name_type, const struct sockaddr_storage *pss,
+ const char *myname, uint16_t port)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_connect_sock_state *state;
+ struct sockaddr_storage *addrs = NULL;
+ unsigned i;
+ unsigned num_addrs = 0;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_connect_sock_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if ((pss == NULL) || is_zero_addr(pss)) {
+
+ /*
+ * Here we cheat. resolve_name_list is not async at all. So
+ * this call will only be really async if the name lookup has
+ * been done externally.
+ */
+
+ status = resolve_name_list(state, host, name_type,
+ &addrs, &num_addrs);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ } else {
+ addrs = talloc_array(state, struct sockaddr_storage, 1);
+ if (tevent_req_nomem(addrs, req)) {
+ return tevent_req_post(req, ev);
+ }
+ addrs[0] = *pss;
+ num_addrs = 1;
+ }
+
+ state->called_names = talloc_array(state, const char *, num_addrs);
+ if (tevent_req_nomem(state->called_names, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->called_types = talloc_array(state, int, num_addrs);
+ if (tevent_req_nomem(state->called_types, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->calling_names = talloc_array(state, const char *, num_addrs);
+ if (tevent_req_nomem(state->calling_names, req)) {
+ return tevent_req_post(req, ev);
+ }
+ for (i=0; i<num_addrs; i++) {
+ state->called_names[i] = host;
+ state->called_types[i] = name_type;
+ state->calling_names[i] = myname;
+ }
+
+ subreq = smbsock_any_connect_send(
+ state, ev, addrs, state->called_names, state->called_types,
+ state->calling_names, NULL, num_addrs, port);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_connect_sock_done, req);
+ return req;
+}
+
+static void cli_connect_sock_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_connect_sock_state *state = tevent_req_data(
+ req, struct cli_connect_sock_state);
+ NTSTATUS status;
+
+ status = smbsock_any_connect_recv(subreq, &state->fd, NULL,
+ &state->port);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ set_socket_options(state->fd, lp_socket_options());
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_connect_sock_recv(struct tevent_req *req,
+ int *pfd, uint16_t *pport)
+{
+ struct cli_connect_sock_state *state = tevent_req_data(
+ req, struct cli_connect_sock_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *pfd = state->fd;
+ *pport = state->port;
+ return NT_STATUS_OK;
+}
+
+struct cli_connect_nb_state {
+ const char *desthost;
+ enum smb_signing_setting signing_state;
+ int flags;
+ struct cli_state *cli;
+};
+
+static void cli_connect_nb_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_connect_nb_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ const char *host, const struct sockaddr_storage *dest_ss,
+ uint16_t port, int name_type, const char *myname,
+ enum smb_signing_setting signing_state, int flags)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_connect_nb_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_connect_nb_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->signing_state = signing_state;
+ state->flags = flags;
+
+ if (host != NULL) {
+ char *p = strchr(host, '#');
+
+ if (p != NULL) {
+ name_type = strtol(p+1, NULL, 16);
+ host = talloc_strndup(state, host, p - host);
+ if (tevent_req_nomem(host, req)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ state->desthost = host;
+ } else if (dest_ss != NULL) {
+ state->desthost = print_canonical_sockaddr(state, dest_ss);
+ if (tevent_req_nomem(state->desthost, req)) {
+ return tevent_req_post(req, ev);
+ }
+ } else {
+ /* No host or dest_ss given. Error out. */
+ tevent_req_error(req, EINVAL);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = cli_connect_sock_send(state, ev, host, name_type, dest_ss,
+ myname, port);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_connect_nb_done, req);
+ return req;
+}
+
+static void cli_connect_nb_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_connect_nb_state *state = tevent_req_data(
+ req, struct cli_connect_nb_state);
+ NTSTATUS status;
+ int fd = 0;
+ uint16_t port;
+
+ status = cli_connect_sock_recv(subreq, &fd, &port);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->cli = cli_state_create(state, fd, state->desthost,
+ state->signing_state, state->flags);
+ if (tevent_req_nomem(state->cli, req)) {
+ close(fd);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_connect_nb_recv(struct tevent_req *req,
+ struct cli_state **pcli)
+{
+ struct cli_connect_nb_state *state = tevent_req_data(
+ req, struct cli_connect_nb_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *pcli = talloc_move(NULL, &state->cli);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_connect_nb(const char *host, const struct sockaddr_storage *dest_ss,
+ uint16_t port, int name_type, const char *myname,
+ enum smb_signing_setting signing_state, int flags, struct cli_state **pcli)
+{
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_connect_nb_send(ev, ev, host, dest_ss, port, name_type,
+ myname, signing_state, flags);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_set_endtime(req, ev, timeval_current_ofs(20, 0))) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_connect_nb_recv(req, pcli);
+fail:
+ TALLOC_FREE(ev);
+ return status;
+}
+
+struct cli_start_connection_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ int min_protocol;
+ int max_protocol;
+};
+
+static void cli_start_connection_connected(struct tevent_req *subreq);
+static void cli_start_connection_done(struct tevent_req *subreq);
+
+/**
+ establishes a connection to after the negprot.
+ @param output_cli A fully initialised cli structure, non-null only on success
+ @param dest_host The netbios name of the remote host
+ @param dest_ss (optional) The the destination IP, NULL for name based lookup
+ @param port (optional) The destination port (0 for default)
+*/
+
+static struct tevent_req *cli_start_connection_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ const char *my_name, const char *dest_host,
+ const struct sockaddr_storage *dest_ss, int port,
+ enum smb_signing_setting signing_state, int flags)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_start_connection_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_start_connection_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+
+ if (flags & CLI_FULL_CONNECTION_IPC) {
+ state->min_protocol = lp_client_ipc_min_protocol();
+ state->max_protocol = lp_client_ipc_max_protocol();
+ } else {
+ state->min_protocol = lp_client_min_protocol();
+ state->max_protocol = lp_client_max_protocol();
+ }
+
+ if (flags & CLI_FULL_CONNECTION_FORCE_SMB1) {
+ state->max_protocol = MIN(state->max_protocol,
+ PROTOCOL_NT1);
+ state->min_protocol = MIN(state->min_protocol,
+ state->max_protocol);
+ }
+
+ if (flags & CLI_FULL_CONNECTION_DISABLE_SMB1) {
+ state->min_protocol = MAX(state->min_protocol,
+ PROTOCOL_SMB2_02);
+ state->max_protocol = MAX(state->max_protocol,
+ state->min_protocol);
+ }
+
+ subreq = cli_connect_nb_send(state, ev, dest_host, dest_ss, port,
+ 0x20, my_name, signing_state, flags);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_start_connection_connected, req);
+ return req;
+}
+
+static void cli_start_connection_connected(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_start_connection_state *state = tevent_req_data(
+ req, struct cli_start_connection_state);
+ NTSTATUS status;
+
+ status = cli_connect_nb_recv(subreq, &state->cli);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ subreq = smbXcli_negprot_send(
+ state,
+ state->ev,
+ state->cli->conn,
+ state->cli->timeout,
+ state->min_protocol,
+ state->max_protocol,
+ WINDOWS_CLIENT_PURE_SMB2_NEGPROT_INITIAL_CREDIT_ASK,
+ NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_start_connection_done, req);
+}
+
+static void cli_start_connection_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_start_connection_state *state = tevent_req_data(
+ req, struct cli_start_connection_state);
+ NTSTATUS status;
+
+ status = smbXcli_negprot_recv(subreq, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+ /* Ensure we ask for some initial credits. */
+ smb2cli_conn_set_max_credits(state->cli->conn,
+ DEFAULT_SMB2_MAX_CREDITS);
+ }
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_start_connection_recv(struct tevent_req *req,
+ struct cli_state **output_cli)
+{
+ struct cli_start_connection_state *state = tevent_req_data(
+ req, struct cli_start_connection_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *output_cli = state->cli;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_start_connection(struct cli_state **output_cli,
+ const char *my_name,
+ const char *dest_host,
+ const struct sockaddr_storage *dest_ss, int port,
+ enum smb_signing_setting signing_state, int flags)
+{
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_start_connection_send(ev, ev, my_name, dest_host, dest_ss,
+ port, signing_state, flags);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_start_connection_recv(req, output_cli);
+fail:
+ TALLOC_FREE(ev);
+ return status;
+}
+
+struct cli_smb1_setup_encryption_blob_state {
+ uint16_t setup[1];
+ uint8_t param[4];
+ NTSTATUS status;
+ DATA_BLOB out;
+ uint16_t enc_ctx_id;
+};
+
+static void cli_smb1_setup_encryption_blob_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_smb1_setup_encryption_blob_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const DATA_BLOB in)
+{
+ struct tevent_req *req = NULL;
+ struct cli_smb1_setup_encryption_blob_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_smb1_setup_encryption_blob_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (in.length > CLI_BUFFER_SIZE) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
+ return tevent_req_post(req, ev);
+ }
+
+ SSVAL(state->setup+0, 0, TRANSACT2_SETFSINFO);
+ SSVAL(state->param, 0, 0);
+ SSVAL(state->param, 2, SMB_REQUEST_TRANSPORT_ENCRYPTION);
+
+ subreq = smb1cli_trans_send(state, ev, cli->conn,
+ SMBtrans2,
+ 0, 0, /* _flags */
+ 0, 0, /* _flags2 */
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ NULL, /* pipe_name */
+ 0, /* fid */
+ 0, /* function */
+ 0, /* flags */
+ state->setup, 1, 0,
+ state->param, 4, 2,
+ in.data, in.length, CLI_BUFFER_SIZE);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ cli_smb1_setup_encryption_blob_done,
+ req);
+
+ return req;
+}
+
+static void cli_smb1_setup_encryption_blob_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct cli_smb1_setup_encryption_blob_state *state =
+ tevent_req_data(req,
+ struct cli_smb1_setup_encryption_blob_state);
+ uint8_t *rparam=NULL, *rdata=NULL;
+ uint32_t num_rparam, num_rdata;
+ NTSTATUS status;
+
+ status = smb1cli_trans_recv(subreq, state,
+ NULL, /* recv_flags */
+ NULL, 0, NULL, /* rsetup */
+ &rparam, 0, &num_rparam,
+ &rdata, 0, &num_rdata);
+ TALLOC_FREE(subreq);
+ state->status = status;
+ if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ status = NT_STATUS_OK;
+ }
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (num_rparam == 2) {
+ state->enc_ctx_id = SVAL(rparam, 0);
+ }
+ TALLOC_FREE(rparam);
+
+ state->out = data_blob_const(rdata, num_rdata);
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_smb1_setup_encryption_blob_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out,
+ uint16_t *enc_ctx_id)
+{
+ struct cli_smb1_setup_encryption_blob_state *state =
+ tevent_req_data(req,
+ struct cli_smb1_setup_encryption_blob_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ status = state->status;
+
+ *out = state->out;
+ talloc_steal(mem_ctx, out->data);
+
+ *enc_ctx_id = state->enc_ctx_id;
+
+ tevent_req_received(req);
+ return status;
+}
+
+struct cli_smb1_setup_encryption_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ struct smb_trans_enc_state *es;
+ DATA_BLOB blob_in;
+ DATA_BLOB blob_out;
+ bool local_ready;
+ bool remote_ready;
+};
+
+static void cli_smb1_setup_encryption_local_next(struct tevent_req *req);
+static void cli_smb1_setup_encryption_local_done(struct tevent_req *subreq);
+static void cli_smb1_setup_encryption_remote_next(struct tevent_req *req);
+static void cli_smb1_setup_encryption_remote_done(struct tevent_req *subreq);
+static void cli_smb1_setup_encryption_ready(struct tevent_req *req);
+
+static struct tevent_req *cli_smb1_setup_encryption_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ struct cli_credentials *creds)
+{
+ struct tevent_req *req = NULL;
+ struct cli_smb1_setup_encryption_state *state = NULL;
+ struct auth_generic_state *ags = NULL;
+ const DATA_BLOB *b = NULL;
+ bool auth_requested = false;
+ const char *target_service = NULL;
+ const char *target_hostname = NULL;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_smb1_setup_encryption_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+
+ auth_requested = cli_credentials_authentication_requested(creds);
+ if (!auth_requested) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
+ return tevent_req_post(req, ev);
+ }
+
+ target_service = "cifs";
+ target_hostname = smbXcli_conn_remote_name(cli->conn);
+
+ status = cli_session_creds_prepare_krb5(cli, creds);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->es = talloc_zero(state, struct smb_trans_enc_state);
+ if (tevent_req_nomem(state->es, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ status = auth_generic_client_prepare(state->es, &ags);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ gensec_want_feature(ags->gensec_security,
+ GENSEC_FEATURE_SIGN);
+ gensec_want_feature(ags->gensec_security,
+ GENSEC_FEATURE_SEAL);
+
+ status = auth_generic_set_creds(ags, creds);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (target_service != NULL) {
+ status = gensec_set_target_service(ags->gensec_security,
+ target_service);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ if (target_hostname != NULL) {
+ status = gensec_set_target_hostname(ags->gensec_security,
+ target_hostname);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ gensec_set_max_update_size(ags->gensec_security,
+ CLI_BUFFER_SIZE);
+
+ b = smbXcli_conn_server_gss_blob(state->cli->conn);
+ if (b != NULL) {
+ state->blob_in = *b;
+ }
+
+ status = auth_generic_client_start(ags, GENSEC_OID_SPNEGO);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * We only need the gensec_security part from here.
+ */
+ state->es->gensec_security = talloc_move(state->es,
+ &ags->gensec_security);
+ TALLOC_FREE(ags);
+
+ cli_smb1_setup_encryption_local_next(req);
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void cli_smb1_setup_encryption_local_next(struct tevent_req *req)
+{
+ struct cli_smb1_setup_encryption_state *state =
+ tevent_req_data(req,
+ struct cli_smb1_setup_encryption_state);
+ struct tevent_req *subreq = NULL;
+
+ if (state->local_ready) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ subreq = gensec_update_send(state, state->ev,
+ state->es->gensec_security,
+ state->blob_in);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_smb1_setup_encryption_local_done, req);
+}
+
+static void cli_smb1_setup_encryption_local_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct cli_smb1_setup_encryption_state *state =
+ tevent_req_data(req,
+ struct cli_smb1_setup_encryption_state);
+ NTSTATUS status;
+
+ status = gensec_update_recv(subreq, state, &state->blob_out);
+ TALLOC_FREE(subreq);
+ state->blob_in = data_blob_null;
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED))
+ {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ state->local_ready = true;
+ }
+
+ /*
+ * We always get NT_STATUS_OK from the server even if it is not ready.
+ * So guess the server is ready when we are ready and already sent
+ * our last blob to the server.
+ */
+ if (state->local_ready && state->blob_out.length == 0) {
+ state->remote_ready = true;
+ }
+
+ if (state->local_ready && state->remote_ready) {
+ cli_smb1_setup_encryption_ready(req);
+ return;
+ }
+
+ cli_smb1_setup_encryption_remote_next(req);
+}
+
+static void cli_smb1_setup_encryption_remote_next(struct tevent_req *req)
+{
+ struct cli_smb1_setup_encryption_state *state =
+ tevent_req_data(req,
+ struct cli_smb1_setup_encryption_state);
+ struct tevent_req *subreq = NULL;
+
+ if (state->remote_ready) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ subreq = cli_smb1_setup_encryption_blob_send(state, state->ev,
+ state->cli, state->blob_out);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ cli_smb1_setup_encryption_remote_done,
+ req);
+}
+
+static void cli_smb1_setup_encryption_remote_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct cli_smb1_setup_encryption_state *state =
+ tevent_req_data(req,
+ struct cli_smb1_setup_encryption_state);
+ NTSTATUS status;
+
+ status = cli_smb1_setup_encryption_blob_recv(subreq, state,
+ &state->blob_in,
+ &state->es->enc_ctx_num);
+ TALLOC_FREE(subreq);
+ data_blob_free(&state->blob_out);
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED))
+ {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ /*
+ * We always get NT_STATUS_OK even if the server is not ready.
+ * So guess the server is ready when we are ready and sent
+ * our last blob to the server.
+ */
+ if (state->local_ready) {
+ state->remote_ready = true;
+ }
+
+ if (state->local_ready && state->remote_ready) {
+ cli_smb1_setup_encryption_ready(req);
+ return;
+ }
+
+ cli_smb1_setup_encryption_local_next(req);
+}
+
+static void cli_smb1_setup_encryption_ready(struct tevent_req *req)
+{
+ struct cli_smb1_setup_encryption_state *state =
+ tevent_req_data(req,
+ struct cli_smb1_setup_encryption_state);
+ struct smb_trans_enc_state *es = NULL;
+
+ if (state->blob_in.length != 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ if (state->blob_out.length != 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ es = talloc_move(state->cli->conn, &state->es);
+ es->enc_on = true;
+ smb1cli_conn_set_encryption(state->cli->conn, es);
+ es = NULL;
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_smb1_setup_encryption_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_smb1_setup_encryption(struct cli_state *cli,
+ struct cli_credentials *creds)
+{
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_smb1_setup_encryption_send(ev, ev, cli, creds);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_smb1_setup_encryption_recv(req);
+ fail:
+ TALLOC_FREE(ev);
+ return status;
+}
+
+/**
+ establishes a connection right up to doing tconX, password specified.
+ @param output_cli A fully initialised cli structure, non-null only on success
+ @param dest_host The netbios name of the remote host
+ @param dest_ip (optional) The the destination IP, NULL for name based lookup
+ @param port (optional) The destination port (0 for default)
+ @param service (optional) The share to make the connection to. Should be 'unqualified' in any way.
+ @param service_type The 'type' of serivice.
+ @param creds The used user credentials
+*/
+
+struct cli_full_connection_creds_state {
+ struct tevent_context *ev;
+ const char *service;
+ const char *service_type;
+ struct cli_credentials *creds;
+ int flags;
+ struct cli_state *cli;
+};
+
+static int cli_full_connection_creds_state_destructor(
+ struct cli_full_connection_creds_state *s)
+{
+ if (s->cli != NULL) {
+ cli_shutdown(s->cli);
+ s->cli = NULL;
+ }
+ return 0;
+}
+
+static void cli_full_connection_creds_conn_done(struct tevent_req *subreq);
+static void cli_full_connection_creds_sess_start(struct tevent_req *req);
+static void cli_full_connection_creds_sess_done(struct tevent_req *subreq);
+static void cli_full_connection_creds_enc_start(struct tevent_req *req);
+static void cli_full_connection_creds_enc_tcon(struct tevent_req *subreq);
+static void cli_full_connection_creds_enc_ver(struct tevent_req *subreq);
+static void cli_full_connection_creds_enc_done(struct tevent_req *subreq);
+static void cli_full_connection_creds_enc_tdis(struct tevent_req *req);
+static void cli_full_connection_creds_enc_finished(struct tevent_req *subreq);
+static void cli_full_connection_creds_tcon_start(struct tevent_req *req);
+static void cli_full_connection_creds_tcon_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_full_connection_creds_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ const char *my_name, const char *dest_host,
+ const struct sockaddr_storage *dest_ss, int port,
+ const char *service, const char *service_type,
+ struct cli_credentials *creds,
+ int flags)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_full_connection_creds_state *state;
+ enum smb_signing_setting signing_state;
+ enum smb_encryption_setting encryption_state =
+ cli_credentials_get_smb_encryption(creds);
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_full_connection_creds_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ talloc_set_destructor(state, cli_full_connection_creds_state_destructor);
+
+ state->ev = ev;
+ state->service = service;
+ state->service_type = service_type;
+ state->creds = creds;
+ state->flags = flags;
+
+ if (flags & CLI_FULL_CONNECTION_IPC) {
+ signing_state = cli_credentials_get_smb_ipc_signing(creds);
+ } else {
+ signing_state = cli_credentials_get_smb_signing(creds);
+ }
+
+ if (encryption_state == SMB_ENCRYPTION_REQUIRED) {
+ if (flags & CLI_FULL_CONNECTION_ANONYMOUS_FALLBACK) {
+ encryption_state = SMB_ENCRYPTION_DESIRED;
+ }
+ }
+
+ if (encryption_state >= SMB_ENCRYPTION_DESIRED) {
+ signing_state = SMB_SIGNING_REQUIRED;
+ }
+
+ subreq = cli_start_connection_send(
+ state, ev, my_name, dest_host, dest_ss, port,
+ signing_state, flags);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ cli_full_connection_creds_conn_done,
+ req);
+ return req;
+}
+
+static void cli_full_connection_creds_conn_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_full_connection_creds_state *state = tevent_req_data(
+ req, struct cli_full_connection_creds_state);
+ NTSTATUS status;
+
+ status = cli_start_connection_recv(subreq, &state->cli);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ cli_full_connection_creds_sess_start(req);
+}
+
+static void cli_full_connection_creds_sess_start(struct tevent_req *req)
+{
+ struct cli_full_connection_creds_state *state = tevent_req_data(
+ req, struct cli_full_connection_creds_state);
+ struct tevent_req *subreq = NULL;
+
+ subreq = cli_session_setup_creds_send(
+ state, state->ev, state->cli, state->creds);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ cli_full_connection_creds_sess_done,
+ req);
+}
+
+static void cli_full_connection_creds_sess_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_full_connection_creds_state *state = tevent_req_data(
+ req, struct cli_full_connection_creds_state);
+ NTSTATUS status;
+
+ status = cli_session_setup_creds_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ if (!NT_STATUS_IS_OK(status) &&
+ (state->flags & CLI_FULL_CONNECTION_ANONYMOUS_FALLBACK)) {
+
+ state->flags &= ~CLI_FULL_CONNECTION_ANONYMOUS_FALLBACK;
+
+ state->creds = cli_credentials_init_anon(state);
+ if (tevent_req_nomem(state->creds, req)) {
+ return;
+ }
+
+ cli_full_connection_creds_sess_start(req);
+ return;
+ }
+
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ cli_full_connection_creds_enc_start(req);
+}
+
+static void cli_full_connection_creds_enc_start(struct tevent_req *req)
+{
+ struct cli_full_connection_creds_state *state = tevent_req_data(
+ req, struct cli_full_connection_creds_state);
+ enum smb_encryption_setting encryption_state =
+ cli_credentials_get_smb_encryption(state->creds);
+ struct tevent_req *subreq = NULL;
+ NTSTATUS status;
+
+ if (encryption_state < SMB_ENCRYPTION_DESIRED) {
+ cli_full_connection_creds_tcon_start(req);
+ return;
+ }
+
+ if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+ status = smb2cli_session_encryption_on(state->cli->smb2.session);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
+ if (encryption_state < SMB_ENCRYPTION_REQUIRED) {
+ cli_full_connection_creds_tcon_start(req);
+ return;
+ }
+ d_printf("Encryption required and "
+ "server doesn't support "
+ "SMB3 encryption - failing connect\n");
+ tevent_req_nterror(req, status);
+ return;
+ } else if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Encryption required and "
+ "setup failed with error %s.\n",
+ nt_errstr(status));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ cli_full_connection_creds_tcon_start(req);
+ return;
+ }
+
+ if (!SERVER_HAS_UNIX_CIFS(state->cli)) {
+ if (encryption_state < SMB_ENCRYPTION_REQUIRED) {
+ cli_full_connection_creds_tcon_start(req);
+ return;
+ }
+
+ status = NT_STATUS_NOT_SUPPORTED;
+ d_printf("Encryption required and "
+ "server doesn't support "
+ "SMB1 Unix Extensions - failing connect\n");
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ /*
+ * We do a tcon on IPC$ just to setup the encryption,
+ * the real tcon will be encrypted then.
+ */
+ subreq = cli_tree_connect_send(state, state->ev, state->cli,
+ "IPC$", "IPC", NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_full_connection_creds_enc_tcon, req);
+}
+
+static void cli_full_connection_creds_enc_tcon(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_full_connection_creds_state *state = tevent_req_data(
+ req, struct cli_full_connection_creds_state);
+ NTSTATUS status;
+
+ status = cli_tree_connect_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ subreq = cli_unix_extensions_version_send(state, state->ev, state->cli);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_full_connection_creds_enc_ver, req);
+}
+
+static void cli_full_connection_creds_enc_ver(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_full_connection_creds_state *state = tevent_req_data(
+ req, struct cli_full_connection_creds_state);
+ enum smb_encryption_setting encryption_state =
+ cli_credentials_get_smb_encryption(state->creds);
+ uint16_t major, minor;
+ uint32_t caplow, caphigh;
+ NTSTATUS status;
+
+ status = cli_unix_extensions_version_recv(subreq,
+ &major, &minor,
+ &caplow,
+ &caphigh);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (encryption_state < SMB_ENCRYPTION_REQUIRED) {
+ /* disconnect ipc$ followed by the real tree connect */
+ cli_full_connection_creds_enc_tdis(req);
+ return;
+ }
+ DEBUG(10, ("%s: cli_unix_extensions_version "
+ "returned %s\n", __func__, nt_errstr(status)));
+ tevent_req_nterror(req, NT_STATUS_UNKNOWN_REVISION);
+ return;
+ }
+
+ if (!(caplow & CIFS_UNIX_TRANSPORT_ENCRYPTION_CAP)) {
+ if (encryption_state < SMB_ENCRYPTION_REQUIRED) {
+ /* disconnect ipc$ followed by the real tree connect */
+ cli_full_connection_creds_enc_tdis(req);
+ return;
+ }
+ DEBUG(10, ("%s: CIFS_UNIX_TRANSPORT_ENCRYPTION_CAP "
+ "not supported\n", __func__));
+ tevent_req_nterror(req, NT_STATUS_UNSUPPORTED_COMPRESSION);
+ return;
+ }
+
+ subreq = cli_smb1_setup_encryption_send(state, state->ev,
+ state->cli,
+ state->creds);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ cli_full_connection_creds_enc_done,
+ req);
+}
+
+static void cli_full_connection_creds_enc_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = cli_smb1_setup_encryption_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ /* disconnect ipc$ followed by the real tree connect */
+ cli_full_connection_creds_enc_tdis(req);
+}
+
+static void cli_full_connection_creds_enc_tdis(struct tevent_req *req)
+{
+ struct cli_full_connection_creds_state *state = tevent_req_data(
+ req, struct cli_full_connection_creds_state);
+ struct tevent_req *subreq = NULL;
+
+ subreq = cli_tdis_send(state, state->ev, state->cli);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ cli_full_connection_creds_enc_finished,
+ req);
+}
+
+static void cli_full_connection_creds_enc_finished(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = cli_tdis_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ cli_full_connection_creds_tcon_start(req);
+}
+
+static void cli_full_connection_creds_tcon_start(struct tevent_req *req)
+{
+ struct cli_full_connection_creds_state *state = tevent_req_data(
+ req, struct cli_full_connection_creds_state);
+ struct tevent_req *subreq = NULL;
+ const char *password = NULL;
+
+ if (state->service == NULL) {
+ tevent_req_done(req);
+ return;
+ }
+
+ password = cli_credentials_get_password(state->creds);
+
+ subreq = cli_tree_connect_send(state, state->ev,
+ state->cli,
+ state->service,
+ state->service_type,
+ password);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ cli_full_connection_creds_tcon_done,
+ req);
+}
+
+static void cli_full_connection_creds_tcon_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = cli_tree_connect_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_full_connection_creds_recv(struct tevent_req *req,
+ struct cli_state **output_cli)
+{
+ struct cli_full_connection_creds_state *state = tevent_req_data(
+ req, struct cli_full_connection_creds_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *output_cli = state->cli;
+ talloc_set_destructor(state, NULL);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_full_connection_creds(struct cli_state **output_cli,
+ const char *my_name,
+ const char *dest_host,
+ const struct sockaddr_storage *dest_ss, int port,
+ const char *service, const char *service_type,
+ struct cli_credentials *creds,
+ int flags)
+{
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_full_connection_creds_send(
+ ev, ev, my_name, dest_host, dest_ss, port, service,
+ service_type, creds, flags);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_full_connection_creds_recv(req, output_cli);
+ fail:
+ TALLOC_FREE(ev);
+ return status;
+}
+
+/****************************************************************************
+ Send an old style tcon.
+****************************************************************************/
+struct cli_raw_tcon_state {
+ uint16_t *ret_vwv;
+};
+
+static void cli_raw_tcon_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_raw_tcon_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli,
+ const char *service, const char *pass, const char *dev)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_raw_tcon_state *state;
+ uint8_t *bytes;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_raw_tcon_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (!lp_client_plaintext_auth() && (*pass)) {
+ DEBUG(1, ("Server requested PLAINTEXT password but 'client plaintext auth = no'"
+ " or 'client ntlmv2 auth = yes'\n"));
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return tevent_req_post(req, ev);
+ }
+
+ TALLOC_FREE(cli->smb1.tcon);
+ cli->smb1.tcon = smbXcli_tcon_create(cli);
+ if (tevent_req_nomem(cli->smb1.tcon, req)) {
+ return tevent_req_post(req, ev);
+ }
+ smb1cli_tcon_set_id(cli->smb1.tcon, UINT16_MAX);
+
+ bytes = talloc_array(state, uint8_t, 0);
+ bytes = smb_bytes_push_bytes(bytes, 4, NULL, 0);
+ bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn),
+ service, strlen(service)+1, NULL);
+ bytes = smb_bytes_push_bytes(bytes, 4, NULL, 0);
+ bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn),
+ pass, strlen(pass)+1, NULL);
+ bytes = smb_bytes_push_bytes(bytes, 4, NULL, 0);
+ bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn),
+ dev, strlen(dev)+1, NULL);
+
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = cli_smb_send(state, ev, cli, SMBtcon, 0, 0, 0, NULL,
+ talloc_get_size(bytes), bytes);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_raw_tcon_done, req);
+ return req;
+}
+
+static void cli_raw_tcon_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_raw_tcon_state *state = tevent_req_data(
+ req, struct cli_raw_tcon_state);
+ NTSTATUS status;
+
+ status = cli_smb_recv(subreq, state, NULL, 2, NULL, &state->ret_vwv,
+ NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_raw_tcon_recv(struct tevent_req *req,
+ uint16_t *max_xmit, uint16_t *tid)
+{
+ struct cli_raw_tcon_state *state = tevent_req_data(
+ req, struct cli_raw_tcon_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *max_xmit = SVAL(state->ret_vwv + 0, 0);
+ *tid = SVAL(state->ret_vwv + 1, 0);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_raw_tcon(struct cli_state *cli,
+ const char *service, const char *pass, const char *dev,
+ uint16_t *max_xmit, uint16_t *tid)
+{
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_raw_tcon_send(ev, ev, cli, service, pass, dev);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_raw_tcon_recv(req, max_xmit, tid);
+fail:
+ TALLOC_FREE(ev);
+ return status;
+}
+
+/* Return a cli_state pointing at the IPC$ share for the given server */
+
+struct cli_state *get_ipc_connect(char *server,
+ struct sockaddr_storage *server_ss,
+ struct cli_credentials *creds)
+{
+ struct cli_state *cli;
+ NTSTATUS nt_status;
+ uint32_t flags = CLI_FULL_CONNECTION_ANONYMOUS_FALLBACK;
+
+ flags |= CLI_FULL_CONNECTION_FORCE_SMB1;
+ flags |= CLI_FULL_CONNECTION_IPC;
+
+ nt_status = cli_full_connection_creds(&cli, NULL, server, server_ss, 0, "IPC$", "IPC",
+ creds,
+ flags);
+
+ if (NT_STATUS_IS_OK(nt_status)) {
+ return cli;
+ }
+ if (is_ipaddress(server)) {
+ /* windows 9* needs a correct NMB name for connections */
+ fstring remote_name;
+
+ if (name_status_find("*", 0, 0, server_ss, remote_name)) {
+ cli = get_ipc_connect(remote_name, server_ss, creds);
+ if (cli)
+ return cli;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * Given the IP address of a master browser on the network, return its
+ * workgroup and connect to it.
+ *
+ * This function is provided to allow additional processing beyond what
+ * get_ipc_connect_master_ip_bcast() does, e.g. to retrieve the list of master
+ * browsers and obtain each master browsers' list of domains (in case the
+ * first master browser is recently on the network and has not yet
+ * synchronized with other master browsers and therefore does not yet have the
+ * entire network browse list)
+ */
+
+struct cli_state *get_ipc_connect_master_ip(TALLOC_CTX *ctx,
+ struct sockaddr_storage *mb_ip,
+ struct cli_credentials *creds,
+ char **pp_workgroup_out)
+{
+ char addr[INET6_ADDRSTRLEN];
+ fstring name;
+ struct cli_state *cli;
+ struct sockaddr_storage server_ss;
+
+ *pp_workgroup_out = NULL;
+
+ print_sockaddr(addr, sizeof(addr), mb_ip);
+ DEBUG(99, ("Looking up name of master browser %s\n",
+ addr));
+
+ /*
+ * Do a name status query to find out the name of the master browser.
+ * We use <01><02>__MSBROWSE__<02>#01 if *#00 fails because a domain
+ * master browser will not respond to a wildcard query (or, at least,
+ * an NT4 server acting as the domain master browser will not).
+ *
+ * We might be able to use ONLY the query on MSBROWSE, but that's not
+ * yet been tested with all Windows versions, so until it is, leave
+ * the original wildcard query as the first choice and fall back to
+ * MSBROWSE if the wildcard query fails.
+ */
+ if (!name_status_find("*", 0, 0x1d, mb_ip, name) &&
+ !name_status_find(MSBROWSE, 1, 0x1d, mb_ip, name)) {
+
+ DEBUG(99, ("Could not retrieve name status for %s\n",
+ addr));
+ return NULL;
+ }
+
+ if (!find_master_ip(name, &server_ss)) {
+ DEBUG(99, ("Could not find master ip for %s\n", name));
+ return NULL;
+ }
+
+ *pp_workgroup_out = talloc_strdup(ctx, name);
+
+ DEBUG(4, ("found master browser %s, %s\n", name, addr));
+
+ print_sockaddr(addr, sizeof(addr), &server_ss);
+ cli = get_ipc_connect(addr, &server_ss, creds);
+
+ return cli;
+}