/* 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; } /* Invoke NTVFS connection hook */ status = ntvfs_connect(req->ntvfs, scfg->name); if (!NT_STATUS_IS_OK(status)) { DEBUG(0,("smb2srv_tcon_backend: NTVFS ntvfs_connect() failed!\n")); 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; 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); }