/* composite session setup function that hides the details of all the different session setup varients, including the multi-pass nature of the spnego varient */ struct composite_context *smb_composite_sesssetup_send(struct smbcli_session *session, struct smb_composite_sesssetup *io) { struct composite_context *c; struct sesssetup_state *state; NTSTATUS status; c = composite_create(session, session->transport->ev); if (c == NULL) return NULL; state = talloc_zero(c, struct sesssetup_state); if (composite_nomem(state, c)) return c; c->private_data = state; state->io = io; talloc_set_destructor(state, sesssetup_state_destructor); /* no session setup at all in earliest protocol varients */ if (session->transport->negotiate.protocol < PROTOCOL_LANMAN1) { ZERO_STRUCT(io->out); composite_done(c); return c; } /* see what session setup interface we will use */ if (session->transport->negotiate.protocol < PROTOCOL_NT1) { status = session_setup_old(c, session, io, &state->req); } else if (!session->transport->options.use_spnego || !(io->in.capabilities & CAP_EXTENDED_SECURITY)) { status = session_setup_nt1(c, session, io, &state->req); } else { status = session_setup_spnego(c, session, io, &state->req); } if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED) || NT_STATUS_IS_OK(status)) { composite_continue_smb(c, state->req, request_handler, c); return c; } composite_error(c, status); return c; }
/* handler for completion of a smbcli_request sub-request */ static void request_handler(struct smbcli_request *req) { struct composite_context *c = (struct composite_context *)req->async.private_data; struct sesssetup_state *state = talloc_get_type(c->private_data, struct sesssetup_state); struct smbcli_session *session = req->session; DATA_BLOB null_data_blob = data_blob(NULL, 0); NTSTATUS session_key_err, nt_status; struct smbcli_request *check_req = NULL; const char *os = NULL; const char *lanman = NULL; if (req->sign_caller_checks) { req->do_not_free = true; check_req = req; } state->remote_status = smb_raw_sesssetup_recv(req, state, &state->setup); c->status = state->remote_status; state->req = NULL; /* * we only need to check the signature if the * NT_STATUS_OK is returned */ if (!NT_STATUS_IS_OK(state->remote_status)) { talloc_free(check_req); check_req = NULL; } switch (state->setup.old.level) { case RAW_SESSSETUP_OLD: state->io->out.vuid = state->setup.old.out.vuid; /* This doesn't work, as this only happens on old * protocols, where this comparison won't match. */ if (NT_STATUS_EQUAL(c->status, NT_STATUS_LOGON_FAILURE)) { /* we neet to reset the vuid for a new try */ session->vuid = 0; if (cli_credentials_wrong_password(state->io->in.credentials)) { nt_status = session_setup_old(c, session, state->io, &state->req); if (NT_STATUS_IS_OK(nt_status)) { talloc_free(check_req); c->status = nt_status; composite_continue_smb(c, state->req, request_handler, c); return; } } } os = state->setup.old.out.os; lanman = state->setup.old.out.lanman; break; case RAW_SESSSETUP_NT1: state->io->out.vuid = state->setup.nt1.out.vuid; if (NT_STATUS_EQUAL(c->status, NT_STATUS_LOGON_FAILURE)) { /* we need to reset the vuid for a new try */ session->vuid = 0; if (cli_credentials_wrong_password(state->io->in.credentials)) { nt_status = session_setup_nt1(c, session, state->io, &state->req); if (NT_STATUS_IS_OK(nt_status)) { talloc_free(check_req); c->status = nt_status; composite_continue_smb(c, state->req, request_handler, c); return; } } } os = state->setup.nt1.out.os; lanman = state->setup.nt1.out.lanman; break; case RAW_SESSSETUP_SPNEGO: state->io->out.vuid = state->setup.spnego.out.vuid; if (NT_STATUS_EQUAL(c->status, NT_STATUS_LOGON_FAILURE)) { const char *principal; /* we need to reset the vuid for a new try */ session->vuid = 0; principal = gensec_get_target_principal(session->gensec); if (principal == NULL) { const char *hostname = gensec_get_target_hostname(session->gensec); const char *service = gensec_get_target_service(session->gensec); if (hostname != NULL && service != NULL) { principal = talloc_asprintf(state, "%s/%s", service, hostname); } } if (cli_credentials_failed_kerberos_login(state->io->in.credentials, principal, &state->logon_retries) || cli_credentials_wrong_password(state->io->in.credentials)) { nt_status = session_setup_spnego(c, session, state->io, &state->req); if (NT_STATUS_IS_OK(nt_status)) { talloc_free(check_req); c->status = nt_status; composite_continue_smb(c, state->req, request_handler, c); return; } } } if (!NT_STATUS_EQUAL(c->status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(c->status)) { break; } if (NT_STATUS_EQUAL(state->gensec_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { /* The status value here, from the earlier pass at GENSEC is * vital to the security of the system. Even if the other end * accepts, if GENSEC claims 'MORE_PROCESSING_REQUIRED' then * you must keep feeding it blobs, or else the remote * host/attacker might avoid mutal authentication * requirements */ state->gensec_status = gensec_update(session->gensec, state, c->event_ctx, state->setup.spnego.out.secblob, &state->setup.spnego.in.secblob); c->status = state->gensec_status; if (!NT_STATUS_EQUAL(c->status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(c->status)) { break; } } else { state->setup.spnego.in.secblob = data_blob(NULL, 0); } if (NT_STATUS_IS_OK(state->remote_status)) { DATA_BLOB session_key; if (state->setup.spnego.in.secblob.length) { c->status = NT_STATUS_INTERNAL_ERROR; break; } session_key_err = gensec_session_key(session->gensec, session, &session_key); if (NT_STATUS_IS_OK(session_key_err)) { smb1cli_conn_activate_signing(session->transport->conn, session_key, null_data_blob); } c->status = smb1cli_session_set_session_key(session->smbXcli, session_key); data_blob_free(&session_key); if (!NT_STATUS_IS_OK(c->status)) { break; } } if (state->setup.spnego.in.secblob.length) { /* * set the session->vuid value only for calling * smb_raw_sesssetup_send() */ uint16_t vuid = session->vuid; session->vuid = state->io->out.vuid; state->req = smb_raw_sesssetup_send(session, &state->setup); session->vuid = vuid; if (state->req && !smb1cli_conn_signing_is_active(state->req->transport->conn)) { state->req->sign_caller_checks = true; } composite_continue_smb(c, state->req, request_handler, c); return; } os = state->setup.spnego.out.os; lanman = state->setup.spnego.out.lanman; break; case RAW_SESSSETUP_SMB2: c->status = NT_STATUS_INTERNAL_ERROR; break; } if (check_req) { bool ok; check_req->sign_caller_checks = false; ok = smb1cli_conn_check_signing(check_req->transport->conn, check_req->in.buffer, 1); if (!ok) { c->status = NT_STATUS_ACCESS_DENIED; } talloc_free(check_req); check_req = NULL; } if (!NT_STATUS_IS_OK(c->status)) { composite_error(c, c->status); return; } if (os) { session->os = talloc_strdup(session, os); if (composite_nomem(session->os, c)) return; } else { session->os = NULL; } if (lanman) { session->lanman = talloc_strdup(session, lanman); if (composite_nomem(session->lanman, c)) return; } else { session->lanman = NULL; } composite_done(c); }
/* handler for completion of a smbcli_request sub-request */ static void request_handler(struct smbcli_request *req) { struct composite_context *c = (struct composite_context *)req->async.private_data; struct sesssetup_state *state = talloc_get_type(c->private_data, struct sesssetup_state); struct smbcli_session *session = req->session; DATA_BLOB session_key = data_blob(NULL, 0); DATA_BLOB null_data_blob = data_blob(NULL, 0); NTSTATUS session_key_err, nt_status; struct smbcli_request *check_req = NULL; const char *os = NULL; const char *lanman = NULL; if (req->sign_caller_checks) { req->do_not_free = true; check_req = req; } state->remote_status = smb_raw_sesssetup_recv(req, state, &state->setup); c->status = state->remote_status; state->req = NULL; /* * we only need to check the signature if the * NT_STATUS_OK is returned */ if (!NT_STATUS_IS_OK(state->remote_status)) { talloc_free(check_req); check_req = NULL; } switch (state->setup.old.level) { case RAW_SESSSETUP_OLD: state->io->out.vuid = state->setup.old.out.vuid; /* This doesn't work, as this only happens on old * protocols, where this comparison won't match. */ if (NT_STATUS_EQUAL(c->status, NT_STATUS_LOGON_FAILURE)) { /* we neet to reset the vuid for a new try */ session->vuid = 0; if (cli_credentials_wrong_password(state->io->in.credentials)) { nt_status = session_setup_old(c, session, state->io, &state->req); if (NT_STATUS_IS_OK(nt_status)) { talloc_free(check_req); c->status = nt_status; composite_continue_smb(c, state->req, request_handler, c); return; } } } os = state->setup.old.out.os; lanman = state->setup.old.out.lanman; break; case RAW_SESSSETUP_NT1: state->io->out.vuid = state->setup.nt1.out.vuid; if (NT_STATUS_EQUAL(c->status, NT_STATUS_LOGON_FAILURE)) { /* we neet to reset the vuid for a new try */ session->vuid = 0; if (cli_credentials_wrong_password(state->io->in.credentials)) { nt_status = session_setup_nt1(c, session, state->io, &state->req); if (NT_STATUS_IS_OK(nt_status)) { talloc_free(check_req); c->status = nt_status; composite_continue_smb(c, state->req, request_handler, c); return; } } } os = state->setup.nt1.out.os; lanman = state->setup.nt1.out.lanman; break; case RAW_SESSSETUP_SPNEGO: state->io->out.vuid = state->setup.spnego.out.vuid; if (NT_STATUS_EQUAL(c->status, NT_STATUS_LOGON_FAILURE)) { /* we need to reset the vuid for a new try */ session->vuid = 0; if (cli_credentials_wrong_password(state->io->in.credentials)) { nt_status = session_setup_spnego(c, session, state->io, &state->req); if (NT_STATUS_IS_OK(nt_status)) { talloc_free(check_req); c->status = nt_status; composite_continue_smb(c, state->req, request_handler, c); return; } } } if (!NT_STATUS_EQUAL(c->status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(c->status)) { break; } if (NT_STATUS_EQUAL(state->gensec_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { /* The status value here, from the earlier pass at GENSEC is * vital to the security of the system. Even if the other end * accepts, if GENSEC claims 'MORE_PROCESSING_REQUIRED' then * you must keep feeding it blobs, or else the remote * host/attacker might avoid mutal authentication * requirements */ state->gensec_status = gensec_update(session->gensec, state, state->setup.spnego.out.secblob, &state->setup.spnego.in.secblob); c->status = state->gensec_status; if (!NT_STATUS_EQUAL(c->status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(c->status)) { break; } } else { state->setup.spnego.in.secblob = data_blob(NULL, 0); } if (NT_STATUS_IS_OK(state->remote_status)) { if (state->setup.spnego.in.secblob.length) { c->status = NT_STATUS_INTERNAL_ERROR; break; } session_key_err = gensec_session_key(session->gensec, session, &session->user_session_key); if (NT_STATUS_IS_OK(session_key_err)) { smbcli_transport_simple_set_signing(session->transport, session->user_session_key, null_data_blob); } } if (state->setup.spnego.in.secblob.length) { /* * set the session->vuid value only for calling * smb_raw_sesssetup_send() */ uint16_t vuid = session->vuid; session->vuid = state->io->out.vuid; state->req = smb_raw_sesssetup_send(session, &state->setup); session->vuid = vuid; if (state->req) { state->req->sign_caller_checks = true; } composite_continue_smb(c, state->req, request_handler, c); return; } os = state->setup.spnego.out.os; lanman = state->setup.spnego.out.lanman; break; case RAW_SESSSETUP_SMB2: c->status = NT_STATUS_INTERNAL_ERROR; break; } if (check_req) { check_req->sign_caller_checks = false; if (!smbcli_request_check_sign_mac(check_req)) { c->status = NT_STATUS_ACCESS_DENIED; } talloc_free(check_req); check_req = NULL; } /* enforce the local signing required flag */ if (NT_STATUS_IS_OK(c->status) && !cli_credentials_is_anonymous(state->io->in.credentials)) { if (!session->transport->negotiate.sign_info.doing_signing && session->transport->negotiate.sign_info.mandatory_signing) { DEBUG(0, ("SMB signing required, but server does not support it\n")); c->status = NT_STATUS_ACCESS_DENIED; } } if (!NT_STATUS_IS_OK(c->status)) { composite_error(c, c->status); return; } if (os) { session->os = talloc_strdup(session, os); if (composite_nomem(session->os, c)) return; } else { session->os = NULL; } if (lanman) { session->lanman = talloc_strdup(session, lanman); if (composite_nomem(session->lanman, c)) return; } else { session->lanman = NULL; } composite_done(c); }