mirror of
https://github.com/samba-team/samba.git
synced 2025-02-04 17:47:26 +03:00
4b21ad9db7
This change brings ntvfs_connect into compliance with other ntvfs functions which take an ntvfs module, an ntvfs request and an smb io union. It now becomes the responsibility of ntvfs modules to examine tcon->generic.level themselves and derive the share name and any other options directly; e.g. const char *sharename; switch (tcon->generic.level) { case RAW_TCON_TCON: sharename = tcon->tcon.in.service; break; case RAW_TCON_TCONX: sharename = tcon->tconx.in.path; break; case RAW_TCON_SMB2: default: return NT_STATUS_INVALID_LEVEL; } if (strncmp(sharename, "\\\\", 2) == 0) { char *p = strchr(sharename+2, '\\'); if (p) { sharename = p + 1; } } service.c smbsrv_tcon_backend() is called before ntvfs_connect and fills in some of the tcon->..out values. For the case of RAW_TCON_TCONX, it filles out tcon->tconx.out.tid and tcon->tconx.out.options For the case of RAW_TCON_TCON it fills out tcon->tcon.out.tid and tcon->tcon.out.max_xmit Thus the ntvfs_connect function for vfs modules may override these values if desired, but are not required to. ntvfs_connect functions are required to fill in the tcon->tconx.out.*_type fields, for RAW_TCON_TCONX, perhaps something like: if (tcon->generic.level == RAW_TCON_TCONX) { tcon->tconx.out.fs_type = ntvfs->ctx->fs_type; tcon->tconx.out.dev_type = ntvfs->ctx->dev_type; } Signed-off-by: Sam Liddicott <sam@liddicott.com> (I fixed the ntvfs_connect() in the smb_server/smb2/ and the RAW_TCON_SMB2 switch case in the modules) Signed-off-by: Stefan Metzmacher <metze@samba.org>
460 lines
13 KiB
C
460 lines
13 KiB
C
/*
|
|
Unix SMB2 implementation.
|
|
|
|
Copyright (C) Stefan Metzmacher 2005
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
#include "libcli/smb2/smb2.h"
|
|
#include "libcli/smb2/smb2_calls.h"
|
|
#include "smb_server/smb_server.h"
|
|
#include "smb_server/service_smb_proto.h"
|
|
#include "smb_server/smb2/smb2_server.h"
|
|
#include "librpc/gen_ndr/security.h"
|
|
#include "smbd/service_stream.h"
|
|
#include "ntvfs/ntvfs.h"
|
|
#include "param/param.h"
|
|
|
|
/*
|
|
send an oplock break request to a client
|
|
*/
|
|
static NTSTATUS smb2srv_send_oplock_break(void *p, struct ntvfs_handle *h, uint8_t level)
|
|
{
|
|
struct smbsrv_handle *handle = talloc_get_type(h->frontend_data.private_data,
|
|
struct smbsrv_handle);
|
|
struct smb2srv_request *req;
|
|
NTSTATUS status;
|
|
|
|
/* setup a dummy request structure */
|
|
req = smb2srv_init_request(handle->tcon->smb_conn);
|
|
NT_STATUS_HAVE_NO_MEMORY(req);
|
|
|
|
req->in.buffer = talloc_array(req, uint8_t,
|
|
NBT_HDR_SIZE + SMB2_MIN_SIZE);
|
|
NT_STATUS_HAVE_NO_MEMORY(req->in.buffer);
|
|
req->in.size = NBT_HDR_SIZE + SMB2_MIN_SIZE;
|
|
req->in.allocated = req->in.size;
|
|
|
|
req->in.hdr = req->in.buffer+ NBT_HDR_SIZE;
|
|
req->in.body = req->in.hdr + SMB2_HDR_BODY;
|
|
req->in.body_size = req->in.size - (SMB2_HDR_BODY+NBT_HDR_SIZE);
|
|
req->in.dynamic = NULL;
|
|
|
|
req->seqnum = UINT64_MAX;
|
|
|
|
smb2srv_setup_bufinfo(req);
|
|
|
|
SIVAL(req->in.hdr, 0, SMB2_MAGIC);
|
|
SSVAL(req->in.hdr, SMB2_HDR_LENGTH, SMB2_HDR_BODY);
|
|
SSVAL(req->in.hdr, SMB2_HDR_EPOCH, 0);
|
|
SIVAL(req->in.hdr, SMB2_HDR_STATUS, 0);
|
|
SSVAL(req->in.hdr, SMB2_HDR_OPCODE, SMB2_OP_BREAK);
|
|
SSVAL(req->in.hdr, SMB2_HDR_CREDIT, 0);
|
|
SIVAL(req->in.hdr, SMB2_HDR_FLAGS, 0);
|
|
SIVAL(req->in.hdr, SMB2_HDR_NEXT_COMMAND, 0);
|
|
SBVAL(req->in.hdr, SMB2_HDR_MESSAGE_ID, 0);
|
|
SIVAL(req->in.hdr, SMB2_HDR_PID, 0);
|
|
SIVAL(req->in.hdr, SMB2_HDR_TID, 0);
|
|
SBVAL(req->in.hdr, SMB2_HDR_SESSION_ID, 0);
|
|
memset(req->in.hdr+SMB2_HDR_SIGNATURE, 0, 16);
|
|
|
|
SSVAL(req->in.body, 0, 2);
|
|
|
|
status = smb2srv_setup_reply(req, 0x18, false, 0);
|
|
NT_STATUS_NOT_OK_RETURN(status);
|
|
|
|
SSVAL(req->out.hdr, SMB2_HDR_CREDIT, 0x0000);
|
|
|
|
SSVAL(req->out.body, 0x02, 0x0001);
|
|
SIVAL(req->out.body, 0x04, 0x00000000);
|
|
smb2srv_push_handle(req->out.body, 0x08, h);
|
|
|
|
smb2srv_send_reply(req);
|
|
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
struct ntvfs_handle *smb2srv_pull_handle(struct smb2srv_request *req, const uint8_t *base, uint_t offset)
|
|
{
|
|
struct smbsrv_tcon *tcon;
|
|
struct smbsrv_handle *handle;
|
|
uint32_t hid;
|
|
uint32_t tid;
|
|
uint64_t uid;
|
|
|
|
/*
|
|
* if there're chained requests used the cached handle
|
|
*
|
|
* TODO: check if this also correct when the given handle
|
|
* isn't all 0xFF.
|
|
*/
|
|
if (req->chained_file_handle) {
|
|
base = req->chained_file_handle;
|
|
offset = 0;
|
|
}
|
|
|
|
hid = IVAL(base, offset);
|
|
tid = IVAL(base, offset + 4);
|
|
uid = BVAL(base, offset + 8);
|
|
|
|
/* if it's the wildcard handle, don't waste time to search it... */
|
|
if (hid == UINT32_MAX && tid == UINT32_MAX && uid == UINT64_MAX) {
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* if the (v)uid part doesn't match the given session the handle isn't
|
|
* valid
|
|
*/
|
|
if (uid != req->session->vuid) {
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* the handle can belong to a different tcon
|
|
* as that TID in the SMB2 header says, but
|
|
* the request should succeed nevertheless!
|
|
*
|
|
* because of this we put the 32 bit TID into the
|
|
* 128 bit handle, so that we can extract the tcon from the
|
|
* handle
|
|
*/
|
|
tcon = req->tcon;
|
|
if (tid != req->tcon->tid) {
|
|
tcon = smbsrv_smb2_tcon_find(req->session, tid, req->request_time);
|
|
if (!tcon) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
handle = smbsrv_smb2_handle_find(tcon, hid, req->request_time);
|
|
if (!handle) {
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* as the smb2srv_tcon is a child object of the smb2srv_session
|
|
* the handle belongs to the correct session!
|
|
*
|
|
* Note: no check is needed here for SMB2
|
|
*/
|
|
|
|
/*
|
|
* as the handle may have overwritten the tcon
|
|
* we need to set it on the request so that the
|
|
* correct ntvfs context will be used for the ntvfs_*() request
|
|
*
|
|
* TODO: check if that's correct for chained requests as well!
|
|
*/
|
|
req->tcon = tcon;
|
|
return handle->ntvfs;
|
|
}
|
|
|
|
void smb2srv_push_handle(uint8_t *base, uint_t offset, struct ntvfs_handle *ntvfs)
|
|
{
|
|
struct smbsrv_handle *handle = talloc_get_type(ntvfs->frontend_data.private_data,
|
|
struct smbsrv_handle);
|
|
|
|
/*
|
|
* the handle is 128 bit on the wire
|
|
*/
|
|
SIVAL(base, offset, handle->hid);
|
|
SIVAL(base, offset + 4, handle->tcon->tid);
|
|
SBVAL(base, offset + 8, handle->session->vuid);
|
|
}
|
|
|
|
static NTSTATUS smb2srv_handle_create_new(void *private_data, struct ntvfs_request *ntvfs, struct ntvfs_handle **_h)
|
|
{
|
|
struct smb2srv_request *req = talloc_get_type(ntvfs->frontend_data.private_data,
|
|
struct smb2srv_request);
|
|
struct smbsrv_handle *handle;
|
|
struct ntvfs_handle *h;
|
|
|
|
handle = smbsrv_handle_new(req->session, req->tcon, req, req->request_time);
|
|
if (!handle) return NT_STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
h = talloc_zero(handle, struct ntvfs_handle);
|
|
if (!h) goto nomem;
|
|
|
|
/*
|
|
* note: we don't set handle->ntvfs yet,
|
|
* this will be done by smbsrv_handle_make_valid()
|
|
* this makes sure the handle is invalid for clients
|
|
* until the ntvfs subsystem has made it valid
|
|
*/
|
|
h->ctx = ntvfs->ctx;
|
|
h->session_info = ntvfs->session_info;
|
|
h->smbpid = ntvfs->smbpid;
|
|
|
|
h->frontend_data.private_data = handle;
|
|
|
|
*_h = h;
|
|
return NT_STATUS_OK;
|
|
nomem:
|
|
talloc_free(handle);
|
|
return NT_STATUS_NO_MEMORY;
|
|
}
|
|
|
|
static NTSTATUS smb2srv_handle_make_valid(void *private_data, struct ntvfs_handle *h)
|
|
{
|
|
struct smbsrv_tcon *tcon = talloc_get_type(private_data, struct smbsrv_tcon);
|
|
struct smbsrv_handle *handle = talloc_get_type(h->frontend_data.private_data,
|
|
struct smbsrv_handle);
|
|
/* this tells the frontend that the handle is valid */
|
|
handle->ntvfs = h;
|
|
/* this moves the smbsrv_request to the smbsrv_tcon memory context */
|
|
talloc_steal(tcon, handle);
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
static void smb2srv_handle_destroy(void *private_data, struct ntvfs_handle *h)
|
|
{
|
|
struct smbsrv_handle *handle = talloc_get_type(h->frontend_data.private_data,
|
|
struct smbsrv_handle);
|
|
talloc_free(handle);
|
|
}
|
|
|
|
static struct ntvfs_handle *smb2srv_handle_search_by_wire_key(void *private_data, struct ntvfs_request *ntvfs, const DATA_BLOB *key)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
static DATA_BLOB smb2srv_handle_get_wire_key(void *private_data, struct ntvfs_handle *handle, TALLOC_CTX *mem_ctx)
|
|
{
|
|
return data_blob(NULL, 0);
|
|
}
|
|
|
|
static NTSTATUS smb2srv_tcon_backend(struct smb2srv_request *req, union smb_tcon *io)
|
|
{
|
|
struct smbsrv_tcon *tcon;
|
|
NTSTATUS status;
|
|
enum ntvfs_type type;
|
|
const char *service = io->smb2.in.path;
|
|
struct share_config *scfg;
|
|
const char *sharetype;
|
|
uint64_t ntvfs_caps = 0;
|
|
|
|
if (strncmp(service, "\\\\", 2) == 0) {
|
|
const char *p = strchr(service+2, '\\');
|
|
if (p) {
|
|
service = p + 1;
|
|
}
|
|
}
|
|
|
|
status = share_get_config(req, req->smb_conn->share_context, service, &scfg);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
DEBUG(0,("smb2srv_tcon_backend: couldn't find service %s\n", service));
|
|
return NT_STATUS_BAD_NETWORK_NAME;
|
|
}
|
|
|
|
if (!socket_check_access(req->smb_conn->connection->socket,
|
|
scfg->name,
|
|
share_string_list_option(req, scfg, SHARE_HOSTS_ALLOW),
|
|
share_string_list_option(req, scfg, SHARE_HOSTS_DENY))) {
|
|
return NT_STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
/* work out what sort of connection this is */
|
|
sharetype = share_string_option(scfg, SHARE_TYPE, "DISK");
|
|
if (sharetype && strcmp(sharetype, "IPC") == 0) {
|
|
type = NTVFS_IPC;
|
|
} else if (sharetype && strcmp(sharetype, "PRINTER") == 0) {
|
|
type = NTVFS_PRINT;
|
|
} else {
|
|
type = NTVFS_DISK;
|
|
}
|
|
|
|
tcon = smbsrv_smb2_tcon_new(req->session, scfg->name);
|
|
if (!tcon) {
|
|
DEBUG(0,("smb2srv_tcon_backend: Couldn't find free connection.\n"));
|
|
return NT_STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
req->tcon = tcon;
|
|
|
|
ntvfs_caps = NTVFS_CLIENT_CAP_LEVEL_II_OPLOCKS;
|
|
|
|
/* init ntvfs function pointers */
|
|
status = ntvfs_init_connection(tcon, scfg, type,
|
|
req->smb_conn->negotiate.protocol,
|
|
ntvfs_caps,
|
|
req->smb_conn->connection->event.ctx,
|
|
req->smb_conn->connection->msg_ctx,
|
|
req->smb_conn->lp_ctx,
|
|
req->smb_conn->connection->server_id,
|
|
&tcon->ntvfs);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
DEBUG(0, ("smb2srv_tcon_backend: ntvfs_init_connection failed for service %s\n",
|
|
scfg->name));
|
|
goto failed;
|
|
}
|
|
|
|
status = ntvfs_set_oplock_handler(tcon->ntvfs, smb2srv_send_oplock_break, tcon);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
DEBUG(0,("smb2srv_tcon_backend: NTVFS failed to set the oplock handler!\n"));
|
|
goto failed;
|
|
}
|
|
|
|
status = ntvfs_set_addr_callbacks(tcon->ntvfs, smbsrv_get_my_addr, smbsrv_get_peer_addr, req->smb_conn);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
DEBUG(0,("smb2srv_tcon_backend: NTVFS failed to set the addr callbacks!\n"));
|
|
goto failed;
|
|
}
|
|
|
|
status = ntvfs_set_handle_callbacks(tcon->ntvfs,
|
|
smb2srv_handle_create_new,
|
|
smb2srv_handle_make_valid,
|
|
smb2srv_handle_destroy,
|
|
smb2srv_handle_search_by_wire_key,
|
|
smb2srv_handle_get_wire_key,
|
|
tcon);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
DEBUG(0,("smb2srv_tcon_backend: NTVFS failed to set the handle callbacks!\n"));
|
|
goto failed;
|
|
}
|
|
|
|
req->ntvfs = ntvfs_request_create(req->tcon->ntvfs, req,
|
|
req->session->session_info,
|
|
SVAL(req->in.hdr, SMB2_HDR_PID),
|
|
req->request_time,
|
|
req, NULL, 0);
|
|
if (!req->ntvfs) {
|
|
status = NT_STATUS_NO_MEMORY;
|
|
goto failed;
|
|
}
|
|
|
|
io->smb2.out.share_type = (unsigned)type; /* 1 - DISK, 2 - Print, 3 - IPC */
|
|
io->smb2.out.reserved = 0;
|
|
io->smb2.out.flags = 0x00000000;
|
|
io->smb2.out.capabilities = 0;
|
|
io->smb2.out.access_mask = SEC_RIGHTS_FILE_ALL;
|
|
|
|
io->smb2.out.tid = tcon->tid;
|
|
|
|
/* Invoke NTVFS connection hook */
|
|
status = ntvfs_connect(req->ntvfs, io);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
DEBUG(0,("smb2srv_tcon_backend: NTVFS ntvfs_connect() failed!\n"));
|
|
goto failed;
|
|
}
|
|
|
|
return NT_STATUS_OK;
|
|
|
|
failed:
|
|
req->tcon = NULL;
|
|
talloc_free(tcon);
|
|
return status;
|
|
}
|
|
|
|
static void smb2srv_tcon_send(struct smb2srv_request *req, union smb_tcon *io)
|
|
{
|
|
uint16_t credit;
|
|
|
|
if (!NT_STATUS_IS_OK(req->status)) {
|
|
smb2srv_send_error(req, req->status);
|
|
return;
|
|
}
|
|
if (io->smb2.out.share_type == NTVFS_IPC) {
|
|
/* if it's an IPC share vista returns 0x0005 */
|
|
credit = 0x0005;
|
|
} else {
|
|
credit = 0x0001;
|
|
}
|
|
|
|
SMB2SRV_CHECK(smb2srv_setup_reply(req, 0x10, false, 0));
|
|
|
|
SIVAL(req->out.hdr, SMB2_HDR_TID, io->smb2.out.tid);
|
|
SSVAL(req->out.hdr, SMB2_HDR_CREDIT,credit);
|
|
|
|
SCVAL(req->out.body, 0x02, io->smb2.out.share_type);
|
|
SCVAL(req->out.body, 0x03, io->smb2.out.reserved);
|
|
SIVAL(req->out.body, 0x04, io->smb2.out.flags);
|
|
SIVAL(req->out.body, 0x08, io->smb2.out.capabilities);
|
|
SIVAL(req->out.body, 0x0C, io->smb2.out.access_mask);
|
|
|
|
smb2srv_send_reply(req);
|
|
}
|
|
|
|
void smb2srv_tcon_recv(struct smb2srv_request *req)
|
|
{
|
|
union smb_tcon *io;
|
|
|
|
SMB2SRV_CHECK_BODY_SIZE(req, 0x08, true);
|
|
SMB2SRV_TALLOC_IO_PTR(io, union smb_tcon);
|
|
|
|
io->smb2.level = RAW_TCON_SMB2;
|
|
io->smb2.in.reserved = SVAL(req->in.body, 0x02);
|
|
SMB2SRV_CHECK(smb2_pull_o16s16_string(&req->in, io, req->in.body+0x04, &io->smb2.in.path));
|
|
|
|
/* the VFS backend does not yet handle NULL paths */
|
|
if (io->smb2.in.path == NULL) {
|
|
io->smb2.in.path = "";
|
|
}
|
|
|
|
req->status = smb2srv_tcon_backend(req, io);
|
|
|
|
if (req->control_flags & SMB2SRV_REQ_CTRL_FLAG_NOT_REPLY) {
|
|
talloc_free(req);
|
|
return;
|
|
}
|
|
smb2srv_tcon_send(req, io);
|
|
}
|
|
|
|
static NTSTATUS smb2srv_tdis_backend(struct smb2srv_request *req)
|
|
{
|
|
/* TODO: call ntvfs backends to close file of this tcon */
|
|
talloc_free(req->tcon);
|
|
req->tcon = NULL;
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
static void smb2srv_tdis_send(struct smb2srv_request *req)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
if (NT_STATUS_IS_ERR(req->status)) {
|
|
smb2srv_send_error(req, req->status);
|
|
return;
|
|
}
|
|
|
|
status = smb2srv_setup_reply(req, 0x04, false, 0);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
smbsrv_terminate_connection(req->smb_conn, nt_errstr(status));
|
|
talloc_free(req);
|
|
return;
|
|
}
|
|
|
|
SSVAL(req->out.body, 0x02, 0);
|
|
|
|
smb2srv_send_reply(req);
|
|
}
|
|
|
|
void smb2srv_tdis_recv(struct smb2srv_request *req)
|
|
{
|
|
uint16_t _pad;
|
|
|
|
SMB2SRV_CHECK_BODY_SIZE(req, 0x04, false);
|
|
|
|
_pad = SVAL(req->in.body, 0x02);
|
|
|
|
req->status = smb2srv_tdis_backend(req);
|
|
|
|
if (req->control_flags & SMB2SRV_REQ_CTRL_FLAG_NOT_REPLY) {
|
|
talloc_free(req);
|
|
return;
|
|
}
|
|
smb2srv_tdis_send(req);
|
|
}
|