diff options
Diffstat (limited to '')
-rw-r--r-- | source4/libcli/smb2/session.c | 477 |
1 files changed, 477 insertions, 0 deletions
diff --git a/source4/libcli/smb2/session.c b/source4/libcli/smb2/session.c new file mode 100644 index 0000000..e94512d --- /dev/null +++ b/source4/libcli/smb2/session.c @@ -0,0 +1,477 @@ +/* + Unix SMB/CIFS implementation. + + SMB2 client session handling + + Copyright (C) Andrew Tridgell 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/network.h" +#include <tevent.h> +#include "lib/util/tevent_ntstatus.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "auth/gensec/gensec.h" +#include "auth/credentials/credentials.h" +#include "../libcli/smb/smbXcli_base.h" + +/** + initialise a smb2_session structure + */ +struct smb2_session *smb2_session_init(struct smb2_transport *transport, + struct gensec_settings *settings, + TALLOC_CTX *parent_ctx) +{ + struct smb2_session *session; + NTSTATUS status; + + session = talloc_zero(parent_ctx, struct smb2_session); + if (!session) { + return NULL; + } + session->transport = talloc_steal(session, transport); + + session->smbXcli = smbXcli_session_create(session, transport->conn); + if (session->smbXcli == NULL) { + talloc_free(session); + return NULL; + } + + /* prepare a gensec context for later use */ + status = gensec_client_start(session, &session->gensec, + settings); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(session); + return NULL; + } + + gensec_want_feature(session->gensec, GENSEC_FEATURE_SESSION_KEY); + + return session; +} + +/* + * Note: that the caller needs to keep 'transport' around as + * long as the returned session is active! + */ +struct smb2_session *smb2_session_channel(struct smb2_transport *transport, + struct gensec_settings *settings, + TALLOC_CTX *parent_ctx, + struct smb2_session *base_session) +{ + struct smb2_session *session; + NTSTATUS status; + + session = talloc_zero(parent_ctx, struct smb2_session); + if (!session) { + return NULL; + } + session->transport = transport; + + status = smb2cli_session_create_channel(session, + base_session->smbXcli, + transport->conn, + &session->smbXcli); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(session); + return NULL; + } + + session->needs_bind = true; + + /* prepare a gensec context for later use */ + status = gensec_client_start(session, &session->gensec, + settings); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(session); + return NULL; + } + + gensec_want_feature(session->gensec, GENSEC_FEATURE_SESSION_KEY); + + return session; +} + +struct smb2_session_setup_spnego_state { + struct tevent_context *ev; + struct smb2_session *session; + struct cli_credentials *credentials; + uint64_t previous_session_id; + bool session_bind; + bool reauth; + NTSTATUS gensec_status; + NTSTATUS remote_status; + DATA_BLOB in_secblob; + DATA_BLOB out_secblob; + struct iovec *recv_iov; +}; + +static void smb2_session_setup_spnego_gensec_next(struct tevent_req *req); +static void smb2_session_setup_spnego_gensec_done(struct tevent_req *subreq); +static void smb2_session_setup_spnego_smb2_next(struct tevent_req *req); +static void smb2_session_setup_spnego_smb2_done(struct tevent_req *subreq); +static void smb2_session_setup_spnego_both_ready(struct tevent_req *req); + +/* + a composite function that does a full SPNEGO session setup + */ +struct tevent_req *smb2_session_setup_spnego_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smb2_session *session, + struct cli_credentials *credentials, + uint64_t previous_session_id) +{ + struct smb2_transport *transport = session->transport; + struct tevent_req *req; + struct smb2_session_setup_spnego_state *state; + uint64_t current_session_id; + const char *chosen_oid; + NTSTATUS status; + const DATA_BLOB *server_gss_blob; + struct timeval endtime; + bool ok; + + req = tevent_req_create(mem_ctx, &state, + struct smb2_session_setup_spnego_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->session = session; + state->credentials = credentials; + state->previous_session_id = previous_session_id; + state->gensec_status = NT_STATUS_MORE_PROCESSING_REQUIRED; + state->remote_status = NT_STATUS_MORE_PROCESSING_REQUIRED; + + endtime = timeval_current_ofs(transport->options.request_timeout, 0); + + ok = tevent_req_set_endtime(req, ev, endtime); + if (!ok) { + return tevent_req_post(req, ev); + } + + current_session_id = smb2cli_session_current_id(state->session->smbXcli); + if (state->session->needs_bind) { + state->session_bind = true; + } else if (current_session_id != 0) { + state->reauth = true; + } + server_gss_blob = smbXcli_conn_server_gss_blob(session->transport->conn); + if (server_gss_blob) { + state->out_secblob = *server_gss_blob; + } + + status = gensec_set_credentials(session->gensec, credentials); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + status = gensec_set_target_hostname(session->gensec, + smbXcli_conn_remote_name(session->transport->conn)); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + status = gensec_set_target_service(session->gensec, "cifs"); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + if (state->out_secblob.length > 0) { + chosen_oid = GENSEC_OID_SPNEGO; + status = gensec_start_mech_by_oid(session->gensec, chosen_oid); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to start set GENSEC client mechanism %s: %s\n", + gensec_get_name_by_oid(session->gensec, + chosen_oid), + nt_errstr(status))); + state->out_secblob = data_blob_null; + chosen_oid = GENSEC_OID_NTLMSSP; + status = gensec_start_mech_by_oid(session->gensec, + chosen_oid); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to start set (fallback) GENSEC client mechanism %s: %s\n", + gensec_get_name_by_oid(session->gensec, + chosen_oid), + nt_errstr(status))); + } + } + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + } else { + chosen_oid = GENSEC_OID_NTLMSSP; + status = gensec_start_mech_by_oid(session->gensec, chosen_oid); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to start set GENSEC client mechanism %s: %s\n", + gensec_get_name_by_oid(session->gensec, + chosen_oid), + nt_errstr(status))); + } + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + } + + smb2_session_setup_spnego_gensec_next(req); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + return req; +} + +static void smb2_session_setup_spnego_gensec_next(struct tevent_req *req) +{ + struct smb2_session_setup_spnego_state *state = + tevent_req_data(req, + struct smb2_session_setup_spnego_state); + struct smb2_session *session = state->session; + struct tevent_req *subreq = NULL; + + if (NT_STATUS_IS_OK(state->gensec_status)) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + subreq = gensec_update_send(state, state->ev, + session->gensec, + state->out_secblob); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, + smb2_session_setup_spnego_gensec_done, + req); +} + +static void smb2_session_setup_spnego_gensec_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct smb2_session_setup_spnego_state *state = + tevent_req_data(req, + struct smb2_session_setup_spnego_state); + NTSTATUS status; + + status = gensec_update_recv(subreq, state, + &state->in_secblob); + state->gensec_status = status; + state->out_secblob = 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(state->remote_status) && + NT_STATUS_IS_OK(state->gensec_status)) { + smb2_session_setup_spnego_both_ready(req); + return; + } + + smb2_session_setup_spnego_smb2_next(req); +} + +static void smb2_session_setup_spnego_smb2_next(struct tevent_req *req) +{ + struct smb2_session_setup_spnego_state *state = + tevent_req_data(req, + struct smb2_session_setup_spnego_state); + struct smb2_session *session = state->session; + uint32_t timeout_msec; + uint8_t in_flags = 0; + struct tevent_req *subreq = NULL; + + if (NT_STATUS_IS_OK(state->remote_status)) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + timeout_msec = session->transport->options.request_timeout * 1000; + + if (state->session_bind) { + in_flags |= SMB2_SESSION_FLAG_BINDING; + } + + subreq = smb2cli_session_setup_send(state, state->ev, + session->transport->conn, + timeout_msec, + session->smbXcli, + in_flags, + 0, /* in_capabilities */ + 0, /* in_channel */ + state->previous_session_id, + &state->in_secblob); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, + smb2_session_setup_spnego_smb2_done, + req); +} + +/* + handle continuations of the spnego session setup +*/ +static void smb2_session_setup_spnego_smb2_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct smb2_session_setup_spnego_state *state = + tevent_req_data(req, + struct smb2_session_setup_spnego_state); + NTSTATUS status; + + status = smb2cli_session_setup_recv(subreq, state, + &state->recv_iov, + &state->out_secblob); + state->remote_status = status; + state->in_secblob = 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(state->remote_status) && + NT_STATUS_IS_OK(state->gensec_status)) { + smb2_session_setup_spnego_both_ready(req); + return; + } + + smb2_session_setup_spnego_gensec_next(req); +} + +static void smb2_session_setup_spnego_both_ready(struct tevent_req *req) +{ + struct smb2_session_setup_spnego_state *state = + tevent_req_data(req, + struct smb2_session_setup_spnego_state); + struct smb2_session *session = state->session; + NTSTATUS status; + DATA_BLOB session_key; + + if (state->out_secblob.length != 0) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + if (state->in_secblob.length != 0) { + tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE); + return; + } + + if (state->reauth) { + tevent_req_done(req); + return; + } + + if (cli_credentials_is_anonymous(state->credentials)) { + /* + * Windows server does not set the + * SMB2_SESSION_FLAG_IS_GUEST nor + * 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(session->gensec, state, + &session_key); + if (tevent_req_nterror(req, status)) { + return; + } + + if (state->session_bind) { + status = smb2cli_session_set_channel_key(session->smbXcli, + session_key, + state->recv_iov); + if (tevent_req_nterror(req, status)) { + return; + } + session->needs_bind = false; + } else { + status = smb2cli_session_set_session_key(session->smbXcli, + session_key, + state->recv_iov); + if (tevent_req_nterror(req, status)) { + return; + } + } + tevent_req_done(req); + return; +} + +/* + receive a composite session setup reply +*/ +NTSTATUS smb2_session_setup_spnego_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +/* + sync version of smb2_session_setup_spnego +*/ +NTSTATUS smb2_session_setup_spnego(struct smb2_session *session, + struct cli_credentials *credentials, + uint64_t previous_session_id) +{ + struct tevent_req *subreq; + NTSTATUS status; + bool ok; + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev = session->transport->ev; + + if (frame == NULL) { + return NT_STATUS_NO_MEMORY; + } + + subreq = smb2_session_setup_spnego_send(frame, ev, + session, credentials, + previous_session_id); + if (subreq == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + ok = tevent_req_poll(subreq, ev); + if (!ok) { + status = map_nt_error_from_unix_common(errno); + TALLOC_FREE(frame); + return status; + } + + status = smb2_session_setup_spnego_recv(subreq); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + + TALLOC_FREE(frame); + return NT_STATUS_OK; +} |