From 8daa83a594a2e98f39d764422bfbdbc62c9efd44 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 19:20:00 +0200 Subject: Adding upstream version 2:4.20.0+dfsg. Signed-off-by: Daniel Baumann --- source3/smbd/smb2_tcon.c | 765 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 765 insertions(+) create mode 100644 source3/smbd/smb2_tcon.c (limited to 'source3/smbd/smb2_tcon.c') diff --git a/source3/smbd/smb2_tcon.c b/source3/smbd/smb2_tcon.c new file mode 100644 index 0000000..b228036 --- /dev/null +++ b/source3/smbd/smb2_tcon.c @@ -0,0 +1,765 @@ +/* + Unix SMB/CIFS implementation. + Core SMB2 server + + Copyright (C) Stefan Metzmacher 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "includes.h" +#include "smbd/smbd.h" +#include "smbd/globals.h" +#include "../libcli/smb/smb_common.h" +#include "../libcli/security/security.h" +#include "auth.h" +#include "lib/param/loadparm.h" +#include "../lib/util/tevent_ntstatus.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_SMB2 + +static struct tevent_req *smbd_smb2_tree_connect_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbd_smb2_request *smb2req, + uint16_t in_flags, + const char *in_path); +static NTSTATUS smbd_smb2_tree_connect_recv(struct tevent_req *req, + uint8_t *out_share_type, + uint32_t *out_share_flags, + uint32_t *out_capabilities, + uint32_t *out_maximal_access, + uint32_t *out_tree_id, + bool *disconnect); + +static void smbd_smb2_request_tcon_done(struct tevent_req *subreq); + +NTSTATUS smbd_smb2_request_process_tcon(struct smbd_smb2_request *req) +{ + struct smbXsrv_connection *xconn = req->xconn; + const uint8_t *inbody; + uint16_t in_flags; + uint16_t in_path_offset; + uint16_t in_path_length; + DATA_BLOB in_path_buffer; + char *in_path_string; + size_t in_path_string_size; + NTSTATUS status; + bool ok; + struct tevent_req *subreq; + + status = smbd_smb2_request_verify_sizes(req, 0x09); + if (!NT_STATUS_IS_OK(status)) { + return smbd_smb2_request_error(req, status); + } + inbody = SMBD_SMB2_IN_BODY_PTR(req); + + if (xconn->protocol >= PROTOCOL_SMB3_11) { + in_flags = SVAL(inbody, 0x02); + } else { + in_flags = 0; + } + in_path_offset = SVAL(inbody, 0x04); + in_path_length = SVAL(inbody, 0x06); + + if (in_path_offset != (SMB2_HDR_BODY + SMBD_SMB2_IN_BODY_LEN(req))) { + return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER); + } + + if (in_path_length > SMBD_SMB2_IN_DYN_LEN(req)) { + return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER); + } + + in_path_buffer.data = SMBD_SMB2_IN_DYN_PTR(req); + in_path_buffer.length = in_path_length; + + ok = convert_string_talloc(req, CH_UTF16, CH_UNIX, + in_path_buffer.data, + in_path_buffer.length, + &in_path_string, + &in_path_string_size); + if (!ok) { + return smbd_smb2_request_error(req, NT_STATUS_ILLEGAL_CHARACTER); + } + + if (in_path_buffer.length == 0) { + in_path_string_size = 0; + } + + if (strlen(in_path_string) != in_path_string_size) { + return smbd_smb2_request_error(req, NT_STATUS_BAD_NETWORK_NAME); + } + + subreq = smbd_smb2_tree_connect_send(req, + req->sconn->ev_ctx, + req, + in_flags, + in_path_string); + if (subreq == NULL) { + return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY); + } + tevent_req_set_callback(subreq, smbd_smb2_request_tcon_done, req); + + /* + * Avoid sending a STATUS_PENDING message, it's very likely + * the client won't expect that. + */ + return smbd_smb2_request_pending_queue(req, subreq, 0); +} + +static void smbd_smb2_request_tcon_done(struct tevent_req *subreq) +{ + struct smbd_smb2_request *req = + tevent_req_callback_data(subreq, + struct smbd_smb2_request); + uint8_t *outhdr; + DATA_BLOB outbody; + uint8_t out_share_type = 0; + uint32_t out_share_flags = 0; + uint32_t out_capabilities = 0; + uint32_t out_maximal_access = 0; + uint32_t out_tree_id = 0; + bool disconnect = false; + NTSTATUS status; + NTSTATUS error; + + status = smbd_smb2_tree_connect_recv(subreq, + &out_share_type, + &out_share_flags, + &out_capabilities, + &out_maximal_access, + &out_tree_id, + &disconnect); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + if (disconnect) { + smbd_server_connection_terminate(req->xconn, + nt_errstr(status)); + return; + } + error = smbd_smb2_request_error(req, status); + if (!NT_STATUS_IS_OK(error)) { + smbd_server_connection_terminate(req->xconn, + nt_errstr(error)); + return; + } + return; + } + + outhdr = SMBD_SMB2_OUT_HDR_PTR(req); + + outbody = smbd_smb2_generate_outbody(req, 0x10); + if (outbody.data == NULL) { + error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY); + if (!NT_STATUS_IS_OK(error)) { + smbd_server_connection_terminate(req->xconn, + nt_errstr(error)); + return; + } + return; + } + + SIVAL(outhdr, SMB2_HDR_TID, out_tree_id); + + SSVAL(outbody.data, 0x00, 0x10); /* struct size */ + SCVAL(outbody.data, 0x02, + out_share_type); /* share type */ + SCVAL(outbody.data, 0x03, 0); /* reserved */ + SIVAL(outbody.data, 0x04, + out_share_flags); /* share flags */ + SIVAL(outbody.data, 0x08, + out_capabilities); /* capabilities */ + SIVAL(outbody.data, 0x0C, + out_maximal_access); /* maximal access */ + + error = smbd_smb2_request_done(req, outbody, NULL); + if (!NT_STATUS_IS_OK(error)) { + smbd_server_connection_terminate(req->xconn, + nt_errstr(error)); + return; + } +} + +static NTSTATUS smbd_smb2_tree_connect(struct smbd_smb2_request *req, + const char *in_path, + uint8_t *out_share_type, + uint32_t *out_share_flags, + uint32_t *out_capabilities, + uint32_t *out_maximal_access, + uint32_t *out_tree_id, + bool *disconnect) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + struct smbXsrv_connection *conn = req->xconn; + struct smbXsrv_session *session = req->session; + struct auth_session_info *session_info = + session->global->auth_session_info; + const char *share = in_path; + char *service = NULL; + int snum = -1; + struct smbXsrv_tcon *tcon; + NTTIME now = timeval_to_nttime(&req->request_time); + connection_struct *compat_conn = NULL; + NTSTATUS status; + bool encryption_desired = req->session->global->encryption_flags & SMBXSRV_ENCRYPTION_DESIRED; + bool encryption_required = req->session->global->encryption_flags & SMBXSRV_ENCRYPTION_REQUIRED; + bool guest_session = false; + bool require_signed_tcon = false; + uint32_t session_global_id; + char *share_name = NULL; + uint8_t encryption_flags = 0; + + *disconnect = false; + + if (strncmp(share, "\\\\", 2) == 0) { + const char *p = strchr(share+2, '\\'); + if (p) { + share = p + 1; + } + } + + DEBUG(10,("smbd_smb2_tree_connect: path[%s] share[%s]\n", + in_path, share)); + + if (security_session_user_level(session_info, NULL) < SECURITY_USER) { + guest_session = true; + } + + if (conn->protocol >= PROTOCOL_SMB3_11 && !guest_session) { + require_signed_tcon = true; + } + + if (require_signed_tcon && !req->do_encryption && !req->do_signing) { + DEBUG(1, ("smbd_smb2_tree_connect: reject request to share " + "[%s] as '%s\\%s' without encryption or signing. " + "Disconnecting.\n", + share, + req->session->global->auth_session_info->info->domain_name, + req->session->global->auth_session_info->info->account_name)); + *disconnect = true; + return NT_STATUS_ACCESS_DENIED; + } + + service = talloc_strdup(talloc_tos(), share); + if(!service) { + return NT_STATUS_NO_MEMORY; + } + + if (!strlower_m(service)) { + DEBUG(2, ("strlower_m %s failed\n", service)); + return NT_STATUS_INVALID_PARAMETER; + } + + /* TODO: do more things... */ + if (strequal(service,HOMES_NAME)) { + if (session->homes_snum == -1) { + DEBUG(2, ("[homes] share not available for " + "user %s because it was not found " + "or created at session setup " + "time\n", + session_info->unix_info->unix_name)); + return NT_STATUS_BAD_NETWORK_NAME; + } + snum = session->homes_snum; + } else if ((session->homes_snum != -1) + && strequal(service, + lp_servicename(talloc_tos(), lp_sub, session->homes_snum))) { + snum = session->homes_snum; + } else { + snum = find_service(talloc_tos(), service, &service); + if (!service) { + return NT_STATUS_NO_MEMORY; + } + } + + if (snum < 0) { + DEBUG(3,("smbd_smb2_tree_connect: couldn't find service %s\n", + service)); + return NT_STATUS_BAD_NETWORK_NAME; + } + + /* Handle non-DFS clients attempting connections to msdfs proxy */ + if (lp_host_msdfs()) { + char *proxy = lp_msdfs_proxy(talloc_tos(), lp_sub, snum); + + if ((proxy != NULL) && (*proxy != '\0')) { + DBG_NOTICE("refusing connection to dfs proxy share " + "'%s' (pointing to %s)\n", + service, + proxy); + TALLOC_FREE(proxy); + return NT_STATUS_BAD_NETWORK_NAME; + } + TALLOC_FREE(proxy); + } + + if ((lp_server_smb_encrypt(snum) >= SMB_ENCRYPTION_DESIRED) && + (conn->smb2.server.cipher != 0)) + { + encryption_desired = true; + } + + if (lp_server_smb_encrypt(snum) == SMB_ENCRYPTION_REQUIRED) { + encryption_desired = true; + encryption_required = true; + } + + if (guest_session && encryption_required) { + DEBUG(1,("reject guest as encryption is required for service %s\n", + service)); + return NT_STATUS_ACCESS_DENIED; + } + + if (conn->smb2.server.cipher == 0) { + if (encryption_required) { + DEBUG(1,("reject tcon with dialect[0x%04X] " + "as encryption is required for service %s\n", + conn->smb2.server.dialect, service)); + return NT_STATUS_ACCESS_DENIED; + } + } + + if (encryption_desired) { + encryption_flags |= SMBXSRV_ENCRYPTION_DESIRED; + } + if (encryption_required) { + encryption_flags |= SMBXSRV_ENCRYPTION_REQUIRED; + } + + session_global_id = req->session->global->session_global_id; + share_name = lp_servicename(talloc_tos(), lp_sub, snum); + if (share_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if ((lp_max_connections(snum) > 0) + && (count_current_connections(lp_const_servicename(snum), true) >= + lp_max_connections(snum))) { + + DBG_WARNING("Max connections (%d) exceeded for [%s][%s]\n", + lp_max_connections(snum), + lp_const_servicename(snum), share_name); + TALLOC_FREE(share_name); + return NT_STATUS_INSUFFICIENT_RESOURCES; + } + + /* create a new tcon as child of the session */ + status = smb2srv_tcon_create(req->session, + session_global_id, + encryption_flags, + share_name, + now, &tcon); + TALLOC_FREE(share_name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + compat_conn = make_connection_smb2(req, + tcon, snum, + "???", + &status); + if (compat_conn == NULL) { + TALLOC_FREE(tcon); + return status; + } + + tcon->compat = talloc_move(tcon, &compat_conn); + + tcon->status = NT_STATUS_OK; + + if (IS_PRINT(tcon->compat)) { + *out_share_type = SMB2_SHARE_TYPE_PRINT; + } else if (IS_IPC(tcon->compat)) { + *out_share_type = SMB2_SHARE_TYPE_PIPE; + } else { + *out_share_type = SMB2_SHARE_TYPE_DISK; + } + + *out_share_flags = 0; + + if (lp_msdfs_root(SNUM(tcon->compat)) && lp_host_msdfs()) { + *out_share_flags |= (SMB2_SHAREFLAG_DFS|SMB2_SHAREFLAG_DFS_ROOT); + *out_capabilities = SMB2_SHARE_CAP_DFS; + } else { + *out_capabilities = 0; + } + + switch(lp_csc_policy(SNUM(tcon->compat))) { + case CSC_POLICY_MANUAL: + break; + case CSC_POLICY_DOCUMENTS: + *out_share_flags |= SMB2_SHAREFLAG_AUTO_CACHING; + break; + case CSC_POLICY_PROGRAMS: + *out_share_flags |= SMB2_SHAREFLAG_VDO_CACHING; + break; + case CSC_POLICY_DISABLE: + *out_share_flags |= SMB2_SHAREFLAG_NO_CACHING; + break; + default: + break; + } + + if (lp_hide_unreadable(SNUM(tcon->compat)) || + lp_hide_unwriteable_files(SNUM(tcon->compat))) { + *out_share_flags |= SMB2_SHAREFLAG_ACCESS_BASED_DIRECTORY_ENUM; + } + + if (encryption_desired) { + *out_share_flags |= SMB2_SHAREFLAG_ENCRYPT_DATA; + } + + /* + * For disk shares we can change the client + * behavior on a cluster... + */ + if (conn->protocol >= PROTOCOL_SMB3_00 && + *out_share_type == SMB2_SHARE_TYPE_DISK) + { + bool persistent = false; /* persistent handles not implemented yet */ + bool cluster = lp_clustering(); + bool scaleout = cluster; + bool witness = cluster && !lp_rpc_start_on_demand_helpers(); + bool asymmetric = false; /* shares are symmetric by default */ + bool announce; + + /* + * In a ctdb cluster shares are continuously available, + * but windows clients mix this with the global persistent + * handles support. + * + * Persistent handles are requested if + * SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY is present + * even without SMB2_CAP_PERSISTENT_HANDLES. + * + * And SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY is + * required for SMB2_SHARE_CAP_CLUSTER to have + * an effect. + * + * So we better don't announce this by default + * until we support persistent handles. + */ + announce = lp_parm_bool(SNUM(tcon->compat), + "smb3 share cap", + "CONTINUOUS AVAILABILITY", + persistent); + if (announce) { + *out_capabilities |= SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY; + } + + /* + * ctdb clusters are always scale out... + */ + announce = lp_parm_bool(SNUM(tcon->compat), + "smb3 share cap", + "SCALE OUT", + scaleout); + if (announce) { + *out_capabilities |= SMB2_SHARE_CAP_SCALEOUT; + } + + /* + * We support the witness service when ctdb is active + */ + announce = lp_parm_bool(SNUM(tcon->compat), + "smb3 share cap", + "CLUSTER", + witness); + if (announce) { + *out_capabilities |= SMB2_SHARE_CAP_CLUSTER; + } + + /* + * Shares in a ctdb cluster are symmetric by design. + * + * But it might be useful to let the client use + * an isolated transport and witness registration for the + * specific share. + */ + if (conn->protocol >= PROTOCOL_SMB3_02) { + announce = lp_parm_bool(SNUM(tcon->compat), + "smb3 share cap", + "ASYMMETRIC", + asymmetric); + } + if (announce) { + *out_capabilities |= SMB2_SHARE_CAP_ASYMMETRIC; + } + } + + *out_maximal_access = tcon->compat->share_access; + + *out_tree_id = tcon->global->tcon_wire_id; + req->last_tid = tcon->global->tcon_wire_id; + + return NT_STATUS_OK; +} + +struct smbd_smb2_tree_connect_state { + const char *in_path; + uint8_t out_share_type; + uint32_t out_share_flags; + uint32_t out_capabilities; + uint32_t out_maximal_access; + uint32_t out_tree_id; + bool disconnect; +}; + +static struct tevent_req *smbd_smb2_tree_connect_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbd_smb2_request *smb2req, + uint16_t in_flags, + const char *in_path) +{ + struct tevent_req *req; + struct smbd_smb2_tree_connect_state *state; + NTSTATUS status; + + req = tevent_req_create(mem_ctx, &state, + struct smbd_smb2_tree_connect_state); + if (req == NULL) { + return NULL; + } + state->in_path = in_path; + + status = smbd_smb2_tree_connect(smb2req, + state->in_path, + &state->out_share_type, + &state->out_share_flags, + &state->out_capabilities, + &state->out_maximal_access, + &state->out_tree_id, + &state->disconnect); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static NTSTATUS smbd_smb2_tree_connect_recv(struct tevent_req *req, + uint8_t *out_share_type, + uint32_t *out_share_flags, + uint32_t *out_capabilities, + uint32_t *out_maximal_access, + uint32_t *out_tree_id, + bool *disconnect) +{ + struct smbd_smb2_tree_connect_state *state = + tevent_req_data(req, + struct smbd_smb2_tree_connect_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *out_share_type = state->out_share_type; + *out_share_flags = state->out_share_flags; + *out_capabilities = state->out_capabilities; + *out_maximal_access = state->out_maximal_access; + *out_tree_id = state->out_tree_id; + *disconnect = state->disconnect; + + tevent_req_received(req); + return NT_STATUS_OK; +} + +static struct tevent_req *smbd_smb2_tdis_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbd_smb2_request *smb2req); +static NTSTATUS smbd_smb2_tdis_recv(struct tevent_req *req); +static void smbd_smb2_request_tdis_done(struct tevent_req *subreq); + +NTSTATUS smbd_smb2_request_process_tdis(struct smbd_smb2_request *req) +{ + NTSTATUS status; + struct tevent_req *subreq = NULL; + + status = smbd_smb2_request_verify_sizes(req, 0x04); + if (!NT_STATUS_IS_OK(status)) { + return smbd_smb2_request_error(req, status); + } + + subreq = smbd_smb2_tdis_send(req, req->sconn->ev_ctx, req); + if (subreq == NULL) { + return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY); + } + tevent_req_set_callback(subreq, smbd_smb2_request_tdis_done, req); + + /* + * Avoid sending a STATUS_PENDING message, it's very likely + * the client won't expect that. + */ + return smbd_smb2_request_pending_queue(req, subreq, 0); +} + +static void smbd_smb2_request_tdis_done(struct tevent_req *subreq) +{ + struct smbd_smb2_request *smb2req = + tevent_req_callback_data(subreq, + struct smbd_smb2_request); + DATA_BLOB outbody; + NTSTATUS status; + NTSTATUS error; + + status = smbd_smb2_tdis_recv(subreq); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + error = smbd_smb2_request_error(smb2req, status); + if (!NT_STATUS_IS_OK(error)) { + smbd_server_connection_terminate(smb2req->xconn, + nt_errstr(error)); + return; + } + return; + } + + outbody = smbd_smb2_generate_outbody(smb2req, 0x04); + if (outbody.data == NULL) { + error = smbd_smb2_request_error(smb2req, NT_STATUS_NO_MEMORY); + if (!NT_STATUS_IS_OK(error)) { + smbd_server_connection_terminate(smb2req->xconn, + nt_errstr(error)); + return; + } + return; + } + + SSVAL(outbody.data, 0x00, 0x04); /* struct size */ + SSVAL(outbody.data, 0x02, 0); /* reserved */ + + error = smbd_smb2_request_done(smb2req, outbody, NULL); + if (!NT_STATUS_IS_OK(error)) { + smbd_server_connection_terminate(smb2req->xconn, + nt_errstr(error)); + return; + } +} + +struct smbd_smb2_tdis_state { + struct smbd_smb2_request *smb2req; + struct tevent_queue *wait_queue; +}; + +static void smbd_smb2_tdis_wait_done(struct tevent_req *subreq); + +static struct tevent_req *smbd_smb2_tdis_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct smbd_smb2_request *smb2req) +{ + struct tevent_req *req; + struct smbd_smb2_tdis_state *state; + struct tevent_req *subreq; + struct smbXsrv_connection *xconn = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct smbd_smb2_tdis_state); + if (req == NULL) { + return NULL; + } + state->smb2req = smb2req; + + state->wait_queue = tevent_queue_create(state, "tdis_wait_queue"); + if (tevent_req_nomem(state->wait_queue, req)) { + return tevent_req_post(req, ev); + } + + /* + * Make sure that no new request will be able to use this tcon. + */ + smb2req->tcon->status = NT_STATUS_NETWORK_NAME_DELETED; + + xconn = smb2req->xconn->client->connections; + for (; xconn != NULL; xconn = xconn->next) { + struct smbd_smb2_request *preq; + + for (preq = xconn->smb2.requests; preq != NULL; preq = preq->next) { + if (preq == smb2req) { + /* Can't cancel current request. */ + continue; + } + if (preq->tcon != smb2req->tcon) { + /* Request on different tcon. */ + continue; + } + + if (preq->subreq != NULL) { + tevent_req_cancel(preq->subreq); + } + + /* + * Now wait until the request is finished. + * + * We don't set a callback, as we just want to block the + * wait queue and the talloc_free() of the request will + * remove the item from the wait queue. + */ + subreq = tevent_queue_wait_send(preq, ev, state->wait_queue); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + } + } + + /* + * Now we add our own waiter to the end of the queue, + * this way we get notified when all pending requests are finished + * and send to the socket. + */ + subreq = tevent_queue_wait_send(state, ev, state->wait_queue); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, smbd_smb2_tdis_wait_done, req); + + return req; +} + +static void smbd_smb2_tdis_wait_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct smbd_smb2_tdis_state *state = tevent_req_data( + req, struct smbd_smb2_tdis_state); + NTSTATUS status; + + tevent_queue_wait_recv(subreq); + TALLOC_FREE(subreq); + + /* + * As we've been awoken, we may have changed + * uid in the meantime. Ensure we're still + * root (SMB2_OP_TDIS has .as_root = true). + */ + change_to_root_user(); + + status = smbXsrv_tcon_disconnect(state->smb2req->tcon, + state->smb2req->tcon->compat->vuid); + if (tevent_req_nterror(req, status)) { + return; + } + + /* We did tear down the tcon. */ + TALLOC_FREE(state->smb2req->tcon); + tevent_req_done(req); +} + +static NTSTATUS smbd_smb2_tdis_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} -- cgit v1.2.3