diff options
Diffstat (limited to 'source4/ntvfs')
75 files changed, 26969 insertions, 0 deletions
diff --git a/source4/ntvfs/README b/source4/ntvfs/README new file mode 100644 index 0000000..c86c9a0 --- /dev/null +++ b/source4/ntvfs/README @@ -0,0 +1,26 @@ +This is the base of the new NTVFS subsystem for Samba. The model for +NTVFS backends is quite different than for the older style VFS +backends, in particular: + +- the NTVFS backends receive windows style file names, although they + are in the unix charset (usually UTF8). This means the backend is + responsible for mapping windows filename conventions to unix + filename conventions if necessary + +- the NTVFS backends are responsible for changing effective UID before + calling any OS local filesystem operations (if needed). The + become_*() functions are provided to make this easier. + +- the NTVFS backends are responsible for resolving DFS paths + +- each NTVFS backend handles either disk, printer or IPC$ shares, + rather than one backend handling all types + +- the entry points of the NTVFS backends correspond closely with basic + SMB operations, wheres the old VFS was modelled directly on the + POSIX filesystem interface. + +- the NTVFS backends are responsible for all semantic mappings, such + as mapping dos file attributes, ACLs, file ownership and file times + + diff --git a/source4/ntvfs/cifs/README b/source4/ntvfs/cifs/README new file mode 100644 index 0000000..f9ab50c --- /dev/null +++ b/source4/ntvfs/cifs/README @@ -0,0 +1,40 @@ +This is the 'CIFS on CIFS' backend for Samba. It provides a NTVFS +backend that talks to a remote CIFS server. The primary aim of this +backend is for debugging and development, although some people may +find it useful as a CIFS gateway. + +There are two modes of operation: Password specified and delegated +credentials. + +Password specified: +------------------- + +This uses a static username/password in the config file, example: + +[myshare] + ntvfs handler = cifs + cifs:server = myserver + cifs:user = tridge + cifs:password = mypass + cifs:domain = TESTDOM + cifs:share = test + + +Delegated credentials: +---------------------- + +If your incoming user is authenticated with Kerberos, and the machine +account for this Samba4 proxy server is 'trusted for delegation', then +the Samba4 proxy can forward the client's credentials to the target. + +You must be joined to the domain (net join <domain> member). + +To set 'trusted for delegation' with MMC, see the checkbox in the +Computer account property page under Users and Computers. + +[myshare] + ntvfs handler = cifs + cifs:server = myserver + cifs:share = test + + diff --git a/source4/ntvfs/cifs/vfs_cifs.c b/source4/ntvfs/cifs/vfs_cifs.c new file mode 100644 index 0000000..121ff57 --- /dev/null +++ b/source4/ntvfs/cifs/vfs_cifs.c @@ -0,0 +1,1263 @@ +/* + Unix SMB/CIFS implementation. + + CIFS-on-CIFS NTVFS filesystem backend + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) James J Myers 2003 <myersjj@samba.org> + + 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/>. +*/ +/* + this implements a CIFS->CIFS NTVFS filesystem backend. + +*/ + +#include "includes.h" +#include "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/smb_composite/smb_composite.h" +#include "auth/auth.h" +#include "auth/credentials/credentials.h" +#include "ntvfs/ntvfs.h" +#include "../lib/util/dlinklist.h" +#include "param/param.h" +#include "libcli/resolve/resolve.h" +#include "../libcli/smb/smbXcli_base.h" + +struct cvfs_file { + struct cvfs_file *prev, *next; + uint16_t fnum; + struct ntvfs_handle *h; +}; + +/* this is stored in ntvfs_private */ +struct cvfs_private { + struct smbcli_tree *tree; + struct smbcli_transport *transport; + struct ntvfs_module_context *ntvfs; + struct async_info *pending; + struct cvfs_file *files; + bool map_generic; + bool map_trans2; +}; + + +/* a structure used to pass information to an async handler */ +struct async_info { + struct async_info *next, *prev; + struct cvfs_private *cvfs; + struct ntvfs_request *req; + struct smbcli_request *c_req; + struct cvfs_file *f; + void *parms; +}; + +NTSTATUS ntvfs_cifs_init(TALLOC_CTX *); + +#define CHECK_UPSTREAM_OPEN do { \ + if (!smbXcli_conn_is_connected(p->transport->conn)) { \ + req->async_states->state|=NTVFS_ASYNC_STATE_CLOSE; \ + return NT_STATUS_CONNECTION_DISCONNECTED; \ + } \ +} while(0) + +#define SETUP_PID do { \ + p->tree->session->pid = req->smbpid; \ + CHECK_UPSTREAM_OPEN; \ +} while(0) + +#define SETUP_FILE_HERE(f) do { \ + f = ntvfs_handle_get_backend_data(io->generic.in.file.ntvfs, ntvfs); \ + if (!f) return NT_STATUS_INVALID_HANDLE; \ + io->generic.in.file.fnum = f->fnum; \ +} while (0) + +#define SETUP_FILE do { \ + struct cvfs_file *f; \ + SETUP_FILE_HERE(f); \ +} while (0) + +#define SETUP_PID_AND_FILE do { \ + SETUP_PID; \ + SETUP_FILE; \ +} while (0) + +#define CIFS_SERVER "cifs:server" +#define CIFS_USER "cifs:user" +#define CIFS_PASSWORD "cifs:password" +#define CIFS_DOMAIN "cifs:domain" +#define CIFS_SHARE "cifs:share" +#define CIFS_USE_MACHINE_ACCT "cifs:use-machine-account" +#define CIFS_USE_S4U2PROXY "cifs:use-s4u2proxy" +#define CIFS_MAP_GENERIC "cifs:map-generic" +#define CIFS_MAP_TRANS2 "cifs:map-trans2" + +#define CIFS_USE_MACHINE_ACCT_DEFAULT false +#define CIFS_USE_S4U2PROXY_DEFAULT false +#define CIFS_MAP_GENERIC_DEFAULT false +#define CIFS_MAP_TRANS2_DEFAULT true + +/* + a handler for oplock break events from the server - these need to be passed + along to the client + */ +static bool oplock_handler(struct smbcli_transport *transport, uint16_t tid, uint16_t fnum, uint8_t level, void *p_private) +{ + struct cvfs_private *p = p_private; + NTSTATUS status; + struct ntvfs_handle *h = NULL; + struct cvfs_file *f; + + for (f=p->files; f; f=f->next) { + if (f->fnum != fnum) continue; + h = f->h; + break; + } + + if (!h) { + DEBUG(5,("vfs_cifs: ignoring oplock break level %d for fnum %d\n", level, fnum)); + return true; + } + + DEBUG(5,("vfs_cifs: sending oplock break level %d for fnum %d\n", level, fnum)); + status = ntvfs_send_oplock_break(p->ntvfs, h, level); + if (!NT_STATUS_IS_OK(status)) return false; + return true; +} + +/* + connect to a share - used when a tree_connect operation comes in. +*/ +static NTSTATUS cvfs_connect(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_tcon *tcon) +{ + NTSTATUS status; + struct cvfs_private *p; + const char *host, *user, *pass, *domain, *remote_share; + struct smb_composite_connect io; + struct composite_context *creq; + struct share_config *scfg = ntvfs->ctx->config; + + struct cli_credentials *credentials; + bool machine_account; + bool s4u2proxy; + const char* sharename; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(req); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + 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: + sharename = tcon->smb2.in.path; + break; + default: + status = NT_STATUS_INVALID_LEVEL; + goto out; + } + + if (strncmp(sharename, "\\\\", 2) == 0) { + char *str = strchr(sharename+2, '\\'); + if (str) { + sharename = str + 1; + } + } + + /* Here we need to determine which server to connect to. + * For now we use parametric options, type cifs. + */ + host = share_string_option(tmp_ctx, scfg, CIFS_SERVER, NULL); + user = share_string_option(tmp_ctx, scfg, CIFS_USER, NULL); + pass = share_string_option(tmp_ctx, scfg, CIFS_PASSWORD, NULL); + domain = share_string_option(tmp_ctx, scfg, CIFS_DOMAIN, NULL); + remote_share = share_string_option(tmp_ctx, scfg, CIFS_SHARE, NULL); + if (!remote_share) { + remote_share = sharename; + } + + machine_account = share_bool_option(scfg, CIFS_USE_MACHINE_ACCT, CIFS_USE_MACHINE_ACCT_DEFAULT); + s4u2proxy = share_bool_option(scfg, CIFS_USE_S4U2PROXY, CIFS_USE_S4U2PROXY_DEFAULT); + + p = talloc_zero(ntvfs, struct cvfs_private); + if (!p) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + ntvfs->private_data = p; + + if (!host) { + DEBUG(1,("CIFS backend: You must supply server\n")); + status = NT_STATUS_INVALID_PARAMETER; + goto out; + } + + if (user && pass) { + DEBUG(5, ("CIFS backend: Using specified password\n")); + credentials = cli_credentials_init(p); + if (!credentials) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + cli_credentials_set_conf(credentials, ntvfs->ctx->lp_ctx); + cli_credentials_set_username(credentials, user, CRED_SPECIFIED); + if (domain) { + cli_credentials_set_domain(credentials, domain, CRED_SPECIFIED); + } + cli_credentials_set_password(credentials, pass, CRED_SPECIFIED); + } else if (machine_account) { + DEBUG(5, ("CIFS backend: Using machine account\n")); + credentials = cli_credentials_init_server(p, + ntvfs->ctx->lp_ctx); + if (credentials == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + } else if (req->session_info->credentials) { + DEBUG(5, ("CIFS backend: Using delegated credentials\n")); + credentials = req->session_info->credentials; + } else if (s4u2proxy) { + struct ccache_container *ccc = NULL; + const char *err_str = NULL; + int ret; + char *impersonate_principal; + char *self_service; + char *target_service; + + impersonate_principal = talloc_asprintf(req, "%s@%s", + req->session_info->info->account_name, + req->session_info->info->domain_name); + + self_service = talloc_asprintf(req, "cifs/%s", + lpcfg_netbios_name(ntvfs->ctx->lp_ctx)); + + target_service = talloc_asprintf(req, "cifs/%s", host); + + DEBUG(5, ("CIFS backend: Using S4U2Proxy credentials\n")); + + credentials = cli_credentials_init_server(p, + ntvfs->ctx->lp_ctx); + if (credentials == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + cli_credentials_invalidate_ccache(credentials, CRED_SPECIFIED); + cli_credentials_set_impersonate_principal(credentials, + impersonate_principal, + self_service); + cli_credentials_set_target_service(credentials, target_service); + ret = cli_credentials_get_ccache(credentials, + ntvfs->ctx->event_ctx, + ntvfs->ctx->lp_ctx, + &ccc, + &err_str); + if (ret != 0) { + status = NT_STATUS_CROSSREALM_DELEGATION_FAILURE; + DEBUG(1,("S4U2Proxy: cli_credentials_get_ccache() gave: ret[%d] str[%s] - %s\n", + ret, err_str, nt_errstr(status))); + goto out; + } + + } else { + DEBUG(1,("CIFS backend: NO delegated credentials found: You must supply server, user and password or the client must supply delegated credentials\n")); + status = NT_STATUS_INTERNAL_ERROR; + goto out; + } + + /* connect to the server, using the smbd event context */ + io.in.dest_host = host; + io.in.dest_ports = lpcfg_smb_ports(ntvfs->ctx->lp_ctx); + io.in.socket_options = lpcfg_socket_options(ntvfs->ctx->lp_ctx); + io.in.called_name = host; + io.in.existing_conn = NULL; + io.in.credentials = credentials; + io.in.fallback_to_anonymous = false; + io.in.workgroup = lpcfg_workgroup(ntvfs->ctx->lp_ctx); + io.in.service = remote_share; + io.in.service_type = "?????"; + io.in.gensec_settings = lpcfg_gensec_settings(p, ntvfs->ctx->lp_ctx); + lpcfg_smbcli_options(ntvfs->ctx->lp_ctx, &io.in.options); + lpcfg_smbcli_session_options(ntvfs->ctx->lp_ctx, &io.in.session_options); + + if (!(ntvfs->ctx->client_caps & NTVFS_CLIENT_CAP_LEVEL_II_OPLOCKS)) { + io.in.options.use_level2_oplocks = false; + } + + creq = smb_composite_connect_send(&io, p, + lpcfg_resolve_context(ntvfs->ctx->lp_ctx), + ntvfs->ctx->event_ctx); + status = smb_composite_connect_recv(creq, p); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + p->tree = io.out.tree; + + p->transport = p->tree->session->transport; + SETUP_PID; + p->ntvfs = ntvfs; + + ntvfs->ctx->fs_type = talloc_strdup(ntvfs->ctx, "NTFS"); + if (ntvfs->ctx->fs_type == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + ntvfs->ctx->dev_type = talloc_strdup(ntvfs->ctx, "A:"); + if (ntvfs->ctx->dev_type == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + 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; + } + + /* we need to receive oplock break requests from the server */ + smbcli_oplock_handler(p->transport, oplock_handler, p); + + p->map_generic = share_bool_option(scfg, CIFS_MAP_GENERIC, CIFS_MAP_GENERIC_DEFAULT); + + p->map_trans2 = share_bool_option(scfg, CIFS_MAP_TRANS2, CIFS_MAP_TRANS2_DEFAULT); + + status = NT_STATUS_OK; + +out: + TALLOC_FREE(tmp_ctx); + return status; +} + +/* + disconnect from a share +*/ +static NTSTATUS cvfs_disconnect(struct ntvfs_module_context *ntvfs) +{ + struct cvfs_private *p = ntvfs->private_data; + struct async_info *a, *an; + + /* first cleanup pending requests */ + for (a=p->pending; a; a = an) { + an = a->next; + smbcli_request_destroy(a->c_req); + talloc_free(a); + } + + talloc_free(p); + ntvfs->private_data = NULL; + + return NT_STATUS_OK; +} + +/* + destroy an async info structure +*/ +static int async_info_destructor(struct async_info *async) +{ + DLIST_REMOVE(async->cvfs->pending, async); + return 0; +} + +/* + a handler for simple async replies + this handler can only be used for functions that don't return any + parameters (those that just return a status code) + */ +static void async_simple(struct smbcli_request *c_req) +{ + struct async_info *async = c_req->async.private_data; + struct ntvfs_request *req = async->req; + req->async_states->status = smbcli_request_simple_recv(c_req); + talloc_free(async); + req->async_states->send_fn(req); +} + + +/* save some typing for the simple functions */ +#define ASYNC_RECV_TAIL_F(io, async_fn, file) do { \ + if (!c_req) return NT_STATUS_UNSUCCESSFUL; \ + { \ + struct async_info *async; \ + async = talloc(req, struct async_info); \ + if (!async) return NT_STATUS_NO_MEMORY; \ + async->parms = io; \ + async->req = req; \ + async->f = file; \ + async->cvfs = p; \ + async->c_req = c_req; \ + DLIST_ADD(p->pending, async); \ + c_req->async.private_data = async; \ + talloc_set_destructor(async, async_info_destructor); \ + } \ + c_req->async.fn = async_fn; \ + req->async_states->state |= NTVFS_ASYNC_STATE_ASYNC; \ + return NT_STATUS_OK; \ +} while (0) + +#define ASYNC_RECV_TAIL(io, async_fn) ASYNC_RECV_TAIL_F(io, async_fn, NULL) + +#define SIMPLE_ASYNC_TAIL ASYNC_RECV_TAIL(NULL, async_simple) + +/* + delete a file - the dirtype specifies the file types to include in the search. + The name can contain CIFS wildcards, but rarely does (except with OS/2 clients) +*/ +static NTSTATUS cvfs_unlink(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_unlink *unl) +{ + struct cvfs_private *p = ntvfs->private_data; + struct smbcli_request *c_req; + + SETUP_PID; + + /* see if the front end will allow us to perform this + function asynchronously. */ + if (!(req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return smb_raw_unlink(p->tree, unl); + } + + c_req = smb_raw_unlink_send(p->tree, unl); + + SIMPLE_ASYNC_TAIL; +} + +/* + a handler for async ioctl replies + */ +static void async_ioctl(struct smbcli_request *c_req) +{ + struct async_info *async = c_req->async.private_data; + struct ntvfs_request *req = async->req; + req->async_states->status = smb_raw_ioctl_recv(c_req, req, async->parms); + talloc_free(async); + req->async_states->send_fn(req); +} + +/* + ioctl interface +*/ +static NTSTATUS cvfs_ioctl(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_ioctl *io) +{ + struct cvfs_private *p = ntvfs->private_data; + struct smbcli_request *c_req; + + SETUP_PID_AND_FILE; + + /* see if the front end will allow us to perform this + function asynchronously. */ + if (!(req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return smb_raw_ioctl(p->tree, req, io); + } + + c_req = smb_raw_ioctl_send(p->tree, io); + + ASYNC_RECV_TAIL(io, async_ioctl); +} + +/* + check if a directory exists +*/ +static NTSTATUS cvfs_chkpath(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_chkpath *cp) +{ + struct cvfs_private *p = ntvfs->private_data; + struct smbcli_request *c_req; + + SETUP_PID; + + if (!(req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return smb_raw_chkpath(p->tree, cp); + } + + c_req = smb_raw_chkpath_send(p->tree, cp); + + SIMPLE_ASYNC_TAIL; +} + +/* + a handler for async qpathinfo replies + */ +static void async_qpathinfo(struct smbcli_request *c_req) +{ + struct async_info *async = c_req->async.private_data; + struct ntvfs_request *req = async->req; + req->async_states->status = smb_raw_pathinfo_recv(c_req, req, async->parms); + talloc_free(async); + req->async_states->send_fn(req); +} + +/* + return info on a pathname +*/ +static NTSTATUS cvfs_qpathinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_fileinfo *info) +{ + struct cvfs_private *p = ntvfs->private_data; + struct smbcli_request *c_req; + + SETUP_PID; + + if (!(req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return smb_raw_pathinfo(p->tree, req, info); + } + + c_req = smb_raw_pathinfo_send(p->tree, info); + + ASYNC_RECV_TAIL(info, async_qpathinfo); +} + +/* + a handler for async qfileinfo replies + */ +static void async_qfileinfo(struct smbcli_request *c_req) +{ + struct async_info *async = c_req->async.private_data; + struct ntvfs_request *req = async->req; + req->async_states->status = smb_raw_fileinfo_recv(c_req, req, async->parms); + talloc_free(async); + req->async_states->send_fn(req); +} + +/* + query info on a open file +*/ +static NTSTATUS cvfs_qfileinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_fileinfo *io) +{ + struct cvfs_private *p = ntvfs->private_data; + struct smbcli_request *c_req; + + SETUP_PID_AND_FILE; + + if (!(req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return smb_raw_fileinfo(p->tree, req, io); + } + + c_req = smb_raw_fileinfo_send(p->tree, io); + + ASYNC_RECV_TAIL(io, async_qfileinfo); +} + + +/* + set info on a pathname +*/ +static NTSTATUS cvfs_setpathinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_setfileinfo *st) +{ + struct cvfs_private *p = ntvfs->private_data; + struct smbcli_request *c_req; + + SETUP_PID; + + if (!(req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return smb_raw_setpathinfo(p->tree, st); + } + + c_req = smb_raw_setpathinfo_send(p->tree, st); + + SIMPLE_ASYNC_TAIL; +} + + +/* + a handler for async open replies + */ +static void async_open(struct smbcli_request *c_req) +{ + struct async_info *async = c_req->async.private_data; + struct cvfs_private *cvfs = async->cvfs; + struct ntvfs_request *req = async->req; + struct cvfs_file *f = async->f; + union smb_open *io = async->parms; + union smb_handle *file; + if (f == NULL) { + goto failed; + } + talloc_free(async); + req->async_states->status = smb_raw_open_recv(c_req, req, io); + SMB_OPEN_OUT_FILE(io, file); + if (file == NULL) { + goto failed; + } + f->fnum = file->fnum; + file->ntvfs = NULL; + if (!NT_STATUS_IS_OK(req->async_states->status)) goto failed; + req->async_states->status = ntvfs_handle_set_backend_data(f->h, cvfs->ntvfs, f); + if (!NT_STATUS_IS_OK(req->async_states->status)) goto failed; + file->ntvfs = f->h; + DLIST_ADD(cvfs->files, f); +failed: + req->async_states->send_fn(req); +} + +/* + open a file +*/ +static NTSTATUS cvfs_open(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_open *io) +{ + struct cvfs_private *p = ntvfs->private_data; + struct smbcli_request *c_req; + struct ntvfs_handle *h; + struct cvfs_file *f; + NTSTATUS status; + + SETUP_PID; + + if (io->generic.level != RAW_OPEN_GENERIC && + p->map_generic) { + return ntvfs_map_open(ntvfs, req, io); + } + + status = ntvfs_handle_new(ntvfs, req, &h); + NT_STATUS_NOT_OK_RETURN(status); + + f = talloc_zero(h, struct cvfs_file); + NT_STATUS_HAVE_NO_MEMORY(f); + f->h = h; + + if (!(req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + union smb_handle *file; + + status = smb_raw_open(p->tree, req, io); + NT_STATUS_NOT_OK_RETURN(status); + + SMB_OPEN_OUT_FILE(io, file); + if (file == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + f->fnum = file->fnum; + file->ntvfs = NULL; + status = ntvfs_handle_set_backend_data(f->h, p->ntvfs, f); + NT_STATUS_NOT_OK_RETURN(status); + file->ntvfs = f->h; + DLIST_ADD(p->files, f); + + return NT_STATUS_OK; + } + + c_req = smb_raw_open_send(p->tree, io); + + ASYNC_RECV_TAIL_F(io, async_open, f); +} + +/* + create a directory +*/ +static NTSTATUS cvfs_mkdir(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_mkdir *md) +{ + struct cvfs_private *p = ntvfs->private_data; + struct smbcli_request *c_req; + + SETUP_PID; + + if (!(req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return smb_raw_mkdir(p->tree, md); + } + + c_req = smb_raw_mkdir_send(p->tree, md); + + SIMPLE_ASYNC_TAIL; +} + +/* + remove a directory +*/ +static NTSTATUS cvfs_rmdir(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, struct smb_rmdir *rd) +{ + struct cvfs_private *p = ntvfs->private_data; + struct smbcli_request *c_req; + + SETUP_PID; + + if (!(req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return smb_raw_rmdir(p->tree, rd); + } + c_req = smb_raw_rmdir_send(p->tree, rd); + + SIMPLE_ASYNC_TAIL; +} + +/* + rename a set of files +*/ +static NTSTATUS cvfs_rename(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_rename *ren) +{ + struct cvfs_private *p = ntvfs->private_data; + struct smbcli_request *c_req; + + SETUP_PID; + + if (ren->nttrans.level == RAW_RENAME_NTTRANS) { + struct cvfs_file *f; + f = ntvfs_handle_get_backend_data(ren->nttrans.in.file.ntvfs, ntvfs); + if (!f) return NT_STATUS_INVALID_HANDLE; + ren->nttrans.in.file.fnum = f->fnum; + } + + if (!(req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return smb_raw_rename(p->tree, ren); + } + + c_req = smb_raw_rename_send(p->tree, ren); + + SIMPLE_ASYNC_TAIL; +} + +/* + copy a set of files +*/ +static NTSTATUS cvfs_copy(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, struct smb_copy *cp) +{ + return NT_STATUS_NOT_SUPPORTED; +} + +/* + a handler for async read replies + */ +static void async_read(struct smbcli_request *c_req) +{ + struct async_info *async = c_req->async.private_data; + struct ntvfs_request *req = async->req; + req->async_states->status = smb_raw_read_recv(c_req, async->parms); + talloc_free(async); + req->async_states->send_fn(req); +} + +/* + read from a file +*/ +static NTSTATUS cvfs_read(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_read *io) +{ + struct cvfs_private *p = ntvfs->private_data; + struct smbcli_request *c_req; + + SETUP_PID; + + if (io->generic.level != RAW_READ_GENERIC && + p->map_generic) { + return ntvfs_map_read(ntvfs, req, io); + } + + SETUP_FILE; + + if (!(req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return smb_raw_read(p->tree, io); + } + + c_req = smb_raw_read_send(p->tree, io); + + ASYNC_RECV_TAIL(io, async_read); +} + +/* + a handler for async write replies + */ +static void async_write(struct smbcli_request *c_req) +{ + struct async_info *async = c_req->async.private_data; + struct ntvfs_request *req = async->req; + req->async_states->status = smb_raw_write_recv(c_req, async->parms); + talloc_free(async); + req->async_states->send_fn(req); +} + +/* + write to a file +*/ +static NTSTATUS cvfs_write(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_write *io) +{ + struct cvfs_private *p = ntvfs->private_data; + struct smbcli_request *c_req; + + SETUP_PID; + + if (io->generic.level != RAW_WRITE_GENERIC && + p->map_generic) { + return ntvfs_map_write(ntvfs, req, io); + } + SETUP_FILE; + + if (!(req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return smb_raw_write(p->tree, io); + } + + c_req = smb_raw_write_send(p->tree, io); + + ASYNC_RECV_TAIL(io, async_write); +} + +/* + a handler for async seek replies + */ +static void async_seek(struct smbcli_request *c_req) +{ + struct async_info *async = c_req->async.private_data; + struct ntvfs_request *req = async->req; + req->async_states->status = smb_raw_seek_recv(c_req, async->parms); + talloc_free(async); + req->async_states->send_fn(req); +} + +/* + seek in a file +*/ +static NTSTATUS cvfs_seek(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_seek *io) +{ + struct cvfs_private *p = ntvfs->private_data; + struct smbcli_request *c_req; + + SETUP_PID_AND_FILE; + + if (!(req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return smb_raw_seek(p->tree, io); + } + + c_req = smb_raw_seek_send(p->tree, io); + + ASYNC_RECV_TAIL(io, async_seek); +} + +/* + flush a file +*/ +static NTSTATUS cvfs_flush(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_flush *io) +{ + struct cvfs_private *p = ntvfs->private_data; + struct smbcli_request *c_req; + + SETUP_PID; + switch (io->generic.level) { + case RAW_FLUSH_FLUSH: + SETUP_FILE; + break; + case RAW_FLUSH_ALL: + io->generic.in.file.fnum = 0xFFFF; + break; + case RAW_FLUSH_SMB2: + return NT_STATUS_INVALID_LEVEL; + } + + if (!(req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return smb_raw_flush(p->tree, io); + } + + c_req = smb_raw_flush_send(p->tree, io); + + SIMPLE_ASYNC_TAIL; +} + +/* + close a file +*/ +static NTSTATUS cvfs_close(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_close *io) +{ + struct cvfs_private *p = ntvfs->private_data; + struct smbcli_request *c_req; + struct cvfs_file *f; + union smb_close io2; + + SETUP_PID; + + if (io->generic.level != RAW_CLOSE_GENERIC && + p->map_generic) { + return ntvfs_map_close(ntvfs, req, io); + } + + if (io->generic.level == RAW_CLOSE_GENERIC) { + ZERO_STRUCT(io2); + io2.close.level = RAW_CLOSE_CLOSE; + io2.close.in.file = io->generic.in.file; + io2.close.in.write_time = io->generic.in.write_time; + io = &io2; + } + + SETUP_FILE_HERE(f); + /* Note, we aren't free-ing f, or it's h here. Should we? + even if file-close fails, we'll remove it from the list, + what else would we do? Maybe we should not remove until + after the proxied call completes? */ + DLIST_REMOVE(p->files, f); + + if (!(req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return smb_raw_close(p->tree, io); + } + + c_req = smb_raw_close_send(p->tree, io); + + SIMPLE_ASYNC_TAIL; +} + +/* + exit - closing files open by the pid +*/ +static NTSTATUS cvfs_exit(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req) +{ + struct cvfs_private *p = ntvfs->private_data; + struct smbcli_request *c_req; + + SETUP_PID; + + if (!(req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return smb_raw_exit(p->tree->session); + } + + c_req = smb_raw_exit_send(p->tree->session); + + SIMPLE_ASYNC_TAIL; +} + +/* + logoff - closing files open by the user +*/ +static NTSTATUS cvfs_logoff(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req) +{ + /* we can't do this right in the cifs backend .... */ + return NT_STATUS_OK; +} + +/* + setup for an async call - nothing to do yet +*/ +static NTSTATUS cvfs_async_setup(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + void *private_data) +{ + return NT_STATUS_OK; +} + +/* + cancel an async call +*/ +static NTSTATUS cvfs_cancel(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req) +{ + struct cvfs_private *p = ntvfs->private_data; + struct async_info *a; + + /* find the matching request */ + for (a=p->pending;a;a=a->next) { + if (a->req == req) { + break; + } + } + + if (a == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + return smb_raw_ntcancel(a->c_req); +} + +/* + lock a byte range +*/ +static NTSTATUS cvfs_lock(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_lock *io) +{ + struct cvfs_private *p = ntvfs->private_data; + struct smbcli_request *c_req; + + SETUP_PID; + + if (io->generic.level != RAW_LOCK_GENERIC && + p->map_generic) { + return ntvfs_map_lock(ntvfs, req, io); + } + SETUP_FILE; + + if (!(req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return smb_raw_lock(p->tree, io); + } + + c_req = smb_raw_lock_send(p->tree, io); + SIMPLE_ASYNC_TAIL; +} + +/* + set info on a open file +*/ +static NTSTATUS cvfs_setfileinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_setfileinfo *io) +{ + struct cvfs_private *p = ntvfs->private_data; + struct smbcli_request *c_req; + + SETUP_PID_AND_FILE; + + if (!(req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return smb_raw_setfileinfo(p->tree, io); + } + c_req = smb_raw_setfileinfo_send(p->tree, io); + + SIMPLE_ASYNC_TAIL; +} + + +/* + a handler for async fsinfo replies + */ +static void async_fsinfo(struct smbcli_request *c_req) +{ + struct async_info *async = c_req->async.private_data; + struct ntvfs_request *req = async->req; + req->async_states->status = smb_raw_fsinfo_recv(c_req, req, async->parms); + talloc_free(async); + req->async_states->send_fn(req); +} + +/* + return filesystem space info +*/ +static NTSTATUS cvfs_fsinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_fsinfo *fs) +{ + struct cvfs_private *p = ntvfs->private_data; + struct smbcli_request *c_req; + + SETUP_PID; + + if (!(req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return smb_raw_fsinfo(p->tree, req, fs); + } + + c_req = smb_raw_fsinfo_send(p->tree, req, fs); + + ASYNC_RECV_TAIL(fs, async_fsinfo); +} + +/* + return print queue info +*/ +static NTSTATUS cvfs_lpq(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_lpq *lpq) +{ + return NT_STATUS_NOT_SUPPORTED; +} + +/* + list files in a directory matching a wildcard pattern +*/ +static NTSTATUS cvfs_search_first(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_search_first *io, + void *search_private, + bool (*callback)(void *, const union smb_search_data *)) +{ + struct cvfs_private *p = ntvfs->private_data; + + SETUP_PID; + + return smb_raw_search_first(p->tree, req, io, search_private, callback); +} + +/* continue a search */ +static NTSTATUS cvfs_search_next(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_search_next *io, + void *search_private, + bool (*callback)(void *, const union smb_search_data *)) +{ + struct cvfs_private *p = ntvfs->private_data; + + SETUP_PID; + + return smb_raw_search_next(p->tree, req, io, search_private, callback); +} + +/* close a search */ +static NTSTATUS cvfs_search_close(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_search_close *io) +{ + struct cvfs_private *p = ntvfs->private_data; + + SETUP_PID; + + return smb_raw_search_close(p->tree, io); +} + +/* + a handler for async trans2 replies + */ +static void async_trans2(struct smbcli_request *c_req) +{ + struct async_info *async = c_req->async.private_data; + struct ntvfs_request *req = async->req; + req->async_states->status = smb_raw_trans2_recv(c_req, req, async->parms); + talloc_free(async); + req->async_states->send_fn(req); +} + +/* raw trans2 */ +static NTSTATUS cvfs_trans2(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + struct smb_trans2 *trans2) +{ + struct cvfs_private *p = ntvfs->private_data; + struct smbcli_request *c_req; + + if (p->map_trans2) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + SETUP_PID; + + if (!(req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return smb_raw_trans2(p->tree, req, trans2); + } + + c_req = smb_raw_trans2_send(p->tree, trans2); + + ASYNC_RECV_TAIL(trans2, async_trans2); +} + + +/* SMBtrans - not used on file shares */ +static NTSTATUS cvfs_trans(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + struct smb_trans2 *trans2) +{ + return NT_STATUS_ACCESS_DENIED; +} + +/* + a handler for async change notify replies + */ +static void async_changenotify(struct smbcli_request *c_req) +{ + struct async_info *async = c_req->async.private_data; + struct ntvfs_request *req = async->req; + req->async_states->status = smb_raw_changenotify_recv(c_req, req, async->parms); + talloc_free(async); + req->async_states->send_fn(req); +} + +/* change notify request - always async */ +static NTSTATUS cvfs_notify(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_notify *io) +{ + struct cvfs_private *p = ntvfs->private_data; + struct smbcli_request *c_req; + int saved_timeout = p->transport->options.request_timeout; + struct cvfs_file *f; + + if (io->nttrans.level != RAW_NOTIFY_NTTRANS) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + SETUP_PID; + + f = ntvfs_handle_get_backend_data(io->nttrans.in.file.ntvfs, ntvfs); + if (!f) return NT_STATUS_INVALID_HANDLE; + io->nttrans.in.file.fnum = f->fnum; + + /* this request doesn't make sense unless its async */ + if (!(req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* we must not timeout on notify requests - they wait + forever */ + p->transport->options.request_timeout = 0; + + c_req = smb_raw_changenotify_send(p->tree, io); + + p->transport->options.request_timeout = saved_timeout; + + ASYNC_RECV_TAIL(io, async_changenotify); +} + +/* + initialise the CIFS->CIFS backend, registering ourselves with the ntvfs subsystem + */ +NTSTATUS ntvfs_cifs_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret; + struct ntvfs_ops ops; + NTVFS_CURRENT_CRITICAL_SIZES(vers); + + ZERO_STRUCT(ops); + + /* fill in the name and type */ + ops.name = "cifs"; + ops.type = NTVFS_DISK; + + /* fill in all the operations */ + ops.connect_fn = cvfs_connect; + ops.disconnect_fn = cvfs_disconnect; + ops.unlink_fn = cvfs_unlink; + ops.chkpath_fn = cvfs_chkpath; + ops.qpathinfo_fn = cvfs_qpathinfo; + ops.setpathinfo_fn = cvfs_setpathinfo; + ops.open_fn = cvfs_open; + ops.mkdir_fn = cvfs_mkdir; + ops.rmdir_fn = cvfs_rmdir; + ops.rename_fn = cvfs_rename; + ops.copy_fn = cvfs_copy; + ops.ioctl_fn = cvfs_ioctl; + ops.read_fn = cvfs_read; + ops.write_fn = cvfs_write; + ops.seek_fn = cvfs_seek; + ops.flush_fn = cvfs_flush; + ops.close_fn = cvfs_close; + ops.exit_fn = cvfs_exit; + ops.lock_fn = cvfs_lock; + ops.setfileinfo_fn = cvfs_setfileinfo; + ops.qfileinfo_fn = cvfs_qfileinfo; + ops.fsinfo_fn = cvfs_fsinfo; + ops.lpq_fn = cvfs_lpq; + ops.search_first_fn = cvfs_search_first; + ops.search_next_fn = cvfs_search_next; + ops.search_close_fn = cvfs_search_close; + ops.trans_fn = cvfs_trans; + ops.logoff_fn = cvfs_logoff; + ops.async_setup_fn = cvfs_async_setup; + ops.cancel_fn = cvfs_cancel; + ops.notify_fn = cvfs_notify; + ops.trans2_fn = cvfs_trans2; + + /* register ourselves with the NTVFS subsystem. We register + under the name 'cifs'. */ + ret = ntvfs_register(&ops, &vers); + + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register CIFS backend!\n")); + } + + return ret; +} diff --git a/source4/ntvfs/common/brlock.c b/source4/ntvfs/common/brlock.c new file mode 100644 index 0000000..d99cc4e --- /dev/null +++ b/source4/ntvfs/common/brlock.c @@ -0,0 +1,136 @@ +/* + Unix SMB/CIFS implementation. + + generic byte range locking code + + Copyright (C) Andrew Tridgell 1992-2004 + Copyright (C) Jeremy Allison 1992-2000 + + 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/>. +*/ + +/* This module implements a tdb based byte range locking service, + replacing the fcntl() based byte range locking previously + used. This allows us to provide the same semantics as NT */ + +#include "includes.h" +#include "system/filesys.h" +#include "messaging/messaging.h" +#include "lib/messaging/irpc.h" +#include "libcli/libcli.h" +#include "cluster/cluster.h" +#include "ntvfs/common/ntvfs_common.h" + +static const struct brlock_ops *ops; + +/* + set the brl backend ops +*/ +void brlock_set_ops(const struct brlock_ops *new_ops) +{ + ops = new_ops; +} + +/* + Open up the brlock database. Close it down using talloc_free(). We + need the imessaging_ctx to allow for pending lock notifications. +*/ +struct brl_context *brlock_init(TALLOC_CTX *mem_ctx, struct server_id server, + struct loadparm_context *lp_ctx, + struct imessaging_context *imessaging_ctx) +{ + if (ops == NULL) { + brl_tdb_init_ops(); + } + return ops->brl_init(mem_ctx, server, lp_ctx, imessaging_ctx); +} + +struct brl_handle *brlock_create_handle(TALLOC_CTX *mem_ctx, struct ntvfs_handle *ntvfs, DATA_BLOB *file_key) +{ + return ops->brl_create_handle(mem_ctx, ntvfs, file_key); +} + +/* + Lock a range of bytes. The lock_type can be a PENDING_*_LOCK, in + which case a real lock is first tried, and if that fails then a + pending lock is created. When the pending lock is triggered (by + someone else closing an overlapping lock range) a messaging + notification is sent, identified by the notify_ptr +*/ +NTSTATUS brlock_lock(struct brl_context *brl, + struct brl_handle *brlh, + uint32_t smbpid, + uint64_t start, uint64_t size, + enum brl_type lock_type, + void *notify_ptr) +{ + return ops->brl_lock(brl, brlh, smbpid, start, size, lock_type, notify_ptr); +} + + +/* + Unlock a range of bytes. +*/ +NTSTATUS brlock_unlock(struct brl_context *brl, + struct brl_handle *brlh, + uint32_t smbpid, + uint64_t start, uint64_t size) +{ + return ops->brl_unlock(brl, brlh, smbpid, start, size); +} + +/* + remove a pending lock. This is called when the caller has either + given up trying to establish a lock or when they have succeeded in + getting it. In either case they no longer need to be notified. +*/ +NTSTATUS brlock_remove_pending(struct brl_context *brl, + struct brl_handle *brlh, + void *notify_ptr) +{ + return ops->brl_remove_pending(brl, brlh, notify_ptr); +} + + +/* + Test if we are allowed to perform IO on a region of an open file +*/ +NTSTATUS brlock_locktest(struct brl_context *brl, + struct brl_handle *brlh, + uint32_t smbpid, + uint64_t start, uint64_t size, + enum brl_type lock_type) +{ + return ops->brl_locktest(brl, brlh, smbpid, start, size, lock_type); +} + + +/* + Remove any locks associated with a open file. +*/ +NTSTATUS brlock_close(struct brl_context *brl, + struct brl_handle *brlh) +{ + return ops->brl_close(brl, brlh); +} + +/* + Get a number of locks associated with a open file. +*/ +NTSTATUS brlock_count(struct brl_context *brl, + struct brl_handle *brlh, + int *count) +{ + return ops->brl_count(brl, brlh, count); +} diff --git a/source4/ntvfs/common/brlock.h b/source4/ntvfs/common/brlock.h new file mode 100644 index 0000000..650136b --- /dev/null +++ b/source4/ntvfs/common/brlock.h @@ -0,0 +1,55 @@ +/* + Unix SMB/CIFS implementation. + + generic byte range locking code - common include + + Copyright (C) Andrew Tridgell 2006 + + 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 "libcli/libcli.h" + +struct brlock_ops { + struct brl_context *(*brl_init)(TALLOC_CTX *, struct server_id , + struct loadparm_context *lp_ctx, + struct imessaging_context *); + struct brl_handle *(*brl_create_handle)(TALLOC_CTX *, struct ntvfs_handle *, DATA_BLOB *); + NTSTATUS (*brl_lock)(struct brl_context *, + struct brl_handle *, + uint32_t , + uint64_t , uint64_t , + enum brl_type , + void *); + NTSTATUS (*brl_unlock)(struct brl_context *, + struct brl_handle *, + uint32_t , + uint64_t , uint64_t ); + NTSTATUS (*brl_remove_pending)(struct brl_context *, + struct brl_handle *, + void *); + NTSTATUS (*brl_locktest)(struct brl_context *, + struct brl_handle *, + uint32_t , + uint64_t , uint64_t , + enum brl_type ); + NTSTATUS (*brl_close)(struct brl_context *, + struct brl_handle *); + NTSTATUS (*brl_count)(struct brl_context *, + struct brl_handle *, + int *count); +}; + +void brlock_set_ops(const struct brlock_ops *new_ops); +void brl_tdb_init_ops(void); diff --git a/source4/ntvfs/common/brlock_tdb.c b/source4/ntvfs/common/brlock_tdb.c new file mode 100644 index 0000000..c4cd76b --- /dev/null +++ b/source4/ntvfs/common/brlock_tdb.c @@ -0,0 +1,775 @@ +/* + Unix SMB/CIFS implementation. + + generic byte range locking code - tdb backend + + Copyright (C) Andrew Tridgell 1992-2006 + Copyright (C) Jeremy Allison 1992-2000 + + 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/>. +*/ + +/* This module implements a tdb based byte range locking service, + replacing the fcntl() based byte range locking previously + used. This allows us to provide the same semantics as NT */ + +#include "includes.h" +#include "system/filesys.h" +#include "messaging/messaging.h" +#include "lib/messaging/irpc.h" +#include "libcli/libcli.h" +#include "cluster/cluster.h" +#include "ntvfs/common/brlock.h" +#include "ntvfs/ntvfs.h" +#include "param/param.h" +#include "dbwrap/dbwrap.h" + +/* + in this module a "DATA_BLOB *file_key" is a blob that uniquely identifies + a file. For a local posix filesystem this will usually be a combination + of the device and inode numbers of the file, but it can be anything + that uniquely identifies a file for locking purposes, as long + as it is applied consistently. +*/ + +/* this struct is typically attached to tcon */ +struct brl_context { + struct db_context *db; + struct server_id server; + struct imessaging_context *imessaging_ctx; +}; + +/* + the lock context contains the elements that define whether one + lock is the same as another lock +*/ +struct lock_context { + struct server_id server; + uint32_t smbpid; + struct brl_context *ctx; +}; + +/* The data in brlock records is an unsorted linear array of these + records. It is unnecessary to store the count as tdb provides the + size of the record */ +struct lock_struct { + struct lock_context context; + struct ntvfs_handle *ntvfs; + uint64_t start; + uint64_t size; + enum brl_type lock_type; + void *notify_ptr; +}; + +/* this struct is attached to on oprn file handle */ +struct brl_handle { + DATA_BLOB key; + struct ntvfs_handle *ntvfs; + struct lock_struct last_lock; +}; + +/* see if we have wrapped locks, which are no longer allowed (windows + * changed this in win7 */ +static bool brl_invalid_lock_range(uint64_t start, uint64_t size) +{ + return (size > 1 && (start + size < start)); +} + +/* + Open up the brlock.tdb database. Close it down using + talloc_free(). We need the imessaging_ctx to allow for + pending lock notifications. +*/ +static struct brl_context *brl_tdb_init(TALLOC_CTX *mem_ctx, struct server_id server, + struct loadparm_context *lp_ctx, + struct imessaging_context *imessaging_ctx) +{ + struct brl_context *brl; + + brl = talloc(mem_ctx, struct brl_context); + if (brl == NULL) { + return NULL; + } + + brl->db = cluster_db_tmp_open(brl, lp_ctx, "brlock", TDB_DEFAULT); + if (brl->db == NULL) { + talloc_free(brl); + return NULL; + } + + brl->server = server; + brl->imessaging_ctx = imessaging_ctx; + + return brl; +} + +static struct brl_handle *brl_tdb_create_handle(TALLOC_CTX *mem_ctx, struct ntvfs_handle *ntvfs, + DATA_BLOB *file_key) +{ + struct brl_handle *brlh; + + brlh = talloc(mem_ctx, struct brl_handle); + if (brlh == NULL) { + return NULL; + } + + brlh->key = *file_key; + brlh->ntvfs = ntvfs; + ZERO_STRUCT(brlh->last_lock); + + return brlh; +} + +/* + see if two locking contexts are equal +*/ +static bool brl_tdb_same_context(struct lock_context *ctx1, struct lock_context *ctx2) +{ + return (cluster_id_equal(&ctx1->server, &ctx2->server) && + ctx1->smbpid == ctx2->smbpid && + ctx1->ctx == ctx2->ctx); +} + +/* + see if lck1 and lck2 overlap + + lck1 is the existing lock. lck2 is the new lock we are + looking at adding +*/ +static bool brl_tdb_overlap(struct lock_struct *lck1, + struct lock_struct *lck2) +{ + /* this extra check is not redundant - it copes with locks + that go beyond the end of 64 bit file space */ + if (lck1->size != 0 && + lck1->start == lck2->start && + lck1->size == lck2->size) { + return true; + } + + if (lck1->start >= (lck2->start+lck2->size) || + lck2->start >= (lck1->start+lck1->size)) { + return false; + } + + /* we have a conflict. Now check to see if lck1 really still + * exists, which involves checking if the process still + * exists. We leave this test to last as its the most + * expensive test, especially when we are clustered */ + /* TODO: need to do this via a server_id_exists() call, which + * hasn't been written yet. When clustered this will need to + * call into ctdb */ + + return true; +} + +/* + See if lock2 can be added when lock1 is in place. +*/ +static bool brl_tdb_conflict(struct lock_struct *lck1, + struct lock_struct *lck2) +{ + /* pending locks don't conflict with anything */ + if (lck1->lock_type >= PENDING_READ_LOCK || + lck2->lock_type >= PENDING_READ_LOCK) { + return false; + } + + if (lck1->lock_type == READ_LOCK && lck2->lock_type == READ_LOCK) { + return false; + } + + if (brl_tdb_same_context(&lck1->context, &lck2->context) && + lck2->lock_type == READ_LOCK && lck1->ntvfs == lck2->ntvfs) { + return false; + } + + return brl_tdb_overlap(lck1, lck2); +} + + +/* + Check to see if this lock conflicts, but ignore our own locks on the + same fnum only. +*/ +static bool brl_tdb_conflict_other(struct lock_struct *lck1, struct lock_struct *lck2) +{ + /* pending locks don't conflict with anything */ + if (lck1->lock_type >= PENDING_READ_LOCK || + lck2->lock_type >= PENDING_READ_LOCK) { + return false; + } + + if (lck1->lock_type == READ_LOCK && lck2->lock_type == READ_LOCK) + return false; + + /* + * note that incoming write calls conflict with existing READ + * locks even if the context is the same. JRA. See LOCKTEST7 + * in smbtorture. + */ + if (brl_tdb_same_context(&lck1->context, &lck2->context) && + lck1->ntvfs == lck2->ntvfs && + (lck2->lock_type == READ_LOCK || lck1->lock_type == WRITE_LOCK)) { + return false; + } + + return brl_tdb_overlap(lck1, lck2); +} + + +/* + amazingly enough, w2k3 "remembers" whether the last lock failure + is the same as this one and changes its error code. I wonder if any + app depends on this? +*/ +static NTSTATUS brl_tdb_lock_failed(struct brl_handle *brlh, struct lock_struct *lock) +{ + /* + * this function is only called for non pending lock! + */ + + /* in SMB2 mode always return NT_STATUS_LOCK_NOT_GRANTED! */ + if (lock->ntvfs->ctx->protocol >= PROTOCOL_SMB2_02) { + return NT_STATUS_LOCK_NOT_GRANTED; + } + + /* + * if the notify_ptr is non NULL, + * it means that we're at the end of a pending lock + * and the real lock is requested after the timeout went by + * In this case we need to remember the last_lock and always + * give FILE_LOCK_CONFLICT + */ + if (lock->notify_ptr) { + brlh->last_lock = *lock; + return NT_STATUS_FILE_LOCK_CONFLICT; + } + + /* + * amazing the little things you learn with a test + * suite. Locks beyond this offset (as a 64 bit + * number!) always generate the conflict error code, + * unless the top bit is set + */ + if (lock->start >= 0xEF000000 && (lock->start >> 63) == 0) { + brlh->last_lock = *lock; + return NT_STATUS_FILE_LOCK_CONFLICT; + } + + /* + * if the current lock matches the last failed lock on the file handle + * and starts at the same offset, then FILE_LOCK_CONFLICT should be returned + */ + if (cluster_id_equal(&lock->context.server, &brlh->last_lock.context.server) && + lock->context.ctx == brlh->last_lock.context.ctx && + lock->ntvfs == brlh->last_lock.ntvfs && + lock->start == brlh->last_lock.start) { + return NT_STATUS_FILE_LOCK_CONFLICT; + } + + brlh->last_lock = *lock; + return NT_STATUS_LOCK_NOT_GRANTED; +} + +/* + Lock a range of bytes. The lock_type can be a PENDING_*_LOCK, in + which case a real lock is first tried, and if that fails then a + pending lock is created. When the pending lock is triggered (by + someone else closing an overlapping lock range) a messaging + notification is sent, identified by the notify_ptr +*/ +static NTSTATUS brl_tdb_lock(struct brl_context *brl, + struct brl_handle *brlh, + uint32_t smbpid, + uint64_t start, uint64_t size, + enum brl_type lock_type, + void *notify_ptr) +{ + TDB_DATA kbuf, dbuf; + int count=0, i; + struct lock_struct lock, *locks=NULL; + NTSTATUS status; + struct db_record *locked; + + kbuf.dptr = brlh->key.data; + kbuf.dsize = brlh->key.length; + + if (brl_invalid_lock_range(start, size)) { + return NT_STATUS_INVALID_LOCK_RANGE; + } + + locked = dbwrap_fetch_locked(brl->db, brl, kbuf); + if (!locked) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + /* if this is a pending lock, then with the chainlock held we + try to get the real lock. If we succeed then we don't need + to make it pending. This prevents a possible race condition + where the pending lock gets created after the lock that is + preventing the real lock gets removed */ + if (lock_type >= PENDING_READ_LOCK) { + enum brl_type rw = (lock_type==PENDING_READ_LOCK? READ_LOCK : WRITE_LOCK); + + /* here we need to force that the last_lock isn't overwritten */ + lock = brlh->last_lock; + status = brl_tdb_lock(brl, brlh, smbpid, start, size, rw, NULL); + brlh->last_lock = lock; + + if (NT_STATUS_IS_OK(status)) { + talloc_free(locked); + return NT_STATUS_OK; + } + } + + dbuf = dbwrap_record_get_value(locked); + + lock.context.smbpid = smbpid; + lock.context.server = brl->server; + lock.context.ctx = brl; + lock.ntvfs = brlh->ntvfs; + lock.context.ctx = brl; + lock.start = start; + lock.size = size; + lock.lock_type = lock_type; + lock.notify_ptr = notify_ptr; + + if (dbuf.dptr) { + /* there are existing locks - make sure they don't conflict */ + locks = (struct lock_struct *)dbuf.dptr; + count = dbuf.dsize / sizeof(*locks); + for (i=0; i<count; i++) { + if (brl_tdb_conflict(&locks[i], &lock)) { + status = brl_tdb_lock_failed(brlh, &lock); + goto fail; + } + } + } + + /* no conflicts - add it to the list of locks */ + /* FIXME: a dbwrap_record_append() would help here! */ + locks = talloc_array(locked, struct lock_struct, count+1); + if (!locks) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + if (dbuf.dsize > 0) { + memcpy(locks, dbuf.dptr, dbuf.dsize); + } + locks[count] = lock; + + dbuf.dptr = (unsigned char *)locks; + dbuf.dsize += sizeof(lock); + + status = dbwrap_record_store(locked, dbuf, TDB_REPLACE); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + + talloc_free(locked); + + /* the caller needs to know if the real lock was granted. If + we have reached here then it must be a pending lock that + was granted, so tell them the lock failed */ + if (lock_type >= PENDING_READ_LOCK) { + return NT_STATUS_LOCK_NOT_GRANTED; + } + + return NT_STATUS_OK; + + fail: + talloc_free(locked); + return status; +} + + +/* + we are removing a lock that might be holding up a pending lock. Scan for pending + locks that cover this range and if we find any then notify the server that it should + retry the lock +*/ +static void brl_tdb_notify_unlock(struct brl_context *brl, + struct lock_struct *locks, int count, + struct lock_struct *removed_lock) +{ + int i, last_notice; + + /* the last_notice logic is to prevent stampeding on a lock + range. It prevents us sending hundreds of notifies on the + same range of bytes. It doesn't prevent all possible + stampedes, but it does prevent the most common problem */ + last_notice = -1; + + for (i=0;i<count;i++) { + if (locks[i].lock_type >= PENDING_READ_LOCK && + brl_tdb_overlap(&locks[i], removed_lock)) { + if (last_notice != -1 && brl_tdb_overlap(&locks[i], &locks[last_notice])) { + continue; + } + if (locks[i].lock_type == PENDING_WRITE_LOCK) { + last_notice = i; + } + imessaging_send_ptr(brl->imessaging_ctx, locks[i].context.server, + MSG_BRL_RETRY, locks[i].notify_ptr); + } + } +} + + +/* + send notifications for all pending locks - the file is being closed by this + user +*/ +static void brl_tdb_notify_all(struct brl_context *brl, + struct lock_struct *locks, int count) +{ + int i; + for (i=0;i<count;i++) { + if (locks->lock_type >= PENDING_READ_LOCK) { + brl_tdb_notify_unlock(brl, locks, count, &locks[i]); + } + } +} + + + +/* + Unlock a range of bytes. +*/ +static NTSTATUS brl_tdb_unlock(struct brl_context *brl, + struct brl_handle *brlh, + uint32_t smbpid, + uint64_t start, uint64_t size) +{ + TDB_DATA kbuf, dbuf; + int count, i; + struct lock_struct *locks, *lock = NULL; + struct lock_context context; + struct db_record *locked; + NTSTATUS status; + + kbuf.dptr = brlh->key.data; + kbuf.dsize = brlh->key.length; + + if (brl_invalid_lock_range(start, size)) { + return NT_STATUS_INVALID_LOCK_RANGE; + } + + locked = dbwrap_fetch_locked(brl->db, brl, kbuf); + if (!locked) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + dbuf = dbwrap_record_get_value(locked); + + context.smbpid = smbpid; + context.server = brl->server; + context.ctx = brl; + + /* there are existing locks - find a match */ + locks = (struct lock_struct *)dbuf.dptr; + count = dbuf.dsize / sizeof(*locks); + + for (i=0; i<count; i++) { + lock = &locks[i]; + if (brl_tdb_same_context(&lock->context, &context) && + lock->ntvfs == brlh->ntvfs && + lock->start == start && + lock->size == size && + lock->lock_type == WRITE_LOCK) { + break; + } + } + if (i < count) goto found; + + for (i=0; i<count; i++) { + lock = &locks[i]; + if (brl_tdb_same_context(&lock->context, &context) && + lock->ntvfs == brlh->ntvfs && + lock->start == start && + lock->size == size && + lock->lock_type < PENDING_READ_LOCK) { + break; + } + } + +found: + if (i < count) { + /* found it - delete it */ + if (count == 1) { + status = dbwrap_record_delete(locked); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + } else { + struct lock_struct removed_lock = *lock; + if (i < count-1) { + memmove(&locks[i], &locks[i+1], + sizeof(*locks)*((count-1) - i)); + } + count--; + + /* send notifications for any relevant pending locks */ + brl_tdb_notify_unlock(brl, locks, count, &removed_lock); + + dbuf.dsize = count * sizeof(*locks); + + status = dbwrap_record_store(locked, dbuf, TDB_REPLACE); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + } + + talloc_free(locked); + return NT_STATUS_OK; + } + + /* we didn't find it */ + status = NT_STATUS_RANGE_NOT_LOCKED; + + fail: + talloc_free(locked); + return status; +} + + +/* + remove a pending lock. This is called when the caller has either + given up trying to establish a lock or when they have succeeded in + getting it. In either case they no longer need to be notified. +*/ +static NTSTATUS brl_tdb_remove_pending(struct brl_context *brl, + struct brl_handle *brlh, + void *notify_ptr) +{ + TDB_DATA kbuf, dbuf; + int count, i; + struct lock_struct *locks; + NTSTATUS status; + struct db_record *locked; + + kbuf.dptr = brlh->key.data; + kbuf.dsize = brlh->key.length; + + locked = dbwrap_fetch_locked(brl->db, brl, kbuf); + if (!locked) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + dbuf = dbwrap_record_get_value(locked); + if (!dbuf.dptr) { + talloc_free(locked); + return NT_STATUS_RANGE_NOT_LOCKED; + } + + /* there are existing locks - find a match */ + locks = (struct lock_struct *)dbuf.dptr; + count = dbuf.dsize / sizeof(*locks); + + for (i=0; i<count; i++) { + struct lock_struct *lock = &locks[i]; + + if (lock->lock_type >= PENDING_READ_LOCK && + lock->notify_ptr == notify_ptr && + cluster_id_equal(&lock->context.server, &brl->server)) { + /* found it - delete it */ + if (count == 1) { + status = dbwrap_record_delete(locked); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + } else { + if (i < count-1) { + memmove(&locks[i], &locks[i+1], + sizeof(*locks)*((count-1) - i)); + } + count--; + dbuf.dsize = count * sizeof(*locks); + status = dbwrap_record_store(locked, dbuf, + TDB_REPLACE); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + } + + talloc_free(locked); + return NT_STATUS_OK; + } + } + + /* we didn't find it */ + status = NT_STATUS_RANGE_NOT_LOCKED; + + fail: + talloc_free(locked); + return status; +} + + +/* + Test if we are allowed to perform IO on a region of an open file +*/ +static NTSTATUS brl_tdb_locktest(struct brl_context *brl, + struct brl_handle *brlh, + uint32_t smbpid, + uint64_t start, uint64_t size, + enum brl_type lock_type) +{ + TDB_DATA kbuf, dbuf; + int count, i; + struct lock_struct lock, *locks; + NTSTATUS status; + + kbuf.dptr = brlh->key.data; + kbuf.dsize = brlh->key.length; + + if (brl_invalid_lock_range(start, size)) { + return NT_STATUS_INVALID_LOCK_RANGE; + } + + status = dbwrap_fetch(brl->db, brl, kbuf, &dbuf); + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + return NT_STATUS_OK; + } else if (!NT_STATUS_IS_OK(status)) { + return status; + } + + lock.context.smbpid = smbpid; + lock.context.server = brl->server; + lock.context.ctx = brl; + lock.ntvfs = brlh->ntvfs; + lock.start = start; + lock.size = size; + lock.lock_type = lock_type; + + /* there are existing locks - make sure they don't conflict */ + locks = (struct lock_struct *)dbuf.dptr; + count = dbuf.dsize / sizeof(*locks); + + for (i=0; i<count; i++) { + if (brl_tdb_conflict_other(&locks[i], &lock)) { + talloc_free(dbuf.dptr); + return NT_STATUS_FILE_LOCK_CONFLICT; + } + } + + talloc_free(dbuf.dptr); + return NT_STATUS_OK; +} + + +/* + Remove any locks associated with a open file. +*/ +static NTSTATUS brl_tdb_close(struct brl_context *brl, + struct brl_handle *brlh) +{ + TDB_DATA kbuf, dbuf; + int count, i, dcount=0; + struct lock_struct *locks; + struct db_record *locked; + NTSTATUS status; + + kbuf.dptr = brlh->key.data; + kbuf.dsize = brlh->key.length; + + locked = dbwrap_fetch_locked(brl->db, brl, kbuf); + if (!locked) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + dbuf = dbwrap_record_get_value(locked); + if (!dbuf.dptr) { + talloc_free(locked); + return NT_STATUS_OK; + } + + /* there are existing locks - remove any for this fnum */ + locks = (struct lock_struct *)dbuf.dptr; + count = dbuf.dsize / sizeof(*locks); + + for (i=0; i<count; i++) { + struct lock_struct *lock = &locks[i]; + + if (lock->context.ctx == brl && + cluster_id_equal(&lock->context.server, &brl->server) && + lock->ntvfs == brlh->ntvfs) { + /* found it - delete it */ + if (count > 1 && i < count-1) { + memmove(&locks[i], &locks[i+1], + sizeof(*locks)*((count-1) - i)); + } + count--; + i--; + dcount++; + } + } + + status = NT_STATUS_OK; + + if (count == 0) { + status = dbwrap_record_delete(locked); + } else if (dcount != 0) { + /* tell all pending lock holders for this file that + they have a chance now. This is a bit indiscriminant, + but works OK */ + brl_tdb_notify_all(brl, locks, count); + + dbuf.dsize = count * sizeof(*locks); + + status = dbwrap_record_store(locked, dbuf, TDB_REPLACE); + } + talloc_free(locked); + + return status; +} + +static NTSTATUS brl_tdb_count(struct brl_context *brl, struct brl_handle *brlh, + int *count) +{ + TDB_DATA kbuf, dbuf; + NTSTATUS status; + + kbuf.dptr = brlh->key.data; + kbuf.dsize = brlh->key.length; + *count = 0; + + status = dbwrap_fetch(brl->db, brl, kbuf, &dbuf); + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + return NT_STATUS_OK; + } else if (!NT_STATUS_IS_OK(status)) { + return status; + } + *count = dbuf.dsize / sizeof(struct lock_struct); + + talloc_free(dbuf.dptr); + + return NT_STATUS_OK; +} + +static const struct brlock_ops brlock_tdb_ops = { + .brl_init = brl_tdb_init, + .brl_create_handle = brl_tdb_create_handle, + .brl_lock = brl_tdb_lock, + .brl_unlock = brl_tdb_unlock, + .brl_remove_pending = brl_tdb_remove_pending, + .brl_locktest = brl_tdb_locktest, + .brl_close = brl_tdb_close, + .brl_count = brl_tdb_count +}; + + +void brl_tdb_init_ops(void) +{ + brlock_set_ops(&brlock_tdb_ops); +} diff --git a/source4/ntvfs/common/init.c b/source4/ntvfs/common/init.c new file mode 100644 index 0000000..f8f8e27 --- /dev/null +++ b/source4/ntvfs/common/init.c @@ -0,0 +1,34 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher 2006 + + 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/>. +*/ + +/* + this is the change notify database. It implements mechanisms for + storing current change notify waiters in a tdb, and checking if a + given event matches any of the stored notify waiiters. +*/ + +#include "includes.h" +#include "ntvfs/sysdep/sys_notify.h" + +NTSTATUS ntvfs_common_init(void); + +NTSTATUS ntvfs_common_init(void) +{ + return sys_notify_init(); +} diff --git a/source4/ntvfs/common/notify.c b/source4/ntvfs/common/notify.c new file mode 100644 index 0000000..a434fab --- /dev/null +++ b/source4/ntvfs/common/notify.c @@ -0,0 +1,687 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Andrew Tridgell 2006 + + 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/>. +*/ + +/* + this is the change notify database. It implements mechanisms for + storing current change notify waiters in a tdb, and checking if a + given event matches any of the stored notify waiiters. +*/ + +#include "includes.h" +#include "system/filesys.h" +#include "messaging/messaging.h" +#include "lib/messaging/irpc.h" +#include "librpc/gen_ndr/ndr_notify.h" +#include "../lib/util/dlinklist.h" +#include "ntvfs/common/ntvfs_common.h" +#include "ntvfs/sysdep/sys_notify.h" +#include "cluster/cluster.h" +#include "param/param.h" +#include "lib/util/tsort.h" +#include "lib/dbwrap/dbwrap.h" +#include "../lib/util/util_tdb.h" + +struct notify_context { + struct db_context *db; + struct server_id server; + struct imessaging_context *imessaging_ctx; + struct notify_list *list; + struct notify_array *array; + int64_t seqnum; + struct sys_notify_context *sys_notify_ctx; +}; + + +struct notify_list { + struct notify_list *next, *prev; + void *private_data; + void (*callback)(void *, const struct notify_event *); + void *sys_notify_handle; + int depth; +}; + +#define NOTIFY_KEY "notify array" + +#define NOTIFY_ENABLE "notify:enable" +#define NOTIFY_ENABLE_DEFAULT true + +static NTSTATUS notify_remove_all(struct notify_context *notify); +static void notify_handler(struct imessaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + size_t num_fds, + int *fds, + DATA_BLOB *data); + +/* + destroy the notify context +*/ +static int notify_destructor(struct notify_context *notify) +{ + imessaging_deregister(notify->imessaging_ctx, MSG_PVFS_NOTIFY, notify); + notify_remove_all(notify); + return 0; +} + +/* + Open up the notify.tdb database. You should close it down using + talloc_free(). We need the imessaging_ctx to allow for notifications + via internal messages +*/ +struct notify_context *notify_init(TALLOC_CTX *mem_ctx, struct server_id server, + struct imessaging_context *imessaging_ctx, + struct loadparm_context *lp_ctx, + struct tevent_context *ev, + struct share_config *scfg) +{ + struct notify_context *notify; + + if (share_bool_option(scfg, NOTIFY_ENABLE, NOTIFY_ENABLE_DEFAULT) != true) { + return NULL; + } + + if (ev == NULL) { + return NULL; + } + + notify = talloc(mem_ctx, struct notify_context); + if (notify == NULL) { + return NULL; + } + + notify->db = cluster_db_tmp_open(notify, lp_ctx, "notify", TDB_SEQNUM); + if (notify->db == NULL) { + talloc_free(notify); + return NULL; + } + + notify->server = server; + notify->imessaging_ctx = imessaging_ctx; + notify->list = NULL; + notify->array = NULL; + notify->seqnum = dbwrap_get_seqnum(notify->db); + + talloc_set_destructor(notify, notify_destructor); + + /* register with the messaging subsystem for the notify + message type */ + imessaging_register(notify->imessaging_ctx, notify, + MSG_PVFS_NOTIFY, notify_handler); + + notify->sys_notify_ctx = sys_notify_context_create(scfg, notify, ev); + + return notify; +} + + +/* + lock the notify db +*/ +static struct db_record *notify_lock(struct notify_context *notify) +{ + TDB_DATA key = string_term_tdb_data(NOTIFY_KEY); + + return dbwrap_fetch_locked(notify->db, notify, key); +} + +static void notify_unlock(struct db_record *lock) +{ + talloc_free(lock); +} + +/* + load the notify array +*/ +static NTSTATUS notify_load(struct notify_context *notify) +{ + TDB_DATA dbuf; + DATA_BLOB blob; + enum ndr_err_code ndr_err; + int seqnum; + NTSTATUS status; + + seqnum = dbwrap_get_seqnum(notify->db); + + if (seqnum == notify->seqnum && notify->array != NULL) { + return NT_STATUS_OK; + } + + notify->seqnum = seqnum; + + talloc_free(notify->array); + notify->array = talloc_zero(notify, struct notify_array); + NT_STATUS_HAVE_NO_MEMORY(notify->array); + + status = dbwrap_fetch_bystring(notify->db, notify, NOTIFY_KEY, &dbuf); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_OK; + } + + blob.data = dbuf.dptr; + blob.length = dbuf.dsize; + + ndr_err = ndr_pull_struct_blob(&blob, notify->array, notify->array, + (ndr_pull_flags_fn_t)ndr_pull_notify_array); + talloc_free(dbuf.dptr); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return ndr_map_error2ntstatus(ndr_err); + } + + return NT_STATUS_OK; +} + +/* + compare notify entries for sorting +*/ +static int notify_compare(const void *p1, const void *p2) +{ + const struct notify_entry *e1 = p1, *e2 = p2; + return strcmp(e1->path, e2->path); +} + +/* + save the notify array +*/ +static NTSTATUS notify_save(struct notify_context *notify) +{ + TDB_DATA dbuf; + DATA_BLOB blob; + enum ndr_err_code ndr_err; + TALLOC_CTX *tmp_ctx; + NTSTATUS status; + + /* if possible, remove some depth arrays */ + while (notify->array->num_depths > 0 && + notify->array->depth[notify->array->num_depths-1].num_entries == 0) { + notify->array->num_depths--; + } + + /* we might just be able to delete the record */ + if (notify->array->num_depths == 0) { + return dbwrap_delete_bystring(notify->db, NOTIFY_KEY); + } + + tmp_ctx = talloc_new(notify); + NT_STATUS_HAVE_NO_MEMORY(tmp_ctx); + + ndr_err = ndr_push_struct_blob(&blob, tmp_ctx, notify->array, + (ndr_push_flags_fn_t)ndr_push_notify_array); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + talloc_free(tmp_ctx); + return ndr_map_error2ntstatus(ndr_err); + } + + dbuf.dptr = blob.data; + dbuf.dsize = blob.length; + + status = dbwrap_store_bystring(notify->db, NOTIFY_KEY, dbuf, + TDB_REPLACE); + talloc_free(tmp_ctx); + return status; +} + + +/* + handle incoming notify messages +*/ +static void notify_handler(struct imessaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + size_t num_fds, + int *fds, + DATA_BLOB *data) +{ + struct notify_context *notify = talloc_get_type(private_data, struct notify_context); + enum ndr_err_code ndr_err; + struct notify_event ev; + TALLOC_CTX *tmp_ctx = talloc_new(notify); + struct notify_list *listel; + + if (num_fds != 0) { + DBG_WARNING("Received %zu fds, ignoring message\n", num_fds); + return; + } + + if (tmp_ctx == NULL) { + return; + } + + ndr_err = ndr_pull_struct_blob(data, tmp_ctx, &ev, + (ndr_pull_flags_fn_t)ndr_pull_notify_event); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + talloc_free(tmp_ctx); + return; + } + + for (listel=notify->list;listel;listel=listel->next) { + if (listel->private_data == ev.private_data) { + listel->callback(listel->private_data, &ev); + break; + } + } + + talloc_free(tmp_ctx); +} + +/* + callback from sys_notify telling us about changes from the OS +*/ +static void sys_notify_callback(struct sys_notify_context *ctx, + void *ptr, struct notify_event *ev) +{ + struct notify_list *listel = talloc_get_type(ptr, struct notify_list); + ev->private_data = listel; + listel->callback(listel->private_data, ev); +} + +/* + add an entry to the notify array +*/ +static NTSTATUS notify_add_array(struct notify_context *notify, struct notify_entry *e, + void *private_data, int depth) +{ + int i; + struct notify_depth *d; + struct notify_entry *ee; + + /* possibly expand the depths array */ + if (depth >= notify->array->num_depths) { + d = talloc_realloc(notify->array, notify->array->depth, + struct notify_depth, depth+1); + NT_STATUS_HAVE_NO_MEMORY(d); + for (i=notify->array->num_depths;i<=depth;i++) { + ZERO_STRUCT(d[i]); + } + notify->array->depth = d; + notify->array->num_depths = depth+1; + } + d = ¬ify->array->depth[depth]; + + /* expand the entries array */ + ee = talloc_realloc(notify->array->depth, d->entries, struct notify_entry, + d->num_entries+1); + NT_STATUS_HAVE_NO_MEMORY(ee); + d->entries = ee; + + d->entries[d->num_entries] = *e; + d->entries[d->num_entries].private_data = private_data; + d->entries[d->num_entries].server = notify->server; + d->entries[d->num_entries].path_len = strlen(e->path); + d->num_entries++; + + d->max_mask |= e->filter; + d->max_mask_subdir |= e->subdir_filter; + + TYPESAFE_QSORT(d->entries, d->num_entries, notify_compare); + + /* recalculate the maximum masks */ + d->max_mask = 0; + d->max_mask_subdir = 0; + + for (i=0;i<d->num_entries;i++) { + d->max_mask |= d->entries[i].filter; + d->max_mask_subdir |= d->entries[i].subdir_filter; + } + + return notify_save(notify); +} + +/* + add a notify watch. This is called when a notify is first setup on a open + directory handle. +*/ +NTSTATUS notify_add(struct notify_context *notify, struct notify_entry *e0, + void (*callback)(void *, const struct notify_event *), + void *private_data) +{ + struct notify_entry e = *e0; + NTSTATUS status; + char *tmp_path = NULL; + struct notify_list *listel; + size_t len; + int depth; + struct db_record *locked; + + /* see if change notify is enabled at all */ + if (notify == NULL) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + locked = notify_lock(notify); + if (!locked) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + status = notify_load(notify); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + /* cope with /. on the end of the path */ + len = strlen(e.path); + if (len > 1 && e.path[len-1] == '.' && e.path[len-2] == '/') { + tmp_path = talloc_strndup(notify, e.path, len-2); + if (tmp_path == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + e.path = tmp_path; + } + + depth = count_chars(e.path, '/'); + + listel = talloc_zero(notify, struct notify_list); + if (listel == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + listel->private_data = private_data; + listel->callback = callback; + listel->depth = depth; + DLIST_ADD(notify->list, listel); + + /* ignore failures from sys_notify */ + if (notify->sys_notify_ctx != NULL) { + /* + this call will modify e.filter and e.subdir_filter + to remove bits handled by the backend + */ + status = sys_notify_watch(notify->sys_notify_ctx, &e, + sys_notify_callback, listel, + &listel->sys_notify_handle); + if (NT_STATUS_IS_OK(status)) { + talloc_steal(listel, listel->sys_notify_handle); + } + } + + /* if the system notify handler couldn't handle some of the + filter bits, or couldn't handle a request for recursion + then we need to install it in the array used for the + intra-samba notify handling */ + if (e.filter != 0 || e.subdir_filter != 0) { + status = notify_add_array(notify, &e, private_data, depth); + } + +done: + notify_unlock(locked); + talloc_free(tmp_path); + + return status; +} + +/* + remove a notify watch. Called when the directory handle is closed +*/ +NTSTATUS notify_remove(struct notify_context *notify, void *private_data) +{ + NTSTATUS status; + struct notify_list *listel; + int i, depth; + struct notify_depth *d; + struct db_record *locked; + + /* see if change notify is enabled at all */ + if (notify == NULL) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + for (listel=notify->list;listel;listel=listel->next) { + if (listel->private_data == private_data) { + DLIST_REMOVE(notify->list, listel); + break; + } + } + if (listel == NULL) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + depth = listel->depth; + + talloc_free(listel); + + locked = notify_lock(notify); + if (!locked) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + status = notify_load(notify); + if (!NT_STATUS_IS_OK(status)) { + notify_unlock(locked); + return status; + } + + if (depth >= notify->array->num_depths) { + notify_unlock(locked); + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + /* we only have to search at the depth of this element */ + d = ¬ify->array->depth[depth]; + + for (i=0;i<d->num_entries;i++) { + if (private_data == d->entries[i].private_data && + cluster_id_equal(¬ify->server, &d->entries[i].server)) { + break; + } + } + if (i == d->num_entries) { + notify_unlock(locked); + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + if (i < d->num_entries-1) { + memmove(&d->entries[i], &d->entries[i+1], + sizeof(d->entries[i])*(d->num_entries-(i+1))); + } + d->num_entries--; + + status = notify_save(notify); + + notify_unlock(locked); + + return status; +} + +/* + remove all notify watches for this messaging server +*/ +static NTSTATUS notify_remove_all(struct notify_context *notify) +{ + NTSTATUS status; + int i, depth, del_count=0; + struct db_record *locked; + + if (notify->list == NULL) { + return NT_STATUS_OK; + } + + locked = notify_lock(notify); + if (!locked) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + status = notify_load(notify); + if (!NT_STATUS_IS_OK(status)) { + notify_unlock(locked); + return status; + } + + /* we have to search for all entries across all depths, looking for matches + for our server id */ + for (depth=0;depth<notify->array->num_depths;depth++) { + struct notify_depth *d = ¬ify->array->depth[depth]; + for (i=0;i<d->num_entries;i++) { + if (cluster_id_equal(¬ify->server, &d->entries[i].server)) { + if (i < d->num_entries-1) { + memmove(&d->entries[i], &d->entries[i+1], + sizeof(d->entries[i])*(d->num_entries-(i+1))); + } + i--; + d->num_entries--; + del_count++; + } + } + } + + if (del_count > 0) { + status = notify_save(notify); + } + + notify_unlock(locked); + + return status; +} + + +/* + send a notify message to another messaging server +*/ +static void notify_send(struct notify_context *notify, struct notify_entry *e, + const char *path, uint32_t action) +{ + struct notify_event ev; + DATA_BLOB data; + NTSTATUS status; + enum ndr_err_code ndr_err; + TALLOC_CTX *tmp_ctx; + + ev.action = action; + ev.dir = discard_const_p(char, ""); + ev.path = path; + ev.private_data = e->private_data; + + tmp_ctx = talloc_new(notify); + + ndr_err = ndr_push_struct_blob(&data, tmp_ctx, &ev, (ndr_push_flags_fn_t)ndr_push_notify_event); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + talloc_free(tmp_ctx); + return; + } + + status = imessaging_send(notify->imessaging_ctx, e->server, + MSG_PVFS_NOTIFY, &data); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(tmp_ctx); + return; + } + + talloc_free(tmp_ctx); +} + + +/* + trigger a notify message for anyone waiting on a matching event + + This function is called a lot, and needs to be very fast. The unusual data structure + and traversal is designed to be fast in the average case, even for large numbers of + notifies +*/ +void notify_trigger(struct notify_context *notify, + uint32_t action, uint32_t filter, const char *path) +{ + NTSTATUS status; + int depth; + const char *p, *next_p; + + /* see if change notify is enabled at all */ + if (notify == NULL) { + return; + } + + status = notify_load(notify); + if (!NT_STATUS_IS_OK(status)) { + return; + } + + /* loop along the given path, working with each directory depth separately */ + for (depth=0,p=path; + p && depth < notify->array->num_depths; + p=next_p,depth++) { + int p_len = p - path; + int min_i, max_i, i; + struct notify_depth *d = ¬ify->array->depth[depth]; + next_p = strchr(p+1, '/'); + + /* see if there are any entries at this depth */ + if (d->num_entries == 0) continue; + + /* try to skip based on the maximum mask. If next_p is + NULL then we know it will be a 'this directory' + match, otherwise it must be a subdir match */ + if (next_p != NULL) { + if (0 == (filter & d->max_mask_subdir)) { + continue; + } + } else { + if (0 == (filter & d->max_mask)) { + continue; + } + } + + /* we know there is an entry here worth looking + for. Use a bisection search to find the first entry + with a matching path */ + min_i = 0; + max_i = d->num_entries-1; + + while (min_i < max_i) { + struct notify_entry *e; + int cmp; + i = (min_i+max_i)/2; + e = &d->entries[i]; + cmp = strncmp(path, e->path, p_len); + if (cmp == 0) { + if (p_len == e->path_len) { + max_i = i; + } else { + max_i = i-1; + } + } else if (cmp < 0) { + max_i = i-1; + } else { + min_i = i+1; + } + } + + if (min_i != max_i) { + /* none match */ + continue; + } + + /* we now know that the entries start at min_i */ + for (i=min_i;i<d->num_entries;i++) { + struct notify_entry *e = &d->entries[i]; + if (p_len != e->path_len || + strncmp(path, e->path, p_len) != 0) break; + if (next_p != NULL) { + if (0 == (filter & e->subdir_filter)) { + continue; + } + } else { + if (0 == (filter & e->filter)) { + continue; + } + } + notify_send(notify, e, path + e->path_len + 1, action); + } + } +} diff --git a/source4/ntvfs/common/ntvfs_common.h b/source4/ntvfs/common/ntvfs_common.h new file mode 100644 index 0000000..37dd553 --- /dev/null +++ b/source4/ntvfs/common/ntvfs_common.h @@ -0,0 +1,32 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher 2007 + + 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/>. +*/ + +#ifndef _NTVFS_COMMON_H_ +#define _NTVFS_COMMON_H_ + +#include "ntvfs/ntvfs.h" + +struct notify_event; +struct notify_entry; + +#include "ntvfs/common/brlock.h" +#include "ntvfs/common/opendb.h" +#include "ntvfs/common/proto.h" + +#endif /* _NTVFS_COMMON_H_ */ diff --git a/source4/ntvfs/common/opendb.c b/source4/ntvfs/common/opendb.c new file mode 100644 index 0000000..29081ef --- /dev/null +++ b/source4/ntvfs/common/opendb.c @@ -0,0 +1,200 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Andrew Tridgell 2004 + + 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/>. +*/ + +/* + this is the open files database. It implements shared storage of + what files are open between server instances, and implements the rules + of shared access to files. + + The caller needs to provide a file_key, which specifies what file + they are talking about. This needs to be a unique key across all + filesystems, and is usually implemented in terms of a device/inode + pair. + + Before any operations can be performed the caller needs to establish + a lock on the record associated with file_key. That is done by + calling odb_lock(). The caller releases this lock by calling + talloc_free() on the returned handle. + + All other operations on a record are done by passing the odb_lock() + handle back to this module. The handle contains internal + information about what file_key is being operated on. +*/ + +#include "includes.h" +#include "ntvfs/ntvfs.h" +#include "ntvfs/common/ntvfs_common.h" +#include "cluster/cluster.h" +#include "param/param.h" + +static const struct opendb_ops *ops; + +/* + set the odb backend ops +*/ +void odb_set_ops(const struct opendb_ops *new_ops) +{ + ops = new_ops; +} + +/* + Open up the openfiles.tdb database. Close it down using + talloc_free(). We need the imessaging_ctx to allow for pending open + notifications. +*/ +struct odb_context *odb_init(TALLOC_CTX *mem_ctx, + struct ntvfs_context *ntvfs_ctx) +{ + if (ops == NULL) { + odb_tdb_init_ops(); + } + return ops->odb_init(mem_ctx, ntvfs_ctx); +} + +/* + get a lock on a entry in the odb. This call returns a lock handle, + which the caller should unlock using talloc_free(). +*/ +struct odb_lock *odb_lock(TALLOC_CTX *mem_ctx, + struct odb_context *odb, DATA_BLOB *file_key) +{ + return ops->odb_lock(mem_ctx, odb, file_key); +} + +DATA_BLOB odb_get_key(TALLOC_CTX *mem_ctx, struct odb_lock *lck) +{ + return ops->odb_get_key(mem_ctx, lck); +} + +/* + register an open file in the open files database. + The share_access rules are implemented by odb_can_open() + and it's needed to call odb_can_open() before + odb_open_file() otherwise NT_STATUS_INTERNAL_ERROR is returned + + Note that the path is only used by the delete on close logic, not + for comparing with other filenames +*/ +NTSTATUS odb_open_file(struct odb_lock *lck, + void *file_handle, const char *path, + int *fd, NTTIME open_write_time, + bool allow_level_II_oplock, + uint32_t oplock_level, uint32_t *oplock_granted) +{ + return ops->odb_open_file(lck, file_handle, path, + fd, open_write_time, + allow_level_II_oplock, + oplock_level, oplock_granted); +} + + +/* + register a pending open file in the open files database +*/ +NTSTATUS odb_open_file_pending(struct odb_lock *lck, void *private_data) +{ + return ops->odb_open_file_pending(lck, private_data); +} + + +/* + remove a opendb entry +*/ +NTSTATUS odb_close_file(struct odb_lock *lck, void *file_handle, + const char **delete_path) +{ + return ops->odb_close_file(lck, file_handle, delete_path); +} + + +/* + remove a pending opendb entry +*/ +NTSTATUS odb_remove_pending(struct odb_lock *lck, void *private_data) +{ + return ops->odb_remove_pending(lck, private_data); +} + + +/* + rename the path in a open file +*/ +NTSTATUS odb_rename(struct odb_lock *lck, const char *path) +{ + return ops->odb_rename(lck, path); +} + +/* + get back the path of an open file +*/ +NTSTATUS odb_get_path(struct odb_lock *lck, const char **path) +{ + return ops->odb_get_path(lck, path); +} + +/* + update delete on close flag on an open file +*/ +NTSTATUS odb_set_delete_on_close(struct odb_lock *lck, bool del_on_close) +{ + return ops->odb_set_delete_on_close(lck, del_on_close); +} + +/* + update the write time on an open file +*/ +NTSTATUS odb_set_write_time(struct odb_lock *lck, + NTTIME write_time, bool force) +{ + return ops->odb_set_write_time(lck, write_time, force); +} + +/* + return the current value of the delete_on_close bit, + and the current write time. +*/ +NTSTATUS odb_get_file_infos(struct odb_context *odb, DATA_BLOB *key, + bool *del_on_close, NTTIME *write_time) +{ + return ops->odb_get_file_infos(odb, key, del_on_close, write_time); +} + +/* + determine if a file can be opened with the given share_access, + create_options and access_mask +*/ +NTSTATUS odb_can_open(struct odb_lock *lck, + uint32_t stream_id, uint32_t share_access, + uint32_t access_mask, bool delete_on_close, + uint32_t open_disposition, bool break_to_none) +{ + return ops->odb_can_open(lck, stream_id, share_access, access_mask, + delete_on_close, open_disposition, break_to_none); +} + +NTSTATUS odb_update_oplock(struct odb_lock *lck, void *file_handle, + uint32_t oplock_level) +{ + return ops->odb_update_oplock(lck, file_handle, oplock_level); +} + +NTSTATUS odb_break_oplocks(struct odb_lock *lck) +{ + return ops->odb_break_oplocks(lck); +} diff --git a/source4/ntvfs/common/opendb.h b/source4/ntvfs/common/opendb.h new file mode 100644 index 0000000..1bfc6aa --- /dev/null +++ b/source4/ntvfs/common/opendb.h @@ -0,0 +1,59 @@ +/* + Unix SMB/CIFS implementation. + + open database code - common include + + Copyright (C) Andrew Tridgell 2007 + + 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/>. +*/ + +struct opendb_ops { + struct odb_context *(*odb_init)(TALLOC_CTX *mem_ctx, + struct ntvfs_context *ntvfs_ctx); + struct odb_lock *(*odb_lock)(TALLOC_CTX *mem_ctx, + struct odb_context *odb, DATA_BLOB *file_key); + DATA_BLOB (*odb_get_key)(TALLOC_CTX *mem_ctx, struct odb_lock *lck); + NTSTATUS (*odb_open_file)(struct odb_lock *lck, + void *file_handle, const char *path, + int *fd, NTTIME open_write_time, + bool allow_level_II_oplock, + uint32_t oplock_level, uint32_t *oplock_granted); + NTSTATUS (*odb_open_file_pending)(struct odb_lock *lck, void *private_data); + NTSTATUS (*odb_close_file)(struct odb_lock *lck, void *file_handle, + const char **delete_path); + NTSTATUS (*odb_remove_pending)(struct odb_lock *lck, void *private_data); + NTSTATUS (*odb_rename)(struct odb_lock *lck, const char *path); + NTSTATUS (*odb_get_path)(struct odb_lock *lck, const char **path); + NTSTATUS (*odb_set_delete_on_close)(struct odb_lock *lck, bool del_on_close); + NTSTATUS (*odb_set_write_time)(struct odb_lock *lck, + NTTIME write_time, bool force); + NTSTATUS (*odb_get_file_infos)(struct odb_context *odb, DATA_BLOB *key, + bool *del_on_close, NTTIME *write_time); + NTSTATUS (*odb_can_open)(struct odb_lock *lck, + uint32_t stream_id, uint32_t share_access, + uint32_t access_mask, bool delete_on_close, + uint32_t open_disposition, bool break_to_none); + NTSTATUS (*odb_update_oplock)(struct odb_lock *lck, void *file_handle, + uint32_t oplock_level); + NTSTATUS (*odb_break_oplocks)(struct odb_lock *lck); +}; + +struct opendb_oplock_break { + void *file_handle; + uint8_t level; +}; + +void odb_set_ops(const struct opendb_ops *new_ops); +void odb_tdb_init_ops(void); diff --git a/source4/ntvfs/common/opendb_tdb.c b/source4/ntvfs/common/opendb_tdb.c new file mode 100644 index 0000000..9fe0b26 --- /dev/null +++ b/source4/ntvfs/common/opendb_tdb.c @@ -0,0 +1,886 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Andrew Tridgell 2004 + Copyright (C) Stefan Metzmacher 2008 + + 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/>. +*/ + +/* + this is the open files database, tdb backend. It implements shared + storage of what files are open between server instances, and + implements the rules of shared access to files. + + The caller needs to provide a file_key, which specifies what file + they are talking about. This needs to be a unique key across all + filesystems, and is usually implemented in terms of a device/inode + pair. + + Before any operations can be performed the caller needs to establish + a lock on the record associated with file_key. That is done by + calling odb_lock(). The caller releases this lock by calling + talloc_free() on the returned handle. + + All other operations on a record are done by passing the odb_lock() + handle back to this module. The handle contains internal + information about what file_key is being operated on. +*/ + +#include "includes.h" +#include "system/filesys.h" +#include "lib/dbwrap/dbwrap.h" +#include "messaging/messaging.h" +#include "lib/messaging/irpc.h" +#include "librpc/gen_ndr/ndr_opendb.h" +#include "ntvfs/ntvfs.h" +#include "ntvfs/common/ntvfs_common.h" +#include "cluster/cluster.h" +#include "param/param.h" +#include "ntvfs/sysdep/sys_lease.h" + +struct odb_context { + struct db_context *db; + struct ntvfs_context *ntvfs_ctx; + bool oplocks; + struct sys_lease_context *lease_ctx; +}; + +/* + an odb lock handle. You must obtain one of these using odb_lock() before doing + any other operations. +*/ +struct odb_lock { + struct odb_context *odb; + struct db_record *locked; + + struct opendb_file file; + + struct { + struct opendb_entry *e; + bool attrs_only; + } can_open; +}; + +static NTSTATUS odb_oplock_break_send(struct imessaging_context *msg_ctx, + struct opendb_entry *e, + uint8_t level); + +/* + Open up the openfiles.tdb database. Close it down using + talloc_free(). We need the imessaging_ctx to allow for pending open + notifications. +*/ +static struct odb_context *odb_tdb_init(TALLOC_CTX *mem_ctx, + struct ntvfs_context *ntvfs_ctx) +{ + struct odb_context *odb; + + odb = talloc(mem_ctx, struct odb_context); + if (odb == NULL) { + return NULL; + } + + odb->db = cluster_db_tmp_open(odb, ntvfs_ctx->lp_ctx, + "openfiles", TDB_DEFAULT); + if (odb->db == NULL) { + talloc_free(odb); + return NULL; + } + + odb->ntvfs_ctx = ntvfs_ctx; + + odb->oplocks = share_bool_option(ntvfs_ctx->config, SHARE_OPLOCKS, SHARE_OPLOCKS_DEFAULT); + + odb->lease_ctx = sys_lease_context_create(ntvfs_ctx->config, odb, + ntvfs_ctx->event_ctx, + ntvfs_ctx->msg_ctx, + odb_oplock_break_send); + + return odb; +} + +static NTSTATUS odb_pull_record(struct odb_lock *lck, struct opendb_file *file); + +/* + get a lock on a entry in the odb. This call returns a lock handle, + which the caller should unlock using talloc_free(). +*/ +static struct odb_lock *odb_tdb_lock(TALLOC_CTX *mem_ctx, + struct odb_context *odb, DATA_BLOB *file_key) +{ + struct odb_lock *lck; + NTSTATUS status; + TDB_DATA key; + + lck = talloc(mem_ctx, struct odb_lock); + if (lck == NULL) { + return NULL; + } + + lck->odb = talloc_reference(lck, odb); + key.dptr = talloc_memdup(lck, file_key->data, file_key->length); + key.dsize = file_key->length; + if (key.dptr == NULL) { + talloc_free(lck); + return NULL; + } + + lck->locked = dbwrap_fetch_locked(odb->db, lck, key); + if (!lck->locked) { + talloc_free(lck); + return NULL; + } + + ZERO_STRUCT(lck->can_open); + + status = odb_pull_record(lck, &lck->file); + if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + /* initialise a blank structure */ + ZERO_STRUCT(lck->file); + } else if (!NT_STATUS_IS_OK(status)) { + talloc_free(lck); + return NULL; + } + + return lck; +} + +static DATA_BLOB odb_tdb_get_key(TALLOC_CTX *mem_ctx, struct odb_lock *lck) +{ + TDB_DATA key = dbwrap_record_get_key(lck->locked); + return data_blob_talloc(mem_ctx, key.dptr, key.dsize); +} + + +/* + determine if two odb_entry structures conflict + + return NT_STATUS_OK on no conflict +*/ +static NTSTATUS share_conflict(struct opendb_entry *e1, + uint32_t stream_id, + uint32_t share_access, + uint32_t access_mask) +{ + /* if either open involves no read.write or delete access then + it can't conflict */ + if (!(e1->access_mask & (SEC_FILE_WRITE_DATA | + SEC_FILE_APPEND_DATA | + SEC_FILE_READ_DATA | + SEC_FILE_EXECUTE | + SEC_STD_DELETE))) { + return NT_STATUS_OK; + } + if (!(access_mask & (SEC_FILE_WRITE_DATA | + SEC_FILE_APPEND_DATA | + SEC_FILE_READ_DATA | + SEC_FILE_EXECUTE | + SEC_STD_DELETE))) { + return NT_STATUS_OK; + } + + /* data IO access masks. This is skipped if the two open handles + are on different streams (as in that case the masks don't + interact) */ + if (e1->stream_id != stream_id) { + return NT_STATUS_OK; + } + +#define CHECK_MASK(am, right, sa, share) \ + if (((am) & (right)) && !((sa) & (share))) return NT_STATUS_SHARING_VIOLATION + + CHECK_MASK(e1->access_mask, SEC_FILE_WRITE_DATA | SEC_FILE_APPEND_DATA, + share_access, NTCREATEX_SHARE_ACCESS_WRITE); + CHECK_MASK(access_mask, SEC_FILE_WRITE_DATA | SEC_FILE_APPEND_DATA, + e1->share_access, NTCREATEX_SHARE_ACCESS_WRITE); + + CHECK_MASK(e1->access_mask, SEC_FILE_READ_DATA | SEC_FILE_EXECUTE, + share_access, NTCREATEX_SHARE_ACCESS_READ); + CHECK_MASK(access_mask, SEC_FILE_READ_DATA | SEC_FILE_EXECUTE, + e1->share_access, NTCREATEX_SHARE_ACCESS_READ); + + CHECK_MASK(e1->access_mask, SEC_STD_DELETE, + share_access, NTCREATEX_SHARE_ACCESS_DELETE); + CHECK_MASK(access_mask, SEC_STD_DELETE, + e1->share_access, NTCREATEX_SHARE_ACCESS_DELETE); +#undef CHECK_MASK + return NT_STATUS_OK; +} + +/* + pull a record, translating from the db format to the opendb_file structure defined + in opendb.idl +*/ +static NTSTATUS odb_pull_record(struct odb_lock *lck, struct opendb_file *file) +{ + TDB_DATA dbuf; + DATA_BLOB blob; + enum ndr_err_code ndr_err; + + dbuf = dbwrap_record_get_value(lck->locked); + if (!dbuf.dptr) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + blob.data = dbuf.dptr; + blob.length = dbuf.dsize; + + ndr_err = ndr_pull_struct_blob(&blob, lck, file, (ndr_pull_flags_fn_t)ndr_pull_opendb_file); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return ndr_map_error2ntstatus(ndr_err); + } + + return NT_STATUS_OK; +} + +/* + push a record, translating from the opendb_file structure defined in opendb.idl +*/ +static NTSTATUS odb_push_record(struct odb_lock *lck, struct opendb_file *file) +{ + TDB_DATA dbuf; + DATA_BLOB blob; + enum ndr_err_code ndr_err; + NTSTATUS status; + + if (file->num_entries == 0) { + return dbwrap_record_delete(lck->locked); + } + + ndr_err = ndr_push_struct_blob(&blob, lck, file, (ndr_push_flags_fn_t)ndr_push_opendb_file); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return ndr_map_error2ntstatus(ndr_err); + } + + dbuf.dptr = blob.data; + dbuf.dsize = blob.length; + + status = dbwrap_record_store(lck->locked, dbuf, TDB_REPLACE); + data_blob_free(&blob); + return status; +} + +/* + send an oplock break to a client +*/ +static NTSTATUS odb_oplock_break_send(struct imessaging_context *msg_ctx, + struct opendb_entry *e, + uint8_t level) +{ + NTSTATUS status; + struct opendb_oplock_break op_break; + DATA_BLOB blob; + + ZERO_STRUCT(op_break); + + /* tell the server handling this open file about the need to send the client + a break */ + op_break.file_handle = e->file_handle; + op_break.level = level; + + blob = data_blob_const(&op_break, sizeof(op_break)); + + status = imessaging_send(msg_ctx, e->server, + MSG_NTVFS_OPLOCK_BREAK, &blob); + NT_STATUS_NOT_OK_RETURN(status); + + return NT_STATUS_OK; +} + +static bool access_attributes_only(uint32_t access_mask, + uint32_t open_disposition, + bool break_to_none) +{ + switch (open_disposition) { + case NTCREATEX_DISP_SUPERSEDE: + case NTCREATEX_DISP_OVERWRITE_IF: + case NTCREATEX_DISP_OVERWRITE: + return false; + default: + break; + } + + if (break_to_none) { + return false; + } + +#define CHECK_MASK(m,g) ((m) && (((m) & ~(g))==0) && (((m) & (g)) != 0)) + return CHECK_MASK(access_mask, + SEC_STD_SYNCHRONIZE | + SEC_FILE_READ_ATTRIBUTE | + SEC_FILE_WRITE_ATTRIBUTE); +#undef CHECK_MASK +} + +static NTSTATUS odb_tdb_open_can_internal(struct odb_context *odb, + const struct opendb_file *file, + uint32_t stream_id, uint32_t share_access, + uint32_t access_mask, bool delete_on_close, + uint32_t open_disposition, bool break_to_none, + bool *_attrs_only) +{ + NTSTATUS status; + uint32_t i; + bool attrs_only = false; + + /* see if anyone has an oplock, which we need to break */ + for (i=0;i<file->num_entries;i++) { + if (file->entries[i].oplock_level == OPLOCK_BATCH) { + bool oplock_return = OPLOCK_BREAK_TO_LEVEL_II; + /* if this is an attribute only access + * it doesn't conflict with a BACTCH oplock + * but we'll not grant the oplock below + */ + attrs_only = access_attributes_only(access_mask, + open_disposition, + break_to_none); + if (attrs_only) { + break; + } + /* a batch oplock caches close calls, which + means the client application might have + already closed the file. We have to allow + this close to propagate by sending a oplock + break request and suspending this call + until the break is acknowledged or the file + is closed */ + if (break_to_none || + !file->entries[i].allow_level_II_oplock) { + oplock_return = OPLOCK_BREAK_TO_NONE; + } + odb_oplock_break_send(odb->ntvfs_ctx->msg_ctx, + &file->entries[i], + oplock_return); + return NT_STATUS_OPLOCK_NOT_GRANTED; + } + } + + if (file->delete_on_close) { + /* while delete on close is set, no new opens are allowed */ + return NT_STATUS_DELETE_PENDING; + } + + if (file->num_entries != 0 && delete_on_close) { + return NT_STATUS_SHARING_VIOLATION; + } + + /* check for sharing violations */ + for (i=0;i<file->num_entries;i++) { + status = share_conflict(&file->entries[i], stream_id, + share_access, access_mask); + NT_STATUS_NOT_OK_RETURN(status); + } + + /* we now know the open could succeed, but we need to check + for any exclusive oplocks. We can't grant a second open + till these are broken. Note that we check for batch oplocks + before checking for sharing violations, and check for + exclusive oplocks afterwards. */ + for (i=0;i<file->num_entries;i++) { + if (file->entries[i].oplock_level == OPLOCK_EXCLUSIVE) { + bool oplock_return = OPLOCK_BREAK_TO_LEVEL_II; + /* if this is an attribute only access + * it doesn't conflict with an EXCLUSIVE oplock + * but we'll not grant the oplock below + */ + attrs_only = access_attributes_only(access_mask, + open_disposition, + break_to_none); + if (attrs_only) { + break; + } + /* + * send an oplock break to the holder of the + * oplock and tell caller to retry later + */ + if (break_to_none || + !file->entries[i].allow_level_II_oplock) { + oplock_return = OPLOCK_BREAK_TO_NONE; + } + odb_oplock_break_send(odb->ntvfs_ctx->msg_ctx, + &file->entries[i], + oplock_return); + return NT_STATUS_OPLOCK_NOT_GRANTED; + } + } + + if (_attrs_only) { + *_attrs_only = attrs_only; + } + return NT_STATUS_OK; +} + +/* + register an open file in the open files database. + The share_access rules are implemented by odb_can_open() + and it's needed to call odb_can_open() before + odb_open_file() otherwise NT_STATUS_INTERNAL_ERROR is returned + + Note that the path is only used by the delete on close logic, not + for comparing with other filenames +*/ +static NTSTATUS odb_tdb_open_file(struct odb_lock *lck, + void *file_handle, const char *path, + int *fd, NTTIME open_write_time, + bool allow_level_II_oplock, + uint32_t oplock_level, uint32_t *oplock_granted) +{ + struct odb_context *odb = lck->odb; + + if (!lck->can_open.e) { + return NT_STATUS_INTERNAL_ERROR; + } + + if (odb->oplocks == false) { + oplock_level = OPLOCK_NONE; + } + + if (!oplock_granted) { + oplock_level = OPLOCK_NONE; + } + + if (lck->file.path == NULL) { + lck->file.path = talloc_strdup(lck, path); + NT_STATUS_HAVE_NO_MEMORY(lck->file.path); + } + + if (lck->file.open_write_time == 0) { + lck->file.open_write_time = open_write_time; + } + + /* + possibly grant an exclusive, batch or level2 oplock + */ + if (lck->can_open.attrs_only) { + oplock_level = OPLOCK_NONE; + } else if (oplock_level == OPLOCK_EXCLUSIVE) { + if (lck->file.num_entries == 0) { + oplock_level = OPLOCK_EXCLUSIVE; + } else if (allow_level_II_oplock) { + oplock_level = OPLOCK_LEVEL_II; + } else { + oplock_level = OPLOCK_NONE; + } + } else if (oplock_level == OPLOCK_BATCH) { + if (lck->file.num_entries == 0) { + oplock_level = OPLOCK_BATCH; + } else if (allow_level_II_oplock) { + oplock_level = OPLOCK_LEVEL_II; + } else { + oplock_level = OPLOCK_NONE; + } + } else if (oplock_level == OPLOCK_LEVEL_II) { + oplock_level = OPLOCK_LEVEL_II; + } else { + oplock_level = OPLOCK_NONE; + } + + lck->can_open.e->file_handle = file_handle; + lck->can_open.e->fd = fd; + lck->can_open.e->allow_level_II_oplock = allow_level_II_oplock; + lck->can_open.e->oplock_level = oplock_level; + + if (odb->lease_ctx && fd) { + NTSTATUS status; + status = sys_lease_setup(odb->lease_ctx, lck->can_open.e); + NT_STATUS_NOT_OK_RETURN(status); + } + + if (oplock_granted) { + if (lck->can_open.e->oplock_level == OPLOCK_EXCLUSIVE) { + *oplock_granted = EXCLUSIVE_OPLOCK_RETURN; + } else if (lck->can_open.e->oplock_level == OPLOCK_BATCH) { + *oplock_granted = BATCH_OPLOCK_RETURN; + } else if (lck->can_open.e->oplock_level == OPLOCK_LEVEL_II) { + *oplock_granted = LEVEL_II_OPLOCK_RETURN; + } else { + *oplock_granted = NO_OPLOCK_RETURN; + } + } + + /* it doesn't conflict, so add it to the end */ + lck->file.entries = talloc_realloc(lck, lck->file.entries, + struct opendb_entry, + lck->file.num_entries+1); + NT_STATUS_HAVE_NO_MEMORY(lck->file.entries); + + lck->file.entries[lck->file.num_entries] = *lck->can_open.e; + lck->file.num_entries++; + + talloc_free(lck->can_open.e); + lck->can_open.e = NULL; + + return odb_push_record(lck, &lck->file); +} + + +/* + register a pending open file in the open files database +*/ +static NTSTATUS odb_tdb_open_file_pending(struct odb_lock *lck, void *private_data) +{ + struct odb_context *odb = lck->odb; + + if (lck->file.path == NULL) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + lck->file.pending = talloc_realloc(lck, lck->file.pending, + struct opendb_pending, + lck->file.num_pending+1); + NT_STATUS_HAVE_NO_MEMORY(lck->file.pending); + + lck->file.pending[lck->file.num_pending].server = odb->ntvfs_ctx->server_id; + lck->file.pending[lck->file.num_pending].notify_ptr = private_data; + + lck->file.num_pending++; + + return odb_push_record(lck, &lck->file); +} + + +/* + remove a opendb entry +*/ +static NTSTATUS odb_tdb_close_file(struct odb_lock *lck, void *file_handle, + const char **_delete_path) +{ + struct odb_context *odb = lck->odb; + const char *delete_path = NULL; + int i; + + if (lck->file.path == NULL) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + /* find the entry, and delete it */ + for (i=0;i<lck->file.num_entries;i++) { + if (file_handle == lck->file.entries[i].file_handle && + cluster_id_equal(&odb->ntvfs_ctx->server_id, &lck->file.entries[i].server)) { + if (lck->file.entries[i].delete_on_close) { + lck->file.delete_on_close = true; + } + if (odb->lease_ctx && lck->file.entries[i].fd) { + NTSTATUS status; + status = sys_lease_remove(odb->lease_ctx, &lck->file.entries[i]); + NT_STATUS_NOT_OK_RETURN(status); + } + if (i < lck->file.num_entries-1) { + memmove(lck->file.entries+i, lck->file.entries+i+1, + (lck->file.num_entries - (i+1)) * + sizeof(struct opendb_entry)); + } + break; + } + } + + if (i == lck->file.num_entries) { + return NT_STATUS_UNSUCCESSFUL; + } + + /* send any pending notifications, removing them once sent */ + for (i=0;i<lck->file.num_pending;i++) { + imessaging_send_ptr(odb->ntvfs_ctx->msg_ctx, + lck->file.pending[i].server, + MSG_PVFS_RETRY_OPEN, + lck->file.pending[i].notify_ptr); + } + lck->file.num_pending = 0; + + lck->file.num_entries--; + + if (lck->file.num_entries == 0 && lck->file.delete_on_close) { + delete_path = lck->file.path; + } + + if (_delete_path) { + *_delete_path = delete_path; + } + + return odb_push_record(lck, &lck->file); +} + +/* + update the oplock level of the client +*/ +static NTSTATUS odb_tdb_update_oplock(struct odb_lock *lck, void *file_handle, + uint32_t oplock_level) +{ + struct odb_context *odb = lck->odb; + int i; + + if (lck->file.path == NULL) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + /* find the entry, and update it */ + for (i=0;i<lck->file.num_entries;i++) { + if (file_handle == lck->file.entries[i].file_handle && + cluster_id_equal(&odb->ntvfs_ctx->server_id, &lck->file.entries[i].server)) { + lck->file.entries[i].oplock_level = oplock_level; + + if (odb->lease_ctx && lck->file.entries[i].fd) { + NTSTATUS status; + status = sys_lease_update(odb->lease_ctx, &lck->file.entries[i]); + NT_STATUS_NOT_OK_RETURN(status); + } + + break; + } + } + + if (i == lck->file.num_entries) { + return NT_STATUS_UNSUCCESSFUL; + } + + /* send any pending notifications, removing them once sent */ + for (i=0;i<lck->file.num_pending;i++) { + imessaging_send_ptr(odb->ntvfs_ctx->msg_ctx, + lck->file.pending[i].server, + MSG_PVFS_RETRY_OPEN, + lck->file.pending[i].notify_ptr); + } + lck->file.num_pending = 0; + + return odb_push_record(lck, &lck->file); +} + +/* + send oplocks breaks to none to all level2 holders +*/ +static NTSTATUS odb_tdb_break_oplocks(struct odb_lock *lck) +{ + struct odb_context *odb = lck->odb; + int i; + bool modified = false; + + /* see if anyone has an oplock, which we need to break */ + for (i=0;i<lck->file.num_entries;i++) { + if (lck->file.entries[i].oplock_level == OPLOCK_LEVEL_II) { + /* + * there could be multiple level2 oplocks + * and we just send a break to none to all of them + * without waiting for a release + */ + odb_oplock_break_send(odb->ntvfs_ctx->msg_ctx, + &lck->file.entries[i], + OPLOCK_BREAK_TO_NONE); + lck->file.entries[i].oplock_level = OPLOCK_NONE; + modified = true; + } + } + + if (modified) { + return odb_push_record(lck, &lck->file); + } + return NT_STATUS_OK; +} + +/* + remove a pending opendb entry +*/ +static NTSTATUS odb_tdb_remove_pending(struct odb_lock *lck, void *private_data) +{ + struct odb_context *odb = lck->odb; + int i; + + if (lck->file.path == NULL) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + /* find the entry, and delete it */ + for (i=0;i<lck->file.num_pending;i++) { + if (private_data == lck->file.pending[i].notify_ptr && + cluster_id_equal(&odb->ntvfs_ctx->server_id, &lck->file.pending[i].server)) { + if (i < lck->file.num_pending-1) { + memmove(lck->file.pending+i, lck->file.pending+i+1, + (lck->file.num_pending - (i+1)) * + sizeof(struct opendb_pending)); + } + break; + } + } + + if (i == lck->file.num_pending) { + return NT_STATUS_UNSUCCESSFUL; + } + + lck->file.num_pending--; + + return odb_push_record(lck, &lck->file); +} + + +/* + rename the path in a open file +*/ +static NTSTATUS odb_tdb_rename(struct odb_lock *lck, const char *path) +{ + if (lck->file.path == NULL) { + /* not having the record at all is OK */ + return NT_STATUS_OK; + } + + lck->file.path = talloc_strdup(lck, path); + NT_STATUS_HAVE_NO_MEMORY(lck->file.path); + + return odb_push_record(lck, &lck->file); +} + +/* + get the path of an open file +*/ +static NTSTATUS odb_tdb_get_path(struct odb_lock *lck, const char **path) +{ + *path = NULL; + + /* we don't ignore NT_STATUS_OBJECT_NAME_NOT_FOUND here */ + if (lck->file.path == NULL) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + *path = lck->file.path; + + return NT_STATUS_OK; +} + +/* + update delete on close flag on an open file +*/ +static NTSTATUS odb_tdb_set_delete_on_close(struct odb_lock *lck, bool del_on_close) +{ + if (lck->file.path == NULL) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + lck->file.delete_on_close = del_on_close; + + return odb_push_record(lck, &lck->file); +} + +/* + update the write time on an open file +*/ +static NTSTATUS odb_tdb_set_write_time(struct odb_lock *lck, + NTTIME write_time, bool force) +{ + if (lck->file.path == NULL) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + if (lck->file.changed_write_time != 0 && !force) { + return NT_STATUS_OK; + } + + lck->file.changed_write_time = write_time; + + return odb_push_record(lck, &lck->file); +} + +/* + return the current value of the delete_on_close bit, and how many + people still have the file open +*/ +static NTSTATUS odb_tdb_get_file_infos(struct odb_context *odb, DATA_BLOB *key, + bool *del_on_close, NTTIME *write_time) +{ + struct odb_lock *lck; + + if (del_on_close) { + *del_on_close = false; + } + if (write_time) { + *write_time = 0; + } + + lck = odb_lock(odb, odb, key); + NT_STATUS_HAVE_NO_MEMORY(lck); + + if (del_on_close) { + *del_on_close = lck->file.delete_on_close; + } + if (write_time) { + if (lck->file.changed_write_time == 0) { + *write_time = lck->file.open_write_time; + } else { + *write_time = lck->file.changed_write_time; + } + } + + talloc_free(lck); + + return NT_STATUS_OK; +} + + +/* + determine if a file can be opened with the given share_access, + create_options and access_mask +*/ +static NTSTATUS odb_tdb_can_open(struct odb_lock *lck, + uint32_t stream_id, uint32_t share_access, + uint32_t access_mask, bool delete_on_close, + uint32_t open_disposition, bool break_to_none) +{ + struct odb_context *odb = lck->odb; + NTSTATUS status; + + status = odb_tdb_open_can_internal(odb, &lck->file, stream_id, + share_access, access_mask, + delete_on_close, open_disposition, + break_to_none, &lck->can_open.attrs_only); + NT_STATUS_NOT_OK_RETURN(status); + + lck->can_open.e = talloc(lck, struct opendb_entry); + NT_STATUS_HAVE_NO_MEMORY(lck->can_open.e); + + lck->can_open.e->server = odb->ntvfs_ctx->server_id; + lck->can_open.e->file_handle = NULL; + lck->can_open.e->fd = NULL; + lck->can_open.e->stream_id = stream_id; + lck->can_open.e->share_access = share_access; + lck->can_open.e->access_mask = access_mask; + lck->can_open.e->delete_on_close = delete_on_close; + lck->can_open.e->allow_level_II_oplock = false; + lck->can_open.e->oplock_level = OPLOCK_NONE; + + return NT_STATUS_OK; +} + + +static const struct opendb_ops opendb_tdb_ops = { + .odb_init = odb_tdb_init, + .odb_lock = odb_tdb_lock, + .odb_get_key = odb_tdb_get_key, + .odb_open_file = odb_tdb_open_file, + .odb_open_file_pending = odb_tdb_open_file_pending, + .odb_close_file = odb_tdb_close_file, + .odb_remove_pending = odb_tdb_remove_pending, + .odb_rename = odb_tdb_rename, + .odb_get_path = odb_tdb_get_path, + .odb_set_delete_on_close = odb_tdb_set_delete_on_close, + .odb_set_write_time = odb_tdb_set_write_time, + .odb_get_file_infos = odb_tdb_get_file_infos, + .odb_can_open = odb_tdb_can_open, + .odb_update_oplock = odb_tdb_update_oplock, + .odb_break_oplocks = odb_tdb_break_oplocks +}; + + +void odb_tdb_init_ops(void) +{ + sys_lease_init(); + odb_set_ops(&opendb_tdb_ops); +} diff --git a/source4/ntvfs/common/wscript_build b/source4/ntvfs/common/wscript_build new file mode 100644 index 0000000..b144472 --- /dev/null +++ b/source4/ntvfs/common/wscript_build @@ -0,0 +1,9 @@ +#!/usr/bin/env python + +bld.SAMBA_SUBSYSTEM('ntvfs_common', + source='init.c brlock.c brlock_tdb.c opendb.c opendb_tdb.c notify.c', + autoproto='proto.h', + deps='util_tdb tdb-wrap', + public_deps='NDR_OPENDB NDR_NOTIFY sys_notify sys_lease share' + ) + diff --git a/source4/ntvfs/ipc/README b/source4/ntvfs/ipc/README new file mode 100644 index 0000000..059a714 --- /dev/null +++ b/source4/ntvfs/ipc/README @@ -0,0 +1,5 @@ +This is the IPC$ backend for Samba. NTVFS operations that are made on +IPC$ shares are directed here by default. Most file operations +are not supported on IPC$ shares. + + diff --git a/source4/ntvfs/ipc/ipc_rap.c b/source4/ntvfs/ipc/ipc_rap.c new file mode 100644 index 0000000..9ddde5e --- /dev/null +++ b/source4/ntvfs/ipc/ipc_rap.c @@ -0,0 +1,511 @@ +/* + Unix SMB/CIFS implementation. + RAP handlers + + Copyright (C) Volker Lendecke 2004 + + 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/raw/interfaces.h" +#include "../librpc/gen_ndr/rap.h" +#include "events/events.h" +#include "ntvfs/ipc/proto.h" +#include "librpc/ndr/libndr.h" +#include "param/param.h" + +#define NDR_RETURN(call) do { \ + enum ndr_err_code _ndr_err; \ + _ndr_err = call; \ + if (!NDR_ERR_CODE_IS_SUCCESS(_ndr_err)) { \ + return ndr_map_error2ntstatus(_ndr_err); \ + } \ +} while (0) + +#define RAP_GOTO(call) do { \ + result = call; \ + if (NT_STATUS_EQUAL(result, NT_STATUS_BUFFER_TOO_SMALL)) {\ + goto buffer_overflow; \ + } \ + if (!NT_STATUS_IS_OK(result)) { \ + goto done; \ + } \ +} while (0) + +#define NDR_GOTO(call) do { \ + enum ndr_err_code _ndr_err; \ + _ndr_err = call; \ + if (!NDR_ERR_CODE_IS_SUCCESS(_ndr_err)) { \ + RAP_GOTO(ndr_map_error2ntstatus(_ndr_err)); \ + } \ +} while (0) + + +#define NERR_notsupported 50 + +struct rap_string_heap { + TALLOC_CTX *mem_ctx; + int offset; + int num_strings; + const char **strings; +}; + +struct rap_heap_save { + int offset, num_strings; +}; + +static void rap_heap_save(struct rap_string_heap *heap, + struct rap_heap_save *save) +{ + save->offset = heap->offset; + save->num_strings = heap->num_strings; +} + +static void rap_heap_restore(struct rap_string_heap *heap, + struct rap_heap_save *save) +{ + heap->offset = save->offset; + heap->num_strings = save->num_strings; +} + +struct rap_call { + struct loadparm_context *lp_ctx; + + TALLOC_CTX *mem_ctx; + uint16_t callno; + const char *paramdesc; + const char *datadesc; + + uint16_t status; + uint16_t convert; + + uint16_t rcv_paramlen, rcv_datalen; + + struct ndr_push *ndr_push_param; + struct ndr_push *ndr_push_data; + struct rap_string_heap *heap; + + struct ndr_pull *ndr_pull_param; + struct ndr_pull *ndr_pull_data; + + struct tevent_context *event_ctx; +}; + +#define RAPNDR_FLAGS (LIBNDR_FLAG_NOALIGN|LIBNDR_FLAG_STR_ASCII|LIBNDR_FLAG_STR_NULLTERM); + +static struct rap_call *new_rap_srv_call(TALLOC_CTX *mem_ctx, + struct tevent_context *ev_ctx, + struct loadparm_context *lp_ctx, + struct smb_trans2 *trans) +{ + struct rap_call *call; + + call = talloc(mem_ctx, struct rap_call); + + if (call == NULL) + return NULL; + + ZERO_STRUCTP(call); + + call->lp_ctx = talloc_reference(call, lp_ctx); + call->event_ctx = ev_ctx; + + call->mem_ctx = mem_ctx; + + call->ndr_pull_param = ndr_pull_init_blob(&trans->in.params, mem_ctx); + call->ndr_pull_param->flags = RAPNDR_FLAGS; + + call->ndr_pull_data = ndr_pull_init_blob(&trans->in.data, mem_ctx); + call->ndr_pull_data->flags = RAPNDR_FLAGS; + + call->heap = talloc(mem_ctx, struct rap_string_heap); + + if (call->heap == NULL) + return NULL; + + ZERO_STRUCTP(call->heap); + + call->heap->mem_ctx = mem_ctx; + + return call; +} + +static NTSTATUS rap_srv_pull_word(struct rap_call *call, uint16_t *result) +{ + enum ndr_err_code ndr_err; + + if (*call->paramdesc++ != 'W') + return NT_STATUS_INVALID_PARAMETER; + + ndr_err = ndr_pull_uint16(call->ndr_pull_param, NDR_SCALARS, result); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return ndr_map_error2ntstatus(ndr_err); + } + + return NT_STATUS_OK; +} + +static NTSTATUS rap_srv_pull_dword(struct rap_call *call, uint32_t *result) +{ + enum ndr_err_code ndr_err; + + if (*call->paramdesc++ != 'D') + return NT_STATUS_INVALID_PARAMETER; + + ndr_err = ndr_pull_uint32(call->ndr_pull_param, NDR_SCALARS, result); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return ndr_map_error2ntstatus(ndr_err); + } + + return NT_STATUS_OK; +} + +static NTSTATUS rap_srv_pull_string(struct rap_call *call, const char **result) +{ + enum ndr_err_code ndr_err; + char paramdesc = *call->paramdesc++; + + if (paramdesc == 'O') { + *result = NULL; + return NT_STATUS_OK; + } + + if (paramdesc != 'z') + return NT_STATUS_INVALID_PARAMETER; + + ndr_err = ndr_pull_string(call->ndr_pull_param, NDR_SCALARS, result); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return ndr_map_error2ntstatus(ndr_err); + } + + return NT_STATUS_OK; +} + +static NTSTATUS rap_srv_pull_bufsize(struct rap_call *call, uint16_t *bufsize) +{ + enum ndr_err_code ndr_err; + + if ( (*call->paramdesc++ != 'r') || (*call->paramdesc++ != 'L') ) + return NT_STATUS_INVALID_PARAMETER; + + ndr_err = ndr_pull_uint16(call->ndr_pull_param, NDR_SCALARS, bufsize); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return ndr_map_error2ntstatus(ndr_err); + } + + call->heap->offset = *bufsize; + + return NT_STATUS_OK; +} + +static NTSTATUS rap_srv_pull_expect_multiple(struct rap_call *call) +{ + if ( (*call->paramdesc++ != 'e') || (*call->paramdesc++ != 'h') ) + return NT_STATUS_INVALID_PARAMETER; + + return NT_STATUS_OK; +} + +static NTSTATUS rap_push_string(struct ndr_push *data_push, + struct rap_string_heap *heap, + const char *str) +{ + size_t space; + + if (str == NULL) + str = ""; + + space = strlen(str)+1; + + if (heap->offset < space) + return NT_STATUS_BUFFER_TOO_SMALL; + + heap->offset -= space; + + NDR_RETURN(ndr_push_uint16(data_push, NDR_SCALARS, heap->offset)); + NDR_RETURN(ndr_push_uint16(data_push, NDR_SCALARS, 0)); + + heap->strings = talloc_realloc(heap->mem_ctx, + heap->strings, + const char *, + heap->num_strings + 1); + + if (heap->strings == NULL) + return NT_STATUS_NO_MEMORY; + + heap->strings[heap->num_strings] = str; + heap->num_strings += 1; + + return NT_STATUS_OK; +} + +static NTSTATUS _rap_netshareenum(struct rap_call *call) +{ + struct rap_NetShareEnum r; + NTSTATUS result; + uint32_t offset_save = 0; + struct rap_heap_save heap_save = {0}; + + RAP_GOTO(rap_srv_pull_word(call, &r.in.level)); + RAP_GOTO(rap_srv_pull_bufsize(call, &r.in.bufsize)); + RAP_GOTO(rap_srv_pull_expect_multiple(call)); + + switch(r.in.level) { + case 0: + if (strcmp(call->datadesc, "B13") != 0) + return NT_STATUS_INVALID_PARAMETER; + break; + case 1: + if (strcmp(call->datadesc, "B13BWz") != 0) + return NT_STATUS_INVALID_PARAMETER; + break; + default: + return NT_STATUS_INVALID_PARAMETER; + break; + } + + result = rap_netshareenum(call, call->event_ctx, call->lp_ctx, &r); + + if (!NT_STATUS_IS_OK(result)) + return result; + + for (r.out.count = 0; r.out.count < r.out.available; r.out.count++) { + + int i = r.out.count; + + offset_save = call->ndr_push_data->offset; + rap_heap_save(call->heap, &heap_save); + + switch(r.in.level) { + case 0: + NDR_GOTO(ndr_push_bytes(call->ndr_push_data, + (const uint8_t *)r.out.info[i].info0.share_name, + sizeof(r.out.info[i].info0.share_name))); + break; + case 1: + NDR_GOTO(ndr_push_bytes(call->ndr_push_data, + (const uint8_t *)r.out.info[i].info1.share_name, + sizeof(r.out.info[i].info1.share_name))); + NDR_GOTO(ndr_push_uint8(call->ndr_push_data, + NDR_SCALARS, r.out.info[i].info1.reserved1)); + NDR_GOTO(ndr_push_uint16(call->ndr_push_data, + NDR_SCALARS, r.out.info[i].info1.share_type)); + + RAP_GOTO(rap_push_string(call->ndr_push_data, + call->heap, + r.out.info[i].info1.comment)); + + break; + } + + if (call->ndr_push_data->offset > call->heap->offset) { + + buffer_overflow: + + call->ndr_push_data->offset = offset_save; + rap_heap_restore(call->heap, &heap_save); + break; + } + } + + call->status = r.out.status; + + NDR_RETURN(ndr_push_uint16(call->ndr_push_param, NDR_SCALARS, r.out.count)); + NDR_RETURN(ndr_push_uint16(call->ndr_push_param, NDR_SCALARS, r.out.available)); + + result = NT_STATUS_OK; + + done: + return result; +} + +static NTSTATUS _rap_netserverenum2(struct rap_call *call) +{ + struct rap_NetServerEnum2 r; + NTSTATUS result; + uint32_t offset_save = 0; + struct rap_heap_save heap_save = {0}; + + RAP_GOTO(rap_srv_pull_word(call, &r.in.level)); + RAP_GOTO(rap_srv_pull_bufsize(call, &r.in.bufsize)); + RAP_GOTO(rap_srv_pull_expect_multiple(call)); + RAP_GOTO(rap_srv_pull_dword(call, &r.in.servertype)); + RAP_GOTO(rap_srv_pull_string(call, &r.in.domain)); + + switch(r.in.level) { + case 0: + if (strcmp(call->datadesc, "B16") != 0) + return NT_STATUS_INVALID_PARAMETER; + break; + case 1: + if (strcmp(call->datadesc, "B16BBDz") != 0) + return NT_STATUS_INVALID_PARAMETER; + break; + default: + return NT_STATUS_INVALID_PARAMETER; + break; + } + + result = rap_netserverenum2(call, call->lp_ctx, &r); + + if (!NT_STATUS_IS_OK(result)) + return result; + + for (r.out.count = 0; r.out.count < r.out.available; r.out.count++) { + + int i = r.out.count; + + offset_save = call->ndr_push_data->offset; + rap_heap_save(call->heap, &heap_save); + + switch(r.in.level) { + case 0: + NDR_GOTO(ndr_push_bytes(call->ndr_push_data, + (const uint8_t *)r.out.info[i].info0.name, + sizeof(r.out.info[i].info0.name))); + break; + case 1: + NDR_GOTO(ndr_push_bytes(call->ndr_push_data, + (const uint8_t *)r.out.info[i].info1.name, + sizeof(r.out.info[i].info1.name))); + NDR_GOTO(ndr_push_uint8(call->ndr_push_data, + NDR_SCALARS, r.out.info[i].info1.version_major)); + NDR_GOTO(ndr_push_uint8(call->ndr_push_data, + NDR_SCALARS, r.out.info[i].info1.version_minor)); + NDR_GOTO(ndr_push_uint32(call->ndr_push_data, + NDR_SCALARS, r.out.info[i].info1.servertype)); + + RAP_GOTO(rap_push_string(call->ndr_push_data, + call->heap, + r.out.info[i].info1.comment)); + + break; + } + + if (call->ndr_push_data->offset > call->heap->offset) { + + buffer_overflow: + + call->ndr_push_data->offset = offset_save; + rap_heap_restore(call->heap, &heap_save); + break; + } + } + + call->status = r.out.status; + + NDR_RETURN(ndr_push_uint16(call->ndr_push_param, NDR_SCALARS, r.out.count)); + NDR_RETURN(ndr_push_uint16(call->ndr_push_param, NDR_SCALARS, r.out.available)); + + result = NT_STATUS_OK; + + done: + return result; +} + +static NTSTATUS api_Unsupported(struct rap_call *call) +{ + call->status = NERR_notsupported; + call->convert = 0; + return NT_STATUS_OK; +} + +static const struct +{ + const char *name; + int id; + NTSTATUS (*fn)(struct rap_call *call); +} api_commands[] = { + {"NetShareEnum", RAP_WshareEnum, _rap_netshareenum }, + {"NetServerEnum2", RAP_NetServerEnum2, _rap_netserverenum2 }, + {NULL, -1, api_Unsupported} +}; + +NTSTATUS ipc_rap_call(TALLOC_CTX *mem_ctx, struct tevent_context *event_ctx, struct loadparm_context *lp_ctx, + struct smb_trans2 *trans) +{ + int i; + NTSTATUS result; + struct rap_call *call; + DATA_BLOB result_param, result_data; + struct ndr_push *final_param; + struct ndr_push *final_data; + + call = new_rap_srv_call(mem_ctx, event_ctx, lp_ctx, trans); + + if (call == NULL) + return NT_STATUS_NO_MEMORY; + + NDR_RETURN(ndr_pull_uint16(call->ndr_pull_param, NDR_SCALARS, &call->callno)); + NDR_RETURN(ndr_pull_string(call->ndr_pull_param, NDR_SCALARS, + &call->paramdesc)); + NDR_RETURN(ndr_pull_string(call->ndr_pull_param, NDR_SCALARS, + &call->datadesc)); + + call->ndr_push_param = ndr_push_init_ctx(call); + call->ndr_push_data = ndr_push_init_ctx(call); + + if ((call->ndr_push_param == NULL) || (call->ndr_push_data == NULL)) + return NT_STATUS_NO_MEMORY; + + call->ndr_push_param->flags = RAPNDR_FLAGS; + call->ndr_push_data->flags = RAPNDR_FLAGS; + + result = NT_STATUS_INVALID_SYSTEM_SERVICE; + + for (i=0; api_commands[i].name != NULL; i++) { + if (api_commands[i].id == call->callno) { + DEBUG(5, ("Running RAP call %s\n", + api_commands[i].name)); + result = api_commands[i].fn(call); + break; + } + } + + if (!NT_STATUS_IS_OK(result)) + return result; + + result_param = ndr_push_blob(call->ndr_push_param); + result_data = ndr_push_blob(call->ndr_push_data); + + final_param = ndr_push_init_ctx(call); + final_data = ndr_push_init_ctx(call); + + if ((final_param == NULL) || (final_data == NULL)) + return NT_STATUS_NO_MEMORY; + + final_param->flags = RAPNDR_FLAGS; + final_data->flags = RAPNDR_FLAGS; + + NDR_RETURN(ndr_push_uint16(final_param, NDR_SCALARS, call->status)); + NDR_RETURN(ndr_push_uint16(final_param, + NDR_SCALARS, call->heap->offset - result_data.length)); + NDR_RETURN(ndr_push_bytes(final_param, result_param.data, + result_param.length)); + + NDR_RETURN(ndr_push_bytes(final_data, result_data.data, + result_data.length)); + + for (i=call->heap->num_strings-1; i>=0; i--) + NDR_RETURN(ndr_push_string(final_data, NDR_SCALARS, + call->heap->strings[i])); + + trans->out.setup_count = 0; + trans->out.setup = NULL; + trans->out.params = ndr_push_blob(final_param); + trans->out.data = ndr_push_blob(final_data); + + return result; +} diff --git a/source4/ntvfs/ipc/rap_server.c b/source4/ntvfs/ipc/rap_server.c new file mode 100644 index 0000000..4c4beca --- /dev/null +++ b/source4/ntvfs/ipc/rap_server.c @@ -0,0 +1,95 @@ +/* + Unix SMB/CIFS implementation. + RAP handlers + + Copyright (C) Volker Lendecke 2004 + + 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 "param/share.h" +#include "../librpc/gen_ndr/rap.h" +#include "libcli/raw/interfaces.h" +#include "librpc/gen_ndr/srvsvc.h" +#include "librpc/gen_ndr/dcerpc.h" +#include "rpc_server/common/common.h" +#include "rpc_server/common/share.h" +#include "param/param.h" +#include "ntvfs/ipc/proto.h" + +/* At this moment these are just dummy functions, but you might get the + * idea. */ + +NTSTATUS rap_netshareenum(TALLOC_CTX *mem_ctx, + struct tevent_context *event_ctx, + struct loadparm_context *lp_ctx, + struct rap_NetShareEnum *r) +{ + NTSTATUS nterr; + const char **snames; + struct share_context *sctx; + struct share_config *scfg; + int i, j, count; + + r->out.status = 0; + r->out.available = 0; + r->out.info = NULL; + + nterr = share_get_context(mem_ctx, lp_ctx, &sctx); + if (!NT_STATUS_IS_OK(nterr)) { + return nterr; + } + + nterr = share_list_all(mem_ctx, sctx, &count, &snames); + if (!NT_STATUS_IS_OK(nterr)) { + return nterr; + } + + r->out.available = count; + r->out.info = talloc_array(mem_ctx, + union rap_share_info, r->out.available); + + for (i = 0, j = 0; i < r->out.available; i++) { + size_t sname_len; + + if (!NT_STATUS_IS_OK(share_get_config(mem_ctx, sctx, snames[i], &scfg))) { + DEBUG(3, ("WARNING: Service [%s] disappeared after enumeration!\n", snames[i])); + continue; + } + /* Make sure we have NUL-termination */ + sname_len = MIN(strlen(snames[i]), + sizeof(r->out.info[j].info1.share_name)); + strlcpy((char *)r->out.info[j].info1.share_name, + snames[i], + sname_len); + r->out.info[i].info1.reserved1 = 0; + r->out.info[i].info1.share_type = dcesrv_common_get_share_type(mem_ctx, NULL, scfg); + r->out.info[i].info1.comment = share_string_option(mem_ctx, scfg, SHARE_COMMENT, ""); + talloc_free(scfg); + j++; + } + r->out.available = j; + + return NT_STATUS_OK; +} + +NTSTATUS rap_netserverenum2(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + struct rap_NetServerEnum2 *r) +{ + r->out.status = 0; + r->out.available = 0; + return NT_STATUS_OK; +} diff --git a/source4/ntvfs/ipc/vfs_ipc.c b/source4/ntvfs/ipc/vfs_ipc.c new file mode 100644 index 0000000..01e2a5d --- /dev/null +++ b/source4/ntvfs/ipc/vfs_ipc.c @@ -0,0 +1,1356 @@ +/* + Unix SMB/CIFS implementation. + default IPC$ NTVFS backend + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Stefan (metze) Metzmacher 2004-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/>. +*/ +/* + this implements the IPC$ backend, called by the NTVFS subsystem to + handle requests on IPC$ shares +*/ + + +#include "includes.h" +#include "../lib/util/dlinklist.h" +#include "ntvfs/ntvfs.h" +#include "../librpc/gen_ndr/rap.h" +#include "ntvfs/ipc/proto.h" +#include "../libcli/smb/smb_constants.h" +#include "param/param.h" +#include "../lib/tsocket/tsocket.h" +#include "../libcli/named_pipe_auth/npa_tstream.h" +#include "auth/auth.h" +#include "auth/auth_sam_reply.h" +#include "lib/socket/socket.h" +#include "auth/credentials/credentials.h" +#include "auth/credentials/credentials_krb5.h" +#include "system/kerberos.h" +#include "system/gssapi.h" +#include "system/locale.h" +#include "system/filesys.h" + +#undef strncasecmp + +/* this is the private structure used to keep the state of an open + ipc$ connection. It needs to keep information about all open + pipes */ +struct ipc_private { + struct ntvfs_module_context *ntvfs; + + /* a list of open pipes */ + struct pipe_state { + struct pipe_state *next, *prev; + struct ipc_private *ipriv; + const char *pipe_name; + struct ntvfs_handle *handle; + struct tstream_context *npipe; + uint16_t file_type; + uint16_t device_state; + uint64_t allocation_size; + struct tevent_queue *write_queue; + struct tevent_queue *read_queue; + } *pipe_list; +}; + + +/* + find a open pipe give a file handle +*/ +static struct pipe_state *pipe_state_find(struct ipc_private *ipriv, struct ntvfs_handle *handle) +{ + struct pipe_state *s; + void *p; + + p = ntvfs_handle_get_backend_data(handle, ipriv->ntvfs); + if (!p) return NULL; + + s = talloc_get_type(p, struct pipe_state); + if (!s) return NULL; + + return s; +} + +/* + find a open pipe give a wire fnum +*/ +static struct pipe_state *pipe_state_find_key(struct ipc_private *ipriv, struct ntvfs_request *req, const DATA_BLOB *key) +{ + struct ntvfs_handle *h; + + h = ntvfs_handle_search_by_wire_key(ipriv->ntvfs, req, key); + if (!h) return NULL; + + return pipe_state_find(ipriv, h); +} + + +/* + connect to a share - always works +*/ +static NTSTATUS ipc_connect(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_tcon* tcon) +{ + struct ipc_private *ipriv; + 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: + sharename = tcon->smb2.in.path; + break; + default: + return NT_STATUS_INVALID_LEVEL; + } + + if (strncmp(sharename, "\\\\", 2) == 0) { + char *p = strchr(sharename+2, '\\'); + if (p) { + sharename = p + 1; + } + } + + ntvfs->ctx->fs_type = talloc_strdup(ntvfs->ctx, "IPC"); + NT_STATUS_HAVE_NO_MEMORY(ntvfs->ctx->fs_type); + + ntvfs->ctx->dev_type = talloc_strdup(ntvfs->ctx, "IPC"); + NT_STATUS_HAVE_NO_MEMORY(ntvfs->ctx->dev_type); + + 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; + } + + /* prepare the private state for this connection */ + ipriv = talloc(ntvfs, struct ipc_private); + NT_STATUS_HAVE_NO_MEMORY(ipriv); + + ntvfs->private_data = ipriv; + + ipriv->ntvfs = ntvfs; + ipriv->pipe_list = NULL; + + return NT_STATUS_OK; +} + +/* + disconnect from a share +*/ +static NTSTATUS ipc_disconnect(struct ntvfs_module_context *ntvfs) +{ + return NT_STATUS_OK; +} + +/* + delete a file +*/ +static NTSTATUS ipc_unlink(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_unlink *unl) +{ + return NT_STATUS_ACCESS_DENIED; +} + +/* + check if a directory exists +*/ +static NTSTATUS ipc_chkpath(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_chkpath *cp) +{ + return NT_STATUS_ACCESS_DENIED; +} + +/* + return info on a pathname +*/ +static NTSTATUS ipc_qpathinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_fileinfo *info) +{ + switch (info->generic.level) { + case RAW_FILEINFO_GENERIC: + return NT_STATUS_INVALID_DEVICE_REQUEST; + case RAW_FILEINFO_GETATTR: + return NT_STATUS_ACCESS_DENIED; + default: + return ntvfs_map_qpathinfo(ntvfs, req, info); + } +} + +/* + set info on a pathname +*/ +static NTSTATUS ipc_setpathinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_setfileinfo *st) +{ + return NT_STATUS_ACCESS_DENIED; +} + + +/* + destroy a open pipe structure +*/ +static int ipc_fd_destructor(struct pipe_state *p) +{ + DLIST_REMOVE(p->ipriv->pipe_list, p); + ntvfs_handle_remove_backend_data(p->handle, p->ipriv->ntvfs); + return 0; +} + +struct ipc_open_state { + struct ipc_private *ipriv; + struct pipe_state *p; + struct ntvfs_request *req; + union smb_open *oi; + struct auth_session_info_transport *session_info_transport; +}; + +static void ipc_open_done(struct tevent_req *subreq); + +/* + check the pipename is valid + */ +static NTSTATUS validate_pipename(const char *name) +{ + while (*name) { + if (!isalnum(*name) && *name != '_') { + return NT_STATUS_INVALID_PARAMETER; + } + name++; + } + return NT_STATUS_OK; +} + +/* + open a file - used for MSRPC pipes +*/ +static NTSTATUS ipc_open(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_open *oi) +{ + NTSTATUS status; + struct pipe_state *p; + struct ipc_private *ipriv = talloc_get_type_abort(ntvfs->private_data, + struct ipc_private); + struct ntvfs_handle *h; + struct ipc_open_state *state; + struct tevent_req *subreq; + const char *fname; + const char *directory; + const struct tsocket_address *remote_client_addr; + const struct tsocket_address *local_server_addr; + + switch (oi->generic.level) { + case RAW_OPEN_NTCREATEX: + case RAW_OPEN_NTTRANS_CREATE: + fname = oi->ntcreatex.in.fname; + while (fname[0] == '\\') fname++; + break; + case RAW_OPEN_OPENX: + fname = oi->openx.in.fname; + while (fname[0] == '\\') fname++; + if (strncasecmp(fname, "PIPE\\", 5) != 0) { + return NT_STATUS_OBJECT_PATH_SYNTAX_BAD; + } + while (fname[0] == '\\') fname++; + break; + case RAW_OPEN_SMB2: + fname = oi->smb2.in.fname; + break; + default: + return NT_STATUS_NOT_SUPPORTED; + } + + directory = talloc_asprintf(req, "%s/np", + lpcfg_ncalrpc_dir(ipriv->ntvfs->ctx->lp_ctx)); + NT_STATUS_HAVE_NO_MEMORY(directory); + + state = talloc(req, struct ipc_open_state); + NT_STATUS_HAVE_NO_MEMORY(state); + + status = ntvfs_handle_new(ntvfs, req, &h); + NT_STATUS_NOT_OK_RETURN(status); + + p = talloc(h, struct pipe_state); + NT_STATUS_HAVE_NO_MEMORY(p); + + /* check for valid characters in name */ + fname = strlower_talloc(p, fname); + + status = validate_pipename(fname); + NT_STATUS_NOT_OK_RETURN(status); + + p->pipe_name = talloc_asprintf(p, "\\pipe\\%s", fname); + NT_STATUS_HAVE_NO_MEMORY(p->pipe_name); + + p->handle = h; + p->ipriv = ipriv; + + p->write_queue = tevent_queue_create(p, "ipc_write_queue"); + NT_STATUS_HAVE_NO_MEMORY(p->write_queue); + + p->read_queue = tevent_queue_create(p, "ipc_read_queue"); + NT_STATUS_HAVE_NO_MEMORY(p->read_queue); + + state->ipriv = ipriv; + state->p = p; + state->req = req; + state->oi = oi; + + status = auth_session_info_transport_from_session(state, + req->session_info, + ipriv->ntvfs->ctx->event_ctx, + ipriv->ntvfs->ctx->lp_ctx, + &state->session_info_transport); + + NT_STATUS_NOT_OK_RETURN(status); + + local_server_addr = ntvfs_get_local_address(ipriv->ntvfs); + remote_client_addr = ntvfs_get_remote_address(ipriv->ntvfs); + + subreq = tstream_npa_connect_send(p, + ipriv->ntvfs->ctx->event_ctx, + directory, + fname, + NCACN_NP, + remote_client_addr, + NULL, + local_server_addr, + NULL, + state->session_info_transport); + NT_STATUS_HAVE_NO_MEMORY(subreq); + tevent_req_set_callback(subreq, ipc_open_done, state); + + req->async_states->state |= NTVFS_ASYNC_STATE_ASYNC; + return NT_STATUS_OK; +} + +static void ipc_open_done(struct tevent_req *subreq) +{ + struct ipc_open_state *state = tevent_req_callback_data(subreq, + struct ipc_open_state); + struct ipc_private *ipriv = state->ipriv; + struct pipe_state *p = state->p; + struct ntvfs_request *req = state->req; + union smb_open *oi = state->oi; + int ret; + int sys_errno; + NTSTATUS status; + + ret = tstream_npa_connect_recv(subreq, &sys_errno, + p, &p->npipe, + &p->file_type, + &p->device_state, + &p->allocation_size); + TALLOC_FREE(subreq); + if (ret == -1) { + status = map_nt_error_from_unix_common(sys_errno); + goto reply; + } + + DLIST_ADD(ipriv->pipe_list, p); + talloc_set_destructor(p, ipc_fd_destructor); + + status = ntvfs_handle_set_backend_data(p->handle, ipriv->ntvfs, p); + if (!NT_STATUS_IS_OK(status)) { + goto reply; + } + + switch (oi->generic.level) { + case RAW_OPEN_NTCREATEX: + ZERO_STRUCT(oi->ntcreatex.out); + oi->ntcreatex.out.file.ntvfs = p->handle; + oi->ntcreatex.out.oplock_level = 0; + oi->ntcreatex.out.create_action = NTCREATEX_ACTION_EXISTED; + oi->ntcreatex.out.create_time = 0; + oi->ntcreatex.out.access_time = 0; + oi->ntcreatex.out.write_time = 0; + oi->ntcreatex.out.change_time = 0; + oi->ntcreatex.out.attrib = FILE_ATTRIBUTE_NORMAL; + oi->ntcreatex.out.alloc_size = p->allocation_size; + oi->ntcreatex.out.size = 0; + oi->ntcreatex.out.file_type = p->file_type; + oi->ntcreatex.out.ipc_state = p->device_state; + oi->ntcreatex.out.is_directory = 0; + break; + case RAW_OPEN_OPENX: + ZERO_STRUCT(oi->openx.out); + oi->openx.out.file.ntvfs = p->handle; + oi->openx.out.attrib = FILE_ATTRIBUTE_NORMAL; + oi->openx.out.write_time = 0; + oi->openx.out.size = 0; + oi->openx.out.access = 0; + oi->openx.out.ftype = p->file_type; + oi->openx.out.devstate = p->device_state; + oi->openx.out.action = 0; + oi->openx.out.unique_fid = 0; + oi->openx.out.access_mask = 0; + oi->openx.out.unknown = 0; + break; + case RAW_OPEN_SMB2: + ZERO_STRUCT(oi->smb2.out); + oi->smb2.out.file.ntvfs = p->handle; + oi->smb2.out.oplock_level = oi->smb2.in.oplock_level; + oi->smb2.out.create_action = NTCREATEX_ACTION_EXISTED; + oi->smb2.out.create_time = 0; + oi->smb2.out.access_time = 0; + oi->smb2.out.write_time = 0; + oi->smb2.out.change_time = 0; + oi->smb2.out.alloc_size = p->allocation_size; + oi->smb2.out.size = 0; + oi->smb2.out.file_attr = FILE_ATTRIBUTE_NORMAL; + oi->smb2.out.reserved2 = 0; + break; + default: + break; + } + +reply: + req->async_states->status = status; + req->async_states->send_fn(req); +} + +/* + create a directory +*/ +static NTSTATUS ipc_mkdir(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_mkdir *md) +{ + return NT_STATUS_ACCESS_DENIED; +} + +/* + remove a directory +*/ +static NTSTATUS ipc_rmdir(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, struct smb_rmdir *rd) +{ + return NT_STATUS_ACCESS_DENIED; +} + +/* + rename a set of files +*/ +static NTSTATUS ipc_rename(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_rename *ren) +{ + return NT_STATUS_ACCESS_DENIED; +} + +/* + copy a set of files +*/ +static NTSTATUS ipc_copy(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, struct smb_copy *cp) +{ + return NT_STATUS_ACCESS_DENIED; +} + +struct ipc_readv_next_vector_state { + uint8_t *buf; + size_t len; + off_t ofs; + size_t remaining; +}; + +static void ipc_readv_next_vector_init(struct ipc_readv_next_vector_state *s, + uint8_t *buf, size_t len) +{ + ZERO_STRUCTP(s); + + s->buf = buf; + s->len = MIN(len, UINT16_MAX); +} + +static int ipc_readv_next_vector(struct tstream_context *stream, + void *private_data, + TALLOC_CTX *mem_ctx, + struct iovec **_vector, + size_t *count) +{ + struct ipc_readv_next_vector_state *state = + (struct ipc_readv_next_vector_state *)private_data; + struct iovec *vector; + ssize_t pending; + size_t wanted; + + if (state->ofs == state->len) { + *_vector = NULL; + *count = 0; + return 0; + } + + pending = tstream_pending_bytes(stream); + if (pending == -1) { + return -1; + } + + if (pending == 0 && state->ofs != 0) { + /* return a short read */ + *_vector = NULL; + *count = 0; + return 0; + } + + if (pending == 0) { + /* we want at least one byte and recheck again */ + wanted = 1; + } else { + size_t missing = state->len - state->ofs; + if (pending > missing) { + /* there's more available */ + state->remaining = pending - missing; + wanted = missing; + } else { + /* read what we can get and recheck in the next cycle */ + wanted = pending; + } + } + + vector = talloc_array(mem_ctx, struct iovec, 1); + if (!vector) { + return -1; + } + + vector[0].iov_base = (char *) (state->buf + state->ofs); + vector[0].iov_len = wanted; + + state->ofs += wanted; + + *_vector = vector; + *count = 1; + return 0; +} + +struct ipc_read_state { + struct ipc_private *ipriv; + struct pipe_state *p; + struct ntvfs_request *req; + union smb_read *rd; + struct ipc_readv_next_vector_state next_vector; +}; + +static void ipc_read_done(struct tevent_req *subreq); + +/* + read from a file +*/ +static NTSTATUS ipc_read(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_read *rd) +{ + struct ipc_private *ipriv = talloc_get_type_abort(ntvfs->private_data, + struct ipc_private); + struct pipe_state *p; + struct ipc_read_state *state; + struct tevent_req *subreq; + + if (rd->generic.level != RAW_READ_GENERIC) { + return ntvfs_map_read(ntvfs, req, rd); + } + + p = pipe_state_find(ipriv, rd->readx.in.file.ntvfs); + if (!p) { + return NT_STATUS_INVALID_HANDLE; + } + + state = talloc(req, struct ipc_read_state); + NT_STATUS_HAVE_NO_MEMORY(state); + + state->ipriv = ipriv; + state->p = p; + state->req = req; + state->rd = rd; + + /* rd->readx.out.data is already allocated */ + ipc_readv_next_vector_init(&state->next_vector, + rd->readx.out.data, + rd->readx.in.maxcnt); + + subreq = tstream_readv_pdu_queue_send(req, + ipriv->ntvfs->ctx->event_ctx, + p->npipe, + p->read_queue, + ipc_readv_next_vector, + &state->next_vector); + NT_STATUS_HAVE_NO_MEMORY(subreq); + tevent_req_set_callback(subreq, ipc_read_done, state); + + req->async_states->state |= NTVFS_ASYNC_STATE_ASYNC; + return NT_STATUS_OK; +} + +static void ipc_read_done(struct tevent_req *subreq) +{ + struct ipc_read_state *state = + tevent_req_callback_data(subreq, + struct ipc_read_state); + struct ntvfs_request *req = state->req; + union smb_read *rd = state->rd; + int ret; + int sys_errno; + NTSTATUS status; + + ret = tstream_readv_pdu_queue_recv(subreq, &sys_errno); + TALLOC_FREE(subreq); + if (ret == -1) { + status = map_nt_error_from_unix_common(sys_errno); + goto reply; + } + + status = NT_STATUS_OK; + if (state->next_vector.remaining > 0) { + status = STATUS_BUFFER_OVERFLOW; + } + + rd->readx.out.remaining = state->next_vector.remaining; + rd->readx.out.compaction_mode = 0; + rd->readx.out.nread = ret; + +reply: + req->async_states->status = status; + req->async_states->send_fn(req); +} + +struct ipc_write_state { + struct ipc_private *ipriv; + struct pipe_state *p; + struct ntvfs_request *req; + union smb_write *wr; + struct iovec iov; +}; + +static void ipc_write_done(struct tevent_req *subreq); + +/* + write to a file +*/ +static NTSTATUS ipc_write(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_write *wr) +{ + struct ipc_private *ipriv = talloc_get_type_abort(ntvfs->private_data, + struct ipc_private); + struct pipe_state *p; + struct tevent_req *subreq; + struct ipc_write_state *state; + + if (wr->generic.level != RAW_WRITE_GENERIC) { + return ntvfs_map_write(ntvfs, req, wr); + } + + p = pipe_state_find(ipriv, wr->writex.in.file.ntvfs); + if (!p) { + return NT_STATUS_INVALID_HANDLE; + } + + state = talloc(req, struct ipc_write_state); + NT_STATUS_HAVE_NO_MEMORY(state); + + state->ipriv = ipriv; + state->p = p; + state->req = req; + state->wr = wr; + state->iov.iov_base = discard_const_p(void, wr->writex.in.data); + state->iov.iov_len = wr->writex.in.count; + + subreq = tstream_writev_queue_send(state, + ipriv->ntvfs->ctx->event_ctx, + p->npipe, + p->write_queue, + &state->iov, 1); + NT_STATUS_HAVE_NO_MEMORY(subreq); + tevent_req_set_callback(subreq, ipc_write_done, state); + + req->async_states->state |= NTVFS_ASYNC_STATE_ASYNC; + return NT_STATUS_OK; +} + +static void ipc_write_done(struct tevent_req *subreq) +{ + struct ipc_write_state *state = + tevent_req_callback_data(subreq, + struct ipc_write_state); + struct ntvfs_request *req = state->req; + union smb_write *wr = state->wr; + int ret; + int sys_errno; + NTSTATUS status; + + ret = tstream_writev_queue_recv(subreq, &sys_errno); + TALLOC_FREE(subreq); + if (ret == -1) { + status = map_nt_error_from_unix_common(sys_errno); + goto reply; + } + + status = NT_STATUS_OK; + + wr->writex.out.nwritten = ret; + wr->writex.out.remaining = 0; + +reply: + req->async_states->status = status; + req->async_states->send_fn(req); +} + +/* + seek in a file +*/ +static NTSTATUS ipc_seek(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_seek *io) +{ + return NT_STATUS_ACCESS_DENIED; +} + +/* + flush a file +*/ +static NTSTATUS ipc_flush(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_flush *io) +{ + return NT_STATUS_ACCESS_DENIED; +} + +/* + close a file +*/ +static NTSTATUS ipc_close(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_close *io) +{ + struct ipc_private *ipriv = talloc_get_type_abort(ntvfs->private_data, + struct ipc_private); + struct pipe_state *p; + + if (io->generic.level != RAW_CLOSE_GENERIC) { + return ntvfs_map_close(ntvfs, req, io); + } + + ZERO_STRUCT(io->generic.out); + + p = pipe_state_find(ipriv, io->generic.in.file.ntvfs); + if (!p) { + return NT_STATUS_INVALID_HANDLE; + } + + talloc_free(p); + + return NT_STATUS_OK; +} + +/* + exit - closing files +*/ +static NTSTATUS ipc_exit(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req) +{ + struct ipc_private *ipriv = talloc_get_type_abort(ntvfs->private_data, + struct ipc_private); + struct pipe_state *p, *next; + + for (p=ipriv->pipe_list; p; p=next) { + next = p->next; + if (p->handle->session_info == req->session_info && + p->handle->smbpid == req->smbpid) { + talloc_free(p); + } + } + + return NT_STATUS_OK; +} + +/* + logoff - closing files open by the user +*/ +static NTSTATUS ipc_logoff(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req) +{ + struct ipc_private *ipriv = talloc_get_type_abort(ntvfs->private_data, + struct ipc_private); + struct pipe_state *p, *next; + + for (p=ipriv->pipe_list; p; p=next) { + next = p->next; + if (p->handle->session_info == req->session_info) { + talloc_free(p); + } + } + + return NT_STATUS_OK; +} + +/* + setup for an async call +*/ +static NTSTATUS ipc_async_setup(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + void *private_data) +{ + return NT_STATUS_OK; +} + +/* + cancel an async call +*/ +static NTSTATUS ipc_cancel(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req) +{ + return NT_STATUS_UNSUCCESSFUL; +} + +/* + lock a byte range +*/ +static NTSTATUS ipc_lock(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_lock *lck) +{ + return NT_STATUS_ACCESS_DENIED; +} + +/* + set info on a open file +*/ +static NTSTATUS ipc_setfileinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_setfileinfo *info) +{ + return NT_STATUS_ACCESS_DENIED; +} + +/* + query info on a open file +*/ +static NTSTATUS ipc_qfileinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_fileinfo *info) +{ + struct ipc_private *ipriv = talloc_get_type_abort(ntvfs->private_data, + struct ipc_private); + struct pipe_state *p = pipe_state_find(ipriv, info->generic.in.file.ntvfs); + if (!p) { + return NT_STATUS_INVALID_HANDLE; + } + switch (info->generic.level) { + case RAW_FILEINFO_GENERIC: + { + ZERO_STRUCT(info->generic.out); + info->generic.out.attrib = FILE_ATTRIBUTE_NORMAL; + info->generic.out.fname.s = strrchr(p->pipe_name, '\\'); + info->generic.out.alloc_size = 4096; + info->generic.out.nlink = 1; + /* What the heck? Match Win2k3: IPC$ pipes are delete pending */ + info->generic.out.delete_pending = 1; + return NT_STATUS_OK; + } + case RAW_FILEINFO_ALT_NAME_INFO: + case RAW_FILEINFO_ALT_NAME_INFORMATION: + case RAW_FILEINFO_STREAM_INFO: + case RAW_FILEINFO_STREAM_INFORMATION: + case RAW_FILEINFO_COMPRESSION_INFO: + case RAW_FILEINFO_COMPRESSION_INFORMATION: + case RAW_FILEINFO_NETWORK_OPEN_INFORMATION: + case RAW_FILEINFO_ATTRIBUTE_TAG_INFORMATION: + return NT_STATUS_INVALID_PARAMETER; + case RAW_FILEINFO_ALL_EAS: + return NT_STATUS_ACCESS_DENIED; + default: + return ntvfs_map_qfileinfo(ntvfs, req, info); + } +} + + +/* + return filesystem info +*/ +static NTSTATUS ipc_fsinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_fsinfo *fs) +{ + return NT_STATUS_ACCESS_DENIED; +} + +/* + return print queue info +*/ +static NTSTATUS ipc_lpq(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_lpq *lpq) +{ + return NT_STATUS_ACCESS_DENIED; +} + +/* + list files in a directory matching a wildcard pattern +*/ +static NTSTATUS ipc_search_first(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_search_first *io, + void *search_private, + bool (*callback)(void *, const union smb_search_data *)) +{ + return NT_STATUS_ACCESS_DENIED; +} + +/* + continue listing files in a directory +*/ +static NTSTATUS ipc_search_next(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_search_next *io, + void *search_private, + bool (*callback)(void *, const union smb_search_data *)) +{ + return NT_STATUS_ACCESS_DENIED; +} + +/* + end listing files in a directory +*/ +static NTSTATUS ipc_search_close(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_search_close *io) +{ + return NT_STATUS_ACCESS_DENIED; +} + +struct ipc_trans_state { + struct ipc_private *ipriv; + struct pipe_state *p; + struct ntvfs_request *req; + struct smb_trans2 *trans; + struct iovec writev_iov; + struct ipc_readv_next_vector_state next_vector; +}; + +static void ipc_trans_writev_done(struct tevent_req *subreq); +static void ipc_trans_readv_done(struct tevent_req *subreq); + +/* SMBtrans - handle a DCERPC command */ +static NTSTATUS ipc_dcerpc_cmd(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, struct smb_trans2 *trans) +{ + struct ipc_private *ipriv = talloc_get_type_abort(ntvfs->private_data, + struct ipc_private); + struct pipe_state *p; + DATA_BLOB fnum_key; + uint16_t fnum; + struct ipc_trans_state *state; + struct tevent_req *subreq; + + /* + * the fnum is in setup[1], a 16 bit value + * the setup[*] values are already in host byteorder + * but ntvfs_handle_search_by_wire_key() expects + * network byteorder + */ + SSVAL(&fnum, 0, trans->in.setup[1]); + fnum_key = data_blob_const(&fnum, 2); + + p = pipe_state_find_key(ipriv, req, &fnum_key); + if (!p) { + return NT_STATUS_INVALID_HANDLE; + } + + /* + * Trans requests are only allowed + * if no other Trans or Read is active + */ + if (tevent_queue_length(p->read_queue) > 0) { + return NT_STATUS_PIPE_BUSY; + } + + state = talloc(req, struct ipc_trans_state); + NT_STATUS_HAVE_NO_MEMORY(state); + + trans->out.setup_count = 0; + trans->out.setup = NULL; + trans->out.params = data_blob(NULL, 0); + trans->out.data = data_blob_talloc(req, NULL, trans->in.max_data); + NT_STATUS_HAVE_NO_MEMORY(trans->out.data.data); + + state->ipriv = ipriv; + state->p = p; + state->req = req; + state->trans = trans; + state->writev_iov.iov_base = (char *) trans->in.data.data; + state->writev_iov.iov_len = trans->in.data.length; + + ipc_readv_next_vector_init(&state->next_vector, + trans->out.data.data, + trans->out.data.length); + + subreq = tstream_writev_queue_send(state, + ipriv->ntvfs->ctx->event_ctx, + p->npipe, + p->write_queue, + &state->writev_iov, 1); + NT_STATUS_HAVE_NO_MEMORY(subreq); + tevent_req_set_callback(subreq, ipc_trans_writev_done, state); + + req->async_states->state |= NTVFS_ASYNC_STATE_ASYNC; + return NT_STATUS_OK; +} + +static void ipc_trans_writev_done(struct tevent_req *subreq) +{ + struct ipc_trans_state *state = + tevent_req_callback_data(subreq, + struct ipc_trans_state); + struct ipc_private *ipriv = state->ipriv; + struct pipe_state *p = state->p; + struct ntvfs_request *req = state->req; + int ret; + int sys_errno; + NTSTATUS status; + + ret = tstream_writev_queue_recv(subreq, &sys_errno); + TALLOC_FREE(subreq); + if (ret == 0) { + status = NT_STATUS_PIPE_DISCONNECTED; + goto reply; + } else if (ret == -1) { + status = map_nt_error_from_unix_common(sys_errno); + goto reply; + } + + subreq = tstream_readv_pdu_queue_send(state, + ipriv->ntvfs->ctx->event_ctx, + p->npipe, + p->read_queue, + ipc_readv_next_vector, + &state->next_vector); + if (!subreq) { + status = NT_STATUS_NO_MEMORY; + goto reply; + } + tevent_req_set_callback(subreq, ipc_trans_readv_done, state); + return; + +reply: + req->async_states->status = status; + req->async_states->send_fn(req); +} + +static void ipc_trans_readv_done(struct tevent_req *subreq) +{ + struct ipc_trans_state *state = + tevent_req_callback_data(subreq, + struct ipc_trans_state); + struct ntvfs_request *req = state->req; + struct smb_trans2 *trans = state->trans; + int ret; + int sys_errno; + NTSTATUS status; + + ret = tstream_readv_pdu_queue_recv(subreq, &sys_errno); + TALLOC_FREE(subreq); + if (ret == -1) { + status = map_nt_error_from_unix_common(sys_errno); + goto reply; + } + + status = NT_STATUS_OK; + if (state->next_vector.remaining > 0) { + status = STATUS_BUFFER_OVERFLOW; + } + + trans->out.data.length = ret; + +reply: + req->async_states->status = status; + req->async_states->send_fn(req); +} + +/* SMBtrans - set named pipe state */ +static NTSTATUS ipc_set_nm_pipe_state(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, struct smb_trans2 *trans) +{ + struct ipc_private *ipriv = talloc_get_type_abort(ntvfs->private_data, + struct ipc_private); + struct pipe_state *p; + DATA_BLOB fnum_key; + + /* the fnum is in setup[1] */ + fnum_key = data_blob_const(&trans->in.setup[1], sizeof(trans->in.setup[1])); + + p = pipe_state_find_key(ipriv, req, &fnum_key); + if (!p) { + return NT_STATUS_INVALID_HANDLE; + } + + if (trans->in.params.length != 2) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* + * TODO: pass this to the tstream_npa logic + */ + p->device_state = SVAL(trans->in.params.data, 0); + + trans->out.setup_count = 0; + trans->out.setup = NULL; + trans->out.params = data_blob(NULL, 0); + trans->out.data = data_blob(NULL, 0); + + return NT_STATUS_OK; +} + + +/* SMBtrans - used to provide access to SMB pipes */ +static NTSTATUS ipc_trans(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, struct smb_trans2 *trans) +{ + NTSTATUS status; + + if (strequal(trans->in.trans_name, "\\PIPE\\LANMAN")) + return ipc_rap_call(req, ntvfs->ctx->event_ctx, ntvfs->ctx->lp_ctx, trans); + + if (trans->in.setup_count != 2) { + return NT_STATUS_INVALID_PARAMETER; + } + + switch (trans->in.setup[0]) { + case TRANSACT_SETNAMEDPIPEHANDLESTATE: + status = ipc_set_nm_pipe_state(ntvfs, req, trans); + break; + case TRANSACT_DCERPCCMD: + status = ipc_dcerpc_cmd(ntvfs, req, trans); + break; + default: + status = NT_STATUS_INVALID_PARAMETER; + break; + } + + return status; +} + +struct ipc_ioctl_state { + struct ipc_private *ipriv; + struct pipe_state *p; + struct ntvfs_request *req; + union smb_ioctl *io; + struct iovec writev_iov; + struct ipc_readv_next_vector_state next_vector; +}; + +static void ipc_ioctl_writev_done(struct tevent_req *subreq); +static void ipc_ioctl_readv_done(struct tevent_req *subreq); + +static NTSTATUS ipc_ioctl_smb2(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_ioctl *io) +{ + struct ipc_private *ipriv = talloc_get_type_abort(ntvfs->private_data, + struct ipc_private); + struct pipe_state *p; + struct ipc_ioctl_state *state; + struct tevent_req *subreq; + + switch (io->smb2.in.function) { + case FSCTL_NAMED_PIPE_READ_WRITE: + break; + + default: + return NT_STATUS_FS_DRIVER_REQUIRED; + } + + p = pipe_state_find(ipriv, io->smb2.in.file.ntvfs); + if (!p) { + return NT_STATUS_INVALID_HANDLE; + } + + /* + * Trans requests are only allowed + * if no other Trans or Read is active + */ + if (tevent_queue_length(p->read_queue) > 0) { + return NT_STATUS_PIPE_BUSY; + } + + state = talloc(req, struct ipc_ioctl_state); + NT_STATUS_HAVE_NO_MEMORY(state); + + io->smb2.out.reserved = 0; + io->smb2.out.function = io->smb2.in.function; + io->smb2.out.flags = 0; + io->smb2.out.reserved2 = 0; + io->smb2.out.in = data_blob_null; + io->smb2.out.out = data_blob_talloc(req, NULL, io->smb2.in.max_output_response); + NT_STATUS_HAVE_NO_MEMORY(io->smb2.out.out.data); + + state->ipriv = ipriv; + state->p = p; + state->req = req; + state->io = io; + state->writev_iov.iov_base = (char *) io->smb2.in.out.data; + state->writev_iov.iov_len = io->smb2.in.out.length; + + ipc_readv_next_vector_init(&state->next_vector, + io->smb2.out.out.data, + io->smb2.out.out.length); + + subreq = tstream_writev_queue_send(state, + ipriv->ntvfs->ctx->event_ctx, + p->npipe, + p->write_queue, + &state->writev_iov, 1); + NT_STATUS_HAVE_NO_MEMORY(subreq); + tevent_req_set_callback(subreq, ipc_ioctl_writev_done, state); + + req->async_states->state |= NTVFS_ASYNC_STATE_ASYNC; + return NT_STATUS_OK; +} + +static void ipc_ioctl_writev_done(struct tevent_req *subreq) +{ + struct ipc_ioctl_state *state = + tevent_req_callback_data(subreq, + struct ipc_ioctl_state); + struct ipc_private *ipriv = state->ipriv; + struct pipe_state *p = state->p; + struct ntvfs_request *req = state->req; + int ret; + int sys_errno; + NTSTATUS status; + + ret = tstream_writev_queue_recv(subreq, &sys_errno); + TALLOC_FREE(subreq); + if (ret == -1) { + status = map_nt_error_from_unix_common(sys_errno); + goto reply; + } + + subreq = tstream_readv_pdu_queue_send(state, + ipriv->ntvfs->ctx->event_ctx, + p->npipe, + p->read_queue, + ipc_readv_next_vector, + &state->next_vector); + if (!subreq) { + status = NT_STATUS_NO_MEMORY; + goto reply; + } + tevent_req_set_callback(subreq, ipc_ioctl_readv_done, state); + return; + +reply: + req->async_states->status = status; + req->async_states->send_fn(req); +} + +static void ipc_ioctl_readv_done(struct tevent_req *subreq) +{ + struct ipc_ioctl_state *state = + tevent_req_callback_data(subreq, + struct ipc_ioctl_state); + struct ntvfs_request *req = state->req; + union smb_ioctl *io = state->io; + int ret; + int sys_errno; + NTSTATUS status; + + ret = tstream_readv_pdu_queue_recv(subreq, &sys_errno); + TALLOC_FREE(subreq); + if (ret == -1) { + status = map_nt_error_from_unix_common(sys_errno); + goto reply; + } + + status = NT_STATUS_OK; + if (state->next_vector.remaining > 0) { + status = STATUS_BUFFER_OVERFLOW; + } + + io->smb2.out.out.length = ret; + +reply: + req->async_states->status = status; + req->async_states->send_fn(req); +} + +/* + ioctl interface +*/ +static NTSTATUS ipc_ioctl(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_ioctl *io) +{ + switch (io->generic.level) { + case RAW_IOCTL_SMB2: + return ipc_ioctl_smb2(ntvfs, req, io); + + case RAW_IOCTL_SMB2_NO_HANDLE: + return NT_STATUS_FS_DRIVER_REQUIRED; + + default: + return NT_STATUS_ACCESS_DENIED; + } +} + + +/* + initialise the IPC backend, registering ourselves with the ntvfs subsystem + */ +NTSTATUS ntvfs_ipc_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret; + struct ntvfs_ops ops; + NTVFS_CURRENT_CRITICAL_SIZES(vers); + + ZERO_STRUCT(ops); + + /* fill in the name and type */ + ops.name = "default"; + ops.type = NTVFS_IPC; + + /* fill in all the operations */ + ops.connect_fn = ipc_connect; + ops.disconnect_fn = ipc_disconnect; + ops.unlink_fn = ipc_unlink; + ops.chkpath_fn = ipc_chkpath; + ops.qpathinfo_fn = ipc_qpathinfo; + ops.setpathinfo_fn = ipc_setpathinfo; + ops.open_fn = ipc_open; + ops.mkdir_fn = ipc_mkdir; + ops.rmdir_fn = ipc_rmdir; + ops.rename_fn = ipc_rename; + ops.copy_fn = ipc_copy; + ops.ioctl_fn = ipc_ioctl; + ops.read_fn = ipc_read; + ops.write_fn = ipc_write; + ops.seek_fn = ipc_seek; + ops.flush_fn = ipc_flush; + ops.close_fn = ipc_close; + ops.exit_fn = ipc_exit; + ops.lock_fn = ipc_lock; + ops.setfileinfo_fn = ipc_setfileinfo; + ops.qfileinfo_fn = ipc_qfileinfo; + ops.fsinfo_fn = ipc_fsinfo; + ops.lpq_fn = ipc_lpq; + ops.search_first_fn = ipc_search_first; + ops.search_next_fn = ipc_search_next; + ops.search_close_fn = ipc_search_close; + ops.trans_fn = ipc_trans; + ops.logoff_fn = ipc_logoff; + ops.async_setup_fn = ipc_async_setup; + ops.cancel_fn = ipc_cancel; + + /* register ourselves with the NTVFS subsystem. */ + ret = ntvfs_register(&ops, &vers); + + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register IPC backend!\n")); + return ret; + } + + return ret; +} diff --git a/source4/ntvfs/ntvfs.h b/source4/ntvfs/ntvfs.h new file mode 100644 index 0000000..b459579 --- /dev/null +++ b/source4/ntvfs/ntvfs.h @@ -0,0 +1,338 @@ +/* + Unix SMB/CIFS implementation. + NTVFS structures and defines + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Stefan Metzmacher 2004 + + 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/>. +*/ + +#ifndef _NTVFS_H_ +#define _NTVFS_H_ + +#include "libcli/raw/interfaces.h" +#include "param/share.h" +#include "librpc/gen_ndr/security.h" +#include "librpc/gen_ndr/server_id.h" + +/* modules can use the following to determine if the interface has changed */ +/* version 1 -> 0 - make module stacking easier -- metze */ +#define NTVFS_INTERFACE_VERSION 0 + +struct ntvfs_module_context; +struct ntvfs_request; + +/* each backend has to be one one of the following 3 basic types. In + earlier versions of Samba backends needed to handle all types, now + we implement them separately. + The values 1..3 match the SMB2 SMB2_SHARE_TYPE_* values + */ +enum ntvfs_type {NTVFS_DISK=1, NTVFS_IPC=2, NTVFS_PRINT=3}; + +/* the ntvfs operations structure - contains function pointers to + the backend implementations of each operation */ +struct ntvfs_ops { + const char *name; + enum ntvfs_type type; + + /* initial setup */ + NTSTATUS (*connect_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_tcon *tcon); + NTSTATUS (*disconnect_fn)(struct ntvfs_module_context *ntvfs); + + /* async_setup - called when a backend is processing a async request */ + NTSTATUS (*async_setup_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + void *private_data); + + /* filesystem operations */ + NTSTATUS (*fsinfo_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_fsinfo *fs); + + /* path operations */ + NTSTATUS (*unlink_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_unlink *unl); + NTSTATUS (*chkpath_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_chkpath *cp); + NTSTATUS (*qpathinfo_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_fileinfo *st); + NTSTATUS (*setpathinfo_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_setfileinfo *st); + NTSTATUS (*mkdir_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_mkdir *md); + NTSTATUS (*rmdir_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + struct smb_rmdir *rd); + NTSTATUS (*rename_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_rename *ren); + NTSTATUS (*copy_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + struct smb_copy *cp); + NTSTATUS (*open_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_open *oi); + + /* directory search */ + NTSTATUS (*search_first_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_search_first *io, void *private_data, + bool (*callback_fn)(void *private_data, const union smb_search_data *file)); + NTSTATUS (*search_next_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_search_next *io, void *private_data, + bool (*callback_fn)(void *private_data, const union smb_search_data *file)); + NTSTATUS (*search_close_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_search_close *io); + + /* operations on open files */ + NTSTATUS (*ioctl_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_ioctl *io); + NTSTATUS (*read_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_read *io); + NTSTATUS (*write_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_write *io); + NTSTATUS (*seek_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_seek *io); + NTSTATUS (*flush_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_flush *flush); + NTSTATUS (*lock_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_lock *lck); + NTSTATUS (*qfileinfo_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_fileinfo *info); + NTSTATUS (*setfileinfo_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_setfileinfo *info); + NTSTATUS (*close_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_close *io); + + /* trans interface - used by IPC backend for pipes and RAP calls */ + NTSTATUS (*trans_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + struct smb_trans2 *trans); + + /* trans2 interface - only used by CIFS backend to prover complete passthru for testing */ + NTSTATUS (*trans2_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + struct smb_trans2 *trans2); + + /* change notify request */ + NTSTATUS (*notify_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_notify *info); + + /* cancel - cancels any pending async request */ + NTSTATUS (*cancel_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req); + + /* printing specific operations */ + NTSTATUS (*lpq_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_lpq *lpq); + + /* logoff - called when a vuid is closed */ + NTSTATUS (*logoff_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req); + NTSTATUS (*exit_fn)(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req); +}; + +struct ntvfs_module_context { + struct ntvfs_module_context *prev, *next; + struct ntvfs_context *ctx; + int depth; + const struct ntvfs_ops *ops; + void *private_data; +}; + +struct ntvfs_context { + enum ntvfs_type type; + + /* the reported filesystem type */ + char *fs_type; + + /* the reported device type */ + char *dev_type; + + enum protocol_types protocol; + + /* + * client capabilities + * this field doesn't use protocol specific + * values! + */ +#define NTVFS_CLIENT_CAP_LEVEL_II_OPLOCKS 0x0000000000000001LLU + uint64_t client_caps; + + /* + * linked list of module contexts + */ + struct ntvfs_module_context *modules; + + struct share_config *config; + + struct server_id server_id; + struct loadparm_context *lp_ctx; + struct tevent_context *event_ctx; + struct imessaging_context *msg_ctx; + + struct { + void *private_data; + NTSTATUS (*handler)(void *private_data, struct ntvfs_handle *handle, uint8_t level); + } oplock; + + struct { + const struct tsocket_address *local_address; + const struct tsocket_address *remote_address; + } client; + + struct { + void *private_data; + NTSTATUS (*create_new)(void *private_data, struct ntvfs_request *req, struct ntvfs_handle **h); + NTSTATUS (*make_valid)(void *private_data, struct ntvfs_handle *h); + void (*destroy)(void *private_data, struct ntvfs_handle *h); + struct ntvfs_handle *(*search_by_wire_key)(void *private_data, struct ntvfs_request *req, const DATA_BLOB *key); + DATA_BLOB (*get_wire_key)(void *private_data, struct ntvfs_handle *handle, TALLOC_CTX *mem_ctx); + } handles; +}; + +/* a set of flags to control handling of request structures */ +#define NTVFS_ASYNC_STATE_ASYNC (1<<1) /* the backend will answer this one later */ +#define NTVFS_ASYNC_STATE_MAY_ASYNC (1<<2) /* the backend is allowed to answer async */ +#define NTVFS_ASYNC_STATE_CLOSE (1<<3) /* the backend session should be closed */ + +/* the ntvfs_async_state structure allows backend functions to + delay replying to requests. To use this, the front end must + set send_fn to a function to be called by the backend + when the reply is finally ready to be sent. The backend + must set status to the status it wants in the + reply. The backend must set the NTVFS_ASYNC_STATE_ASYNC + control_flag on the request to indicate that it wishes to + delay the reply + + If NTVFS_ASYNC_STATE_MAY_ASYNC is not set then the backend cannot + ask for a delayed reply for this request + + note that the private_data pointer is private to the layer which alloced this struct +*/ +struct ntvfs_async_state { + struct ntvfs_async_state *prev, *next; + /* the async handling infos */ + unsigned int state; + void *private_data; + void (*send_fn)(struct ntvfs_request *); + NTSTATUS status; + + /* the passthru module's per session private data */ + struct ntvfs_module_context *ntvfs; +}; + +struct ntvfs_request { + /* the ntvfs_context this requests belongs to */ + struct ntvfs_context *ctx; + + /* ntvfs per request async states */ + struct ntvfs_async_state *async_states; + + /* the session_info, with security_token and maybe delegated credentials */ + struct auth_session_info *session_info; + + /* the smb pid is needed for locking contexts */ + uint32_t smbpid; + + /* + * client capabilities + * this field doesn't use protocol specific + * values! + * see NTVFS_CLIENT_CAP_* + */ + uint64_t client_caps; + + /* some statistics for the management tools */ + struct { + /* the system time when the request arrived */ + struct timeval request_time; + } statistics; + + struct { + void *private_data; + } frontend_data; +}; + +struct ntvfs_handle { + struct ntvfs_context *ctx; + + struct auth_session_info *session_info; + + uint16_t smbpid; + + struct ntvfs_handle_data { + struct ntvfs_handle_data *prev, *next; + struct ntvfs_module_context *owner; + void *private_data;/* this must be a valid talloc pointer */ + } *backend_data; + + struct { + void *private_data; + } frontend_data; +}; + +/* this structure is used by backends to determine the size of some critical types */ +struct ntvfs_critical_sizes { + int interface_version; + int sizeof_ntvfs_critical_sizes; + int sizeof_ntvfs_context; + int sizeof_ntvfs_module_context; + int sizeof_ntvfs_ops; + int sizeof_ntvfs_async_state; + int sizeof_ntvfs_request; + int sizeof_ntvfs_handle; + int sizeof_ntvfs_handle_data; +}; + +#define NTVFS_CURRENT_CRITICAL_SIZES(c) \ + struct ntvfs_critical_sizes c = { \ + .interface_version = NTVFS_INTERFACE_VERSION, \ + .sizeof_ntvfs_critical_sizes = sizeof(struct ntvfs_critical_sizes), \ + .sizeof_ntvfs_context = sizeof(struct ntvfs_context), \ + .sizeof_ntvfs_module_context = sizeof(struct ntvfs_module_context), \ + .sizeof_ntvfs_ops = sizeof(struct ntvfs_ops), \ + .sizeof_ntvfs_async_state = sizeof(struct ntvfs_async_state), \ + .sizeof_ntvfs_request = sizeof(struct ntvfs_request), \ + .sizeof_ntvfs_handle = sizeof(struct ntvfs_handle), \ + .sizeof_ntvfs_handle_data = sizeof(struct ntvfs_handle_data), \ + } + +struct imessaging_context; +#include "librpc/gen_ndr/security.h" +#include "librpc/gen_ndr/notify.h" +#include "ntvfs/ntvfs_proto.h" + +#endif /* _NTVFS_H_ */ diff --git a/source4/ntvfs/ntvfs_base.c b/source4/ntvfs/ntvfs_base.c new file mode 100644 index 0000000..5c438bb --- /dev/null +++ b/source4/ntvfs/ntvfs_base.c @@ -0,0 +1,249 @@ +/* + Unix SMB/CIFS implementation. + NTVFS base code + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Stefan (metze) Metzmacher 2004 + + 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/>. +*/ +/* + this implements the core code for all NTVFS modules. Backends register themselves here. +*/ + +#include "includes.h" +#include "../lib/util/dlinklist.h" +#include "ntvfs/ntvfs.h" +#include "param/param.h" +#include "lib/util/samba_modules.h" + +/* the list of currently registered NTVFS backends, note that there + * can be more than one backend with the same name, as long as they + * have different typesx */ +static struct ntvfs_backend { + const struct ntvfs_ops *ops; +} *backends = NULL; +static int num_backends; + +/* + register a NTVFS backend. + + The 'name' can be later used by other backends to find the operations + structure for this backend. + + The 'type' is used to specify whether this is for a disk, printer or IPC$ share +*/ +NTSTATUS ntvfs_register(const struct ntvfs_ops *ops, + const struct ntvfs_critical_sizes *const sizes) +{ + struct ntvfs_ops *new_ops; + + if (ntvfs_interface_differs(sizes)) { + DEBUG(0, ("NTVFS backend '%s' for type %d " + "failed version check\n", + ops->name, (int)ops->type)); + return NT_STATUS_BAD_FUNCTION_TABLE; + } + + if (ntvfs_backend_byname(ops->name, ops->type) != NULL) { + /* its already registered! */ + DEBUG(0,("NTVFS backend '%s' for type %d already registered\n", + ops->name, (int)ops->type)); + return NT_STATUS_OBJECT_NAME_COLLISION; + } + + backends = realloc_p(backends, struct ntvfs_backend, num_backends+1); + if (!backends) { + smb_panic("out of memory in ntvfs_register"); + } + + new_ops = smb_xmemdup(ops, sizeof(*ops)); + new_ops->name = smb_xstrdup(ops->name); + + backends[num_backends].ops = new_ops; + + num_backends++; + + DEBUG(3,("NTVFS backend '%s' for type %d registered\n", + ops->name,ops->type)); + + return NT_STATUS_OK; +} + + +/* + return the operations structure for a named backend of the specified type +*/ +const struct ntvfs_ops *ntvfs_backend_byname(const char *name, enum ntvfs_type type) +{ + int i; + + for (i=0;i<num_backends;i++) { + if (backends[i].ops->type == type && + strcmp(backends[i].ops->name, name) == 0) { + return backends[i].ops; + } + } + + return NULL; +} + + +/* + return the NTVFS interface version, and the size of some critical types + This can be used by backends to either detect compilation errors, or provide + multiple implementations for different smbd compilation options in one module +*/ + +static const NTVFS_CURRENT_CRITICAL_SIZES(critical_sizes); + +const struct ntvfs_critical_sizes *ntvfs_interface_version(void) +{ + return &critical_sizes; +} + +bool ntvfs_interface_differs(const struct ntvfs_critical_sizes *const iface) +{ + /* The comparison would be easier with memcmp, but compiler-interset + * alignment padding is not guaranteed to be zeroed. + */ + +#define FIELD_DIFFERS(field) (iface->field != critical_sizes.field) + + if (FIELD_DIFFERS(interface_version)) + return true; + + if (FIELD_DIFFERS(sizeof_ntvfs_critical_sizes)) + return true; + + if (FIELD_DIFFERS(sizeof_ntvfs_context)) + return true; + + if (FIELD_DIFFERS(sizeof_ntvfs_module_context)) + return true; + + if (FIELD_DIFFERS(sizeof_ntvfs_ops)) + return true; + + if (FIELD_DIFFERS(sizeof_ntvfs_async_state)) + return true; + + if (FIELD_DIFFERS(sizeof_ntvfs_request)) + return true; + + /* Versions match. */ + return false; + +#undef FIELD_DIFFERS +} + +/* + initialise a connection structure to point at a NTVFS backend +*/ +NTSTATUS ntvfs_init_connection(TALLOC_CTX *mem_ctx, struct share_config *scfg, enum ntvfs_type type, + enum protocol_types protocol, + uint64_t ntvfs_client_caps, + struct tevent_context *ev, struct imessaging_context *msg, + struct loadparm_context *lp_ctx, + struct server_id server_id, struct ntvfs_context **_ctx) +{ + const char **handlers = share_string_list_option(mem_ctx, scfg, SHARE_NTVFS_HANDLER); + int i; + struct ntvfs_context *ctx; + + if (!handlers) { + return NT_STATUS_INTERNAL_ERROR; + } + + ctx = talloc_zero(mem_ctx, struct ntvfs_context); + NT_STATUS_HAVE_NO_MEMORY(ctx); + ctx->protocol = protocol; + ctx->client_caps = ntvfs_client_caps; + ctx->type = type; + ctx->config = talloc_steal(ctx, scfg); + ctx->event_ctx = ev; + ctx->msg_ctx = msg; + ctx->server_id = server_id; + ctx->lp_ctx = lp_ctx; + + for (i=0; handlers[i]; i++) { + struct ntvfs_module_context *ntvfs; + + ntvfs = talloc_zero(ctx, struct ntvfs_module_context); + NT_STATUS_HAVE_NO_MEMORY(ntvfs); + ntvfs->ctx = ctx; + ntvfs->ops = ntvfs_backend_byname(handlers[i], ctx->type); + if (!ntvfs->ops) { + DEBUG(1,("ntvfs_init_connection: failed to find backend=%s, type=%d\n", + handlers[i], ctx->type)); + return NT_STATUS_INTERNAL_ERROR; + } + ntvfs->depth = i; + DLIST_ADD_END(ctx->modules, ntvfs); + } + + if (!ctx->modules) { + return NT_STATUS_INTERNAL_ERROR; + } + + *_ctx = ctx; + return NT_STATUS_OK; +} + +/* + adds the IPC$ share, needed for RPC calls + */ +static NTSTATUS ntvfs_add_ipc_share(struct loadparm_context *lp_ctx) +{ + struct loadparm_service *ipc; + + if (lpcfg_service(lp_ctx, "IPC$")) { + /* it has already been defined in smb.conf or elsewhere */ + return NT_STATUS_OK; + } + + ipc = lpcfg_add_service(lp_ctx, NULL, "IPC$"); + NT_STATUS_HAVE_NO_MEMORY(ipc); + + lpcfg_do_service_parameter(lp_ctx, ipc, "comment", "IPC Service"); + lpcfg_do_service_parameter(lp_ctx, ipc, "path", "/dev/null"); + lpcfg_do_service_parameter(lp_ctx, ipc, "ntvfs handler", "default"); + lpcfg_do_service_parameter(lp_ctx, ipc, "browseable", "No"); + lpcfg_do_service_parameter(lp_ctx, ipc, "fstype", "IPC"); + + return NT_STATUS_OK; +} + +NTSTATUS ntvfs_init(struct loadparm_context *lp_ctx) +{ + static bool initialized = false; +#define _MODULE_PROTO(init) extern NTSTATUS init(TALLOC_CTX *); + STATIC_ntvfs_MODULES_PROTO; + init_module_fn static_init[] = { STATIC_ntvfs_MODULES }; + init_module_fn *shared_init; + + if (initialized) return NT_STATUS_OK; + initialized = true; + + shared_init = load_samba_modules(NULL, "ntvfs"); + + run_init_functions(NULL, static_init); + run_init_functions(NULL, shared_init); + + talloc_free(shared_init); + + ntvfs_add_ipc_share(lp_ctx); + + return NT_STATUS_OK; +} diff --git a/source4/ntvfs/ntvfs_generic.c b/source4/ntvfs/ntvfs_generic.c new file mode 100644 index 0000000..de0ae2c --- /dev/null +++ b/source4/ntvfs/ntvfs_generic.c @@ -0,0 +1,1648 @@ +/* + Unix SMB/CIFS implementation. + + NTVFS generic level mapping code + + Copyright (C) Andrew Tridgell 2003-2004 + + 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/>. +*/ +/* + this implements mappings between info levels for NTVFS backend calls + + the idea is that each of these functions implements one of the NTVFS + backend calls in terms of the 'generic' call. All backends that use + these functions must supply the generic call, but can if it wants to + also implement other levels if the need arises + + this allows backend writers to only implement one variant of each + call unless they need fine grained control of the calls. +*/ + +#include "includes.h" +#include "ntvfs/ntvfs.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" + +#undef strcasecmp + +/* a second stage function converts from the out parameters of the generic + call onto the out parameters of the specific call made */ +typedef NTSTATUS (*second_stage_t)(struct ntvfs_module_context *, + struct ntvfs_request *, + void *, void *, NTSTATUS); + +/* + this structure holds the async state for pending mapped async calls +*/ +struct ntvfs_map_async { + struct ntvfs_module_context *ntvfs; + void *io, *io2; + second_stage_t fn; +}; + +/* + this is a async wrapper, called from the backend when it has completed + a function that it has decided to reply to in an async fashion +*/ +static void ntvfs_map_async_send(struct ntvfs_request *req) +{ + struct ntvfs_map_async *m = talloc_get_type(req->async_states->private_data, + struct ntvfs_map_async); + + ntvfs_async_state_pop(req); + + /* call the _finish function setup in ntvfs_map_async_setup() */ + req->async_states->status = m->fn(m->ntvfs, req, m->io, m->io2, req->async_states->status); + + /* call the send function from the next module up */ + req->async_states->send_fn(req); +} + +/* + prepare for calling a ntvfs backend with async support + io is the original call structure + io2 is the new call structure for the mapped call + fn is a second stage function for processing the out arguments +*/ +static NTSTATUS ntvfs_map_async_setup(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + void *io, void *io2, + second_stage_t fn) +{ + struct ntvfs_map_async *m; + m = talloc(req, struct ntvfs_map_async); + if (m == NULL) { + return NT_STATUS_NO_MEMORY; + } + m->ntvfs = ntvfs; + m->io = io; + m->io2 = io2; + m->fn = fn; + return ntvfs_async_state_push(ntvfs, req, m, ntvfs_map_async_send); +} + +/* + called when first stage processing is complete. +*/ +static NTSTATUS ntvfs_map_async_finish(struct ntvfs_request *req, NTSTATUS status) +{ + struct ntvfs_map_async *m; + + /* if the backend has decided to reply in an async fashion then + we don't need to do any work here */ + if (req->async_states->state & NTVFS_ASYNC_STATE_ASYNC) { + return status; + } + + /* the backend is replying immediately. call the 2nd stage function after popping our local + async state */ + m = talloc_get_type(req->async_states->private_data, + struct ntvfs_map_async); + + ntvfs_async_state_pop(req); + + return m->fn(m->ntvfs, req, m->io, m->io2, status); +} + +/* + see if a filename ends in EXE COM DLL or SYM. This is needed for the + DENY_DOS mapping for OpenX +*/ +bool is_exe_filename(const char *fname) +{ + char *p; + p = strrchr(fname, '.'); + if (!p) { + return false; + } + p++; + if (strcasecmp(p, "EXE") == 0 || + strcasecmp(p, "COM") == 0 || + strcasecmp(p, "DLL") == 0 || + strcasecmp(p, "SYM") == 0) { + return true; + } + return false; +} + + +/* + NTVFS openx to ntcreatex mapper +*/ +static NTSTATUS ntvfs_map_open_finish(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_open *io, + union smb_open *io2, + NTSTATUS status) +{ + time_t write_time = 0; + uint32_t set_size = 0; + union smb_setfileinfo *sf; + unsigned int state; + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + switch (io->generic.level) { + case RAW_OPEN_OPEN: + io->openold.out.file.ntvfs = io2->generic.out.file.ntvfs; + io->openold.out.attrib = io2->generic.out.attrib; + io->openold.out.write_time = nt_time_to_unix(io2->generic.out.write_time); + io->openold.out.size = io2->generic.out.size; + io->openold.out.rmode = io->openold.in.open_mode; + break; + + case RAW_OPEN_OPENX: + io->openx.out.file.ntvfs = io2->generic.out.file.ntvfs; + io->openx.out.attrib = io2->generic.out.attrib; + io->openx.out.write_time = nt_time_to_unix(io2->generic.out.write_time); + io->openx.out.size = io2->generic.out.size; + io->openx.out.access = (io->openx.in.open_mode & OPENX_MODE_ACCESS_MASK); + io->openx.out.ftype = 0; + io->openx.out.devstate = 0; + io->openx.out.action = io2->generic.out.create_action; + io->openx.out.unique_fid = 0; + io->openx.out.access_mask = SEC_STD_ALL; + io->openx.out.unknown = 0; + + /* we need to extend the file to the requested size if + it was newly created */ + if (io2->generic.out.create_action == NTCREATEX_ACTION_CREATED) { + set_size = io->openx.in.size; + } + break; + + case RAW_OPEN_T2OPEN: + io->t2open.out.file.ntvfs = io2->generic.out.file.ntvfs; + io->t2open.out.attrib = io2->generic.out.attrib; + io->t2open.out.write_time = nt_time_to_unix(io2->generic.out.write_time); + io->t2open.out.size = io2->generic.out.size; + io->t2open.out.access = io->t2open.in.open_mode; + io->t2open.out.ftype = 0; + io->t2open.out.devstate = 0; + io->t2open.out.action = io2->generic.out.create_action; + io->t2open.out.file_id = 0; + break; + + case RAW_OPEN_MKNEW: + case RAW_OPEN_CREATE: + io->mknew.out.file.ntvfs= io2->generic.out.file.ntvfs; + write_time = io->mknew.in.write_time; + break; + + case RAW_OPEN_CTEMP: + io->ctemp.out.file.ntvfs= io2->generic.out.file.ntvfs; + io->ctemp.out.name = talloc_strdup(req, io2->generic.in.fname + + strlen(io->ctemp.in.directory) + 1); + NT_STATUS_HAVE_NO_MEMORY(io->ctemp.out.name); + break; + + case RAW_OPEN_SMB2: + ZERO_STRUCT(io->smb2.out); + io->smb2.out.file.ntvfs = io2->generic.out.file.ntvfs; + switch (io2->generic.out.oplock_level) { + case BATCH_OPLOCK_RETURN: + io->smb2.out.oplock_level = SMB2_OPLOCK_LEVEL_BATCH; + break; + case EXCLUSIVE_OPLOCK_RETURN: + io->smb2.out.oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE; + break; + case LEVEL_II_OPLOCK_RETURN: + io->smb2.out.oplock_level = SMB2_OPLOCK_LEVEL_II; + break; + default: + io->smb2.out.oplock_level = SMB2_OPLOCK_LEVEL_NONE; + break; + } + io->smb2.out.reserved = 0; + io->smb2.out.create_action = io2->generic.out.create_action; + io->smb2.out.create_time = io2->generic.out.create_time; + io->smb2.out.access_time = io2->generic.out.access_time; + io->smb2.out.write_time = io2->generic.out.write_time; + io->smb2.out.change_time = io2->generic.out.change_time; + io->smb2.out.alloc_size = io2->generic.out.alloc_size; + io->smb2.out.size = io2->generic.out.size; + io->smb2.out.file_attr = io2->generic.out.attrib; + io->smb2.out.reserved2 = 0; + io->smb2.out.maximal_access = io2->generic.out.maximal_access; + memcpy(io->smb2.out.on_disk_id, io2->generic.out.on_disk_id, + sizeof(io2->generic.out.on_disk_id)); + break; + + default: + return NT_STATUS_INVALID_LEVEL; + } + + /* doing a secondary request async is more trouble than its + worth */ + state = req->async_states->state; + req->async_states->state &= ~NTVFS_ASYNC_STATE_MAY_ASYNC; + + if (write_time != 0) { + sf = talloc(req, union smb_setfileinfo); + NT_STATUS_HAVE_NO_MEMORY(sf); + sf->generic.level = RAW_SFILEINFO_STANDARD; + sf->generic.in.file.ntvfs = io2->generic.out.file.ntvfs; + sf->standard.in.create_time = 0; + sf->standard.in.write_time = write_time; + sf->standard.in.access_time = 0; + status = ntvfs->ops->setfileinfo_fn(ntvfs, req, sf); + } + + if (set_size != 0) { + sf = talloc(req, union smb_setfileinfo); + NT_STATUS_HAVE_NO_MEMORY(sf); + sf->generic.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION; + sf->generic.in.file.ntvfs = io2->generic.out.file.ntvfs; + sf->end_of_file_info.in.size = set_size; + status = ntvfs->ops->setfileinfo_fn(ntvfs, req, sf); + if (NT_STATUS_IS_OK(status)) { + io->openx.out.size = io->openx.in.size; + } + } + + req->async_states->state = state; + + return NT_STATUS_OK; +} + +/* + the core of the mapping between openx style parameters and ntcreatex + parameters +*/ +static NTSTATUS map_openx_open(uint16_t flags, uint16_t open_mode, + uint16_t open_func, const char *fname, + union smb_open *io2) +{ + io2->generic.in.create_options = NTCREATEX_OPTIONS_NON_DIRECTORY_FILE; + io2->generic.in.private_flags = 0; + + if (flags & OPENX_FLAGS_REQUEST_OPLOCK) { + io2->generic.in.flags |= NTCREATEX_FLAGS_REQUEST_OPLOCK; + } + if (flags & OPENX_FLAGS_REQUEST_BATCH_OPLOCK) { + io2->generic.in.flags |= NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK; + } + + switch (open_mode & OPENX_MODE_ACCESS_MASK) { + case OPENX_MODE_ACCESS_READ: + case OPENX_MODE_ACCESS_EXEC: + io2->generic.in.access_mask = SEC_RIGHTS_FILE_READ; + break; + case OPENX_MODE_ACCESS_WRITE: + io2->generic.in.access_mask = SEC_RIGHTS_FILE_WRITE; + break; + case OPENX_MODE_ACCESS_RDWR: + case OPENX_MODE_ACCESS_FCB: + io2->generic.in.access_mask = + SEC_RIGHTS_FILE_READ | + SEC_RIGHTS_FILE_WRITE; + break; + default: + return NT_STATUS_DOS(ERRDOS, ERRbadaccess); + } + + switch (open_mode & OPENX_MODE_DENY_MASK) { + case OPENX_MODE_DENY_READ: + io2->generic.in.share_access = NTCREATEX_SHARE_ACCESS_WRITE; + break; + case OPENX_MODE_DENY_WRITE: + io2->generic.in.share_access = NTCREATEX_SHARE_ACCESS_READ; + break; + case OPENX_MODE_DENY_ALL: + io2->generic.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + break; + case OPENX_MODE_DENY_NONE: + io2->generic.in.share_access = + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + break; + case OPENX_MODE_DENY_DOS: + /* DENY_DOS is quite strange - it depends on the filename! */ + io2->generic.in.private_flags |= + NTCREATEX_FLAG_DENY_DOS; + if (is_exe_filename(fname)) { + io2->generic.in.share_access = + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + } else { + if ((open_mode & OPENX_MODE_ACCESS_MASK) == OPENX_MODE_ACCESS_READ) { + io2->generic.in.share_access = NTCREATEX_SHARE_ACCESS_READ; + } else { + io2->generic.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + } + } + break; + case OPENX_MODE_DENY_FCB: + io2->generic.in.private_flags |= NTCREATEX_FLAG_DENY_FCB; + io2->generic.in.share_access = NTCREATEX_SHARE_ACCESS_NONE; + break; + default: + return NT_STATUS_DOS(ERRDOS, ERRbadaccess); + } + + switch (open_func) { + case (OPENX_OPEN_FUNC_OPEN): + io2->generic.in.open_disposition = NTCREATEX_DISP_OPEN; + break; + case (OPENX_OPEN_FUNC_TRUNC): + io2->generic.in.open_disposition = NTCREATEX_DISP_OVERWRITE; + break; + case (OPENX_OPEN_FUNC_FAIL | OPENX_OPEN_FUNC_CREATE): + io2->generic.in.open_disposition = NTCREATEX_DISP_CREATE; + break; + case (OPENX_OPEN_FUNC_OPEN | OPENX_OPEN_FUNC_CREATE): + io2->generic.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + break; + case (OPENX_OPEN_FUNC_TRUNC | OPENX_OPEN_FUNC_CREATE): + io2->generic.in.open_disposition = NTCREATEX_DISP_OVERWRITE_IF; + break; + default: + /* this one is very strange */ + if ((open_mode & OPENX_MODE_ACCESS_MASK) == OPENX_MODE_ACCESS_EXEC) { + io2->generic.in.open_disposition = NTCREATEX_DISP_CREATE; + break; + } + return NT_STATUS_DOS(ERRDOS, ERRbadaccess); + } + + return NT_STATUS_OK; +} + +/* + NTVFS open generic to any mapper +*/ +NTSTATUS ntvfs_map_open(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_open *io) +{ + NTSTATUS status; + union smb_open *io2; + + io2 = talloc_zero(req, union smb_open); + if (io2 == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = ntvfs_map_async_setup(ntvfs, req, + io, io2, + (second_stage_t)ntvfs_map_open_finish); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + io2->generic.level = RAW_OPEN_GENERIC; + + switch (io->generic.level) { + case RAW_OPEN_OPENX: + status = map_openx_open(io->openx.in.flags, + io->openx.in.open_mode, + io->openx.in.open_func, + io->openx.in.fname, + io2); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + io2->generic.in.file_attr = io->openx.in.file_attrs; + io2->generic.in.fname = io->openx.in.fname; + + status = ntvfs->ops->open_fn(ntvfs, req, io2); + break; + + + case RAW_OPEN_OPEN: + status = map_openx_open(0, + io->openold.in.open_mode, + OPENX_OPEN_FUNC_OPEN, + io->openold.in.fname, + io2); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + io2->generic.in.file_attr = io->openold.in.search_attrs; + io2->generic.in.fname = io->openold.in.fname; + + status = ntvfs->ops->open_fn(ntvfs, req, io2); + break; + + case RAW_OPEN_T2OPEN: + io2->generic.level = RAW_OPEN_NTTRANS_CREATE; + + if (io->t2open.in.open_func == 0) { + status = NT_STATUS_OBJECT_NAME_COLLISION; + goto done; + } + + status = map_openx_open(io->t2open.in.flags, + io->t2open.in.open_mode, + io->t2open.in.open_func, + io->t2open.in.fname, + io2); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + io2->generic.in.file_attr = io->t2open.in.file_attrs; + io2->generic.in.fname = io->t2open.in.fname; + io2->generic.in.ea_list = talloc(io2, struct smb_ea_list); + io2->generic.in.ea_list->num_eas = io->t2open.in.num_eas; + io2->generic.in.ea_list->eas = io->t2open.in.eas; + + status = ntvfs->ops->open_fn(ntvfs, req, io2); + break; + + case RAW_OPEN_MKNEW: + io2->generic.in.file_attr = io->mknew.in.attrib; + io2->generic.in.fname = io->mknew.in.fname; + io2->generic.in.open_disposition = NTCREATEX_DISP_CREATE; + io2->generic.in.access_mask = + SEC_RIGHTS_FILE_READ | + SEC_RIGHTS_FILE_WRITE; + io2->generic.in.share_access = + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + status = ntvfs->ops->open_fn(ntvfs, req, io2); + break; + + case RAW_OPEN_CREATE: + io2->generic.in.file_attr = io->mknew.in.attrib; + io2->generic.in.fname = io->mknew.in.fname; + io2->generic.in.open_disposition = NTCREATEX_DISP_OPEN_IF; + io2->generic.in.access_mask = + SEC_RIGHTS_FILE_READ | + SEC_RIGHTS_FILE_WRITE; + io2->generic.in.share_access = + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + status = ntvfs->ops->open_fn(ntvfs, req, io2); + break; + + case RAW_OPEN_CTEMP: + io2->generic.in.file_attr = io->ctemp.in.attrib; + io2->generic.in.fname = + talloc_asprintf(io2, "%s\\SRV%s", + io->ctemp.in.directory, + generate_random_str_list(io2, 5, "0123456789")); + io2->generic.in.open_disposition = NTCREATEX_DISP_CREATE; + io2->generic.in.access_mask = + SEC_RIGHTS_FILE_READ | + SEC_RIGHTS_FILE_WRITE; + io2->generic.in.share_access = + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + status = ntvfs->ops->open_fn(ntvfs, req, io2); + break; + case RAW_OPEN_SMB2: + switch (io->smb2.in.oplock_level) { + case SMB2_OPLOCK_LEVEL_BATCH: + io2->generic.in.flags = NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK | + NTCREATEX_FLAGS_REQUEST_OPLOCK; + break; + case SMB2_OPLOCK_LEVEL_EXCLUSIVE: + io2->generic.in.flags = NTCREATEX_FLAGS_REQUEST_OPLOCK; + break; + default: + io2->generic.in.flags = 0; + break; + } + io2->generic.in.root_fid.fnum = 0; + io2->generic.in.access_mask = io->smb2.in.desired_access; + io2->generic.in.alloc_size = io->smb2.in.alloc_size; + io2->generic.in.file_attr = io->smb2.in.file_attributes; + io2->generic.in.share_access = io->smb2.in.share_access; + io2->generic.in.open_disposition= io->smb2.in.create_disposition; + io2->generic.in.create_options = io->smb2.in.create_options; + io2->generic.in.impersonation = io->smb2.in.impersonation_level; + io2->generic.in.security_flags = 0; + io2->generic.in.fname = io->smb2.in.fname; + io2->generic.in.sec_desc = io->smb2.in.sec_desc; + io2->generic.in.ea_list = &io->smb2.in.eas; + io2->generic.in.query_maximal_access = io->smb2.in.query_maximal_access; + io2->generic.in.query_on_disk_id = io->smb2.in.query_on_disk_id; + io2->generic.in.private_flags = 0; + + /* we don't support timewarp yet */ + if (io->smb2.in.timewarp != 0) { + status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + break; + } + + /* we need to check these bits before we check the private mask */ + if (io2->generic.in.create_options & SMB2_CREATE_OPTIONS_NOT_SUPPORTED_MASK) { + DEBUG(2,(__location__ " create_options 0x%x not supported\n", + io2->generic.in.create_options)); + status = NT_STATUS_NOT_SUPPORTED; + break; + } + + /* TODO: find out why only SMB2 ignores these */ + io2->generic.in.create_options &= ~NTCREATEX_OPTIONS_SYNC_ALERT; + io2->generic.in.create_options &= ~NTCREATEX_OPTIONS_ASYNC_ALERT; + + status = ntvfs->ops->open_fn(ntvfs, req, io2); + break; + + default: + status = NT_STATUS_INVALID_LEVEL; + break; + } +done: + return ntvfs_map_async_finish(req, status); +} + + +/* + NTVFS any to fsinfo mapper +*/ +static NTSTATUS ntvfs_map_fsinfo_finish(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_fsinfo *fs, + union smb_fsinfo *fs2, + NTSTATUS status) +{ + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* and convert it to the required level */ + switch (fs->generic.level) { + case RAW_QFS_DSKATTR: { + /* map from generic to DSKATTR */ + unsigned int bpunit = 64; + + /* we need to scale the sizes to fit */ + for (bpunit=64; bpunit<0x10000; bpunit *= 2) { + if (fs2->generic.out.blocks_total * (double)fs2->generic.out.block_size < bpunit * 512 * 65535.0) { + break; + } + } + + fs->dskattr.out.blocks_per_unit = bpunit; + fs->dskattr.out.block_size = 512; + fs->dskattr.out.units_total = + (fs2->generic.out.blocks_total * (double)fs2->generic.out.block_size) / (bpunit * 512); + fs->dskattr.out.units_free = + (fs2->generic.out.blocks_free * (double)fs2->generic.out.block_size) / (bpunit * 512); + + /* we must return a maximum of 2G to old DOS systems, or they get very confused */ + if (bpunit > 64 && req->ctx->protocol <= PROTOCOL_LANMAN2) { + fs->dskattr.out.blocks_per_unit = 64; + fs->dskattr.out.units_total = 0xFFFF; + fs->dskattr.out.units_free = 0xFFFF; + } + return NT_STATUS_OK; + } + + case RAW_QFS_ALLOCATION: + fs->allocation.out.fs_id = fs2->generic.out.fs_id; + fs->allocation.out.total_alloc_units = fs2->generic.out.blocks_total; + fs->allocation.out.avail_alloc_units = fs2->generic.out.blocks_free; + fs->allocation.out.sectors_per_unit = 1; + fs->allocation.out.bytes_per_sector = fs2->generic.out.block_size; + return NT_STATUS_OK; + + case RAW_QFS_VOLUME: + fs->volume.out.serial_number = fs2->generic.out.serial_number; + fs->volume.out.volume_name.s = fs2->generic.out.volume_name; + return NT_STATUS_OK; + + case RAW_QFS_VOLUME_INFO: + case RAW_QFS_VOLUME_INFORMATION: + fs->volume_info.out.create_time = fs2->generic.out.create_time; + fs->volume_info.out.serial_number = fs2->generic.out.serial_number; + fs->volume_info.out.volume_name.s = fs2->generic.out.volume_name; + return NT_STATUS_OK; + + case RAW_QFS_SIZE_INFO: + case RAW_QFS_SIZE_INFORMATION: + fs->size_info.out.total_alloc_units = fs2->generic.out.blocks_total; + fs->size_info.out.avail_alloc_units = fs2->generic.out.blocks_free; + fs->size_info.out.sectors_per_unit = 1; + fs->size_info.out.bytes_per_sector = fs2->generic.out.block_size; + return NT_STATUS_OK; + + case RAW_QFS_DEVICE_INFO: + case RAW_QFS_DEVICE_INFORMATION: + fs->device_info.out.device_type = fs2->generic.out.device_type; + fs->device_info.out.characteristics = fs2->generic.out.device_characteristics; + return NT_STATUS_OK; + + case RAW_QFS_ATTRIBUTE_INFO: + case RAW_QFS_ATTRIBUTE_INFORMATION: + fs->attribute_info.out.fs_attr = fs2->generic.out.fs_attr; + fs->attribute_info.out.max_file_component_length = fs2->generic.out.max_file_component_length; + fs->attribute_info.out.fs_type.s = fs2->generic.out.fs_type; + return NT_STATUS_OK; + + case RAW_QFS_QUOTA_INFORMATION: + ZERO_STRUCT(fs->quota_information.out.unknown); + fs->quota_information.out.quota_soft = fs2->generic.out.quota_soft; + fs->quota_information.out.quota_hard = fs2->generic.out.quota_hard; + fs->quota_information.out.quota_flags = fs2->generic.out.quota_flags; + return NT_STATUS_OK; + + case RAW_QFS_FULL_SIZE_INFORMATION: + fs->full_size_information.out.total_alloc_units = fs2->generic.out.blocks_total; + fs->full_size_information.out.call_avail_alloc_units = fs2->generic.out.blocks_free; + fs->full_size_information.out.actual_avail_alloc_units = fs2->generic.out.blocks_free; + fs->full_size_information.out.sectors_per_unit = 1; + fs->full_size_information.out.bytes_per_sector = fs2->generic.out.block_size; + return NT_STATUS_OK; + + case RAW_QFS_OBJECTID_INFORMATION: + fs->objectid_information.out.guid = fs2->generic.out.guid; + ZERO_STRUCT(fs->objectid_information.out.unknown); + return NT_STATUS_OK; + + case RAW_QFS_SECTOR_SIZE_INFORMATION: + fs->sector_size_info.out.logical_bytes_per_sector + = fs2->generic.out.block_size; + fs->sector_size_info.out.phys_bytes_per_sector_atomic + = fs2->generic.out.block_size; + fs->sector_size_info.out.phys_bytes_per_sector_perf + = fs2->generic.out.block_size; + fs->sector_size_info.out.fs_effective_phys_bytes_per_sector_atomic + = fs2->generic.out.block_size; + fs->sector_size_info.out.flags + = QFS_SSINFO_FLAGS_ALIGNED_DEVICE + | QFS_SSINFO_FLAGS_PARTITION_ALIGNED_ON_DEVICE; + fs->sector_size_info.out.byte_off_sector_align = 0; + fs->sector_size_info.out.byte_off_partition_align = 0; + return NT_STATUS_OK; + + case RAW_QFS_GENERIC: + case RAW_QFS_UNIX_INFO: + return NT_STATUS_INVALID_LEVEL; + } + + return NT_STATUS_INVALID_LEVEL; +} + +/* + NTVFS fsinfo any to generic mapper +*/ +NTSTATUS ntvfs_map_fsinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_fsinfo *fs) +{ + NTSTATUS status; + union smb_fsinfo *fs2; + + fs2 = talloc(req, union smb_fsinfo); + if (fs2 == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (fs->generic.level == RAW_QFS_GENERIC) { + return NT_STATUS_INVALID_LEVEL; + } + + status = ntvfs_map_async_setup(ntvfs, req, fs, fs2, + (second_stage_t)ntvfs_map_fsinfo_finish); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* ask the backend for the generic info */ + fs2->generic.level = RAW_QFS_GENERIC; + + status = ntvfs->ops->fsinfo_fn(ntvfs, req, fs2); + return ntvfs_map_async_finish(req, status); +} + + +/* + NTVFS fileinfo generic to any mapper +*/ +NTSTATUS ntvfs_map_fileinfo(TALLOC_CTX *mem_ctx, + union smb_fileinfo *info, + union smb_fileinfo *info2) +{ + int i; + /* and convert it to the required level using results in info2 */ + switch (info->generic.level) { + case RAW_FILEINFO_GETATTR: + info->getattr.out.attrib = info2->generic.out.attrib & 0xff; + info->getattr.out.size = info2->generic.out.size; + info->getattr.out.write_time = nt_time_to_unix(info2->generic.out.write_time); + return NT_STATUS_OK; + + case RAW_FILEINFO_GETATTRE: + info->getattre.out.attrib = info2->generic.out.attrib; + info->getattre.out.size = info2->generic.out.size; + info->getattre.out.write_time = nt_time_to_unix(info2->generic.out.write_time); + info->getattre.out.create_time = nt_time_to_unix(info2->generic.out.create_time); + info->getattre.out.access_time = nt_time_to_unix(info2->generic.out.access_time); + info->getattre.out.alloc_size = info2->generic.out.alloc_size; + return NT_STATUS_OK; + + case RAW_FILEINFO_NETWORK_OPEN_INFORMATION: + info->network_open_information.out.create_time = info2->generic.out.create_time; + info->network_open_information.out.access_time = info2->generic.out.access_time; + info->network_open_information.out.write_time = info2->generic.out.write_time; + info->network_open_information.out.change_time = info2->generic.out.change_time; + info->network_open_information.out.alloc_size = info2->generic.out.alloc_size; + info->network_open_information.out.size = info2->generic.out.size; + info->network_open_information.out.attrib = info2->generic.out.attrib; + return NT_STATUS_OK; + + case RAW_FILEINFO_ALL_INFO: + case RAW_FILEINFO_ALL_INFORMATION: + info->all_info.out.create_time = info2->generic.out.create_time; + info->all_info.out.access_time = info2->generic.out.access_time; + info->all_info.out.write_time = info2->generic.out.write_time; + info->all_info.out.change_time = info2->generic.out.change_time; + info->all_info.out.attrib = info2->generic.out.attrib; + info->all_info.out.alloc_size = info2->generic.out.alloc_size; + info->all_info.out.size = info2->generic.out.size; + info->all_info.out.nlink = info2->generic.out.nlink; + info->all_info.out.delete_pending = info2->generic.out.delete_pending; + info->all_info.out.directory = info2->generic.out.directory; + info->all_info.out.ea_size = info2->generic.out.ea_size; + info->all_info.out.fname.s = info2->generic.out.fname.s; + info->all_info.out.fname.private_length = info2->generic.out.fname.private_length; + return NT_STATUS_OK; + + case RAW_FILEINFO_BASIC_INFO: + case RAW_FILEINFO_BASIC_INFORMATION: + info->basic_info.out.create_time = info2->generic.out.create_time; + info->basic_info.out.access_time = info2->generic.out.access_time; + info->basic_info.out.write_time = info2->generic.out.write_time; + info->basic_info.out.change_time = info2->generic.out.change_time; + info->basic_info.out.attrib = info2->generic.out.attrib; + return NT_STATUS_OK; + + case RAW_FILEINFO_STANDARD: + info->standard.out.create_time = nt_time_to_unix(info2->generic.out.create_time); + info->standard.out.access_time = nt_time_to_unix(info2->generic.out.access_time); + info->standard.out.write_time = nt_time_to_unix(info2->generic.out.write_time); + info->standard.out.size = info2->generic.out.size; + info->standard.out.alloc_size = info2->generic.out.alloc_size; + info->standard.out.attrib = info2->generic.out.attrib; + return NT_STATUS_OK; + + case RAW_FILEINFO_EA_SIZE: + info->ea_size.out.create_time = nt_time_to_unix(info2->generic.out.create_time); + info->ea_size.out.access_time = nt_time_to_unix(info2->generic.out.access_time); + info->ea_size.out.write_time = nt_time_to_unix(info2->generic.out.write_time); + info->ea_size.out.size = info2->generic.out.size; + info->ea_size.out.alloc_size = info2->generic.out.alloc_size; + info->ea_size.out.attrib = info2->generic.out.attrib; + info->ea_size.out.ea_size = info2->generic.out.ea_size; + return NT_STATUS_OK; + + case RAW_FILEINFO_STANDARD_INFO: + case RAW_FILEINFO_STANDARD_INFORMATION: + info->standard_info.out.alloc_size = info2->generic.out.alloc_size; + info->standard_info.out.size = info2->generic.out.size; + info->standard_info.out.nlink = info2->generic.out.nlink; + info->standard_info.out.delete_pending = info2->generic.out.delete_pending; + info->standard_info.out.directory = info2->generic.out.directory; + return NT_STATUS_OK; + + case RAW_FILEINFO_INTERNAL_INFORMATION: + info->internal_information.out.file_id = info2->generic.out.file_id; + return NT_STATUS_OK; + + case RAW_FILEINFO_EA_INFO: + case RAW_FILEINFO_EA_INFORMATION: + info->ea_info.out.ea_size = info2->generic.out.ea_size; + return NT_STATUS_OK; + + case RAW_FILEINFO_ATTRIBUTE_TAG_INFORMATION: + info->attribute_tag_information.out.attrib = info2->generic.out.attrib; + info->attribute_tag_information.out.reparse_tag = info2->generic.out.reparse_tag; + return NT_STATUS_OK; + + case RAW_FILEINFO_STREAM_INFO: + case RAW_FILEINFO_STREAM_INFORMATION: + info->stream_info.out.num_streams = info2->generic.out.num_streams; + if (info->stream_info.out.num_streams > 0) { + info->stream_info.out.streams = + talloc_array(mem_ctx, + struct stream_struct, + info->stream_info.out.num_streams); + if (!info->stream_info.out.streams) { + DEBUG(2,("ntvfs_map_fileinfo: no memory for %d streams\n", + info->stream_info.out.num_streams)); + return NT_STATUS_NO_MEMORY; + } + for (i=0; i < info->stream_info.out.num_streams; i++) { + info->stream_info.out.streams[i] = info2->generic.out.streams[i]; + info->stream_info.out.streams[i].stream_name.s = + talloc_strdup(info->stream_info.out.streams, + info2->generic.out.streams[i].stream_name.s); + if (!info->stream_info.out.streams[i].stream_name.s) { + DEBUG(2,("ntvfs_map_fileinfo: no memory for stream_name\n")); + return NT_STATUS_NO_MEMORY; + } + } + } + return NT_STATUS_OK; + + case RAW_FILEINFO_NAME_INFO: + case RAW_FILEINFO_NAME_INFORMATION: + info->name_info.out.fname.s = talloc_strdup(mem_ctx, info2->generic.out.fname.s); + NT_STATUS_HAVE_NO_MEMORY(info->name_info.out.fname.s); + info->name_info.out.fname.private_length = info2->generic.out.fname.private_length; + return NT_STATUS_OK; + + case RAW_FILEINFO_ALT_NAME_INFO: + case RAW_FILEINFO_ALT_NAME_INFORMATION: + case RAW_FILEINFO_SMB2_ALT_NAME_INFORMATION: + info->alt_name_info.out.fname.s = talloc_strdup(mem_ctx, info2->generic.out.alt_fname.s); + NT_STATUS_HAVE_NO_MEMORY(info->alt_name_info.out.fname.s); + info->alt_name_info.out.fname.private_length = info2->generic.out.alt_fname.private_length; + return NT_STATUS_OK; + + case RAW_FILEINFO_POSITION_INFORMATION: + info->position_information.out.position = info2->generic.out.position; + return NT_STATUS_OK; + + case RAW_FILEINFO_ALL_EAS: + info->all_eas.out.num_eas = info2->generic.out.num_eas; + if (info->all_eas.out.num_eas > 0) { + info->all_eas.out.eas = talloc_array(mem_ctx, + struct ea_struct, + info->all_eas.out.num_eas); + if (!info->all_eas.out.eas) { + DEBUG(2,("ntvfs_map_fileinfo: no memory for %d eas\n", + info->all_eas.out.num_eas)); + return NT_STATUS_NO_MEMORY; + } + for (i = 0; i < info->all_eas.out.num_eas; i++) { + info->all_eas.out.eas[i] = info2->generic.out.eas[i]; + info->all_eas.out.eas[i].name.s = + talloc_strdup(info->all_eas.out.eas, + info2->generic.out.eas[i].name.s); + if (!info->all_eas.out.eas[i].name.s) { + DEBUG(2,("ntvfs_map_fileinfo: no memory for stream_name\n")); + return NT_STATUS_NO_MEMORY; + } + info->all_eas.out.eas[i].value.data = + (uint8_t *)talloc_memdup(info->all_eas.out.eas, + info2->generic.out.eas[i].value.data, + info2->generic.out.eas[i].value.length); + if (!info->all_eas.out.eas[i].value.data) { + DEBUG(2,("ntvfs_map_fileinfo: no memory for stream_name\n")); + return NT_STATUS_NO_MEMORY; + } + } + } + return NT_STATUS_OK; + + case RAW_FILEINFO_IS_NAME_VALID: + return NT_STATUS_OK; + + case RAW_FILEINFO_COMPRESSION_INFO: + case RAW_FILEINFO_COMPRESSION_INFORMATION: + info->compression_info.out.compressed_size = info2->generic.out.compressed_size; + info->compression_info.out.format = info2->generic.out.format; + info->compression_info.out.unit_shift = info2->generic.out.unit_shift; + info->compression_info.out.chunk_shift = info2->generic.out.chunk_shift; + info->compression_info.out.cluster_shift = info2->generic.out.cluster_shift; + return NT_STATUS_OK; + + case RAW_FILEINFO_ACCESS_INFORMATION: + info->access_information.out.access_flags = info2->generic.out.access_flags; + return NT_STATUS_OK; + + case RAW_FILEINFO_MODE_INFORMATION: + info->mode_information.out.mode = info2->generic.out.mode; + return NT_STATUS_OK; + + case RAW_FILEINFO_ALIGNMENT_INFORMATION: + info->alignment_information.out.alignment_requirement = + info2->generic.out.alignment_requirement; + return NT_STATUS_OK; + case RAW_FILEINFO_UNIX_BASIC: +#if 1 + return NT_STATUS_INVALID_LEVEL; +#else + info->unix_basic_info.out.end_of_file = info2->generic.out.end_of_file; + info->unix_basic_info.out.num_bytes = info2->generic.out.size; + info->unix_basic_info.out.status_change_time = info2->generic.out.change_time; + info->unix_basic_info.out.access_time = info2->generic.out.access_time; + info->unix_basic_info.out.change_time = info2->generic.out.change_time; + info->unix_basic_info.out.uid = info2->generic.out.uid; + info->unix_basic_info.out.gid = info2->generic.out.gid; + info->unix_basic_info.out.file_type = info2->generic.out.file_type; + info->unix_basic_info.out.dev_major = info2->generic.out.device; + info->unix_basic_info.out.dev_minor = info2->generic.out.device; + info->unix_basic_info.out.unique_id = info2->generic.out.inode; + info->unix_basic_info.out.permissions = info2->generic.out.permissions; + info->unix_basic_info.out.nlink = info2->generic.out.nlink; + return NT_STATUS_OK; +#endif + case RAW_FILEINFO_UNIX_LINK: +#if 1 + return NT_STATUS_INVALID_LEVEL; +#else + info->unix_link_info.out.link_dest = info2->generic.out.link_dest; + return NT_STATUS_OK; +#endif + case RAW_FILEINFO_GENERIC: + case RAW_FILEINFO_SEC_DESC: + case RAW_FILEINFO_EA_LIST: + case RAW_FILEINFO_UNIX_INFO2: + case RAW_FILEINFO_SMB2_ALL_EAS: + case RAW_FILEINFO_SMB2_ALL_INFORMATION: + return NT_STATUS_INVALID_LEVEL; + case RAW_FILEINFO_NORMALIZED_NAME_INFORMATION: + return NT_STATUS_NOT_SUPPORTED; + } + + return NT_STATUS_INVALID_LEVEL; +} + +/* + NTVFS any to fileinfo mapper +*/ +static NTSTATUS ntvfs_map_qfileinfo_finish(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_fileinfo *info, + union smb_fileinfo *info2, + NTSTATUS status) +{ + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return ntvfs_map_fileinfo(req, info, info2); +} + +/* + NTVFS fileinfo generic to any mapper +*/ +NTSTATUS ntvfs_map_qfileinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_fileinfo *info) +{ + NTSTATUS status; + union smb_fileinfo *info2; + + info2 = talloc(req, union smb_fileinfo); + if (info2 == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (info->generic.level == RAW_FILEINFO_GENERIC) { + return NT_STATUS_INVALID_LEVEL; + } + + status = ntvfs_map_async_setup(ntvfs, req, info, info2, + (second_stage_t)ntvfs_map_qfileinfo_finish); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* ask the backend for the generic info */ + info2->generic.level = RAW_FILEINFO_GENERIC; + info2->generic.in.file.ntvfs= info->generic.in.file.ntvfs; + + status = ntvfs->ops->qfileinfo_fn(ntvfs, req, info2); + return ntvfs_map_async_finish(req, status); +} + +/* + NTVFS any to fileinfo mapper +*/ +static NTSTATUS ntvfs_map_qpathinfo_finish(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_fileinfo *info, + union smb_fileinfo *info2, + NTSTATUS status) +{ + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return ntvfs_map_fileinfo(req, info, info2); +} + +/* + NTVFS pathinfo generic to any mapper +*/ +NTSTATUS ntvfs_map_qpathinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_fileinfo *info) +{ + NTSTATUS status; + union smb_fileinfo *info2; + + info2 = talloc(req, union smb_fileinfo); + if (info2 == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (info->generic.level == RAW_FILEINFO_GENERIC) { + return NT_STATUS_INVALID_LEVEL; + } + + status = ntvfs_map_async_setup(ntvfs, req, info, info2, + (second_stage_t)ntvfs_map_qpathinfo_finish); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* ask the backend for the generic info */ + info2->generic.level = RAW_FILEINFO_GENERIC; + info2->generic.in.file.path = info->generic.in.file.path; + + status = ntvfs->ops->qpathinfo_fn(ntvfs, req, info2); + return ntvfs_map_async_finish(req, status); +} + + +/* + NTVFS lock generic to any mapper +*/ +NTSTATUS ntvfs_map_lock(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_lock *lck) +{ + union smb_lock *lck2; + struct smb_lock_entry *locks; + + lck2 = talloc(req, union smb_lock); + if (lck2 == NULL) { + return NT_STATUS_NO_MEMORY; + } + + locks = talloc_array(lck2, struct smb_lock_entry, 1); + if (locks == NULL) { + return NT_STATUS_NO_MEMORY; + } + + switch (lck->generic.level) { + case RAW_LOCK_LOCKX: + return NT_STATUS_INVALID_LEVEL; + + case RAW_LOCK_LOCK: + lck2->generic.level = RAW_LOCK_GENERIC; + lck2->generic.in.file.ntvfs= lck->lock.in.file.ntvfs; + lck2->generic.in.mode = 0; + lck2->generic.in.timeout = 0; + lck2->generic.in.ulock_cnt = 0; + lck2->generic.in.lock_cnt = 1; + lck2->generic.in.locks = locks; + locks->pid = req->smbpid; + locks->offset = lck->lock.in.offset; + locks->count = lck->lock.in.count; + break; + + case RAW_LOCK_UNLOCK: + lck2->generic.level = RAW_LOCK_GENERIC; + lck2->generic.in.file.ntvfs= lck->unlock.in.file.ntvfs; + lck2->generic.in.mode = 0; + lck2->generic.in.timeout = 0; + lck2->generic.in.ulock_cnt = 1; + lck2->generic.in.lock_cnt = 0; + lck2->generic.in.locks = locks; + locks->pid = req->smbpid; + locks->offset = lck->unlock.in.offset; + locks->count = lck->unlock.in.count; + break; + + case RAW_LOCK_SMB2: { + /* this is only approximate! We need to change the + generic structure to fix this properly */ + int i; + bool isunlock; + if (lck->smb2.in.lock_count < 1) { + return NT_STATUS_INVALID_PARAMETER; + } + + lck2->generic.level = RAW_LOCK_GENERIC; + lck2->generic.in.file.ntvfs= lck->smb2.in.file.ntvfs; + lck2->generic.in.timeout = UINT32_MAX; + lck2->generic.in.mode = 0; + lck2->generic.in.lock_cnt = 0; + lck2->generic.in.ulock_cnt = 0; + lck2->generic.in.locks = talloc_zero_array(lck2, struct smb_lock_entry, + lck->smb2.in.lock_count); + if (lck2->generic.in.locks == NULL) { + return NT_STATUS_NO_MEMORY; + } + /* only the first lock gives the UNLOCK bit - see + MS-SMB2 3.3.5.14 */ + if (lck->smb2.in.locks[0].flags & SMB2_LOCK_FLAG_UNLOCK) { + if (lck->smb2.in.locks[0].flags & SMB2_LOCK_FLAG_FAIL_IMMEDIATELY) { + return NT_STATUS_INVALID_PARAMETER; + } + lck2->generic.in.ulock_cnt = lck->smb2.in.lock_count; + isunlock = true; + } else { + lck2->generic.in.lock_cnt = lck->smb2.in.lock_count; + isunlock = false; + } + for (i=0;i<lck->smb2.in.lock_count;i++) { + if (!isunlock && + lck->smb2.in.locks[i].flags == SMB2_LOCK_FLAG_NONE) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (lck->smb2.in.locks[i].flags & ~SMB2_LOCK_FLAG_ALL_MASK) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (isunlock && + (lck->smb2.in.locks[i].flags & + (SMB2_LOCK_FLAG_SHARED|SMB2_LOCK_FLAG_EXCLUSIVE))) { + return NT_STATUS_INVALID_PARAMETER; + } + if (!isunlock && + (lck->smb2.in.locks[i].flags & SMB2_LOCK_FLAG_UNLOCK)) { + return NT_STATUS_INVALID_PARAMETER; + } + lck2->generic.in.locks[i].pid = req->smbpid; + lck2->generic.in.locks[i].offset = lck->smb2.in.locks[i].offset; + lck2->generic.in.locks[i].count = lck->smb2.in.locks[i].length; + if (!(lck->smb2.in.locks[i].flags & SMB2_LOCK_FLAG_EXCLUSIVE)) { + lck2->generic.in.mode = LOCKING_ANDX_SHARED_LOCK; + } + if (lck->smb2.in.locks[i].flags & SMB2_LOCK_FLAG_FAIL_IMMEDIATELY) { + lck2->generic.in.timeout = 0; + } + } + /* initialize output value */ + lck->smb2.out.reserved = 0; + break; + } + + case RAW_LOCK_SMB2_BREAK: + lck2->generic.level = RAW_LOCK_GENERIC; + lck2->generic.in.file.ntvfs = lck->smb2_break.in.file.ntvfs; + lck2->generic.in.mode = LOCKING_ANDX_OPLOCK_RELEASE | + ((lck->smb2_break.in.oplock_level << 8) & 0xFF00); + lck2->generic.in.timeout = 0; + lck2->generic.in.ulock_cnt = 0; + lck2->generic.in.lock_cnt = 0; + lck2->generic.in.locks = NULL; + + /* initialize output value */ + lck->smb2_break.out.oplock_level= lck->smb2_break.in.oplock_level; + lck->smb2_break.out.reserved = lck->smb2_break.in.reserved; + lck->smb2_break.out.reserved2 = lck->smb2_break.in.reserved2; + lck->smb2_break.out.file = lck->smb2_break.in.file; + break; + } + + /* + * we don't need to call ntvfs_map_async_setup() here, + * as lock() doesn't have any output fields + */ + + return ntvfs->ops->lock_fn(ntvfs, req, lck2); +} + + +/* + NTVFS write generic to any mapper +*/ +static NTSTATUS ntvfs_map_write_finish(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_write *wr, + union smb_write *wr2, + NTSTATUS status) +{ + union smb_lock *lck; + union smb_close *cl; + unsigned int state; + + if (NT_STATUS_IS_ERR(status)) { + return status; + } + + switch (wr->generic.level) { + case RAW_WRITE_WRITE: + wr->write.out.nwritten = wr2->generic.out.nwritten; + break; + + case RAW_WRITE_WRITEUNLOCK: + wr->writeunlock.out.nwritten = wr2->generic.out.nwritten; + + lck = talloc(wr2, union smb_lock); + if (lck == NULL) { + return NT_STATUS_NO_MEMORY; + } + + lck->unlock.level = RAW_LOCK_UNLOCK; + lck->unlock.in.file.ntvfs = wr->writeunlock.in.file.ntvfs; + lck->unlock.in.count = wr->writeunlock.in.count; + lck->unlock.in.offset = wr->writeunlock.in.offset; + + if (lck->unlock.in.count != 0) { + /* do the lock sync for now */ + state = req->async_states->state; + req->async_states->state &= ~NTVFS_ASYNC_STATE_MAY_ASYNC; + status = ntvfs->ops->lock_fn(ntvfs, req, lck); + req->async_states->state = state; + } + break; + + case RAW_WRITE_WRITECLOSE: + wr->writeclose.out.nwritten = wr2->generic.out.nwritten; + + cl = talloc(wr2, union smb_close); + if (cl == NULL) { + return NT_STATUS_NO_MEMORY; + } + + cl->close.level = RAW_CLOSE_CLOSE; + cl->close.in.file.ntvfs = wr->writeclose.in.file.ntvfs; + cl->close.in.write_time = wr->writeclose.in.mtime; + + if (wr2->generic.in.count != 0) { + /* do the close sync for now */ + state = req->async_states->state; + req->async_states->state &= ~NTVFS_ASYNC_STATE_MAY_ASYNC; + status = ntvfs->ops->close_fn(ntvfs, req, cl); + req->async_states->state = state; + } + break; + + case RAW_WRITE_SPLWRITE: + break; + + case RAW_WRITE_SMB2: + wr->smb2.out._pad = 0; + wr->smb2.out.nwritten = wr2->generic.out.nwritten; + wr->smb2.out.unknown1 = 0; + break; + + default: + return NT_STATUS_INVALID_LEVEL; + } + + return status; +} + + +/* + NTVFS write generic to any mapper +*/ +NTSTATUS ntvfs_map_write(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_write *wr) +{ + union smb_write *wr2; + NTSTATUS status; + + wr2 = talloc(req, union smb_write); + if (wr2 == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = ntvfs_map_async_setup(ntvfs, req, wr, wr2, + (second_stage_t)ntvfs_map_write_finish); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + wr2->writex.level = RAW_WRITE_GENERIC; + + switch (wr->generic.level) { + case RAW_WRITE_WRITEX: + status = NT_STATUS_INVALID_LEVEL; + break; + + case RAW_WRITE_WRITE: + wr2->writex.in.file.ntvfs= wr->write.in.file.ntvfs; + wr2->writex.in.offset = wr->write.in.offset; + wr2->writex.in.wmode = 0; + wr2->writex.in.remaining = wr->write.in.remaining; + wr2->writex.in.count = wr->write.in.count; + wr2->writex.in.data = wr->write.in.data; + status = ntvfs->ops->write_fn(ntvfs, req, wr2); + break; + + case RAW_WRITE_WRITEUNLOCK: + wr2->writex.in.file.ntvfs= wr->writeunlock.in.file.ntvfs; + wr2->writex.in.offset = wr->writeunlock.in.offset; + wr2->writex.in.wmode = 0; + wr2->writex.in.remaining = wr->writeunlock.in.remaining; + wr2->writex.in.count = wr->writeunlock.in.count; + wr2->writex.in.data = wr->writeunlock.in.data; + status = ntvfs->ops->write_fn(ntvfs, req, wr2); + break; + + case RAW_WRITE_WRITECLOSE: + wr2->writex.in.file.ntvfs= wr->writeclose.in.file.ntvfs; + wr2->writex.in.offset = wr->writeclose.in.offset; + wr2->writex.in.wmode = 0; + wr2->writex.in.remaining = 0; + wr2->writex.in.count = wr->writeclose.in.count; + wr2->writex.in.data = wr->writeclose.in.data; + status = ntvfs->ops->write_fn(ntvfs, req, wr2); + break; + + case RAW_WRITE_SPLWRITE: + wr2->writex.in.file.ntvfs= wr->splwrite.in.file.ntvfs; + wr2->writex.in.offset = 0; + wr2->writex.in.wmode = 0; + wr2->writex.in.remaining = 0; + wr2->writex.in.count = wr->splwrite.in.count; + wr2->writex.in.data = wr->splwrite.in.data; + status = ntvfs->ops->write_fn(ntvfs, req, wr2); + break; + + case RAW_WRITE_SMB2: + wr2->writex.in.file.ntvfs= wr->smb2.in.file.ntvfs; + wr2->writex.in.offset = wr->smb2.in.offset; + wr2->writex.in.wmode = 0; + wr2->writex.in.remaining = 0; + wr2->writex.in.count = wr->smb2.in.data.length; + wr2->writex.in.data = wr->smb2.in.data.data; + status = ntvfs->ops->write_fn(ntvfs, req, wr2); + } + + return ntvfs_map_async_finish(req, status); +} + + +/* + NTVFS read generic to any mapper - finish the out mapping +*/ +static NTSTATUS ntvfs_map_read_finish(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_read *rd, + union smb_read *rd2, + NTSTATUS status) +{ + switch (rd->generic.level) { + case RAW_READ_READ: + rd->read.out.nread = rd2->generic.out.nread; + break; + case RAW_READ_READBRAW: + rd->readbraw.out.nread = rd2->generic.out.nread; + break; + case RAW_READ_LOCKREAD: + rd->lockread.out.nread = rd2->generic.out.nread; + break; + case RAW_READ_SMB2: + rd->smb2.out.data.length= rd2->generic.out.nread; + rd->smb2.out.remaining = 0; + rd->smb2.out.reserved = 0; + break; + default: + return NT_STATUS_INVALID_LEVEL; + } + + return status; +} + +/* + NTVFS read* to readx mapper +*/ +NTSTATUS ntvfs_map_read(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_read *rd) +{ + union smb_read *rd2; + union smb_lock *lck; + NTSTATUS status; + unsigned int state; + + rd2 = talloc(req, union smb_read); + if (rd2 == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = ntvfs_map_async_setup(ntvfs, req, rd, rd2, + (second_stage_t)ntvfs_map_read_finish); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + rd2->readx.level = RAW_READ_READX; + rd2->readx.in.read_for_execute = false; + + switch (rd->generic.level) { + case RAW_READ_READX: + status = NT_STATUS_INVALID_LEVEL; + break; + + case RAW_READ_READ: + rd2->readx.in.file.ntvfs= rd->read.in.file.ntvfs; + rd2->readx.in.offset = rd->read.in.offset; + rd2->readx.in.mincnt = rd->read.in.count; + rd2->readx.in.maxcnt = rd->read.in.count; + rd2->readx.in.remaining = rd->read.in.remaining; + rd2->readx.out.data = rd->read.out.data; + status = ntvfs->ops->read_fn(ntvfs, req, rd2); + break; + + case RAW_READ_READBRAW: + rd2->readx.in.file.ntvfs= rd->readbraw.in.file.ntvfs; + rd2->readx.in.offset = rd->readbraw.in.offset; + rd2->readx.in.mincnt = rd->readbraw.in.mincnt; + rd2->readx.in.maxcnt = rd->readbraw.in.maxcnt; + rd2->readx.in.remaining = 0; + rd2->readx.out.data = rd->readbraw.out.data; + status = ntvfs->ops->read_fn(ntvfs, req, rd2); + break; + + case RAW_READ_LOCKREAD: + /* do the initial lock sync for now */ + state = req->async_states->state; + req->async_states->state &= ~NTVFS_ASYNC_STATE_MAY_ASYNC; + + lck = talloc(rd2, union smb_lock); + if (lck == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + lck->lock.level = RAW_LOCK_LOCK; + lck->lock.in.file.ntvfs = rd->lockread.in.file.ntvfs; + lck->lock.in.count = rd->lockread.in.count; + lck->lock.in.offset = rd->lockread.in.offset; + status = ntvfs->ops->lock_fn(ntvfs, req, lck); + req->async_states->state = state; + + rd2->readx.in.file.ntvfs= rd->lockread.in.file.ntvfs; + rd2->readx.in.offset = rd->lockread.in.offset; + rd2->readx.in.mincnt = rd->lockread.in.count; + rd2->readx.in.maxcnt = rd->lockread.in.count; + rd2->readx.in.remaining = rd->lockread.in.remaining; + rd2->readx.out.data = rd->lockread.out.data; + + if (NT_STATUS_IS_OK(status)) { + status = ntvfs->ops->read_fn(ntvfs, req, rd2); + } + break; + + case RAW_READ_SMB2: + rd2->readx.in.file.ntvfs= rd->smb2.in.file.ntvfs; + rd2->readx.in.offset = rd->smb2.in.offset; + rd2->readx.in.mincnt = rd->smb2.in.min_count; + rd2->readx.in.maxcnt = rd->smb2.in.length; + rd2->readx.in.remaining = 0; + rd2->readx.out.data = rd->smb2.out.data.data; + status = ntvfs->ops->read_fn(ntvfs, req, rd2); + break; + } + +done: + return ntvfs_map_async_finish(req, status); +} + + +/* + NTVFS close generic to any mapper +*/ +static NTSTATUS ntvfs_map_close_finish(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_close *cl, + union smb_close *cl2, + NTSTATUS status) +{ + NT_STATUS_NOT_OK_RETURN(status); + + switch (cl->generic.level) { + case RAW_CLOSE_SMB2: + cl->smb2.out.flags = cl2->generic.out.flags; + cl->smb2.out._pad = 0; + cl->smb2.out.create_time = cl2->generic.out.create_time; + cl->smb2.out.access_time = cl2->generic.out.access_time; + cl->smb2.out.write_time = cl2->generic.out.write_time; + cl->smb2.out.change_time = cl2->generic.out.change_time; + cl->smb2.out.alloc_size = cl2->generic.out.alloc_size; + cl->smb2.out.size = cl2->generic.out.size; + cl->smb2.out.file_attr = cl2->generic.out.file_attr; + break; + default: + break; + } + + return status; +} + +/* + NTVFS close generic to any mapper +*/ +NTSTATUS ntvfs_map_close(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_close *cl) +{ + union smb_close *cl2; + NTSTATUS status; + + cl2 = talloc(req, union smb_close); + if (cl2 == NULL) { + return NT_STATUS_NO_MEMORY; + } + + switch (cl->generic.level) { + case RAW_CLOSE_GENERIC: + return NT_STATUS_INVALID_LEVEL; + + case RAW_CLOSE_CLOSE: + cl2->generic.level = RAW_CLOSE_GENERIC; + cl2->generic.in.file = cl->close.in.file; + cl2->generic.in.write_time = cl->close.in.write_time; + cl2->generic.in.flags = 0; + break; + + case RAW_CLOSE_SPLCLOSE: + cl2->generic.level = RAW_CLOSE_GENERIC; + cl2->generic.in.file = cl->splclose.in.file; + cl2->generic.in.write_time = 0; + cl2->generic.in.flags = 0; + break; + + case RAW_CLOSE_SMB2: + cl2->generic.level = RAW_CLOSE_GENERIC; + cl2->generic.in.file = cl->smb2.in.file; + cl2->generic.in.write_time = 0; + cl2->generic.in.flags = cl->smb2.in.flags; + break; + } + + status = ntvfs_map_async_setup(ntvfs, req, cl, cl2, + (second_stage_t)ntvfs_map_close_finish); + NT_STATUS_NOT_OK_RETURN(status); + + status = ntvfs->ops->close_fn(ntvfs, req, cl2); + + return ntvfs_map_async_finish(req, status); +} + +/* + NTVFS notify generic to any mapper +*/ +static NTSTATUS ntvfs_map_notify_finish(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_notify *nt, + union smb_notify *nt2, + NTSTATUS status) +{ + NT_STATUS_NOT_OK_RETURN(status); + + switch (nt->nttrans.level) { + case RAW_NOTIFY_SMB2: + if (nt2->nttrans.out.num_changes == 0) { + return NT_STATUS_NOTIFY_ENUM_DIR; + } + nt->smb2.out.num_changes = nt2->nttrans.out.num_changes; + nt->smb2.out.changes = talloc_steal(req, nt2->nttrans.out.changes); + break; + + default: + return NT_STATUS_INVALID_LEVEL; + } + + return status; +} + + +/* + NTVFS notify generic to any mapper +*/ +NTSTATUS ntvfs_map_notify(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_notify *nt) +{ + union smb_notify *nt2; + NTSTATUS status; + + nt2 = talloc(req, union smb_notify); + NT_STATUS_HAVE_NO_MEMORY(nt2); + + status = ntvfs_map_async_setup(ntvfs, req, nt, nt2, + (second_stage_t)ntvfs_map_notify_finish); + NT_STATUS_NOT_OK_RETURN(status); + + nt2->nttrans.level = RAW_NOTIFY_NTTRANS; + + switch (nt->nttrans.level) { + case RAW_NOTIFY_NTTRANS: + status = NT_STATUS_INVALID_LEVEL; + break; + + case RAW_NOTIFY_SMB2: + nt2->nttrans.in.file.ntvfs = nt->smb2.in.file.ntvfs; + nt2->nttrans.in.buffer_size = nt->smb2.in.buffer_size; + nt2->nttrans.in.completion_filter = nt->smb2.in.completion_filter; + nt2->nttrans.in.recursive = nt->smb2.in.recursive; + status = ntvfs->ops->notify_fn(ntvfs, req, nt2); + break; + } + + return ntvfs_map_async_finish(req, status); +} diff --git a/source4/ntvfs/ntvfs_interface.c b/source4/ntvfs/ntvfs_interface.c new file mode 100644 index 0000000..a9e3101 --- /dev/null +++ b/source4/ntvfs/ntvfs_interface.c @@ -0,0 +1,702 @@ +/* + Unix SMB/CIFS implementation. + NTVFS interface functions + + Copyright (C) Stefan (metze) Metzmacher 2004 + + 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 "ntvfs/ntvfs.h" +#include "lib/tsocket/tsocket.h" + +/* connect/disconnect */ +NTSTATUS ntvfs_connect(struct ntvfs_request *req, union smb_tcon *tcon) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->connect_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->connect_fn(ntvfs, req, tcon); +} + +NTSTATUS ntvfs_disconnect(struct ntvfs_context *ntvfs_ctx) +{ + struct ntvfs_module_context *ntvfs; + if (ntvfs_ctx == NULL) { + return NT_STATUS_INVALID_CONNECTION; + } + ntvfs = ntvfs_ctx->modules; + if (!ntvfs->ops->disconnect_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->disconnect_fn(ntvfs); +} + +/* async setup - called by a backend that wants to setup any state for + a async request */ +NTSTATUS ntvfs_async_setup(struct ntvfs_request *req, void *private_data) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->async_setup_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->async_setup_fn(ntvfs, req, private_data); +} + +/* filesystem operations */ +NTSTATUS ntvfs_fsinfo(struct ntvfs_request *req, union smb_fsinfo *fs) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->fsinfo_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->fsinfo_fn(ntvfs, req, fs); +} + +/* path operations */ +NTSTATUS ntvfs_unlink(struct ntvfs_request *req, union smb_unlink *unl) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->unlink_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->unlink_fn(ntvfs, req, unl); +} + +NTSTATUS ntvfs_chkpath(struct ntvfs_request *req, union smb_chkpath *cp) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->chkpath_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->chkpath_fn(ntvfs, req, cp); +} + +NTSTATUS ntvfs_qpathinfo(struct ntvfs_request *req, union smb_fileinfo *st) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->qpathinfo_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->qpathinfo_fn(ntvfs, req, st); +} + +NTSTATUS ntvfs_setpathinfo(struct ntvfs_request *req, union smb_setfileinfo *st) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->setpathinfo_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->setpathinfo_fn(ntvfs, req, st); +} + +NTSTATUS ntvfs_open(struct ntvfs_request *req, union smb_open *oi) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->open_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->open_fn(ntvfs, req, oi); +} + +NTSTATUS ntvfs_mkdir(struct ntvfs_request *req, union smb_mkdir *md) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->mkdir_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->mkdir_fn(ntvfs, req, md); +} + +NTSTATUS ntvfs_rmdir(struct ntvfs_request *req, struct smb_rmdir *rd) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->rmdir_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->rmdir_fn(ntvfs, req, rd); +} + +NTSTATUS ntvfs_rename(struct ntvfs_request *req, union smb_rename *ren) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->rename_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->rename_fn(ntvfs, req, ren); +} + +NTSTATUS ntvfs_copy(struct ntvfs_request *req, struct smb_copy *cp) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->copy_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->copy_fn(ntvfs, req, cp); +} + +/* directory search */ +NTSTATUS ntvfs_search_first(struct ntvfs_request *req, union smb_search_first *io, void *private_data, + bool ntvfs_callback(void *private_data, const union smb_search_data *file)) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->search_first_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->search_first_fn(ntvfs, req, io, private_data, ntvfs_callback); +} + +NTSTATUS ntvfs_search_next(struct ntvfs_request *req, union smb_search_next *io, void *private_data, + bool ntvfs_callback(void *private_data, const union smb_search_data *file)) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->search_next_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->search_next_fn(ntvfs, req, io, private_data, ntvfs_callback); +} + +NTSTATUS ntvfs_search_close(struct ntvfs_request *req, union smb_search_close *io) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->search_close_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->search_close_fn(ntvfs, req, io); +} + +/* operations on open files */ +NTSTATUS ntvfs_ioctl(struct ntvfs_request *req, union smb_ioctl *io) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->ioctl_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->ioctl_fn(ntvfs, req, io); +} + +NTSTATUS ntvfs_read(struct ntvfs_request *req, union smb_read *io) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->read_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->read_fn(ntvfs, req, io); +} + +NTSTATUS ntvfs_write(struct ntvfs_request *req, union smb_write *io) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->write_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->write_fn(ntvfs, req, io); +} + +NTSTATUS ntvfs_seek(struct ntvfs_request *req, union smb_seek *io) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->seek_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->seek_fn(ntvfs, req, io); +} + +NTSTATUS ntvfs_flush(struct ntvfs_request *req, + union smb_flush *flush) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->flush_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->flush_fn(ntvfs, req, flush); +} + +NTSTATUS ntvfs_lock(struct ntvfs_request *req, union smb_lock *lck) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->lock_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->lock_fn(ntvfs, req, lck); +} + +NTSTATUS ntvfs_qfileinfo(struct ntvfs_request *req, union smb_fileinfo *info) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->qfileinfo_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->qfileinfo_fn(ntvfs, req, info); +} + +NTSTATUS ntvfs_setfileinfo(struct ntvfs_request *req, union smb_setfileinfo *info) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->setfileinfo_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->setfileinfo_fn(ntvfs, req, info); +} + +NTSTATUS ntvfs_close(struct ntvfs_request *req, union smb_close *io) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->close_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->close_fn(ntvfs, req, io); +} + +/* trans interface - used by IPC backend for pipes and RAP calls */ +NTSTATUS ntvfs_trans(struct ntvfs_request *req, struct smb_trans2 *trans) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->trans_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->trans_fn(ntvfs, req, trans); +} + +/* trans2 interface - only used by CIFS backend to prover complete passthru for testing */ +NTSTATUS ntvfs_trans2(struct ntvfs_request *req, struct smb_trans2 *trans2) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->trans2_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->trans2_fn(ntvfs, req, trans2); +} + +/* printing specific operations */ +NTSTATUS ntvfs_lpq(struct ntvfs_request *req, union smb_lpq *lpq) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->lpq_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->lpq_fn(ntvfs, req, lpq); +} + +/* logoff - called when a vuid is closed */ +NTSTATUS ntvfs_logoff(struct ntvfs_request *req) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->logoff_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->logoff_fn(ntvfs, req); +} + +NTSTATUS ntvfs_exit(struct ntvfs_request *req) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->exit_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->exit_fn(ntvfs, req); +} + +/* + change notify request +*/ +NTSTATUS ntvfs_notify(struct ntvfs_request *req, union smb_notify *info) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->notify_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->notify_fn(ntvfs, req, info); +} + +/* + cancel an outstanding async request +*/ +NTSTATUS ntvfs_cancel(struct ntvfs_request *req) +{ + struct ntvfs_module_context *ntvfs = req->ctx->modules; + if (!ntvfs->ops->cancel_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ops->cancel_fn(ntvfs, req); +} + +/* initial setup */ +NTSTATUS ntvfs_next_connect(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_tcon *tcon) +{ + if (!ntvfs->next || !ntvfs->next->ops->connect_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->connect_fn(ntvfs->next, req, tcon); +} + +NTSTATUS ntvfs_next_disconnect(struct ntvfs_module_context *ntvfs) +{ + if (!ntvfs->next || !ntvfs->next->ops->disconnect_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->disconnect_fn(ntvfs->next); +} + +/* async_setup - called when setting up for a async request */ +NTSTATUS ntvfs_next_async_setup(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + void *private_data) +{ + if (!ntvfs->next || !ntvfs->next->ops->async_setup_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->async_setup_fn(ntvfs->next, req, private_data); +} + +/* filesystem operations */ +NTSTATUS ntvfs_next_fsinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_fsinfo *fs) +{ + if (!ntvfs->next || !ntvfs->next->ops->fsinfo_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->fsinfo_fn(ntvfs->next, req, fs); +} + +/* path operations */ +NTSTATUS ntvfs_next_unlink(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_unlink *unl) +{ + if (!ntvfs->next || !ntvfs->next->ops->unlink_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->unlink_fn(ntvfs->next, req, unl); +} + +NTSTATUS ntvfs_next_chkpath(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_chkpath *cp) +{ + if (!ntvfs->next || !ntvfs->next->ops->chkpath_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->chkpath_fn(ntvfs->next, req, cp); +} + +NTSTATUS ntvfs_next_qpathinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_fileinfo *st) +{ + if (!ntvfs->next || !ntvfs->next->ops->qpathinfo_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->qpathinfo_fn(ntvfs->next, req, st); +} + +NTSTATUS ntvfs_next_setpathinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_setfileinfo *st) +{ + if (!ntvfs->next || !ntvfs->next->ops->setpathinfo_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->setpathinfo_fn(ntvfs->next, req, st); +} + +NTSTATUS ntvfs_next_mkdir(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_mkdir *md) +{ + if (!ntvfs->next || !ntvfs->next->ops->mkdir_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->mkdir_fn(ntvfs->next, req, md); +} + +NTSTATUS ntvfs_next_rmdir(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + struct smb_rmdir *rd) +{ + if (!ntvfs->next || !ntvfs->next->ops->rmdir_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->rmdir_fn(ntvfs->next, req, rd); +} + +NTSTATUS ntvfs_next_rename(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_rename *ren) +{ + if (!ntvfs->next || !ntvfs->next->ops->rename_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->rename_fn(ntvfs->next, req, ren); +} + +NTSTATUS ntvfs_next_copy(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + struct smb_copy *cp) +{ + if (!ntvfs->next || !ntvfs->next->ops->copy_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->copy_fn(ntvfs->next, req, cp); +} + +NTSTATUS ntvfs_next_open(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_open *oi) +{ + if (!ntvfs->next || !ntvfs->next->ops->open_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->open_fn(ntvfs->next, req, oi); +} + + +/* directory search */ +NTSTATUS ntvfs_next_search_first(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_search_first *io, void *private_data, + bool (*callback)(void *private_data, const union smb_search_data *file)) +{ + if (!ntvfs->next || !ntvfs->next->ops->search_first_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->search_first_fn(ntvfs->next, req, io, private_data, callback); +} + +NTSTATUS ntvfs_next_search_next(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_search_next *io, void *private_data, + bool (*callback)(void *private_data, const union smb_search_data *file)) +{ + if (!ntvfs->next || !ntvfs->next->ops->search_next_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->search_next_fn(ntvfs->next, req, io, private_data, callback); +} + +NTSTATUS ntvfs_next_search_close(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_search_close *io) +{ + if (!ntvfs->next || !ntvfs->next->ops->search_close_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->search_close_fn(ntvfs->next, req, io); +} + +/* operations on open files */ +NTSTATUS ntvfs_next_ioctl(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_ioctl *io) +{ + if (!ntvfs->next || !ntvfs->next->ops->ioctl_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->ioctl_fn(ntvfs->next, req, io); +} + +NTSTATUS ntvfs_next_read(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_read *io) +{ + if (!ntvfs->next || !ntvfs->next->ops->read_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->read_fn(ntvfs->next, req, io); +} + +NTSTATUS ntvfs_next_write(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_write *io) +{ + if (!ntvfs->next || !ntvfs->next->ops->write_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->write_fn(ntvfs->next, req, io); +} + +NTSTATUS ntvfs_next_seek(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_seek *io) +{ + if (!ntvfs->next || !ntvfs->next->ops->seek_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->seek_fn(ntvfs->next, req, io); +} + +NTSTATUS ntvfs_next_flush(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_flush *flush) +{ + if (!ntvfs->next || !ntvfs->next->ops->flush_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->flush_fn(ntvfs->next, req, flush); +} + +NTSTATUS ntvfs_next_lock(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_lock *lck) +{ + if (!ntvfs->next || !ntvfs->next->ops->lock_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->lock_fn(ntvfs->next, req, lck); +} + +NTSTATUS ntvfs_next_qfileinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_fileinfo *info) +{ + if (!ntvfs->next || !ntvfs->next->ops->qfileinfo_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->qfileinfo_fn(ntvfs->next, req, info); +} + +NTSTATUS ntvfs_next_setfileinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_setfileinfo *info) +{ + if (!ntvfs->next || !ntvfs->next->ops->setfileinfo_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->setfileinfo_fn(ntvfs->next, req, info); +} + +NTSTATUS ntvfs_next_close(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_close *io) +{ + if (!ntvfs->next || !ntvfs->next->ops->close_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->close_fn(ntvfs->next, req, io); +} + +/* trans interface - used by IPC backend for pipes and RAP calls */ +NTSTATUS ntvfs_next_trans(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + struct smb_trans2 *trans) +{ + if (!ntvfs->next || !ntvfs->next->ops->trans_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->trans_fn(ntvfs->next, req, trans); +} + +/* + change notify request +*/ +NTSTATUS ntvfs_next_notify(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_notify *info) +{ + if (!ntvfs->next || !ntvfs->next->ops->notify_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->notify_fn(ntvfs->next, req, info); +} + +/* cancel - called to cancel an outstanding async request */ +NTSTATUS ntvfs_next_cancel(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req) +{ + if (!ntvfs->next || !ntvfs->next->ops->cancel_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->cancel_fn(ntvfs->next, req); +} + +/* printing specific operations */ +NTSTATUS ntvfs_next_lpq(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_lpq *lpq) +{ + if (!ntvfs->next || !ntvfs->next->ops->lpq_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->lpq_fn(ntvfs->next, req, lpq); +} + + +/* logoff - called when a vuid is closed */ +NTSTATUS ntvfs_next_logoff(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req) +{ + if (!ntvfs->next || !ntvfs->next->ops->logoff_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->logoff_fn(ntvfs->next, req); +} + +NTSTATUS ntvfs_next_exit(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req) +{ + if (!ntvfs->next || !ntvfs->next->ops->exit_fn) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->next->ops->exit_fn(ntvfs->next, req); +} + +/* client connection callback */ +NTSTATUS ntvfs_set_addresses(struct ntvfs_context *ntvfs, + const struct tsocket_address *local_address, + const struct tsocket_address *remote_address) +{ + ntvfs->client.local_address = tsocket_address_copy(local_address, ntvfs); + NT_STATUS_HAVE_NO_MEMORY(ntvfs->client.local_address); + + ntvfs->client.remote_address = tsocket_address_copy(remote_address, ntvfs); + NT_STATUS_HAVE_NO_MEMORY(ntvfs->client.remote_address); + + return NT_STATUS_OK; +} + +const struct tsocket_address *ntvfs_get_local_address(struct ntvfs_module_context *ntvfs) +{ + return ntvfs->ctx->client.local_address; +} + +const struct tsocket_address *ntvfs_get_remote_address(struct ntvfs_module_context *ntvfs) +{ + return ntvfs->ctx->client.remote_address; +} + +/* oplock helpers */ +NTSTATUS ntvfs_set_oplock_handler(struct ntvfs_context *ntvfs, + NTSTATUS (*handler)(void *private_data, struct ntvfs_handle *handle, uint8_t level), + void *private_data) +{ + ntvfs->oplock.handler = handler; + ntvfs->oplock.private_data = private_data; + return NT_STATUS_OK; +} + +NTSTATUS ntvfs_send_oplock_break(struct ntvfs_module_context *ntvfs, + struct ntvfs_handle *handle, uint8_t level) +{ + if (!ntvfs->ctx->oplock.handler) { + return NT_STATUS_OK; + } + + return ntvfs->ctx->oplock.handler(ntvfs->ctx->oplock.private_data, handle, level); +} + diff --git a/source4/ntvfs/ntvfs_util.c b/source4/ntvfs/ntvfs_util.c new file mode 100644 index 0000000..2888ef4 --- /dev/null +++ b/source4/ntvfs/ntvfs_util.c @@ -0,0 +1,197 @@ +/* + Unix SMB/CIFS implementation. + NTVFS utility code + Copyright (C) Stefan Metzmacher 2004 + + 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/>. +*/ +/* + this implements common utility functions that many NTVFS backends may wish to use +*/ + +#include "includes.h" +#include "../lib/util/dlinklist.h" +#include "ntvfs/ntvfs.h" + + +struct ntvfs_request *ntvfs_request_create(struct ntvfs_context *ctx, TALLOC_CTX *mem_ctx, + struct auth_session_info *session_info, + uint16_t smbpid, + struct timeval request_time, + void *private_data, + void (*send_fn)(struct ntvfs_request *), + uint32_t state) +{ + struct ntvfs_request *req; + struct ntvfs_async_state *async; + + req = talloc(mem_ctx, struct ntvfs_request); + if (!req) return NULL; + req->ctx = ctx; + req->async_states = NULL; + req->session_info = session_info; + req->smbpid = smbpid; + req->client_caps = ctx->client_caps; + req->statistics.request_time = request_time; + + async = talloc(req, struct ntvfs_async_state); + if (!async) goto failed; + + async->state = state; + async->private_data = private_data; + async->send_fn = send_fn; + async->status = NT_STATUS_INTERNAL_ERROR; + async->ntvfs = NULL; + + DLIST_ADD(req->async_states, async); + + return req; +failed: + talloc_free(req); + return NULL; +} + +NTSTATUS ntvfs_async_state_push(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + void *private_data, + void (*send_fn)(struct ntvfs_request *)) +{ + struct ntvfs_async_state *async; + + async = talloc(req, struct ntvfs_async_state); + NT_STATUS_HAVE_NO_MEMORY(async); + + async->state = req->async_states->state; + async->private_data = private_data; + async->send_fn = send_fn; + async->status = NT_STATUS_INTERNAL_ERROR; + + async->ntvfs = ntvfs; + + DLIST_ADD(req->async_states, async); + + return NT_STATUS_OK; +} + +void ntvfs_async_state_pop(struct ntvfs_request *req) +{ + struct ntvfs_async_state *async; + + async = req->async_states; + + DLIST_REMOVE(req->async_states, async); + + req->async_states->state = async->state; + req->async_states->status = async->status; + + talloc_free(async); +} + +NTSTATUS ntvfs_handle_new(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + struct ntvfs_handle **h) +{ + if (!ntvfs->ctx->handles.create_new) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return ntvfs->ctx->handles.create_new(ntvfs->ctx->handles.private_data, req, h); +} + +NTSTATUS ntvfs_handle_set_backend_data(struct ntvfs_handle *h, + struct ntvfs_module_context *ntvfs, + TALLOC_CTX *private_data) +{ + struct ntvfs_handle_data *d; + bool first_time = h->backend_data?false:true; + + for (d=h->backend_data; d; d = d->next) { + if (d->owner != ntvfs) continue; + d->private_data = talloc_steal(d, private_data); + return NT_STATUS_OK; + } + + d = talloc(h, struct ntvfs_handle_data); + NT_STATUS_HAVE_NO_MEMORY(d); + d->owner = ntvfs; + d->private_data = talloc_steal(d, private_data); + + DLIST_ADD(h->backend_data, d); + + if (first_time) { + NTSTATUS status; + status = h->ctx->handles.make_valid(h->ctx->handles.private_data, h); + NT_STATUS_NOT_OK_RETURN(status); + } + + return NT_STATUS_OK; +} + +void *ntvfs_handle_get_backend_data(struct ntvfs_handle *h, + struct ntvfs_module_context *ntvfs) +{ + struct ntvfs_handle_data *d; + + for (d=h->backend_data; d; d = d->next) { + if (d->owner != ntvfs) continue; + return d->private_data; + } + + return NULL; +} + +void ntvfs_handle_remove_backend_data(struct ntvfs_handle *h, + struct ntvfs_module_context *ntvfs) +{ + struct ntvfs_handle_data *d,*n; + + for (d=h->backend_data; d; d = n) { + n = d->next; + if (d->owner != ntvfs) continue; + DLIST_REMOVE(h->backend_data, d); + talloc_free(d); + d = NULL; + } + + if (h->backend_data) return; + + /* if there's no backend_data anymore, destroy the handle */ + h->ctx->handles.destroy(h->ctx->handles.private_data, h); +} + +struct ntvfs_handle *ntvfs_handle_search_by_wire_key(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + const DATA_BLOB *key) +{ + if (!ntvfs->ctx->handles.search_by_wire_key) { + return NULL; + } + return ntvfs->ctx->handles.search_by_wire_key(ntvfs->ctx->handles.private_data, req, key); +} + +NTSTATUS ntvfs_set_handle_callbacks(struct ntvfs_context *ntvfs, + NTSTATUS (*create_new)(void *private_data, struct ntvfs_request *req, struct ntvfs_handle **h), + NTSTATUS (*make_valid)(void *private_data, struct ntvfs_handle *h), + void (*destroy)(void *private_data, struct ntvfs_handle *h), + struct ntvfs_handle *(*search_by_wire_key)(void *private_data, struct ntvfs_request *req, const DATA_BLOB *key), + DATA_BLOB (*get_wire_key)(void *private_data, struct ntvfs_handle *handle, TALLOC_CTX *mem_ctx), + void *private_data) +{ + ntvfs->handles.create_new = create_new; + ntvfs->handles.make_valid = make_valid; + ntvfs->handles.destroy = destroy; + ntvfs->handles.search_by_wire_key = search_by_wire_key; + ntvfs->handles.get_wire_key = get_wire_key; + ntvfs->handles.private_data = private_data; + return NT_STATUS_OK; +} diff --git a/source4/ntvfs/posix/posix_eadb.c b/source4/ntvfs/posix/posix_eadb.c new file mode 100644 index 0000000..2d181fd --- /dev/null +++ b/source4/ntvfs/posix/posix_eadb.c @@ -0,0 +1,299 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - xattr support using a tdb + + Copyright (C) Andrew Tridgell 2004 + + 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 "lib/tdb_wrap/tdb_wrap.h" +#ifdef WITH_NTVFS_FILESERVER +#include "vfs_posix.h" +#endif +#include "posix_eadb.h" + +#define XATTR_LIST_ATTR ".xattr_list" + +/* + we need to maintain a list of attributes on each file, so that unlink + can automatically clean them up +*/ +static NTSTATUS posix_eadb_add_list(struct tdb_wrap *ea_tdb, TALLOC_CTX *ctx, const char *attr_name, + const char *fname, int fd) +{ + DATA_BLOB blob; + TALLOC_CTX *mem_ctx; + NTSTATUS status; + size_t len; + + if (strcmp(attr_name, XATTR_LIST_ATTR) == 0) { + return NT_STATUS_OK; + } + + mem_ctx = talloc_new(ctx); + + status = pull_xattr_blob_tdb_raw(ea_tdb, mem_ctx, XATTR_LIST_ATTR, + fname, fd, 100, &blob); + if (NT_STATUS_IS_OK(status)) { + const char *s; + + for (s = (const char *)blob.data; + s < (const char *)(blob.data + blob.length); + s += strlen(s) + 1) { + if (strcmp(attr_name, s) == 0) { + talloc_free(mem_ctx); + return NT_STATUS_OK; + } + } + } else { + blob = data_blob(NULL, 0); + /* No need to parse an empty blob */ + } + + len = strlen(attr_name) + 1; + + blob.data = talloc_realloc(mem_ctx, blob.data, uint8_t, blob.length + len); + if (blob.data == NULL) { + talloc_free(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + memcpy(blob.data + blob.length, attr_name, len); + blob.length += len; + + status = push_xattr_blob_tdb_raw(ea_tdb, XATTR_LIST_ATTR, fname, fd, &blob); + talloc_free(mem_ctx); + + return status; +} + +/* + form a key for using in the ea_tdb +*/ +static NTSTATUS get_ea_tdb_key(TALLOC_CTX *mem_ctx, + const char *attr_name, + const char *fname, int fd, + TDB_DATA *key) +{ + struct stat st; + size_t len = strlen(attr_name); + + if (fd == -1) { + if (stat(fname, &st) == -1) { + return NT_STATUS_NOT_FOUND; + } + } else { + if (fstat(fd, &st) == -1) { + return NT_STATUS_NOT_FOUND; + } + } + + key->dptr = talloc_array(mem_ctx, uint8_t, 16 + len); + if (key->dptr == NULL) { + return NT_STATUS_NO_MEMORY; + } + key->dsize = 16 + len; + + SBVAL(key->dptr, 0, st.st_dev); + SBVAL(key->dptr, 8, st.st_ino); + memcpy(key->dptr+16, attr_name, len); + + return NT_STATUS_OK; +} + + + +/* + pull a xattr as a blob, using the ea_tdb_context tdb +*/ +NTSTATUS pull_xattr_blob_tdb_raw(struct tdb_wrap *ea_tdb, + TALLOC_CTX *mem_ctx, + const char *attr_name, + const char *fname, + int fd, + size_t estimated_size, + DATA_BLOB *blob) +{ + TDB_DATA tkey, tdata; + NTSTATUS status; + + status = get_ea_tdb_key(mem_ctx, attr_name, fname, fd, &tkey); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + tdata = tdb_fetch(ea_tdb->tdb, tkey); + if (tdata.dptr == NULL) { + return NT_STATUS_NOT_FOUND; + } + + *blob = data_blob_talloc(mem_ctx, tdata.dptr, tdata.dsize); + free(tdata.dptr); + if (blob->data == NULL) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +/* + push a xattr as a blob, using ea_tdb +*/ +NTSTATUS push_xattr_blob_tdb_raw(struct tdb_wrap *ea_tdb, + const char *attr_name, + const char *fname, + int fd, + const DATA_BLOB *blob) +{ + TDB_DATA tkey, tdata; + NTSTATUS status; + TALLOC_CTX *mem_ctx = talloc_new(ea_tdb); + if (!mem_ctx) { + return NT_STATUS_NO_MEMORY; + } + + status = get_ea_tdb_key(mem_ctx, attr_name, fname, fd, &tkey); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(mem_ctx); + return status; + } + + tdata.dptr = blob->data; + tdata.dsize = blob->length; + + if (tdb_chainlock(ea_tdb->tdb, tkey) != 0) { + talloc_free(mem_ctx); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + status = posix_eadb_add_list(ea_tdb,mem_ctx, attr_name, fname, fd); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(mem_ctx); + goto done; + } + + if (tdb_store(ea_tdb->tdb, tkey, tdata, TDB_REPLACE) != 0) { + status = NT_STATUS_INTERNAL_DB_CORRUPTION; + } + +done: + tdb_chainunlock(ea_tdb->tdb, tkey); + talloc_free(mem_ctx); + return status; +} + + +/* + delete a xattr +*/ +NTSTATUS delete_posix_eadb_raw(struct tdb_wrap *ea_tdb, const char *attr_name, + const char *fname, int fd) +{ + TDB_DATA tkey; + NTSTATUS status; + + status = get_ea_tdb_key(NULL, attr_name, fname, fd, &tkey); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (tdb_delete(ea_tdb->tdb, tkey) != 0) { + talloc_free(tkey.dptr); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + talloc_free(tkey.dptr); + return NT_STATUS_OK; +} + + +/* + delete all xattrs for a file +*/ +NTSTATUS unlink_posix_eadb_raw(struct tdb_wrap *ea_tdb, const char *fname, int fd) +{ + TALLOC_CTX *mem_ctx = talloc_new(ea_tdb); + DATA_BLOB blob; + const char *s; + NTSTATUS status; + + status = pull_xattr_blob_tdb_raw(ea_tdb, mem_ctx, XATTR_LIST_ATTR, + fname, fd, 100, &blob); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(mem_ctx); + return NT_STATUS_OK; + } + + for (s=(const char *)blob.data; s < (const char *)(blob.data+blob.length); s += strlen(s) + 1) { + delete_posix_eadb_raw(ea_tdb, s, fname, -1); + } + + status = delete_posix_eadb_raw(ea_tdb, XATTR_LIST_ATTR, fname, fd); + talloc_free(mem_ctx); + return status; +} + +/* + list all xattrs for a file +*/ +NTSTATUS list_posix_eadb_raw(struct tdb_wrap *ea_tdb, TALLOC_CTX *mem_ctx, + const char *fname, int fd, + DATA_BLOB *list) +{ + return pull_xattr_blob_tdb_raw(ea_tdb, mem_ctx, XATTR_LIST_ATTR, + fname, fd, 100, list); +} + +#ifdef WITH_NTVFS_FILESERVER +NTSTATUS pull_xattr_blob_tdb(struct pvfs_state *pvfs_state, + TALLOC_CTX *mem_ctx, + const char *attr_name, + const char *fname, + int fd, + size_t estimated_size, + DATA_BLOB *blob) +{ + return pull_xattr_blob_tdb_raw(pvfs_state->ea_db,mem_ctx,attr_name,fname,fd,estimated_size,blob); +} + +NTSTATUS push_xattr_blob_tdb(struct pvfs_state *pvfs_state, + const char *attr_name, + const char *fname, + int fd, + const DATA_BLOB *blob) +{ + return push_xattr_blob_tdb_raw(pvfs_state->ea_db, attr_name, fname, fd, blob); +} + +/* + delete a xattr +*/ +NTSTATUS delete_posix_eadb(struct pvfs_state *pvfs_state, const char *attr_name, + const char *fname, int fd) +{ + return delete_posix_eadb_raw(pvfs_state->ea_db, + attr_name, fname, fd); +} + +/* + delete all xattrs for a file +*/ +NTSTATUS unlink_posix_eadb(struct pvfs_state *pvfs_state, const char *fname) +{ + return unlink_posix_eadb_raw(pvfs_state->ea_db, fname, -1); +} + +#endif diff --git a/source4/ntvfs/posix/posix_eadb.h b/source4/ntvfs/posix/posix_eadb.h new file mode 100644 index 0000000..14b9439 --- /dev/null +++ b/source4/ntvfs/posix/posix_eadb.h @@ -0,0 +1,20 @@ +/* + Unix SMB/CIFS implementation. Xattr manipulation bindings. + Copyright (C) Andrew Bartlett 2011 + + 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/>. +*/ + +struct pvfs_state; +#include "source4/ntvfs/posix/posix_eadb_proto.h" diff --git a/source4/ntvfs/posix/pvfs_acl.c b/source4/ntvfs/posix/pvfs_acl.c new file mode 100644 index 0000000..e643293 --- /dev/null +++ b/source4/ntvfs/posix/pvfs_acl.c @@ -0,0 +1,1083 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - ACL support + + Copyright (C) Andrew Tridgell 2004 + + 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/passwd.h" +#include "auth/auth.h" +#include "vfs_posix.h" +#include "librpc/gen_ndr/xattr.h" +#include "libcli/security/security.h" +#include "param/param.h" +#include "../lib/util/unix_privs.h" +#include "lib/util/samba_modules.h" + +/* the list of currently registered ACL backends */ +static struct pvfs_acl_backend { + const struct pvfs_acl_ops *ops; +} *backends = NULL; +static int num_backends; + +/* + register a pvfs acl backend. + + The 'name' can be later used by other backends to find the operations + structure for this backend. +*/ +NTSTATUS pvfs_acl_register(TALLOC_CTX *ctx, const struct pvfs_acl_ops *ops) +{ + struct pvfs_acl_ops *new_ops; + + if (pvfs_acl_backend_byname(ops->name) != NULL) { + DEBUG(0,("pvfs acl backend '%s' already registered\n", ops->name)); + return NT_STATUS_OBJECT_NAME_COLLISION; + } + + backends = talloc_realloc(ctx, backends, + struct pvfs_acl_backend, num_backends+1); + NT_STATUS_HAVE_NO_MEMORY(backends); + + new_ops = (struct pvfs_acl_ops *)talloc_memdup(backends, ops, sizeof(*ops)); + new_ops->name = talloc_strdup(new_ops, ops->name); + + backends[num_backends].ops = new_ops; + + num_backends++; + + DEBUG(3,("NTVFS backend '%s' registered\n", ops->name)); + + return NT_STATUS_OK; +} + + +/* + return the operations structure for a named backend +*/ +const struct pvfs_acl_ops *pvfs_acl_backend_byname(const char *name) +{ + int i; + + for (i=0;i<num_backends;i++) { + if (strcmp(backends[i].ops->name, name) == 0) { + return backends[i].ops; + } + } + + return NULL; +} + +NTSTATUS pvfs_acl_init(void) +{ + static bool initialized = false; +#define _MODULE_PROTO(init) extern NTSTATUS init(TALLOC_CTX *); + STATIC_pvfs_acl_MODULES_PROTO; + init_module_fn static_init[] = { STATIC_pvfs_acl_MODULES }; + init_module_fn *shared_init; + + if (initialized) return NT_STATUS_OK; + initialized = true; + + shared_init = load_samba_modules(NULL, "pvfs_acl"); + + run_init_functions(NULL, static_init); + run_init_functions(NULL, shared_init); + + talloc_free(shared_init); + + return NT_STATUS_OK; +} + + +/* + map a single access_mask from generic to specific bits for files/dirs +*/ +static uint32_t pvfs_translate_mask(uint32_t access_mask) +{ + if (access_mask & SEC_MASK_GENERIC) { + if (access_mask & SEC_GENERIC_READ) access_mask |= SEC_RIGHTS_FILE_READ; + if (access_mask & SEC_GENERIC_WRITE) access_mask |= SEC_RIGHTS_FILE_WRITE; + if (access_mask & SEC_GENERIC_EXECUTE) access_mask |= SEC_RIGHTS_FILE_EXECUTE; + if (access_mask & SEC_GENERIC_ALL) access_mask |= SEC_RIGHTS_FILE_ALL; + access_mask &= ~SEC_MASK_GENERIC; + } + return access_mask; +} + + +/* + map any generic access bits in the given acl + this relies on the fact that the mappings for files and directories + are the same +*/ +static void pvfs_translate_generic_bits(struct security_acl *acl) +{ + unsigned i; + + if (!acl) return; + + for (i=0;i<acl->num_aces;i++) { + struct security_ace *ace = &acl->aces[i]; + ace->access_mask = pvfs_translate_mask(ace->access_mask); + } +} + + +/* + setup a default ACL for a file +*/ +static NTSTATUS pvfs_default_acl(struct pvfs_state *pvfs, + struct ntvfs_request *req, + struct pvfs_filename *name, int fd, + struct security_descriptor **psd) +{ + struct security_descriptor *sd; + NTSTATUS status; + struct security_ace ace = {}; + mode_t mode; + struct id_map *ids; + + *psd = security_descriptor_initialise(req); + if (*psd == NULL) { + return NT_STATUS_NO_MEMORY; + } + sd = *psd; + + ids = talloc_zero_array(sd, struct id_map, 2); + NT_STATUS_HAVE_NO_MEMORY(ids); + + ids[0].xid.id = name->st.st_uid; + ids[0].xid.type = ID_TYPE_UID; + ids[0].sid = NULL; + + ids[1].xid.id = name->st.st_gid; + ids[1].xid.type = ID_TYPE_GID; + ids[1].sid = NULL; + + status = wbc_xids_to_sids(ids, 2); + NT_STATUS_NOT_OK_RETURN(status); + + sd->owner_sid = talloc_steal(sd, ids[0].sid); + sd->group_sid = talloc_steal(sd, ids[1].sid); + + talloc_free(ids); + sd->type |= SEC_DESC_DACL_PRESENT; + + mode = name->st.st_mode; + + /* + we provide up to 4 ACEs + - Owner + - Group + - Everyone + - Administrator + */ + + + /* setup owner ACE */ + ace.type = SEC_ACE_TYPE_ACCESS_ALLOWED; + ace.flags = 0; + ace.trustee = *sd->owner_sid; + ace.access_mask = 0; + + if (mode & S_IRUSR) { + if (mode & S_IWUSR) { + ace.access_mask |= SEC_RIGHTS_FILE_ALL; + } else { + ace.access_mask |= SEC_RIGHTS_FILE_READ | SEC_FILE_EXECUTE; + } + } + if (mode & S_IWUSR) { + ace.access_mask |= SEC_RIGHTS_FILE_WRITE | SEC_STD_DELETE; + } + if (ace.access_mask) { + security_descriptor_dacl_add(sd, &ace); + } + + + /* setup group ACE */ + ace.trustee = *sd->group_sid; + ace.access_mask = 0; + if (mode & S_IRGRP) { + ace.access_mask |= SEC_RIGHTS_FILE_READ | SEC_FILE_EXECUTE; + } + if (mode & S_IWGRP) { + /* note that delete is not granted - this matches posix behaviour */ + ace.access_mask |= SEC_RIGHTS_FILE_WRITE; + } + if (ace.access_mask) { + security_descriptor_dacl_add(sd, &ace); + } + + /* setup other ACE */ + ace.trustee = global_sid_World; + ace.access_mask = 0; + if (mode & S_IROTH) { + ace.access_mask |= SEC_RIGHTS_FILE_READ | SEC_FILE_EXECUTE; + } + if (mode & S_IWOTH) { + ace.access_mask |= SEC_RIGHTS_FILE_WRITE; + } + if (ace.access_mask) { + security_descriptor_dacl_add(sd, &ace); + } + + /* setup system ACE */ + ace.trustee = global_sid_System; + ace.access_mask = SEC_RIGHTS_FILE_ALL; + security_descriptor_dacl_add(sd, &ace); + + return NT_STATUS_OK; +} + + +/* + omit any security_descriptor elements not specified in the given + secinfo flags +*/ +static void normalise_sd_flags(struct security_descriptor *sd, uint32_t secinfo_flags) +{ + if (!(secinfo_flags & SECINFO_OWNER)) { + sd->owner_sid = NULL; + } + if (!(secinfo_flags & SECINFO_GROUP)) { + sd->group_sid = NULL; + } + if (!(secinfo_flags & SECINFO_DACL)) { + sd->dacl = NULL; + } + if (!(secinfo_flags & SECINFO_SACL)) { + sd->sacl = NULL; + } +} + +static bool pvfs_privileged_access(uid_t uid) +{ + uid_t euid; + + if (uid_wrapper_enabled()) { + setenv("UID_WRAPPER_MYUID", "1", 1); + } + + euid = geteuid(); + + if (uid_wrapper_enabled()) { + unsetenv("UID_WRAPPER_MYUID"); + } + + return (uid == euid); +} + +/* + answer a setfileinfo for an ACL +*/ +NTSTATUS pvfs_acl_set(struct pvfs_state *pvfs, + struct ntvfs_request *req, + struct pvfs_filename *name, int fd, + uint32_t access_mask, + union smb_setfileinfo *info) +{ + uint32_t secinfo_flags = info->set_secdesc.in.secinfo_flags; + struct security_descriptor *new_sd, *sd, orig_sd; + NTSTATUS status = NT_STATUS_NOT_FOUND; + uid_t old_uid = -1; + gid_t old_gid = -1; + uid_t new_uid = -1; + gid_t new_gid = -1; + struct id_map *ids; + + if (pvfs->acl_ops != NULL) { + status = pvfs->acl_ops->acl_load(pvfs, name, fd, req, &sd); + } + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + status = pvfs_default_acl(pvfs, req, name, fd, &sd); + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + ids = talloc(req, struct id_map); + NT_STATUS_HAVE_NO_MEMORY(ids); + ZERO_STRUCT(ids->xid); + ids->sid = NULL; + ids->status = ID_UNKNOWN; + + new_sd = info->set_secdesc.in.sd; + orig_sd = *sd; + + old_uid = name->st.st_uid; + old_gid = name->st.st_gid; + + /* only set the elements that have been specified */ + if (secinfo_flags & SECINFO_OWNER) { + if (!(access_mask & SEC_STD_WRITE_OWNER)) { + return NT_STATUS_ACCESS_DENIED; + } + if (!dom_sid_equal(sd->owner_sid, new_sd->owner_sid)) { + ids->sid = new_sd->owner_sid; + status = wbc_sids_to_xids(ids, 1); + NT_STATUS_NOT_OK_RETURN(status); + + if (ids->xid.type == ID_TYPE_BOTH || + ids->xid.type == ID_TYPE_UID) { + new_uid = ids->xid.id; + } + } + sd->owner_sid = new_sd->owner_sid; + } + + if (secinfo_flags & SECINFO_GROUP) { + if (!(access_mask & SEC_STD_WRITE_OWNER)) { + return NT_STATUS_ACCESS_DENIED; + } + if (!dom_sid_equal(sd->group_sid, new_sd->group_sid)) { + ids->sid = new_sd->group_sid; + status = wbc_sids_to_xids(ids, 1); + NT_STATUS_NOT_OK_RETURN(status); + + if (ids->xid.type == ID_TYPE_BOTH || + ids->xid.type == ID_TYPE_GID) { + new_gid = ids->xid.id; + } + + } + sd->group_sid = new_sd->group_sid; + } + + if (secinfo_flags & SECINFO_DACL) { + if (!(access_mask & SEC_STD_WRITE_DAC)) { + return NT_STATUS_ACCESS_DENIED; + } + sd->dacl = new_sd->dacl; + pvfs_translate_generic_bits(sd->dacl); + sd->type |= SEC_DESC_DACL_PRESENT; + } + + if (secinfo_flags & SECINFO_SACL) { + if (!(access_mask & SEC_FLAG_SYSTEM_SECURITY)) { + return NT_STATUS_ACCESS_DENIED; + } + sd->sacl = new_sd->sacl; + pvfs_translate_generic_bits(sd->sacl); + sd->type |= SEC_DESC_SACL_PRESENT; + } + + if (secinfo_flags & SECINFO_PROTECTED_DACL) { + if (new_sd->type & SEC_DESC_DACL_PROTECTED) { + sd->type |= SEC_DESC_DACL_PROTECTED; + } else { + sd->type &= ~SEC_DESC_DACL_PROTECTED; + } + } + + if (secinfo_flags & SECINFO_PROTECTED_SACL) { + if (new_sd->type & SEC_DESC_SACL_PROTECTED) { + sd->type |= SEC_DESC_SACL_PROTECTED; + } else { + sd->type &= ~SEC_DESC_SACL_PROTECTED; + } + } + + if (new_uid == old_uid) { + new_uid = -1; + } + + if (new_gid == old_gid) { + new_gid = -1; + } + + /* if there's something to change try it */ + if (new_uid != -1 || new_gid != -1) { + int ret; + if (fd == -1) { + ret = chown(name->full_name, new_uid, new_gid); + } else { + ret = fchown(fd, new_uid, new_gid); + } + if (errno == EPERM) { + if (pvfs_privileged_access(name->st.st_uid)) { + ret = 0; + } else { + /* try again as root if we have SEC_PRIV_RESTORE or + SEC_PRIV_TAKE_OWNERSHIP */ + if (security_token_has_privilege(req->session_info->security_token, + SEC_PRIV_RESTORE) || + security_token_has_privilege(req->session_info->security_token, + SEC_PRIV_TAKE_OWNERSHIP)) { + void *privs; + privs = root_privileges(); + if (fd == -1) { + ret = chown(name->full_name, new_uid, new_gid); + } else { + ret = fchown(fd, new_uid, new_gid); + } + talloc_free(privs); + } + } + } + if (ret == -1) { + return pvfs_map_errno(pvfs, errno); + } + } + + /* we avoid saving if the sd is the same. This means when clients + copy files and end up copying the default sd that we don't + needlessly use xattrs */ + if (!security_descriptor_equal(sd, &orig_sd) && pvfs->acl_ops) { + status = pvfs->acl_ops->acl_save(pvfs, name, fd, sd); + } + + return status; +} + + +/* + answer a fileinfo query for the ACL +*/ +NTSTATUS pvfs_acl_query(struct pvfs_state *pvfs, + struct ntvfs_request *req, + struct pvfs_filename *name, int fd, + union smb_fileinfo *info) +{ + NTSTATUS status = NT_STATUS_NOT_FOUND; + struct security_descriptor *sd; + + if (pvfs->acl_ops) { + status = pvfs->acl_ops->acl_load(pvfs, name, fd, req, &sd); + } + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + status = pvfs_default_acl(pvfs, req, name, fd, &sd); + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + normalise_sd_flags(sd, info->query_secdesc.in.secinfo_flags); + + info->query_secdesc.out.sd = sd; + + return NT_STATUS_OK; +} + + +/* + check the read only bit against any of the write access bits +*/ +static bool pvfs_read_only(struct pvfs_state *pvfs, uint32_t access_mask) +{ + if ((pvfs->flags & PVFS_FLAG_READONLY) && + (access_mask & (SEC_FILE_WRITE_DATA | + SEC_FILE_APPEND_DATA | + SEC_FILE_WRITE_EA | + SEC_FILE_WRITE_ATTRIBUTE | + SEC_STD_DELETE | + SEC_STD_WRITE_DAC | + SEC_STD_WRITE_OWNER | + SEC_DIR_DELETE_CHILD))) { + return true; + } + return false; +} + +/* + see if we are a member of the appropriate unix group + */ +static bool pvfs_group_member(struct pvfs_state *pvfs, gid_t gid) +{ + int i, ngroups; + gid_t *groups; + if (getegid() == gid) { + return true; + } + ngroups = getgroups(0, NULL); + if (ngroups <= 0) { + return false; + } + groups = talloc_array(pvfs, gid_t, ngroups); + if (groups == NULL) { + return false; + } + if (getgroups(ngroups, groups) != ngroups) { + talloc_free(groups); + return false; + } + for (i=0; i<ngroups; i++) { + if (groups[i] == gid) break; + } + talloc_free(groups); + return i < ngroups; +} + +/* + default access check function based on unix permissions + doing this saves on building a full security descriptor + for the common case of access check on files with no + specific NT ACL + + If name is NULL then treat as a new file creation +*/ +static NTSTATUS pvfs_access_check_unix(struct pvfs_state *pvfs, + struct ntvfs_request *req, + struct pvfs_filename *name, + uint32_t *access_mask) +{ + uint32_t max_bits = 0; + struct security_token *token = req->session_info->security_token; + + if (pvfs_read_only(pvfs, *access_mask)) { + return NT_STATUS_ACCESS_DENIED; + } + + if (name == NULL) { + max_bits |= SEC_RIGHTS_FILE_ALL | SEC_STD_ALL; + } else if (pvfs_privileged_access(name->st.st_uid)) { + /* use the IxUSR bits */ + if ((name->st.st_mode & S_IWUSR)) { + max_bits |= SEC_RIGHTS_FILE_ALL | SEC_STD_ALL; + } else if ((name->st.st_mode & (S_IRUSR | S_IXUSR))) { + max_bits |= SEC_RIGHTS_FILE_READ | SEC_RIGHTS_FILE_EXECUTE | SEC_STD_ALL; + } + } else if (pvfs_group_member(pvfs, name->st.st_gid)) { + /* use the IxGRP bits */ + if ((name->st.st_mode & S_IWGRP)) { + max_bits |= SEC_RIGHTS_FILE_ALL | SEC_STD_ALL; + } else if ((name->st.st_mode & (S_IRGRP | S_IXGRP))) { + max_bits |= SEC_RIGHTS_FILE_READ | SEC_RIGHTS_FILE_EXECUTE | SEC_STD_ALL; + } + } else { + /* use the IxOTH bits */ + if ((name->st.st_mode & S_IWOTH)) { + max_bits |= SEC_RIGHTS_FILE_ALL | SEC_STD_ALL; + } else if ((name->st.st_mode & (S_IROTH | S_IXOTH))) { + max_bits |= SEC_RIGHTS_FILE_READ | SEC_RIGHTS_FILE_EXECUTE | SEC_STD_ALL; + } + } + + if (*access_mask & SEC_FLAG_MAXIMUM_ALLOWED) { + *access_mask |= max_bits; + *access_mask &= ~SEC_FLAG_MAXIMUM_ALLOWED; + } + + if ((*access_mask & SEC_FLAG_SYSTEM_SECURITY) && + security_token_has_privilege(token, SEC_PRIV_SECURITY)) { + max_bits |= SEC_FLAG_SYSTEM_SECURITY; + } + + if (((*access_mask & ~max_bits) & SEC_RIGHTS_PRIV_RESTORE) && + security_token_has_privilege(token, SEC_PRIV_RESTORE)) { + max_bits |= ~(SEC_RIGHTS_PRIV_RESTORE); + } + if (((*access_mask & ~max_bits) & SEC_RIGHTS_PRIV_BACKUP) && + security_token_has_privilege(token, SEC_PRIV_BACKUP)) { + max_bits |= ~(SEC_RIGHTS_PRIV_BACKUP); + } + + if (*access_mask & ~max_bits) { + DEBUG(5,(__location__ " denied access to '%s' - wanted 0x%08x but got 0x%08x (missing 0x%08x)\n", + name?name->full_name:"(new file)", *access_mask, max_bits, *access_mask & ~max_bits)); + return NT_STATUS_ACCESS_DENIED; + } + + if (pvfs->ntvfs->ctx->protocol < PROTOCOL_SMB2_02) { + /* on SMB, this bit is always granted, even if not + asked for */ + *access_mask |= SEC_FILE_READ_ATTRIBUTE; + } + + return NT_STATUS_OK; +} + + +/* + check the security descriptor on a file, if any + + *access_mask is modified with the access actually granted +*/ +NTSTATUS pvfs_access_check(struct pvfs_state *pvfs, + struct ntvfs_request *req, + struct pvfs_filename *name, + uint32_t *access_mask) +{ + struct security_token *token = req->session_info->security_token; + struct xattr_NTACL *acl; + NTSTATUS status; + struct security_descriptor *sd; + bool allow_delete = false; + + /* on SMB2 a blank access mask is always denied */ + if (pvfs->ntvfs->ctx->protocol >= PROTOCOL_SMB2_02 && + *access_mask == 0) { + return NT_STATUS_ACCESS_DENIED; + } + + if (pvfs_read_only(pvfs, *access_mask)) { + return NT_STATUS_ACCESS_DENIED; + } + + if (*access_mask & SEC_FLAG_MAXIMUM_ALLOWED || + *access_mask & SEC_STD_DELETE) { + status = pvfs_access_check_parent(pvfs, req, + name, SEC_DIR_DELETE_CHILD); + if (NT_STATUS_IS_OK(status)) { + allow_delete = true; + *access_mask &= ~SEC_STD_DELETE; + } + } + + acl = talloc(req, struct xattr_NTACL); + if (acl == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* expand the generic access bits to file specific bits */ + *access_mask = pvfs_translate_mask(*access_mask); + if (pvfs->ntvfs->ctx->protocol < PROTOCOL_SMB2_02) { + *access_mask &= ~SEC_FILE_READ_ATTRIBUTE; + } + + status = pvfs_acl_load(pvfs, name, -1, acl); + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + talloc_free(acl); + status = pvfs_access_check_unix(pvfs, req, name, access_mask); + goto done; + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + switch (acl->version) { + case 1: + sd = acl->info.sd; + break; + default: + return NT_STATUS_INVALID_ACL; + } + + /* check the acl against the required access mask */ + status = se_file_access_check(sd, token, false, *access_mask, access_mask); + talloc_free(acl); + + /* if we used a NT acl, then allow access override if the + share allows for posix permission override + */ + if (NT_STATUS_IS_OK(status)) { + name->allow_override = (pvfs->flags & PVFS_FLAG_PERM_OVERRIDE) != 0; + } + +done: + if (pvfs->ntvfs->ctx->protocol < PROTOCOL_SMB2_02) { + /* on SMB, this bit is always granted, even if not + asked for */ + *access_mask |= SEC_FILE_READ_ATTRIBUTE; + } + + if (allow_delete) { + *access_mask |= SEC_STD_DELETE; + } + + return status; +} + + +/* + a simplified interface to access check, designed for calls that + do not take or return an access check mask +*/ +NTSTATUS pvfs_access_check_simple(struct pvfs_state *pvfs, + struct ntvfs_request *req, + struct pvfs_filename *name, + uint32_t access_needed) +{ + if (access_needed == 0) { + return NT_STATUS_OK; + } + return pvfs_access_check(pvfs, req, name, &access_needed); +} + +/* + access check for creating a new file/directory +*/ +NTSTATUS pvfs_access_check_create(struct pvfs_state *pvfs, + struct ntvfs_request *req, + struct pvfs_filename *name, + uint32_t *access_mask, + bool container, + struct security_descriptor **sd) +{ + struct pvfs_filename *parent; + NTSTATUS status; + uint32_t parent_mask; + bool allow_delete = false; + + if (pvfs_read_only(pvfs, *access_mask)) { + return NT_STATUS_ACCESS_DENIED; + } + + status = pvfs_resolve_parent(pvfs, req, name, &parent); + NT_STATUS_NOT_OK_RETURN(status); + + if (container) { + parent_mask = SEC_DIR_ADD_SUBDIR; + } else { + parent_mask = SEC_DIR_ADD_FILE; + } + if (*access_mask & SEC_FLAG_MAXIMUM_ALLOWED || + *access_mask & SEC_STD_DELETE) { + parent_mask |= SEC_DIR_DELETE_CHILD; + } + + status = pvfs_access_check(pvfs, req, parent, &parent_mask); + if (NT_STATUS_IS_OK(status)) { + if (parent_mask & SEC_DIR_DELETE_CHILD) { + allow_delete = true; + } + } else if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + /* + * on ACCESS_DENIED we get the rejected bits + * remove the non critical SEC_DIR_DELETE_CHILD + * and check if something else was rejected. + */ + parent_mask &= ~SEC_DIR_DELETE_CHILD; + if (parent_mask != 0) { + return NT_STATUS_ACCESS_DENIED; + } + status = NT_STATUS_OK; + } else { + return status; + } + + if (*sd == NULL) { + status = pvfs_acl_inherited_sd(pvfs, req, req, parent, container, sd); + } + + talloc_free(parent); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* expand the generic access bits to file specific bits */ + *access_mask = pvfs_translate_mask(*access_mask); + + if (*access_mask & SEC_FLAG_MAXIMUM_ALLOWED) { + *access_mask |= SEC_RIGHTS_FILE_ALL; + *access_mask &= ~SEC_FLAG_MAXIMUM_ALLOWED; + } + + if (pvfs->ntvfs->ctx->protocol < PROTOCOL_SMB2_02) { + /* on SMB, this bit is always granted, even if not + asked for */ + *access_mask |= SEC_FILE_READ_ATTRIBUTE; + } + + if (allow_delete) { + *access_mask |= SEC_STD_DELETE; + } + + return NT_STATUS_OK; +} + +/* + access check for creating a new file/directory - no access mask supplied +*/ +NTSTATUS pvfs_access_check_parent(struct pvfs_state *pvfs, + struct ntvfs_request *req, + struct pvfs_filename *name, + uint32_t access_mask) +{ + struct pvfs_filename *parent; + NTSTATUS status; + + status = pvfs_resolve_parent(pvfs, req, name, &parent); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = pvfs_access_check_simple(pvfs, req, parent, access_mask); + if (NT_STATUS_IS_OK(status) && parent->allow_override) { + name->allow_override = true; + } + return status; +} + + +/* + determine if an ACE is inheritable +*/ +static bool pvfs_inheritable_ace(struct pvfs_state *pvfs, + const struct security_ace *ace, + bool container) +{ + if (!container) { + return (ace->flags & SEC_ACE_FLAG_OBJECT_INHERIT) != 0; + } + + if (ace->flags & SEC_ACE_FLAG_CONTAINER_INHERIT) { + return true; + } + + if ((ace->flags & SEC_ACE_FLAG_OBJECT_INHERIT) && + !(ace->flags & SEC_ACE_FLAG_NO_PROPAGATE_INHERIT)) { + return true; + } + + return false; +} + +/* + this is the core of ACL inheritance. It copies any inheritable + aces from the parent SD to the child SD. Note that the algorithm + depends on whether the child is a container or not +*/ +static NTSTATUS pvfs_acl_inherit_aces(struct pvfs_state *pvfs, + struct security_descriptor *parent_sd, + struct security_descriptor *sd, + bool container) +{ + int i; + + for (i=0;i<parent_sd->dacl->num_aces;i++) { + struct security_ace ace = parent_sd->dacl->aces[i]; + NTSTATUS status; + const struct dom_sid *creator = NULL, *new_id = NULL; + uint32_t orig_flags; + + if (!pvfs_inheritable_ace(pvfs, &ace, container)) { + continue; + } + + orig_flags = ace.flags; + + /* see the RAW-ACLS inheritance test for details on these rules */ + if (!container) { + ace.flags = 0; + } else { + ace.flags &= ~SEC_ACE_FLAG_INHERIT_ONLY; + + if (!(ace.flags & SEC_ACE_FLAG_CONTAINER_INHERIT)) { + ace.flags |= SEC_ACE_FLAG_INHERIT_ONLY; + } + if (ace.flags & SEC_ACE_FLAG_NO_PROPAGATE_INHERIT) { + ace.flags = 0; + } + } + + /* the CREATOR sids are special when inherited */ + if (dom_sid_equal(&ace.trustee, pvfs->sid_cache.creator_owner)) { + creator = pvfs->sid_cache.creator_owner; + new_id = sd->owner_sid; + } else if (dom_sid_equal(&ace.trustee, pvfs->sid_cache.creator_group)) { + creator = pvfs->sid_cache.creator_group; + new_id = sd->group_sid; + } else { + new_id = &ace.trustee; + } + + if (creator && container && + (ace.flags & SEC_ACE_FLAG_CONTAINER_INHERIT)) { + uint32_t flags = ace.flags; + + ace.trustee = *new_id; + ace.flags = 0; + status = security_descriptor_dacl_add(sd, &ace); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + ace.trustee = *creator; + ace.flags = flags | SEC_ACE_FLAG_INHERIT_ONLY; + status = security_descriptor_dacl_add(sd, &ace); + } else if (container && + !(orig_flags & SEC_ACE_FLAG_NO_PROPAGATE_INHERIT)) { + status = security_descriptor_dacl_add(sd, &ace); + } else { + ace.trustee = *new_id; + status = security_descriptor_dacl_add(sd, &ace); + } + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + return NT_STATUS_OK; +} + + + +/* + calculate the ACL on a new file/directory based on the inherited ACL + from the parent. If there is no inherited ACL then return a NULL + ACL, which means the default ACL should be used +*/ +NTSTATUS pvfs_acl_inherited_sd(struct pvfs_state *pvfs, + TALLOC_CTX *mem_ctx, + struct ntvfs_request *req, + struct pvfs_filename *parent, + bool container, + struct security_descriptor **ret_sd) +{ + struct xattr_NTACL *acl; + NTSTATUS status; + struct security_descriptor *parent_sd, *sd; + struct id_map *ids; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + + *ret_sd = NULL; + + acl = talloc(req, struct xattr_NTACL); + if (acl == NULL) { + TALLOC_FREE(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + status = pvfs_acl_load(pvfs, parent, -1, acl); + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + talloc_free(tmp_ctx); + return NT_STATUS_OK; + } + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(tmp_ctx); + return status; + } + + switch (acl->version) { + case 1: + parent_sd = acl->info.sd; + break; + default: + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_ACL; + } + + if (parent_sd == NULL || + parent_sd->dacl == NULL || + parent_sd->dacl->num_aces == 0) { + /* go with the default ACL */ + talloc_free(tmp_ctx); + return NT_STATUS_OK; + } + + /* create the new sd */ + sd = security_descriptor_initialise(req); + if (sd == NULL) { + TALLOC_FREE(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + ids = talloc_array(sd, struct id_map, 2); + if (ids == NULL) { + TALLOC_FREE(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + ids[0].xid.id = geteuid(); + ids[0].xid.type = ID_TYPE_UID; + ids[0].sid = NULL; + ids[0].status = ID_UNKNOWN; + + ids[1].xid.id = getegid(); + ids[1].xid.type = ID_TYPE_GID; + ids[1].sid = NULL; + ids[1].status = ID_UNKNOWN; + + status = wbc_xids_to_sids(ids, 2); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(tmp_ctx); + return status; + } + + sd->owner_sid = talloc_steal(sd, ids[0].sid); + sd->group_sid = talloc_steal(sd, ids[1].sid); + + sd->type |= SEC_DESC_DACL_PRESENT; + + /* fill in the aces from the parent */ + status = pvfs_acl_inherit_aces(pvfs, parent_sd, sd, container); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(tmp_ctx); + return status; + } + + /* if there is nothing to inherit then we fallback to the + default acl */ + if (sd->dacl == NULL || sd->dacl->num_aces == 0) { + talloc_free(tmp_ctx); + return NT_STATUS_OK; + } + + *ret_sd = talloc_steal(mem_ctx, sd); + + talloc_free(tmp_ctx); + return NT_STATUS_OK; +} + + +/* + setup an ACL on a new file/directory based on the inherited ACL from + the parent. If there is no inherited ACL then we don't set anything, + as the default ACL applies anyway +*/ +NTSTATUS pvfs_acl_inherit(struct pvfs_state *pvfs, + struct ntvfs_request *req, + struct pvfs_filename *name, + int fd) +{ + struct xattr_NTACL acl; + NTSTATUS status; + struct security_descriptor *sd; + struct pvfs_filename *parent; + bool container; + + /* form the parents path */ + status = pvfs_resolve_parent(pvfs, req, name, &parent); + NT_STATUS_NOT_OK_RETURN(status); + + container = (name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) ? true:false; + + status = pvfs_acl_inherited_sd(pvfs, req, req, parent, container, &sd); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(parent); + return status; + } + + if (sd == NULL) { + return NT_STATUS_OK; + } + + acl.version = 1; + acl.info.sd = sd; + + status = pvfs_acl_save(pvfs, name, fd, &acl); + talloc_free(sd); + talloc_free(parent); + + return status; +} + +/* + return the maximum allowed access mask +*/ +NTSTATUS pvfs_access_maximal_allowed(struct pvfs_state *pvfs, + struct ntvfs_request *req, + struct pvfs_filename *name, + uint32_t *maximal_access) +{ + *maximal_access = SEC_FLAG_MAXIMUM_ALLOWED; + return pvfs_access_check(pvfs, req, name, maximal_access); +} diff --git a/source4/ntvfs/posix/pvfs_acl_nfs4.c b/source4/ntvfs/posix/pvfs_acl_nfs4.c new file mode 100644 index 0000000..3ddc1b6 --- /dev/null +++ b/source4/ntvfs/posix/pvfs_acl_nfs4.c @@ -0,0 +1,199 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - NT ACLs mapped to NFS4 ACLs, as per + http://www.suse.de/~agruen/nfs4acl/ + + Copyright (C) Andrew Tridgell 2006 + + 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 "vfs_posix.h" +#include "../lib/util/unix_privs.h" +#include "librpc/gen_ndr/ndr_nfs4acl.h" +#include "libcli/security/security.h" + +NTSTATUS pvfs_acl_nfs4_init(TALLOC_CTX *); + +#define ACE4_IDENTIFIER_GROUP 0x40 + +/* + load the current ACL from system.nfs4acl +*/ +static NTSTATUS pvfs_acl_load_nfs4(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd, + TALLOC_CTX *mem_ctx, + struct security_descriptor **psd) +{ + NTSTATUS status; + struct nfs4acl *acl; + struct security_descriptor *sd; + int i, num_ids; + struct id_map *ids; + + acl = talloc_zero(mem_ctx, struct nfs4acl); + NT_STATUS_HAVE_NO_MEMORY(acl); + + status = pvfs_xattr_ndr_load(pvfs, mem_ctx, name->full_name, fd, + NFS4ACL_NDR_XATTR_NAME, + acl, (void *) ndr_pull_nfs4acl); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(acl); + return status; + } + + *psd = security_descriptor_initialise(mem_ctx); + NT_STATUS_HAVE_NO_MEMORY(*psd); + + sd = *psd; + + sd->type |= acl->a_flags; + + /* the number of ids to map is the acl count plus uid and gid */ + num_ids = acl->a_count +2; + ids = talloc_array(sd, struct id_map, num_ids); + NT_STATUS_HAVE_NO_MEMORY(ids); + + ids[0].xid.id = name->st.st_uid; + ids[0].xid.type = ID_TYPE_UID; + ids[0].sid = NULL; + ids[0].status = ID_UNKNOWN; + + ids[1].xid.id = name->st.st_gid; + ids[1].xid.type = ID_TYPE_GID; + ids[1].sid = NULL; + ids[1].status = ID_UNKNOWN; + + for (i=0;i<acl->a_count;i++) { + struct nfs4ace *a = &acl->ace[i]; + ids[i+2].xid.id = a->e_id; + if (a->e_flags & ACE4_IDENTIFIER_GROUP) { + ids[i+2].xid.type = ID_TYPE_GID; + } else { + ids[i+2].xid.type = ID_TYPE_UID; + } + ids[i+2].sid = NULL; + ids[i+2].status = ID_UNKNOWN; + } + + /* Allocate memory for the sids from the security descriptor to be on + * the safe side. */ + status = wbc_xids_to_sids(ids, num_ids); + NT_STATUS_NOT_OK_RETURN(status); + + sd->owner_sid = talloc_steal(sd, ids[0].sid); + sd->group_sid = talloc_steal(sd, ids[1].sid); + + for (i=0;i<acl->a_count;i++) { + struct nfs4ace *a = &acl->ace[i]; + struct security_ace ace = {}; + ace.type = a->e_type; + ace.flags = a->e_flags; + ace.access_mask = a->e_mask; + ace.trustee = *ids[i+2].sid; + security_descriptor_dacl_add(sd, &ace); + } + + return NT_STATUS_OK; +} + +/* + save the acl for a file into system.nfs4acl +*/ +static NTSTATUS pvfs_acl_save_nfs4(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd, + struct security_descriptor *sd) +{ + NTSTATUS status; + void *privs; + struct nfs4acl acl; + int i; + TALLOC_CTX *tmp_ctx; + struct id_map *ids; + + tmp_ctx = talloc_new(pvfs); + NT_STATUS_HAVE_NO_MEMORY(tmp_ctx); + + acl.a_version = 0; + acl.a_flags = sd->type; + acl.a_count = sd->dacl?sd->dacl->num_aces:0; + acl.a_owner_mask = 0; + acl.a_group_mask = 0; + acl.a_other_mask = 0; + + acl.ace = talloc_array(tmp_ctx, struct nfs4ace, acl.a_count); + if (!acl.ace) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + ids = talloc_array(tmp_ctx, struct id_map, acl.a_count); + if (ids == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + for (i=0;i<acl.a_count;i++) { + struct security_ace *ace = &sd->dacl->aces[i]; + ZERO_STRUCT(ids[i].xid); + ids[i].sid = dom_sid_dup(ids, &ace->trustee); + if (ids[i].sid == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + ids[i].status = ID_UNKNOWN; + } + + status = wbc_sids_to_xids(ids, acl.a_count); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(tmp_ctx); + return status; + } + + for (i=0;i<acl.a_count;i++) { + struct nfs4ace *a = &acl.ace[i]; + struct security_ace *ace = &sd->dacl->aces[i]; + a->e_type = ace->type; + a->e_flags = ace->flags; + a->e_mask = ace->access_mask; + if (ids[i].xid.type != ID_TYPE_UID) { + a->e_flags |= ACE4_IDENTIFIER_GROUP; + } + a->e_id = ids[i].xid.id; + a->e_who = ""; + } + + privs = root_privileges(); + status = pvfs_xattr_ndr_save(pvfs, name->full_name, fd, + NFS4ACL_NDR_XATTR_NAME, + &acl, (void *) ndr_push_nfs4acl); + talloc_free(privs); + + talloc_free(tmp_ctx); + return status; +} + + +/* + initialise pvfs acl NFS4 backend +*/ +NTSTATUS pvfs_acl_nfs4_init(TALLOC_CTX *ctx) +{ + struct pvfs_acl_ops ops = { + .name = "nfs4acl", + .acl_load = pvfs_acl_load_nfs4, + .acl_save = pvfs_acl_save_nfs4 + }; + return pvfs_acl_register(ctx, &ops); +} diff --git a/source4/ntvfs/posix/pvfs_acl_xattr.c b/source4/ntvfs/posix/pvfs_acl_xattr.c new file mode 100644 index 0000000..1f569ca --- /dev/null +++ b/source4/ntvfs/posix/pvfs_acl_xattr.c @@ -0,0 +1,104 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - NT ACLs in xattrs + + Copyright (C) Andrew Tridgell 2006 + + 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 "vfs_posix.h" +#include "../lib/util/unix_privs.h" +#include "librpc/gen_ndr/ndr_xattr.h" + +NTSTATUS pvfs_acl_xattr_init(TALLOC_CTX *); + +/* + load the current ACL from extended attributes +*/ +static NTSTATUS pvfs_acl_load_xattr(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd, + TALLOC_CTX *mem_ctx, + struct security_descriptor **sd) +{ + NTSTATUS status; + struct xattr_NTACL *acl; + + if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE)) { + return NT_STATUS_NOT_FOUND; + } + + acl = talloc_zero(mem_ctx, struct xattr_NTACL); + NT_STATUS_HAVE_NO_MEMORY(acl); + + status = pvfs_xattr_ndr_load(pvfs, mem_ctx, name->full_name, fd, + XATTR_NTACL_NAME, + acl, (void *) ndr_pull_xattr_NTACL); + + if (!NT_STATUS_IS_OK(status)) { + talloc_free(acl); + return status; + } + + if (acl->version != 1) { + talloc_free(acl); + return NT_STATUS_INVALID_ACL; + } + + *sd = talloc_steal(mem_ctx, acl->info.sd); + + return NT_STATUS_OK; +} + +/* + save the acl for a file into filesystem xattr +*/ +static NTSTATUS pvfs_acl_save_xattr(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd, + struct security_descriptor *sd) +{ + NTSTATUS status; + void *privs; + struct xattr_NTACL acl; + + if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE)) { + return NT_STATUS_OK; + } + + acl.version = 1; + acl.info.sd = sd; + + /* this xattr is in the "system" namespace, so we need + admin privileges to set it */ + privs = root_privileges(); + status = pvfs_xattr_ndr_save(pvfs, name->full_name, fd, + XATTR_NTACL_NAME, + &acl, (void *) ndr_push_xattr_NTACL); + talloc_free(privs); + return status; +} + + +/* + initialise pvfs acl xattr backend +*/ +NTSTATUS pvfs_acl_xattr_init(TALLOC_CTX *ctx) +{ + struct pvfs_acl_ops ops = { + .name = "xattr", + .acl_load = pvfs_acl_load_xattr, + .acl_save = pvfs_acl_save_xattr + }; + return pvfs_acl_register(ctx, &ops); +} diff --git a/source4/ntvfs/posix/pvfs_dirlist.c b/source4/ntvfs/posix/pvfs_dirlist.c new file mode 100644 index 0000000..1207287 --- /dev/null +++ b/source4/ntvfs/posix/pvfs_dirlist.c @@ -0,0 +1,411 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Andrew Tridgell 2004 + + 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/>. +*/ +/* + directory listing functions for posix backend +*/ + +#include "includes.h" +#include "vfs_posix.h" +#include "system/dir.h" + +#define NAME_CACHE_SIZE 100 + +struct name_cache_entry { + char *name; + off_t offset; +}; + +struct pvfs_dir { + struct pvfs_state *pvfs; + bool no_wildcard; + char *single_name; + const char *pattern; + off_t offset; + DIR *dir; + const char *unix_path; + bool end_of_search; + struct name_cache_entry *name_cache; + uint32_t name_cache_index; +}; + +/* these three numbers are chosen to minimise the chances of a bad + interaction with the OS value for 'end of directory'. On IRIX + telldir() returns 0xFFFFFFFF at the end of a directory, and that + caused an infinite loop with the original values of 0,1,2 + + On XFS on linux telldir returns 0x7FFFFFFF at the end of a + directory. Thus the change from 0x80000002, as otherwise + 0x7FFFFFFF+0x80000002==1==DIR_OFFSET_DOTDOT +*/ +#define DIR_OFFSET_DOT 0 +#define DIR_OFFSET_DOTDOT 1 +#define DIR_OFFSET_BASE 0x80000022 + +/* + a special directory listing case where the pattern has no wildcard. We can just do a single stat() + thus avoiding the more expensive directory scan +*/ +static NTSTATUS pvfs_list_no_wildcard(struct pvfs_state *pvfs, struct pvfs_filename *name, + const char *pattern, struct pvfs_dir *dir) +{ + if (!name->exists) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + dir->pvfs = pvfs; + dir->no_wildcard = true; + dir->end_of_search = false; + dir->unix_path = talloc_strdup(dir, name->full_name); + if (!dir->unix_path) { + return NT_STATUS_NO_MEMORY; + } + + dir->single_name = talloc_strdup(dir, pattern); + if (!dir->single_name) { + return NT_STATUS_NO_MEMORY; + } + + dir->dir = NULL; + dir->offset = 0; + dir->pattern = NULL; + + return NT_STATUS_OK; +} + +/* + destroy an open search +*/ +static int pvfs_dirlist_destructor(struct pvfs_dir *dir) +{ + if (dir->dir) closedir(dir->dir); + return 0; +} + +/* + start to read a directory + + if the pattern matches no files then we return NT_STATUS_OK, with dir->count = 0 +*/ +NTSTATUS pvfs_list_start(struct pvfs_state *pvfs, struct pvfs_filename *name, + TALLOC_CTX *mem_ctx, struct pvfs_dir **dirp) +{ + char *pattern; + struct pvfs_dir *dir; + + (*dirp) = talloc_zero(mem_ctx, struct pvfs_dir); + if (*dirp == NULL) { + return NT_STATUS_NO_MEMORY; + } + + dir = *dirp; + + /* split the unix path into a directory + pattern */ + pattern = strrchr(name->full_name, '/'); + if (!pattern) { + /* this should not happen, as pvfs_unix_path is supposed to + return an absolute path */ + return NT_STATUS_UNSUCCESSFUL; + } + + *pattern++ = 0; + + if (!name->has_wildcard) { + return pvfs_list_no_wildcard(pvfs, name, pattern, dir); + } + + dir->unix_path = talloc_strdup(dir, name->full_name); + if (!dir->unix_path) { + return NT_STATUS_NO_MEMORY; + } + + dir->pattern = talloc_strdup(dir, pattern); + if (dir->pattern == NULL) { + return NT_STATUS_NO_MEMORY; + } + + dir->dir = opendir(name->full_name); + if (!dir->dir) { + return pvfs_map_errno(pvfs, errno); + } + + dir->pvfs = pvfs; + dir->no_wildcard = false; + dir->end_of_search = false; + dir->offset = DIR_OFFSET_DOT; + dir->name_cache = talloc_zero_array(dir, + struct name_cache_entry, + NAME_CACHE_SIZE); + if (dir->name_cache == NULL) { + talloc_free(dir); + return NT_STATUS_NO_MEMORY; + } + + talloc_set_destructor(dir, pvfs_dirlist_destructor); + + return NT_STATUS_OK; +} + +/* + add an entry to the local cache +*/ +static void dcache_add(struct pvfs_dir *dir, const char *name) +{ + struct name_cache_entry *e; + + dir->name_cache_index = (dir->name_cache_index+1) % NAME_CACHE_SIZE; + e = &dir->name_cache[dir->name_cache_index]; + + if (e->name) talloc_free(e->name); + + e->name = talloc_strdup(dir->name_cache, name); + e->offset = dir->offset; +} + +/* + return the next entry +*/ +const char *pvfs_list_next(struct pvfs_dir *dir, off_t *ofs) +{ + struct dirent *de; + enum protocol_types protocol = dir->pvfs->ntvfs->ctx->protocol; + + /* non-wildcard searches are easy */ + if (dir->no_wildcard) { + dir->end_of_search = true; + if (*ofs != 0) return NULL; + (*ofs)++; + return dir->single_name; + } + + /* . and .. are handled separately as some unix systems will + not return them first in a directory, but windows client + may assume that these entries always appear first */ + if (*ofs == DIR_OFFSET_DOT) { + (*ofs) = DIR_OFFSET_DOTDOT; + dir->offset = *ofs; + if (ms_fnmatch_protocol(dir->pattern, ".", protocol, + false) == 0) { + dcache_add(dir, "."); + return "."; + } + } + + if (*ofs == DIR_OFFSET_DOTDOT) { + (*ofs) = DIR_OFFSET_BASE; + dir->offset = *ofs; + if (ms_fnmatch_protocol(dir->pattern, "..", protocol, + false) == 0) { + dcache_add(dir, ".."); + return ".."; + } + } + + if (*ofs == DIR_OFFSET_BASE) { + rewinddir(dir->dir); + } else if (*ofs != dir->offset) { + seekdir(dir->dir, (*ofs) - DIR_OFFSET_BASE); + } + dir->offset = *ofs; + + while ((de = readdir(dir->dir))) { + const char *dname = de->d_name; + + if (ISDOT(dname) || ISDOTDOT(dname)) { + continue; + } + + if (ms_fnmatch_protocol(dir->pattern, dname, protocol, + false) != 0) { + char *short_name = pvfs_short_name_component(dir->pvfs, dname); + if (short_name == NULL || + ms_fnmatch_protocol(dir->pattern, short_name, + protocol, false) != 0) { + talloc_free(short_name); + continue; + } + talloc_free(short_name); + } + + /* Casting is necessary to avoid signed integer overflow. */ + dir->offset = (unsigned long)telldir(dir->dir) + (unsigned long)DIR_OFFSET_BASE; + (*ofs) = dir->offset; + + dcache_add(dir, dname); + + return dname; + } + + dir->end_of_search = true; + return NULL; +} + +/* + return unix directory of an open search +*/ +const char *pvfs_list_unix_path(struct pvfs_dir *dir) +{ + return dir->unix_path; +} + +/* + return true if end of search has been reached +*/ +bool pvfs_list_eos(struct pvfs_dir *dir, off_t ofs) +{ + return dir->end_of_search; +} + +/* + seek to the given name +*/ +NTSTATUS pvfs_list_seek(struct pvfs_dir *dir, const char *name, off_t *ofs) +{ + struct dirent *de; + int i; + + dir->end_of_search = false; + + if (ISDOT(name)) { + dir->offset = DIR_OFFSET_DOTDOT; + *ofs = dir->offset; + return NT_STATUS_OK; + } + + if (ISDOTDOT(name)) { + dir->offset = DIR_OFFSET_BASE; + *ofs = dir->offset; + return NT_STATUS_OK; + } + + for (i=dir->name_cache_index;i>=0;i--) { + struct name_cache_entry *e = &dir->name_cache[i]; + if (e->name && strcasecmp_m(name, e->name) == 0) { + *ofs = e->offset; + return NT_STATUS_OK; + } + } + for (i=NAME_CACHE_SIZE-1;i>dir->name_cache_index;i--) { + struct name_cache_entry *e = &dir->name_cache[i]; + if (e->name && strcasecmp_m(name, e->name) == 0) { + *ofs = e->offset; + return NT_STATUS_OK; + } + } + + rewinddir(dir->dir); + + while ((de = readdir(dir->dir))) { + if (strcasecmp_m(name, de->d_name) == 0) { + /* Casting is necessary to avoid signed integer overflow. */ + dir->offset = (unsigned long)telldir(dir->dir) + (unsigned long)DIR_OFFSET_BASE; + *ofs = dir->offset; + return NT_STATUS_OK; + } + } + + dir->end_of_search = true; + + return NT_STATUS_OBJECT_NAME_NOT_FOUND; +} + +/* + seek to the given offset +*/ +NTSTATUS pvfs_list_seek_ofs(struct pvfs_dir *dir, uint32_t resume_key, off_t *ofs) +{ + struct dirent *de; + int i; + + dir->end_of_search = false; + + if (resume_key == DIR_OFFSET_DOT) { + *ofs = DIR_OFFSET_DOTDOT; + return NT_STATUS_OK; + } + + if (resume_key == DIR_OFFSET_DOTDOT) { + *ofs = DIR_OFFSET_BASE; + return NT_STATUS_OK; + } + + if (resume_key == DIR_OFFSET_BASE) { + rewinddir(dir->dir); + if ((de=readdir(dir->dir)) == NULL) { + dir->end_of_search = true; + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + /* Casting is necessary to avoid signed integer overflow. */ + *ofs = (unsigned long)telldir(dir->dir) + (unsigned long)DIR_OFFSET_BASE; + dir->offset = *ofs; + return NT_STATUS_OK; + } + + for (i=dir->name_cache_index;i>=0;i--) { + struct name_cache_entry *e = &dir->name_cache[i]; + if (resume_key == (uint32_t)e->offset) { + *ofs = e->offset; + return NT_STATUS_OK; + } + } + for (i=NAME_CACHE_SIZE-1;i>dir->name_cache_index;i--) { + struct name_cache_entry *e = &dir->name_cache[i]; + if (resume_key == (uint32_t)e->offset) { + *ofs = e->offset; + return NT_STATUS_OK; + } + } + + rewinddir(dir->dir); + + while ((de = readdir(dir->dir))) { + /* Casting is necessary to avoid signed integer overflow. */ + dir->offset = (unsigned long)telldir(dir->dir) + (unsigned long)DIR_OFFSET_BASE; + if (resume_key == (uint32_t)dir->offset) { + *ofs = dir->offset; + return NT_STATUS_OK; + } + } + + dir->end_of_search = true; + + return NT_STATUS_OBJECT_NAME_NOT_FOUND; +} + + +/* + see if a directory is empty +*/ +bool pvfs_directory_empty(struct pvfs_state *pvfs, struct pvfs_filename *name) +{ + struct dirent *de; + DIR *dir = opendir(name->full_name); + if (dir == NULL) { + return true; + } + + while ((de = readdir(dir))) { + if (!ISDOT(de->d_name) && !ISDOTDOT(de->d_name)) { + closedir(dir); + return false; + } + } + + closedir(dir); + return true; +} diff --git a/source4/ntvfs/posix/pvfs_fileinfo.c b/source4/ntvfs/posix/pvfs_fileinfo.c new file mode 100644 index 0000000..977ea4f --- /dev/null +++ b/source4/ntvfs/posix/pvfs_fileinfo.c @@ -0,0 +1,158 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - + + Copyright (C) Andrew Tridgell 2004 + + 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 "vfs_posix.h" +#include "lib/util/time.h" + +/**************************************************************************** + Change a unix mode to a dos mode. +****************************************************************************/ +static uint32_t dos_mode_from_stat(struct pvfs_state *pvfs, struct stat *st) +{ + int result = 0; + + if ((st->st_mode & S_IWUSR) == 0) + result |= FILE_ATTRIBUTE_READONLY; + + if ((pvfs->flags & PVFS_FLAG_MAP_ARCHIVE) && ((st->st_mode & S_IXUSR) != 0)) + result |= FILE_ATTRIBUTE_ARCHIVE; + + if ((pvfs->flags & PVFS_FLAG_MAP_SYSTEM) && ((st->st_mode & S_IXGRP) != 0)) + result |= FILE_ATTRIBUTE_SYSTEM; + + if ((pvfs->flags & PVFS_FLAG_MAP_HIDDEN) && ((st->st_mode & S_IXOTH) != 0)) + result |= FILE_ATTRIBUTE_HIDDEN; + + if (S_ISDIR(st->st_mode)) + result = FILE_ATTRIBUTE_DIRECTORY | (result & FILE_ATTRIBUTE_READONLY); + + return result; +} + + + +/* + fill in the dos file attributes for a file +*/ +NTSTATUS pvfs_fill_dos_info(struct pvfs_state *pvfs, struct pvfs_filename *name, + unsigned int flags, int fd) +{ + NTSTATUS status; + DATA_BLOB lkey; + NTTIME write_time; + + /* make directories appear as size 0 with 1 link */ + if (S_ISDIR(name->st.st_mode)) { + name->st.st_size = 0; + name->st.st_nlink = 1; + } else if (name->stream_id == 0) { + name->stream_name = NULL; + } + + /* for now just use the simple samba mapping */ + unix_to_nt_time(&name->dos.create_time, name->st.st_ctime); + unix_to_nt_time(&name->dos.access_time, name->st.st_atime); + unix_to_nt_time(&name->dos.write_time, name->st.st_mtime); + unix_to_nt_time(&name->dos.change_time, name->st.st_ctime); + name->dos.create_time += get_ctimensec(&name->st) / 100; + name->dos.access_time += get_atimensec(&name->st) / 100; + name->dos.write_time += get_mtimensec(&name->st) / 100; + name->dos.change_time += get_ctimensec(&name->st) / 100; + name->dos.attrib = dos_mode_from_stat(pvfs, &name->st); + name->dos.alloc_size = pvfs_round_alloc_size(pvfs, name->st.st_size); + name->dos.nlink = name->st.st_nlink; + name->dos.ea_size = 4; /* TODO: Fill this in without hitting the stream bad in pvfs_doseas_load() */ + if (pvfs->ntvfs->ctx->protocol >= PROTOCOL_SMB2_02) { + /* SMB2 represents a null EA with zero bytes */ + name->dos.ea_size = 0; + } + + name->dos.file_id = (((uint64_t)name->st.st_dev)<<32) | name->st.st_ino; + name->dos.flags = 0; + + status = pvfs_dosattrib_load(pvfs, name, fd); + NT_STATUS_NOT_OK_RETURN(status); + + if (flags & PVFS_RESOLVE_NO_OPENDB) { + return NT_STATUS_OK; + } + + status = pvfs_locking_key(name, name, &lkey); + NT_STATUS_NOT_OK_RETURN(status); + + status = odb_get_file_infos(pvfs->odb_context, &lkey, + NULL, &write_time); + data_blob_free(&lkey); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1,("WARNING: odb_get_file_infos: %s\n", nt_errstr(status))); + return status; + } + + if (!null_time(write_time)) { + name->dos.write_time = write_time; + } + + return NT_STATUS_OK; +} + + +/* + return a set of unix file permissions for a new file or directory +*/ +mode_t pvfs_fileperms(struct pvfs_state *pvfs, uint32_t attrib) +{ + mode_t mode = (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH); + + if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE) && + (attrib & FILE_ATTRIBUTE_READONLY)) { + mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); + } + + if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE)) { + if ((attrib & FILE_ATTRIBUTE_ARCHIVE) && + (pvfs->flags & PVFS_FLAG_MAP_ARCHIVE)) { + mode |= S_IXUSR; + } + if ((attrib & FILE_ATTRIBUTE_SYSTEM) && + (pvfs->flags & PVFS_FLAG_MAP_SYSTEM)) { + mode |= S_IXGRP; + } + if ((attrib & FILE_ATTRIBUTE_HIDDEN) && + (pvfs->flags & PVFS_FLAG_MAP_HIDDEN)) { + mode |= S_IXOTH; + } + } + + if (attrib & FILE_ATTRIBUTE_DIRECTORY) { + mode |= (S_IFDIR | S_IWUSR); + mode |= (S_IXUSR | S_IXGRP | S_IXOTH); + mode &= pvfs->options.dir_mask; + mode |= pvfs->options.force_dir_mode; + } else { + mode &= pvfs->options.create_mask; + mode |= pvfs->options.force_create_mode; + } + + return mode; +} + + diff --git a/source4/ntvfs/posix/pvfs_flush.c b/source4/ntvfs/posix/pvfs_flush.c new file mode 100644 index 0000000..f425fcc --- /dev/null +++ b/source4/ntvfs/posix/pvfs_flush.c @@ -0,0 +1,80 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - flush + + Copyright (C) Andrew Tridgell 2004 + + 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 "vfs_posix.h" + +/* + flush a single open file +*/ +static void pvfs_flush_file(struct pvfs_state *pvfs, struct pvfs_file *f) +{ + if (f->handle->fd == -1) { + return; + } + if (pvfs->flags & PVFS_FLAG_STRICT_SYNC) { + fsync(f->handle->fd); + } +} + +/* + flush a fnum +*/ +NTSTATUS pvfs_flush(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_flush *io) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + struct pvfs_file *f; + + switch (io->generic.level) { + case RAW_FLUSH_FLUSH: + case RAW_FLUSH_SMB2: + /* TODO: take care of io->smb2.in.unknown */ + f = pvfs_find_fd(pvfs, req, io->generic.in.file.ntvfs); + if (!f) { + return NT_STATUS_INVALID_HANDLE; + } + pvfs_flush_file(pvfs, f); + io->smb2.out.reserved = 0; + return NT_STATUS_OK; + + case RAW_FLUSH_ALL: + if (!(pvfs->flags & PVFS_FLAG_STRICT_SYNC)) { + return NT_STATUS_OK; + } + + /* + * they are asking to flush all open files + * for the given SMBPID + */ + for (f=pvfs->files.list;f;f=f->next) { + if (f->ntvfs->smbpid != req->smbpid) continue; + + pvfs_flush_file(pvfs, f); + } + + return NT_STATUS_OK; + } + + return NT_STATUS_INVALID_LEVEL; +} diff --git a/source4/ntvfs/posix/pvfs_fsinfo.c b/source4/ntvfs/posix/pvfs_fsinfo.c new file mode 100644 index 0000000..c355c19 --- /dev/null +++ b/source4/ntvfs/posix/pvfs_fsinfo.c @@ -0,0 +1,224 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - fsinfo + + Copyright (C) Andrew Tridgell 2004 + + 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 "vfs_posix.h" +#include "librpc/gen_ndr/xattr.h" +#include "librpc/ndr/libndr.h" + +/* We use libblkid out of e2fsprogs to identify UUID of a volume */ +#ifdef HAVE_LIBBLKID +#include <blkid/blkid.h> +#endif + +static NTSTATUS pvfs_blkid_fs_uuid(struct pvfs_state *pvfs, struct stat *st, struct GUID *uuid) +{ +#ifdef HAVE_LIBBLKID + NTSTATUS status; + char *uuid_value = NULL; + char *devname = NULL; + + devname = blkid_devno_to_devname(st->st_dev); + if (!devname) { + ZERO_STRUCTP(uuid); + return NT_STATUS_OK; + } + + uuid_value = blkid_get_tag_value(NULL, "UUID", devname); + free(devname); + if (!uuid_value) { + ZERO_STRUCTP(uuid); + return NT_STATUS_OK; + } + + status = GUID_from_string(uuid_value, uuid); + free(uuid_value); + if (!NT_STATUS_IS_OK(status)) { + ZERO_STRUCTP(uuid); + return NT_STATUS_OK; + } + return NT_STATUS_OK; +#else + ZERO_STRUCTP(uuid); + return NT_STATUS_OK; +#endif +} + +static NTSTATUS pvfs_cache_base_fs_uuid(struct pvfs_state *pvfs, struct stat *st) +{ + NTSTATUS status; + struct GUID uuid; + + if (pvfs->base_fs_uuid) return NT_STATUS_OK; + + status = pvfs_blkid_fs_uuid(pvfs, st, &uuid); + NT_STATUS_NOT_OK_RETURN(status); + + pvfs->base_fs_uuid = talloc(pvfs, struct GUID); + NT_STATUS_HAVE_NO_MEMORY(pvfs->base_fs_uuid); + *pvfs->base_fs_uuid = uuid; + + return NT_STATUS_OK; +} +/* + return filesystem space info +*/ +NTSTATUS pvfs_fsinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_fsinfo *fs) +{ + NTSTATUS status; + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + uint64_t blocks_free, blocks_total; + unsigned int bpunit; + struct stat st; + const uint16_t block_size = 512; + + /* only some levels need the expensive sys_fsusage() call */ + switch (fs->generic.level) { + case RAW_QFS_DSKATTR: + case RAW_QFS_ALLOCATION: + case RAW_QFS_SIZE_INFO: + case RAW_QFS_SIZE_INFORMATION: + case RAW_QFS_FULL_SIZE_INFORMATION: + if (sys_fsusage(pvfs->base_directory, &blocks_free, &blocks_total) == -1) { + return pvfs_map_errno(pvfs, errno); + } + break; + default: + break; + } + + if (stat(pvfs->base_directory, &st) != 0) { + return NT_STATUS_DISK_CORRUPT_ERROR; + } + + /* now fill in the out fields */ + switch (fs->generic.level) { + case RAW_QFS_GENERIC: + return NT_STATUS_INVALID_LEVEL; + + case RAW_QFS_DSKATTR: + /* we need to scale the sizes to fit */ + for (bpunit=64; bpunit<0x10000; bpunit *= 2) { + if (blocks_total * (double)block_size < bpunit * 512 * 65535.0) { + break; + } + } + fs->dskattr.out.blocks_per_unit = bpunit; + fs->dskattr.out.block_size = block_size; + fs->dskattr.out.units_total = (blocks_total * (double)block_size) / (bpunit * 512); + fs->dskattr.out.units_free = (blocks_free * (double)block_size) / (bpunit * 512); + + /* we must return a maximum of 2G to old DOS systems, or they get very confused */ + if (bpunit > 64 && req->ctx->protocol <= PROTOCOL_LANMAN2) { + fs->dskattr.out.blocks_per_unit = 64; + fs->dskattr.out.units_total = 0xFFFF; + fs->dskattr.out.units_free = 0xFFFF; + } + return NT_STATUS_OK; + + case RAW_QFS_ALLOCATION: + fs->allocation.out.fs_id = st.st_dev; + fs->allocation.out.total_alloc_units = blocks_total; + fs->allocation.out.avail_alloc_units = blocks_free; + fs->allocation.out.sectors_per_unit = 1; + fs->allocation.out.bytes_per_sector = block_size; + return NT_STATUS_OK; + + case RAW_QFS_VOLUME: + fs->volume.out.serial_number = st.st_ino; + fs->volume.out.volume_name.s = pvfs->share_name; + return NT_STATUS_OK; + + case RAW_QFS_VOLUME_INFO: + case RAW_QFS_VOLUME_INFORMATION: + unix_to_nt_time(&fs->volume_info.out.create_time, st.st_ctime); + fs->volume_info.out.serial_number = st.st_ino; + fs->volume_info.out.volume_name.s = pvfs->share_name; + return NT_STATUS_OK; + + case RAW_QFS_SIZE_INFO: + case RAW_QFS_SIZE_INFORMATION: + fs->size_info.out.total_alloc_units = blocks_total; + fs->size_info.out.avail_alloc_units = blocks_free; + fs->size_info.out.sectors_per_unit = 1; + fs->size_info.out.bytes_per_sector = block_size; + return NT_STATUS_OK; + + case RAW_QFS_DEVICE_INFO: + case RAW_QFS_DEVICE_INFORMATION: + fs->device_info.out.device_type = 0; + fs->device_info.out.characteristics = 0; + return NT_STATUS_OK; + + case RAW_QFS_ATTRIBUTE_INFO: + case RAW_QFS_ATTRIBUTE_INFORMATION: + fs->attribute_info.out.fs_attr = pvfs->fs_attribs; + fs->attribute_info.out.max_file_component_length = 255; + fs->attribute_info.out.fs_type.s = ntvfs->ctx->fs_type; + return NT_STATUS_OK; + + case RAW_QFS_QUOTA_INFORMATION: + ZERO_STRUCT(fs->quota_information.out.unknown); + fs->quota_information.out.quota_soft = 0; + fs->quota_information.out.quota_hard = 0; + fs->quota_information.out.quota_flags = 0; + return NT_STATUS_OK; + + case RAW_QFS_FULL_SIZE_INFORMATION: + fs->full_size_information.out.total_alloc_units = blocks_total; + fs->full_size_information.out.call_avail_alloc_units = blocks_free; + fs->full_size_information.out.actual_avail_alloc_units = blocks_free; + fs->full_size_information.out.sectors_per_unit = 1; + fs->full_size_information.out.bytes_per_sector = block_size; + return NT_STATUS_OK; + + case RAW_QFS_OBJECTID_INFORMATION: + ZERO_STRUCT(fs->objectid_information.out.guid); + ZERO_STRUCT(fs->objectid_information.out.unknown); + + status = pvfs_cache_base_fs_uuid(pvfs, &st); + NT_STATUS_NOT_OK_RETURN(status); + + fs->objectid_information.out.guid = *pvfs->base_fs_uuid; + return NT_STATUS_OK; + + case RAW_QFS_SECTOR_SIZE_INFORMATION: + fs->sector_size_info.out.logical_bytes_per_sector = block_size; + fs->sector_size_info.out.phys_bytes_per_sector_atomic + = block_size; + fs->sector_size_info.out.phys_bytes_per_sector_perf + = block_size; + fs->sector_size_info.out.fs_effective_phys_bytes_per_sector_atomic + = block_size; + fs->sector_size_info.out.flags + = QFS_SSINFO_FLAGS_ALIGNED_DEVICE + | QFS_SSINFO_FLAGS_PARTITION_ALIGNED_ON_DEVICE; + fs->sector_size_info.out.byte_off_sector_align = 0; + fs->sector_size_info.out.byte_off_partition_align = 0; + return NT_STATUS_OK; + + default: + break; + } + return NT_STATUS_INVALID_LEVEL; +} diff --git a/source4/ntvfs/posix/pvfs_ioctl.c b/source4/ntvfs/posix/pvfs_ioctl.c new file mode 100644 index 0000000..1d5e8fd --- /dev/null +++ b/source4/ntvfs/posix/pvfs_ioctl.c @@ -0,0 +1,82 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - open and close + + Copyright (C) Andrew Tridgell 2004 + + 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 "vfs_posix.h" +#include "../libcli/smb/smb_constants.h" + +/* + old ioctl interface +*/ +static NTSTATUS pvfs_ioctl_old(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_ioctl *io) +{ + return NT_STATUS_DOS(ERRSRV, ERRerror); +} + +/* + nt ioctl interface +*/ +static NTSTATUS pvfs_ntioctl(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_ioctl *io) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + struct pvfs_file *f; + + f = pvfs_find_fd(pvfs, req, io->ntioctl.in.file.ntvfs); + if (!f) { + return NT_STATUS_INVALID_HANDLE; + } + + switch (io->ntioctl.in.function) { + case FSCTL_SET_SPARSE: + /* maybe some posix systems have a way of marking + a file non-sparse? */ + io->ntioctl.out.blob = data_blob(NULL, 0); + return NT_STATUS_OK; + } + + return NT_STATUS_NOT_SUPPORTED; +} + +/* + ioctl interface +*/ +NTSTATUS pvfs_ioctl(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_ioctl *io) +{ + switch (io->generic.level) { + case RAW_IOCTL_IOCTL: + return pvfs_ioctl_old(ntvfs, req, io); + + case RAW_IOCTL_NTIOCTL: + return pvfs_ntioctl(ntvfs, req, io); + + case RAW_IOCTL_SMB2: + case RAW_IOCTL_SMB2_NO_HANDLE: + /* see WSPP SMB2 test 46 */ + return NT_STATUS_INVALID_DEVICE_REQUEST; + } + + return NT_STATUS_INVALID_LEVEL; +} diff --git a/source4/ntvfs/posix/pvfs_lock.c b/source4/ntvfs/posix/pvfs_lock.c new file mode 100644 index 0000000..0802ded --- /dev/null +++ b/source4/ntvfs/posix/pvfs_lock.c @@ -0,0 +1,404 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - locking + + Copyright (C) Andrew Tridgell 2004 + + 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 "vfs_posix.h" +#include "system/time.h" +#include "../lib/util/dlinklist.h" +#include "messaging/messaging.h" + + +/* + check if we can perform IO on a range that might be locked +*/ +NTSTATUS pvfs_check_lock(struct pvfs_state *pvfs, + struct pvfs_file *f, + uint32_t smbpid, + uint64_t offset, uint64_t count, + enum brl_type rw) +{ + if (!(pvfs->flags & PVFS_FLAG_STRICT_LOCKING)) { + return NT_STATUS_OK; + } + + return brlock_locktest(pvfs->brl_context, + f->brl_handle, + smbpid, + offset, count, rw); +} + +/* this state structure holds information about a lock we are waiting on */ +struct pvfs_pending_lock { + struct pvfs_pending_lock *next, *prev; + struct pvfs_state *pvfs; + union smb_lock *lck; + struct pvfs_file *f; + struct ntvfs_request *req; + int pending_lock; + struct pvfs_wait *wait_handle; + struct timeval end_time; +}; + +/* + a secondary attempt to setup a lock has failed - back out + the locks we did get and send an error +*/ +static void pvfs_lock_async_failed(struct pvfs_state *pvfs, + struct ntvfs_request *req, + struct pvfs_file *f, + struct smb_lock_entry *locks, + int i, + NTSTATUS status) +{ + /* undo the locks we just did */ + for (i--;i>=0;i--) { + brlock_unlock(pvfs->brl_context, + f->brl_handle, + locks[i].pid, + locks[i].offset, + locks[i].count); + f->lock_count--; + } + req->async_states->status = status; + req->async_states->send_fn(req); +} + + +/* + called when we receive a pending lock notification. It means that + either our lock timed out or someone else has unlocked a overlapping + range, so we should try the lock again. Note that on timeout we + do retry the lock, giving it a last chance. +*/ +static void pvfs_pending_lock_continue(void *private_data, enum pvfs_wait_notice reason) +{ + struct pvfs_pending_lock *pending = talloc_get_type(private_data, + struct pvfs_pending_lock); + struct pvfs_state *pvfs = pending->pvfs; + struct pvfs_file *f = pending->f; + struct ntvfs_request *req = pending->req; + union smb_lock *lck = pending->lck; + struct smb_lock_entry *locks; + enum brl_type rw; + NTSTATUS status; + int i; + bool timed_out; + + timed_out = (reason != PVFS_WAIT_EVENT); + + locks = lck->lockx.in.locks + lck->lockx.in.ulock_cnt; + + if (lck->lockx.in.mode & LOCKING_ANDX_SHARED_LOCK) { + rw = READ_LOCK; + } else { + rw = WRITE_LOCK; + } + + DLIST_REMOVE(f->pending_list, pending); + + /* we don't retry on a cancel */ + if (reason == PVFS_WAIT_CANCEL) { + if (pvfs->ntvfs->ctx->protocol < PROTOCOL_SMB2_02) { + status = NT_STATUS_FILE_LOCK_CONFLICT; + } else { + status = NT_STATUS_CANCELLED; + } + } else { + /* + * here it's important to pass the pending pointer + * because with this we'll get the correct error code + * FILE_LOCK_CONFLICT in the error case + */ + status = brlock_lock(pvfs->brl_context, + f->brl_handle, + locks[pending->pending_lock].pid, + locks[pending->pending_lock].offset, + locks[pending->pending_lock].count, + rw, pending); + } + if (NT_STATUS_IS_OK(status)) { + f->lock_count++; + timed_out = false; + } + + /* if we have failed and timed out, or succeeded, then we + don't need the pending lock any more */ + if (NT_STATUS_IS_OK(status) || timed_out) { + NTSTATUS status2; + status2 = brlock_remove_pending(pvfs->brl_context, + f->brl_handle, pending); + if (!NT_STATUS_IS_OK(status2)) { + DEBUG(0,("pvfs_lock: failed to remove pending lock - %s\n", nt_errstr(status2))); + } + talloc_free(pending->wait_handle); + } + + if (!NT_STATUS_IS_OK(status)) { + if (timed_out) { + /* no more chances */ + pvfs_lock_async_failed(pvfs, req, f, locks, pending->pending_lock, status); + talloc_free(pending); + } else { + /* we can try again */ + DLIST_ADD(f->pending_list, pending); + } + return; + } + + /* if we haven't timed out yet, then we can do more pending locks */ + if (rw == READ_LOCK) { + rw = PENDING_READ_LOCK; + } else { + rw = PENDING_WRITE_LOCK; + } + + /* we've now got the pending lock. try and get the rest, which might + lead to more pending locks */ + for (i=pending->pending_lock+1;i<lck->lockx.in.lock_cnt;i++) { + pending->pending_lock = i; + + status = brlock_lock(pvfs->brl_context, + f->brl_handle, + locks[i].pid, + locks[i].offset, + locks[i].count, + rw, pending); + if (!NT_STATUS_IS_OK(status)) { + /* a timed lock failed - setup a wait message to handle + the pending lock notification or a timeout */ + pending->wait_handle = pvfs_wait_message(pvfs, req, MSG_BRL_RETRY, + pending->end_time, + pvfs_pending_lock_continue, + pending); + if (pending->wait_handle == NULL) { + pvfs_lock_async_failed(pvfs, req, f, locks, i, NT_STATUS_NO_MEMORY); + talloc_free(pending); + } else { + talloc_steal(pending, pending->wait_handle); + DLIST_ADD(f->pending_list, pending); + } + return; + } + + f->lock_count++; + } + + /* we've managed to get all the locks. Tell the client */ + req->async_states->status = NT_STATUS_OK; + req->async_states->send_fn(req); + talloc_free(pending); +} + + +/* + called when we close a file that might have locks +*/ +void pvfs_lock_close(struct pvfs_state *pvfs, struct pvfs_file *f) +{ + struct pvfs_pending_lock *p, *next; + + if (f->lock_count || f->pending_list) { + DEBUG(5,("pvfs_lock: removing %.0f locks on close\n", + (double)f->lock_count)); + brlock_close(f->pvfs->brl_context, f->brl_handle); + f->lock_count = 0; + } + + /* reply to all the pending lock requests, telling them the + lock failed */ + for (p=f->pending_list;p;p=next) { + next = p->next; + DLIST_REMOVE(f->pending_list, p); + p->req->async_states->status = NT_STATUS_RANGE_NOT_LOCKED; + p->req->async_states->send_fn(p->req); + } +} + + +/* + cancel a set of locks +*/ +static NTSTATUS pvfs_lock_cancel(struct pvfs_state *pvfs, struct ntvfs_request *req, union smb_lock *lck, + struct pvfs_file *f) +{ + struct pvfs_pending_lock *p; + + for (p=f->pending_list;p;p=p->next) { + /* check if the lock request matches exactly - you can only cancel with exact matches */ + if (p->lck->lockx.in.ulock_cnt == lck->lockx.in.ulock_cnt && + p->lck->lockx.in.lock_cnt == lck->lockx.in.lock_cnt && + p->lck->lockx.in.file.ntvfs== lck->lockx.in.file.ntvfs && + p->lck->lockx.in.mode == (lck->lockx.in.mode & ~LOCKING_ANDX_CANCEL_LOCK)) { + int i; + + for (i=0;i<lck->lockx.in.ulock_cnt + lck->lockx.in.lock_cnt;i++) { + if (p->lck->lockx.in.locks[i].pid != lck->lockx.in.locks[i].pid || + p->lck->lockx.in.locks[i].offset != lck->lockx.in.locks[i].offset || + p->lck->lockx.in.locks[i].count != lck->lockx.in.locks[i].count) { + break; + } + } + if (i < lck->lockx.in.ulock_cnt) continue; + + /* an exact match! we can cancel it, which is equivalent + to triggering the timeout early */ + pvfs_pending_lock_continue(p, PVFS_WAIT_TIMEOUT); + return NT_STATUS_OK; + } + } + + return NT_STATUS_DOS(ERRDOS, ERRcancelviolation); +} + + +/* + lock or unlock a byte range +*/ +NTSTATUS pvfs_lock(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_lock *lck) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + struct pvfs_file *f; + struct smb_lock_entry *locks; + int i; + enum brl_type rw; + struct pvfs_pending_lock *pending = NULL; + NTSTATUS status; + + if (lck->generic.level != RAW_LOCK_GENERIC) { + return ntvfs_map_lock(ntvfs, req, lck); + } + + if (lck->lockx.in.mode & LOCKING_ANDX_OPLOCK_RELEASE) { + return pvfs_oplock_release(ntvfs, req, lck); + } + + f = pvfs_find_fd(pvfs, req, lck->lockx.in.file.ntvfs); + if (!f) { + return NT_STATUS_INVALID_HANDLE; + } + + if (f->handle->fd == -1) { + return NT_STATUS_FILE_IS_A_DIRECTORY; + } + + status = pvfs_break_level2_oplocks(f); + NT_STATUS_NOT_OK_RETURN(status); + + if (lck->lockx.in.timeout != 0 && + (req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + pending = talloc(f, struct pvfs_pending_lock); + if (pending == NULL) { + return NT_STATUS_NO_MEMORY; + } + + pending->pvfs = pvfs; + pending->lck = lck; + pending->f = f; + pending->req = req; + + pending->end_time = + timeval_current_ofs_msec(lck->lockx.in.timeout); + } + + if (lck->lockx.in.mode & LOCKING_ANDX_SHARED_LOCK) { + rw = pending? PENDING_READ_LOCK : READ_LOCK; + } else { + rw = pending? PENDING_WRITE_LOCK : WRITE_LOCK; + } + + if (lck->lockx.in.mode & LOCKING_ANDX_CANCEL_LOCK) { + talloc_free(pending); + return pvfs_lock_cancel(pvfs, req, lck, f); + } + + if (lck->lockx.in.mode & LOCKING_ANDX_CHANGE_LOCKTYPE) { + /* this seems to not be supported by any windows server, + or used by any clients */ + talloc_free(pending); + return NT_STATUS_DOS(ERRDOS, ERRnoatomiclocks); + } + + /* the unlocks happen first */ + locks = lck->lockx.in.locks; + + for (i=0;i<lck->lockx.in.ulock_cnt;i++) { + status = brlock_unlock(pvfs->brl_context, + f->brl_handle, + locks[i].pid, + locks[i].offset, + locks[i].count); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(pending); + return status; + } + f->lock_count--; + } + + locks += i; + + for (i=0;i<lck->lockx.in.lock_cnt;i++) { + if (pending) { + pending->pending_lock = i; + } + + status = brlock_lock(pvfs->brl_context, + f->brl_handle, + locks[i].pid, + locks[i].offset, + locks[i].count, + rw, pending); + if (!NT_STATUS_IS_OK(status)) { + if (pending) { + /* a timed lock failed - setup a wait message to handle + the pending lock notification or a timeout */ + pending->wait_handle = pvfs_wait_message(pvfs, req, MSG_BRL_RETRY, + pending->end_time, + pvfs_pending_lock_continue, + pending); + if (pending->wait_handle == NULL) { + talloc_free(pending); + return NT_STATUS_NO_MEMORY; + } + talloc_steal(pending, pending->wait_handle); + DLIST_ADD(f->pending_list, pending); + return NT_STATUS_OK; + } + + /* undo the locks we just did */ + for (i--;i>=0;i--) { + brlock_unlock(pvfs->brl_context, + f->brl_handle, + locks[i].pid, + locks[i].offset, + locks[i].count); + f->lock_count--; + } + talloc_free(pending); + return status; + } + f->lock_count++; + } + + talloc_free(pending); + return NT_STATUS_OK; +} diff --git a/source4/ntvfs/posix/pvfs_mkdir.c b/source4/ntvfs/posix/pvfs_mkdir.c new file mode 100644 index 0000000..2cf43ab --- /dev/null +++ b/source4/ntvfs/posix/pvfs_mkdir.c @@ -0,0 +1,196 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - mkdir and rmdir + + Copyright (C) Andrew Tridgell 2004 + + 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/dir.h" +#include "vfs_posix.h" +#include "librpc/gen_ndr/security.h" + +/* + create a directory with EAs +*/ +static NTSTATUS pvfs_t2mkdir(struct pvfs_state *pvfs, + struct ntvfs_request *req, union smb_mkdir *md) +{ + NTSTATUS status; + struct pvfs_filename *name; + mode_t mode; + + /* resolve the cifs name to a posix name */ + status = pvfs_resolve_name(pvfs, req, md->t2mkdir.in.path, 0, &name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (name->exists) { + return NT_STATUS_OBJECT_NAME_COLLISION; + } + + status = pvfs_access_check_parent(pvfs, req, name, SEC_DIR_ADD_FILE); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + mode = pvfs_fileperms(pvfs, FILE_ATTRIBUTE_DIRECTORY); + + if (pvfs_sys_mkdir(pvfs, name->full_name, mode, name->allow_override) == -1) { + return pvfs_map_errno(pvfs, errno); + } + + pvfs_xattr_unlink_hook(pvfs, name->full_name); + + status = pvfs_resolve_name(pvfs, req, md->t2mkdir.in.path, 0, &name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (!name->exists || + !(name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY)) { + return NT_STATUS_INTERNAL_ERROR; + } + + /* setup an inherited acl from the parent */ + status = pvfs_acl_inherit(pvfs, req, name, -1); + if (!NT_STATUS_IS_OK(status)) { + pvfs_sys_rmdir(pvfs, name->full_name, name->allow_override); + return status; + } + + /* setup any EAs that were asked for */ + status = pvfs_setfileinfo_ea_set(pvfs, name, -1, + md->t2mkdir.in.num_eas, + md->t2mkdir.in.eas); + if (!NT_STATUS_IS_OK(status)) { + pvfs_sys_rmdir(pvfs, name->full_name, name->allow_override); + return status; + } + + notify_trigger(pvfs->notify_context, + NOTIFY_ACTION_ADDED, + FILE_NOTIFY_CHANGE_DIR_NAME, + name->full_name); + + return NT_STATUS_OK; +} + +/* + create a directory +*/ +NTSTATUS pvfs_mkdir(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_mkdir *md) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + NTSTATUS status; + struct pvfs_filename *name; + mode_t mode; + + if (md->generic.level == RAW_MKDIR_T2MKDIR) { + return pvfs_t2mkdir(pvfs, req, md); + } + + if (md->generic.level != RAW_MKDIR_MKDIR) { + return NT_STATUS_INVALID_LEVEL; + } + + /* resolve the cifs name to a posix name */ + status = pvfs_resolve_name(pvfs, req, md->mkdir.in.path, 0, &name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (name->exists) { + return NT_STATUS_OBJECT_NAME_COLLISION; + } + + status = pvfs_access_check_parent(pvfs, req, name, SEC_DIR_ADD_FILE); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + mode = pvfs_fileperms(pvfs, FILE_ATTRIBUTE_DIRECTORY); + + if (pvfs_sys_mkdir(pvfs, name->full_name, mode, name->allow_override) == -1) { + return pvfs_map_errno(pvfs, errno); + } + + pvfs_xattr_unlink_hook(pvfs, name->full_name); + + /* setup an inherited acl from the parent */ + status = pvfs_acl_inherit(pvfs, req, name, -1); + if (!NT_STATUS_IS_OK(status)) { + pvfs_sys_rmdir(pvfs, name->full_name, name->allow_override); + return status; + } + + notify_trigger(pvfs->notify_context, + NOTIFY_ACTION_ADDED, + FILE_NOTIFY_CHANGE_DIR_NAME, + name->full_name); + + return NT_STATUS_OK; +} + +/* + remove a directory +*/ +NTSTATUS pvfs_rmdir(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, struct smb_rmdir *rd) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + NTSTATUS status; + struct pvfs_filename *name; + + /* resolve the cifs name to a posix name */ + status = pvfs_resolve_name(pvfs, req, rd->in.path, 0, &name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (!name->exists) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + status = pvfs_access_check_simple(pvfs, req, name, SEC_STD_DELETE); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = pvfs_xattr_unlink_hook(pvfs, name->full_name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (pvfs_sys_rmdir(pvfs, name->full_name, name->allow_override) == -1) { + /* some olders systems don't return ENOTEMPTY to rmdir() */ + if (errno == EEXIST) { + return NT_STATUS_DIRECTORY_NOT_EMPTY; + } + return pvfs_map_errno(pvfs, errno); + } + + notify_trigger(pvfs->notify_context, + NOTIFY_ACTION_REMOVED, + FILE_NOTIFY_CHANGE_DIR_NAME, + name->full_name); + + return NT_STATUS_OK; +} diff --git a/source4/ntvfs/posix/pvfs_notify.c b/source4/ntvfs/posix/pvfs_notify.c new file mode 100644 index 0000000..91a151b --- /dev/null +++ b/source4/ntvfs/posix/pvfs_notify.c @@ -0,0 +1,300 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - notify + + Copyright (C) Andrew Tridgell 2006 + + 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 "vfs_posix.h" +#include "lib/messaging/irpc.h" +#include "messaging/messaging.h" +#include "../lib/util/dlinklist.h" +#include "lib/events/events.h" + +/* pending notifies buffer, hung off struct pvfs_file for open directories + that have used change notify */ +struct pvfs_notify_buffer { + struct pvfs_file *f; + uint32_t num_changes; + struct notify_changes *changes; + uint32_t max_buffer_size; + uint32_t current_buffer_size; + bool overflowed; + + /* a list of requests waiting for events on this handle */ + struct notify_pending { + struct notify_pending *next, *prev; + struct ntvfs_request *req; + union smb_notify *info; + } *pending; +}; + +/* + send a notify on the next event run. +*/ +static void pvfs_notify_send_next(struct tevent_context *ev, struct tevent_timer *te, + struct timeval t, void *ptr) +{ + struct ntvfs_request *req = talloc_get_type(ptr, struct ntvfs_request); + req->async_states->send_fn(req); +} + + +/* + send a reply to a pending notify request +*/ +static void pvfs_notify_send(struct pvfs_notify_buffer *notify_buffer, + NTSTATUS status, bool immediate) +{ + struct notify_pending *pending = notify_buffer->pending; + struct ntvfs_request *req; + union smb_notify *info; + + if (notify_buffer->current_buffer_size > notify_buffer->max_buffer_size && + notify_buffer->num_changes != 0) { + /* on buffer overflow return no changes and destroys the notify buffer */ + notify_buffer->num_changes = 0; + while (notify_buffer->pending) { + pvfs_notify_send(notify_buffer, NT_STATUS_OK, immediate); + } + notify_buffer->overflowed = true; + return; + } + + /* see if there is anyone waiting */ + if (notify_buffer->pending == NULL) { + return; + } + + DLIST_REMOVE(notify_buffer->pending, pending); + + req = pending->req; + info = pending->info; + + info->nttrans.out.num_changes = notify_buffer->num_changes; + info->nttrans.out.changes = talloc_steal(req, notify_buffer->changes); + notify_buffer->num_changes = 0; + notify_buffer->overflowed = false; + notify_buffer->changes = NULL; + notify_buffer->current_buffer_size = 0; + + talloc_free(pending); + + if (info->nttrans.out.num_changes != 0) { + status = NT_STATUS_OK; + } + + req->async_states->status = status; + + if (immediate) { + req->async_states->send_fn(req); + return; + } + + /* we can't call pvfs_notify_send() directly here, as that + would free the request, and the ntvfs modules above us + could use it, so call it on the next event */ + tevent_add_timer(req->ctx->event_ctx, + req, timeval_zero(), pvfs_notify_send_next, req); +} + +/* + destroy a notify buffer. Called when the handle is closed + */ +static int pvfs_notify_destructor(struct pvfs_notify_buffer *n) +{ + notify_remove(n->f->pvfs->notify_context, n); + n->f->notify_buffer = NULL; + pvfs_notify_send(n, NT_STATUS_OK, true); + return 0; +} + + +/* + called when a async notify event comes in +*/ +static void pvfs_notify_callback(void *private_data, const struct notify_event *ev) +{ + struct pvfs_notify_buffer *n = talloc_get_type(private_data, struct pvfs_notify_buffer); + size_t len; + struct notify_changes *n2; + char *new_path; + + if (n->overflowed) { + return; + } + + n2 = talloc_realloc(n, n->changes, struct notify_changes, n->num_changes+1); + if (n2 == NULL) { + /* nothing much we can do for this */ + return; + } + n->changes = n2; + + new_path = talloc_strdup(n->changes, ev->path); + if (new_path == NULL) { + return; + } + string_replace(new_path, '/', '\\'); + + n->changes[n->num_changes].action = ev->action; + n->changes[n->num_changes].name.s = new_path; + n->num_changes++; + + /* + work out how much room this will take in the buffer + */ + len = 12 + strlen_m(ev->path)*2; + if (len & 3) { + len += 4 - (len & 3); + } + n->current_buffer_size += len; + + /* send what we have, unless its the first part of a rename */ + if (ev->action != NOTIFY_ACTION_OLD_NAME) { + pvfs_notify_send(n, NT_STATUS_OK, true); + } +} + +/* + setup a notify buffer on a directory handle +*/ +static NTSTATUS pvfs_notify_setup(struct pvfs_state *pvfs, struct pvfs_file *f, + uint32_t buffer_size, uint32_t filter, bool recursive) +{ + NTSTATUS status; + struct notify_entry e; + + /* We may not fill in all the elements in this entry - + * structure may in future be shared with Samba3 */ + ZERO_STRUCT(e); + + /* We may not fill in all the elements in this entry - + * structure may in future be shared with Samba3 */ + ZERO_STRUCT(e); + + f->notify_buffer = talloc_zero(f, struct pvfs_notify_buffer); + NT_STATUS_HAVE_NO_MEMORY(f->notify_buffer); + + f->notify_buffer->max_buffer_size = buffer_size; + f->notify_buffer->f = f; + + e.filter = filter; + e.path = f->handle->name->full_name; + if (recursive) { + e.subdir_filter = filter; + } else { + e.subdir_filter = 0; + } + + status = notify_add(pvfs->notify_context, &e, + pvfs_notify_callback, f->notify_buffer); + NT_STATUS_NOT_OK_RETURN(status); + + talloc_set_destructor(f->notify_buffer, pvfs_notify_destructor); + + return NT_STATUS_OK; +} + +/* + called from the pvfs_wait code when either an event has come in, or + the notify request has been cancelled +*/ +static void pvfs_notify_end(void *private_data, enum pvfs_wait_notice reason) +{ + struct pvfs_notify_buffer *notify_buffer = talloc_get_type(private_data, + struct pvfs_notify_buffer); + if (reason == PVFS_WAIT_CANCEL) { + pvfs_notify_send(notify_buffer, NT_STATUS_CANCELLED, false); + } else { + pvfs_notify_send(notify_buffer, NT_STATUS_OK, true); + } +} + +/* change notify request - always async. This request blocks until the + event buffer is non-empty */ +NTSTATUS pvfs_notify(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_notify *info) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + struct pvfs_file *f; + NTSTATUS status; + struct notify_pending *pending; + + if (info->nttrans.level != RAW_NOTIFY_NTTRANS) { + return ntvfs_map_notify(ntvfs, req, info); + } + + f = pvfs_find_fd(pvfs, req, info->nttrans.in.file.ntvfs); + if (!f) { + return NT_STATUS_INVALID_HANDLE; + } + + /* this request doesn't make sense unless its async */ + if (!(req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* its only valid for directories */ + if (f->handle->fd != -1) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* if the handle doesn't currently have a notify buffer then + create one */ + if (f->notify_buffer == NULL) { + status = pvfs_notify_setup(pvfs, f, + info->nttrans.in.buffer_size, + info->nttrans.in.completion_filter, + info->nttrans.in.recursive); + NT_STATUS_NOT_OK_RETURN(status); + } + + /* we update the max_buffer_size on each call, but we do not + update the recursive flag or filter */ + f->notify_buffer->max_buffer_size = info->nttrans.in.buffer_size; + + pending = talloc(f->notify_buffer, struct notify_pending); + NT_STATUS_HAVE_NO_MEMORY(pending); + + pending->req = talloc_reference(pending, req); + NT_STATUS_HAVE_NO_MEMORY(pending->req); + pending->info = info; + + DLIST_ADD_END(f->notify_buffer->pending, pending); + + /* if the buffer is empty then start waiting */ + if (f->notify_buffer->num_changes == 0 && + !f->notify_buffer->overflowed) { + struct pvfs_wait *wait_handle; + wait_handle = pvfs_wait_message(pvfs, req, -1, + timeval_zero(), + pvfs_notify_end, + f->notify_buffer); + NT_STATUS_HAVE_NO_MEMORY(wait_handle); + talloc_steal(req, wait_handle); + return NT_STATUS_OK; + } + + req->async_states->state |= NTVFS_ASYNC_STATE_ASYNC; + pvfs_notify_send(f->notify_buffer, NT_STATUS_OK, false); + + return NT_STATUS_OK; +} diff --git a/source4/ntvfs/posix/pvfs_open.c b/source4/ntvfs/posix/pvfs_open.c new file mode 100644 index 0000000..860861d --- /dev/null +++ b/source4/ntvfs/posix/pvfs_open.c @@ -0,0 +1,2094 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - open and close + + Copyright (C) Andrew Tridgell 2004 + + 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 "vfs_posix.h" +#include "system/dir.h" +#include "system/time.h" +#include "../lib/util/dlinklist.h" +#include "messaging/messaging.h" +#include "librpc/gen_ndr/xattr.h" + +/* + find open file handle given fnum +*/ +struct pvfs_file *pvfs_find_fd(struct pvfs_state *pvfs, + struct ntvfs_request *req, struct ntvfs_handle *h) +{ + void *p; + struct pvfs_file *f; + + p = ntvfs_handle_get_backend_data(h, pvfs->ntvfs); + if (!p) return NULL; + + f = talloc_get_type(p, struct pvfs_file); + if (!f) return NULL; + + return f; +} + +/* + cleanup a open directory handle +*/ +static int pvfs_dir_handle_destructor(struct pvfs_file_handle *h) +{ + if (h->have_opendb_entry) { + struct odb_lock *lck; + NTSTATUS status; + const char *delete_path = NULL; + + lck = odb_lock(h, h->pvfs->odb_context, &h->odb_locking_key); + if (lck == NULL) { + DEBUG(0,("Unable to lock opendb for close\n")); + return 0; + } + + status = odb_close_file(lck, h, &delete_path); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Unable to remove opendb entry for '%s' - %s\n", + h->name->full_name, nt_errstr(status))); + } + + if (h->name->stream_name == NULL && delete_path) { + status = pvfs_xattr_unlink_hook(h->pvfs, delete_path); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Warning: xattr unlink hook failed for '%s' - %s\n", + delete_path, nt_errstr(status))); + } + if (pvfs_sys_rmdir(h->pvfs, delete_path, h->name->allow_override) != 0) { + DEBUG(0,("pvfs_dir_handle_destructor: failed to rmdir '%s' - %s\n", + delete_path, strerror(errno))); + } + } + + talloc_free(lck); + } + + return 0; +} + +/* + cleanup a open directory fnum +*/ +static int pvfs_dir_fnum_destructor(struct pvfs_file *f) +{ + DLIST_REMOVE(f->pvfs->files.list, f); + ntvfs_handle_remove_backend_data(f->ntvfs, f->pvfs->ntvfs); + + return 0; +} + +/* + setup any EAs and the ACL on newly created files/directories +*/ +static NTSTATUS pvfs_open_setup_eas_acl(struct pvfs_state *pvfs, + struct ntvfs_request *req, + struct pvfs_filename *name, + int fd, struct pvfs_file *f, + union smb_open *io, + struct security_descriptor *sd) +{ + NTSTATUS status = NT_STATUS_OK; + + /* setup any EAs that were asked for */ + if (io->ntcreatex.in.ea_list) { + status = pvfs_setfileinfo_ea_set(pvfs, name, fd, + io->ntcreatex.in.ea_list->num_eas, + io->ntcreatex.in.ea_list->eas); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + /* setup an initial sec_desc if requested */ + if (sd && (sd->type & SEC_DESC_DACL_PRESENT)) { + union smb_setfileinfo set; +/* + * TODO: set the full ACL! + * - vista denies the creation of the file with NT_STATUS_PRIVILEGE_NOT_HELD, + * when a SACL is present on the sd, + * but the user doesn't have SeSecurityPrivilege + * - w2k3 allows it + */ + set.set_secdesc.in.file.ntvfs = f->ntvfs; + set.set_secdesc.in.secinfo_flags = SECINFO_DACL; + set.set_secdesc.in.sd = sd; + + status = pvfs_acl_set(pvfs, req, name, fd, SEC_STD_WRITE_DAC, &set); + } + + return status; +} + +/* + form the lock context used for opendb locking. Note that we must + zero here to take account of possible padding on some architectures +*/ +NTSTATUS pvfs_locking_key(struct pvfs_filename *name, + TALLOC_CTX *mem_ctx, DATA_BLOB *key) +{ + struct { + dev_t device; + ino_t inode; + } lock_context; + ZERO_STRUCT(lock_context); + + lock_context.device = name->st.st_dev; + lock_context.inode = name->st.st_ino; + + *key = data_blob_talloc(mem_ctx, &lock_context, sizeof(lock_context)); + if (key->data == NULL) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + + +/* + open a directory +*/ +static NTSTATUS pvfs_open_directory(struct pvfs_state *pvfs, + struct ntvfs_request *req, + struct pvfs_filename *name, + union smb_open *io) +{ + struct pvfs_file *f; + struct ntvfs_handle *h; + NTSTATUS status; + uint32_t create_action; + uint32_t access_mask = io->generic.in.access_mask; + struct odb_lock *lck; + bool del_on_close; + uint32_t create_options; + uint32_t share_access; + bool forced; + struct security_descriptor *sd = NULL; + + create_options = io->generic.in.create_options; + share_access = io->generic.in.share_access; + + forced = (io->generic.in.create_options & NTCREATEX_OPTIONS_DIRECTORY)?true:false; + + if (name->stream_name) { + if (forced) { + return NT_STATUS_NOT_A_DIRECTORY; + } else { + return NT_STATUS_FILE_IS_A_DIRECTORY; + } + } + + /* if the client says it must be a directory, and it isn't, + then fail */ + if (name->exists && !(name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY)) { + return NT_STATUS_NOT_A_DIRECTORY; + } + + /* found with gentest */ + if (io->ntcreatex.in.access_mask == SEC_FLAG_MAXIMUM_ALLOWED && + (io->ntcreatex.in.create_options & NTCREATEX_OPTIONS_DIRECTORY) && + (io->ntcreatex.in.create_options & NTCREATEX_OPTIONS_DELETE_ON_CLOSE)) { + DEBUG(3,(__location__ ": Invalid access_mask/create_options 0x%08x 0x%08x for %s\n", + io->ntcreatex.in.access_mask, io->ntcreatex.in.create_options, name->original_name)); + return NT_STATUS_INVALID_PARAMETER; + } + + switch (io->generic.in.open_disposition) { + case NTCREATEX_DISP_OPEN_IF: + break; + + case NTCREATEX_DISP_OPEN: + if (!name->exists) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + break; + + case NTCREATEX_DISP_CREATE: + if (name->exists) { + return NT_STATUS_OBJECT_NAME_COLLISION; + } + break; + + case NTCREATEX_DISP_OVERWRITE_IF: + case NTCREATEX_DISP_OVERWRITE: + case NTCREATEX_DISP_SUPERSEDE: + default: + DEBUG(3,(__location__ ": Invalid open disposition 0x%08x for %s\n", + io->generic.in.open_disposition, name->original_name)); + return NT_STATUS_INVALID_PARAMETER; + } + + status = ntvfs_handle_new(pvfs->ntvfs, req, &h); + NT_STATUS_NOT_OK_RETURN(status); + + f = talloc(h, struct pvfs_file); + if (f == NULL) { + return NT_STATUS_NO_MEMORY; + } + + f->handle = talloc(f, struct pvfs_file_handle); + if (f->handle == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (name->exists) { + /* check the security descriptor */ + status = pvfs_access_check(pvfs, req, name, &access_mask); + } else { + sd = io->ntcreatex.in.sec_desc; + status = pvfs_access_check_create(pvfs, req, name, &access_mask, true, &sd); + } + NT_STATUS_NOT_OK_RETURN(status); + + if (io->generic.in.query_maximal_access) { + status = pvfs_access_maximal_allowed(pvfs, req, name, + &io->generic.out.maximal_access); + NT_STATUS_NOT_OK_RETURN(status); + } + + f->ntvfs = h; + f->pvfs = pvfs; + f->pending_list = NULL; + f->lock_count = 0; + f->share_access = io->generic.in.share_access; + f->impersonation = io->generic.in.impersonation; + f->access_mask = access_mask; + f->brl_handle = NULL; + f->notify_buffer = NULL; + f->search = NULL; + + f->handle->pvfs = pvfs; + f->handle->name = talloc_steal(f->handle, name); + f->handle->fd = -1; + f->handle->odb_locking_key = data_blob(NULL, 0); + f->handle->create_options = io->generic.in.create_options; + f->handle->private_flags = io->generic.in.private_flags; + f->handle->seek_offset = 0; + f->handle->position = 0; + f->handle->mode = 0; + f->handle->oplock = NULL; + ZERO_STRUCT(f->handle->write_time); + f->handle->open_completed = false; + + if ((create_options & NTCREATEX_OPTIONS_DELETE_ON_CLOSE) && + pvfs_directory_empty(pvfs, f->handle->name)) { + del_on_close = true; + } else { + del_on_close = false; + } + + if (name->exists) { + /* form the lock context used for opendb locking */ + status = pvfs_locking_key(name, f->handle, &f->handle->odb_locking_key); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* get a lock on this file before the actual open */ + lck = odb_lock(req, pvfs->odb_context, &f->handle->odb_locking_key); + if (lck == NULL) { + DEBUG(0,("pvfs_open: failed to lock file '%s' in opendb\n", + name->full_name)); + /* we were supposed to do a blocking lock, so something + is badly wrong! */ + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + /* see if we are allowed to open at the same time as existing opens */ + status = odb_can_open(lck, name->stream_id, + share_access, access_mask, del_on_close, + io->generic.in.open_disposition, false); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(lck); + return status; + } + + /* now really mark the file as open */ + status = odb_open_file(lck, f->handle, name->full_name, + NULL, name->dos.write_time, + false, OPLOCK_NONE, NULL); + + if (!NT_STATUS_IS_OK(status)) { + talloc_free(lck); + return status; + } + + f->handle->have_opendb_entry = true; + } + + DLIST_ADD(pvfs->files.list, f); + + /* setup destructors to avoid leaks on abnormal termination */ + talloc_set_destructor(f->handle, pvfs_dir_handle_destructor); + talloc_set_destructor(f, pvfs_dir_fnum_destructor); + + if (!name->exists) { + uint32_t attrib = io->generic.in.file_attr | FILE_ATTRIBUTE_DIRECTORY; + mode_t mode = pvfs_fileperms(pvfs, attrib); + + if (pvfs_sys_mkdir(pvfs, name->full_name, mode, name->allow_override) == -1) { + return pvfs_map_errno(pvfs,errno); + } + + pvfs_xattr_unlink_hook(pvfs, name->full_name); + + status = pvfs_resolve_name(pvfs, req, io->ntcreatex.in.fname, 0, &name); + if (!NT_STATUS_IS_OK(status)) { + goto cleanup_delete; + } + + status = pvfs_open_setup_eas_acl(pvfs, req, name, -1, f, io, sd); + if (!NT_STATUS_IS_OK(status)) { + goto cleanup_delete; + } + + /* form the lock context used for opendb locking */ + status = pvfs_locking_key(name, f->handle, &f->handle->odb_locking_key); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + lck = odb_lock(req, pvfs->odb_context, &f->handle->odb_locking_key); + if (lck == NULL) { + DEBUG(0,("pvfs_open: failed to lock file '%s' in opendb\n", + name->full_name)); + /* we were supposed to do a blocking lock, so something + is badly wrong! */ + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + status = odb_can_open(lck, name->stream_id, + share_access, access_mask, del_on_close, + io->generic.in.open_disposition, false); + + if (!NT_STATUS_IS_OK(status)) { + goto cleanup_delete; + } + + status = odb_open_file(lck, f->handle, name->full_name, + NULL, name->dos.write_time, + false, OPLOCK_NONE, NULL); + + if (!NT_STATUS_IS_OK(status)) { + goto cleanup_delete; + } + + f->handle->have_opendb_entry = true; + + create_action = NTCREATEX_ACTION_CREATED; + + notify_trigger(pvfs->notify_context, + NOTIFY_ACTION_ADDED, + FILE_NOTIFY_CHANGE_DIR_NAME, + name->full_name); + } else { + create_action = NTCREATEX_ACTION_EXISTED; + } + + if (!name->exists) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + if (io->generic.in.query_on_disk_id) { + ZERO_ARRAY(io->generic.out.on_disk_id); + SBVAL(io->generic.out.on_disk_id, 0, name->st.st_ino); + SBVAL(io->generic.out.on_disk_id, 8, name->st.st_dev); + } + + /* the open succeeded, keep this handle permanently */ + status = ntvfs_handle_set_backend_data(h, pvfs->ntvfs, f); + if (!NT_STATUS_IS_OK(status)) { + goto cleanup_delete; + } + + f->handle->open_completed = true; + + io->generic.out.oplock_level = OPLOCK_NONE; + io->generic.out.file.ntvfs = h; + io->generic.out.create_action = create_action; + io->generic.out.create_time = name->dos.create_time; + io->generic.out.access_time = name->dos.access_time; + io->generic.out.write_time = name->dos.write_time; + io->generic.out.change_time = name->dos.change_time; + io->generic.out.attrib = name->dos.attrib; + io->generic.out.alloc_size = name->dos.alloc_size; + io->generic.out.size = name->st.st_size; + io->generic.out.file_type = FILE_TYPE_DISK; + io->generic.out.ipc_state = 0; + io->generic.out.is_directory = 1; + + return NT_STATUS_OK; + +cleanup_delete: + pvfs_sys_rmdir(pvfs, name->full_name, name->allow_override); + return status; +} + +/* + destroy a struct pvfs_file_handle +*/ +static int pvfs_handle_destructor(struct pvfs_file_handle *h) +{ + talloc_free(h->write_time.update_event); + h->write_time.update_event = NULL; + + if ((h->create_options & NTCREATEX_OPTIONS_DELETE_ON_CLOSE) && + h->name->stream_name) { + NTSTATUS status; + status = pvfs_stream_delete(h->pvfs, h->name, h->fd); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Failed to delete stream '%s' on close of '%s'\n", + h->name->stream_name, h->name->full_name)); + } + } + + if (h->fd != -1) { + if (close(h->fd) != 0) { + DEBUG(0,("pvfs_handle_destructor: close(%d) failed for %s - %s\n", + h->fd, h->name->full_name, strerror(errno))); + } + h->fd = -1; + } + + if (!h->write_time.update_forced && + h->write_time.update_on_close && + h->write_time.close_time == 0) { + struct timeval tv; + tv = timeval_current(); + h->write_time.close_time = timeval_to_nttime(&tv); + } + + if (h->have_opendb_entry) { + struct odb_lock *lck; + NTSTATUS status; + const char *delete_path = NULL; + + lck = odb_lock(h, h->pvfs->odb_context, &h->odb_locking_key); + if (lck == NULL) { + DEBUG(0,("Unable to lock opendb for close\n")); + return 0; + } + + if (h->write_time.update_forced) { + status = odb_get_file_infos(h->pvfs->odb_context, + &h->odb_locking_key, + NULL, + &h->write_time.close_time); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Unable get write time for '%s' - %s\n", + h->name->full_name, nt_errstr(status))); + } + + h->write_time.update_forced = false; + h->write_time.update_on_close = true; + } else if (h->write_time.update_on_close) { + status = odb_set_write_time(lck, h->write_time.close_time, true); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Unable set write time for '%s' - %s\n", + h->name->full_name, nt_errstr(status))); + } + } + + status = odb_close_file(lck, h, &delete_path); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Unable to remove opendb entry for '%s' - %s\n", + h->name->full_name, nt_errstr(status))); + } + + if (h->name->stream_name == NULL && + h->open_completed && delete_path) { + status = pvfs_xattr_unlink_hook(h->pvfs, delete_path); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Warning: xattr unlink hook failed for '%s' - %s\n", + delete_path, nt_errstr(status))); + } + if (pvfs_sys_unlink(h->pvfs, delete_path, h->name->allow_override) != 0) { + DEBUG(0,("pvfs_close: failed to delete '%s' - %s\n", + delete_path, strerror(errno))); + } else { + notify_trigger(h->pvfs->notify_context, + NOTIFY_ACTION_REMOVED, + FILE_NOTIFY_CHANGE_FILE_NAME, + delete_path); + } + h->write_time.update_on_close = false; + } + + talloc_free(lck); + } + + if (h->write_time.update_on_close) { + struct timeval tv[2]; + + nttime_to_timeval(&tv[0], h->name->dos.access_time); + nttime_to_timeval(&tv[1], h->write_time.close_time); + + if (!timeval_is_zero(&tv[0]) || !timeval_is_zero(&tv[1])) { + if (utimes(h->name->full_name, tv) == -1) { + DEBUG(3,("pvfs_handle_destructor: utimes() failed '%s' - %s\n", + h->name->full_name, strerror(errno))); + } + } + } + + return 0; +} + + +/* + destroy a struct pvfs_file +*/ +static int pvfs_fnum_destructor(struct pvfs_file *f) +{ + DLIST_REMOVE(f->pvfs->files.list, f); + pvfs_lock_close(f->pvfs, f); + ntvfs_handle_remove_backend_data(f->ntvfs, f->pvfs->ntvfs); + + return 0; +} + + +/* + form the lock context used for byte range locking. This is separate + from the locking key used for opendb locking as it needs to take + account of file streams (each stream is a separate byte range + locking space) +*/ +static NTSTATUS pvfs_brl_locking_handle(TALLOC_CTX *mem_ctx, + struct pvfs_filename *name, + struct ntvfs_handle *ntvfs, + struct brl_handle **_h) +{ + DATA_BLOB odb_key, key; + NTSTATUS status; + struct brl_handle *h; + + status = pvfs_locking_key(name, mem_ctx, &odb_key); + NT_STATUS_NOT_OK_RETURN(status); + + if (name->stream_name == NULL) { + key = odb_key; + } else { + key = data_blob_talloc(mem_ctx, NULL, + odb_key.length + strlen(name->stream_name) + 1); + NT_STATUS_HAVE_NO_MEMORY(key.data); + memcpy(key.data, odb_key.data, odb_key.length); + memcpy(key.data + odb_key.length, + name->stream_name, strlen(name->stream_name) + 1); + data_blob_free(&odb_key); + } + + h = brlock_create_handle(mem_ctx, ntvfs, &key); + NT_STATUS_HAVE_NO_MEMORY(h); + + *_h = h; + return NT_STATUS_OK; +} + +/* + create a new file +*/ +static NTSTATUS pvfs_create_file(struct pvfs_state *pvfs, + struct ntvfs_request *req, + struct pvfs_filename *name, + union smb_open *io) +{ + struct pvfs_file *f; + NTSTATUS status; + struct ntvfs_handle *h; + int flags, fd; + struct odb_lock *lck; + uint32_t create_options = io->generic.in.create_options; + uint32_t share_access = io->generic.in.share_access; + uint32_t access_mask = io->generic.in.access_mask; + mode_t mode; + uint32_t attrib; + bool del_on_close; + struct pvfs_filename *parent; + uint32_t oplock_level = OPLOCK_NONE, oplock_granted; + bool allow_level_II_oplock = false; + struct security_descriptor *sd = NULL; + + if (io->ntcreatex.in.file_attr & ~FILE_ATTRIBUTE_ALL_MASK) { + DEBUG(3,(__location__ ": Invalid file_attr 0x%08x for %s\n", + io->ntcreatex.in.file_attr, name->original_name)); + return NT_STATUS_INVALID_PARAMETER; + } + + if (io->ntcreatex.in.file_attr & FILE_ATTRIBUTE_ENCRYPTED) { + DEBUG(3,(__location__ ": Invalid encryption request for %s\n", + name->original_name)); + return NT_STATUS_ACCESS_DENIED; + } + + if ((io->ntcreatex.in.file_attr & FILE_ATTRIBUTE_READONLY) && + (create_options & NTCREATEX_OPTIONS_DELETE_ON_CLOSE)) { + DEBUG(4,(__location__ ": Invalid delete on close for readonly file %s\n", + name->original_name)); + return NT_STATUS_CANNOT_DELETE; + } + + sd = io->ntcreatex.in.sec_desc; + status = pvfs_access_check_create(pvfs, req, name, &access_mask, false, &sd); + NT_STATUS_NOT_OK_RETURN(status); + + /* check that the parent isn't opened with delete on close set */ + status = pvfs_resolve_parent(pvfs, req, name, &parent); + if (NT_STATUS_IS_OK(status)) { + DATA_BLOB locking_key; + status = pvfs_locking_key(parent, req, &locking_key); + NT_STATUS_NOT_OK_RETURN(status); + status = odb_get_file_infos(pvfs->odb_context, &locking_key, + &del_on_close, NULL); + NT_STATUS_NOT_OK_RETURN(status); + if (del_on_close) { + return NT_STATUS_DELETE_PENDING; + } + } + + if (access_mask & (SEC_FILE_WRITE_DATA | SEC_FILE_APPEND_DATA)) { + flags = O_RDWR; + } else { + flags = O_RDONLY; + } + + status = ntvfs_handle_new(pvfs->ntvfs, req, &h); + NT_STATUS_NOT_OK_RETURN(status); + + f = talloc(h, struct pvfs_file); + NT_STATUS_HAVE_NO_MEMORY(f); + + f->handle = talloc(f, struct pvfs_file_handle); + NT_STATUS_HAVE_NO_MEMORY(f->handle); + + attrib = io->ntcreatex.in.file_attr | FILE_ATTRIBUTE_ARCHIVE; + mode = pvfs_fileperms(pvfs, attrib); + + /* create the file */ + fd = pvfs_sys_open(pvfs, name->full_name, flags | O_CREAT | O_EXCL| O_NONBLOCK, mode, name->allow_override); + if (fd == -1) { + return pvfs_map_errno(pvfs, errno); + } + + pvfs_xattr_unlink_hook(pvfs, name->full_name); + + /* if this was a stream create then create the stream as well */ + if (name->stream_name) { + status = pvfs_stream_create(pvfs, name, fd); + if (!NT_STATUS_IS_OK(status)) { + close(fd); + return status; + } + } + + /* re-resolve the open fd */ + status = pvfs_resolve_name_fd(pvfs, fd, name, 0); + if (!NT_STATUS_IS_OK(status)) { + close(fd); + return status; + } + + /* support initial alloc sizes */ + name->dos.alloc_size = io->ntcreatex.in.alloc_size; + name->dos.attrib = attrib; + status = pvfs_dosattrib_save(pvfs, name, fd); + if (!NT_STATUS_IS_OK(status)) { + goto cleanup_delete; + } + + + status = pvfs_open_setup_eas_acl(pvfs, req, name, fd, f, io, sd); + if (!NT_STATUS_IS_OK(status)) { + goto cleanup_delete; + } + + if (io->generic.in.query_maximal_access) { + status = pvfs_access_maximal_allowed(pvfs, req, name, + &io->generic.out.maximal_access); + if (!NT_STATUS_IS_OK(status)) { + goto cleanup_delete; + } + } + + if (io->generic.in.query_on_disk_id) { + ZERO_ARRAY(io->generic.out.on_disk_id); + SBVAL(io->generic.out.on_disk_id, 0, name->st.st_ino); + SBVAL(io->generic.out.on_disk_id, 8, name->st.st_dev); + } + + /* form the lock context used for byte range locking and + opendb locking */ + status = pvfs_locking_key(name, f->handle, &f->handle->odb_locking_key); + if (!NT_STATUS_IS_OK(status)) { + goto cleanup_delete; + } + + status = pvfs_brl_locking_handle(f, name, h, &f->brl_handle); + if (!NT_STATUS_IS_OK(status)) { + goto cleanup_delete; + } + + /* grab a lock on the open file record */ + lck = odb_lock(req, pvfs->odb_context, &f->handle->odb_locking_key); + if (lck == NULL) { + DEBUG(0,("pvfs_open: failed to lock file '%s' in opendb\n", + name->full_name)); + /* we were supposed to do a blocking lock, so something + is badly wrong! */ + status = NT_STATUS_INTERNAL_DB_CORRUPTION; + goto cleanup_delete; + } + + if (create_options & NTCREATEX_OPTIONS_DELETE_ON_CLOSE) { + del_on_close = true; + } else { + del_on_close = false; + } + + if (pvfs->flags & PVFS_FLAG_FAKE_OPLOCKS) { + oplock_level = OPLOCK_NONE; + } else if (io->ntcreatex.in.flags & NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK) { + oplock_level = OPLOCK_BATCH; + } else if (io->ntcreatex.in.flags & NTCREATEX_FLAGS_REQUEST_OPLOCK) { + oplock_level = OPLOCK_EXCLUSIVE; + } + + if (req->client_caps & NTVFS_CLIENT_CAP_LEVEL_II_OPLOCKS) { + allow_level_II_oplock = true; + } + + status = odb_can_open(lck, name->stream_id, + share_access, access_mask, del_on_close, + io->generic.in.open_disposition, false); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(lck); + /* bad news, we must have hit a race - we don't delete the file + here as the most likely scenario is that someone else created + the file at the same time */ + close(fd); + return status; + } + + f->ntvfs = h; + f->pvfs = pvfs; + f->pending_list = NULL; + f->lock_count = 0; + f->share_access = io->generic.in.share_access; + f->access_mask = access_mask; + f->impersonation = io->generic.in.impersonation; + f->notify_buffer = NULL; + f->search = NULL; + + f->handle->pvfs = pvfs; + f->handle->name = talloc_steal(f->handle, name); + f->handle->fd = fd; + f->handle->create_options = io->generic.in.create_options; + f->handle->private_flags = io->generic.in.private_flags; + f->handle->seek_offset = 0; + f->handle->position = 0; + f->handle->mode = 0; + f->handle->oplock = NULL; + f->handle->have_opendb_entry = true; + ZERO_STRUCT(f->handle->write_time); + f->handle->open_completed = false; + + status = odb_open_file(lck, f->handle, name->full_name, + &f->handle->fd, name->dos.write_time, + allow_level_II_oplock, + oplock_level, &oplock_granted); + talloc_free(lck); + if (!NT_STATUS_IS_OK(status)) { + /* bad news, we must have hit a race - we don't delete the file + here as the most likely scenario is that someone else created + the file at the same time */ + close(fd); + return status; + } + + DLIST_ADD(pvfs->files.list, f); + + /* setup a destructor to avoid file descriptor leaks on + abnormal termination */ + talloc_set_destructor(f, pvfs_fnum_destructor); + talloc_set_destructor(f->handle, pvfs_handle_destructor); + + if (pvfs->flags & PVFS_FLAG_FAKE_OPLOCKS) { + oplock_granted = OPLOCK_BATCH; + } else if (oplock_granted != OPLOCK_NONE) { + status = pvfs_setup_oplock(f, oplock_granted); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + io->generic.out.oplock_level = oplock_granted; + io->generic.out.file.ntvfs = f->ntvfs; + io->generic.out.create_action = NTCREATEX_ACTION_CREATED; + io->generic.out.create_time = name->dos.create_time; + io->generic.out.access_time = name->dos.access_time; + io->generic.out.write_time = name->dos.write_time; + io->generic.out.change_time = name->dos.change_time; + io->generic.out.attrib = name->dos.attrib; + io->generic.out.alloc_size = name->dos.alloc_size; + io->generic.out.size = name->st.st_size; + io->generic.out.file_type = FILE_TYPE_DISK; + io->generic.out.ipc_state = 0; + io->generic.out.is_directory = 0; + + /* success - keep the file handle */ + status = ntvfs_handle_set_backend_data(h, pvfs->ntvfs, f); + if (!NT_STATUS_IS_OK(status)) { + goto cleanup_delete; + } + + f->handle->open_completed = true; + + notify_trigger(pvfs->notify_context, + NOTIFY_ACTION_ADDED, + FILE_NOTIFY_CHANGE_FILE_NAME, + name->full_name); + + return NT_STATUS_OK; + +cleanup_delete: + close(fd); + pvfs_sys_unlink(pvfs, name->full_name, name->allow_override); + return status; +} + +/* + state of a pending retry +*/ +struct pvfs_odb_retry { + struct ntvfs_module_context *ntvfs; + struct ntvfs_request *req; + DATA_BLOB odb_locking_key; + void *io; + void *private_data; + void (*callback)(struct pvfs_odb_retry *r, + struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + void *io, + void *private_data, + enum pvfs_wait_notice reason); +}; + +/* destroy a pending request */ +static int pvfs_odb_retry_destructor(struct pvfs_odb_retry *r) +{ + struct pvfs_state *pvfs = talloc_get_type(r->ntvfs->private_data, + struct pvfs_state); + if (r->odb_locking_key.data) { + struct odb_lock *lck; + lck = odb_lock(r->req, pvfs->odb_context, &r->odb_locking_key); + if (lck != NULL) { + odb_remove_pending(lck, r); + } + talloc_free(lck); + } + return 0; +} + +static void pvfs_odb_retry_callback(void *_r, enum pvfs_wait_notice reason) +{ + struct pvfs_odb_retry *r = talloc_get_type(_r, struct pvfs_odb_retry); + + if (reason == PVFS_WAIT_EVENT) { + /* + * The pending odb entry is already removed. + * We use a null locking key to indicate this + * to the destructor. + */ + data_blob_free(&r->odb_locking_key); + } + + r->callback(r, r->ntvfs, r->req, r->io, r->private_data, reason); +} + +/* + setup for a retry of a request that was rejected + by odb_can_open() +*/ +NTSTATUS pvfs_odb_retry_setup(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + struct odb_lock *lck, + struct timeval end_time, + void *io, + void *private_data, + void (*callback)(struct pvfs_odb_retry *r, + struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + void *io, + void *private_data, + enum pvfs_wait_notice reason)) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + struct pvfs_odb_retry *r; + struct pvfs_wait *wait_handle; + NTSTATUS status; + + r = talloc(req, struct pvfs_odb_retry); + NT_STATUS_HAVE_NO_MEMORY(r); + + r->ntvfs = ntvfs; + r->req = req; + r->io = io; + r->private_data = private_data; + r->callback = callback; + r->odb_locking_key = odb_get_key(r, lck); + if (r->odb_locking_key.data == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* setup a pending lock */ + status = odb_open_file_pending(lck, r); + if (NT_STATUS_EQUAL(NT_STATUS_OBJECT_NAME_NOT_FOUND,status)) { + /* + * maybe only a unix application + * has the file open + */ + data_blob_free(&r->odb_locking_key); + } else if (!NT_STATUS_IS_OK(status)) { + return status; + } + + talloc_free(lck); + + talloc_set_destructor(r, pvfs_odb_retry_destructor); + + wait_handle = pvfs_wait_message(pvfs, req, + MSG_PVFS_RETRY_OPEN, end_time, + pvfs_odb_retry_callback, r); + if (wait_handle == NULL) { + return NT_STATUS_NO_MEMORY; + } + + talloc_steal(r, wait_handle); + + return NT_STATUS_OK; +} + +/* + retry an open after a sharing violation +*/ +static void pvfs_retry_open_sharing(struct pvfs_odb_retry *r, + struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + void *_io, + void *private_data, + enum pvfs_wait_notice reason) +{ + union smb_open *io = talloc_get_type(_io, union smb_open); + struct timeval *final_timeout = NULL; + NTSTATUS status; + + if (private_data) { + final_timeout = talloc_get_type(private_data, + struct timeval); + } + + /* w2k3 ignores SMBntcancel for outstanding open requests. It's probably + just a bug in their server, but we better do the same */ + if (reason == PVFS_WAIT_CANCEL) { + return; + } + + if (reason == PVFS_WAIT_TIMEOUT) { + if (final_timeout && + !timeval_expired(final_timeout)) { + /* + * we need to retry periodictly + * after an EAGAIN as there's + * no way the kernel tell us + * an oplock is released. + */ + goto retry; + } + /* if it timed out, then give the failure + immediately */ + talloc_free(r); + req->async_states->status = NT_STATUS_SHARING_VIOLATION; + req->async_states->send_fn(req); + return; + } + +retry: + talloc_free(r); + + /* try the open again, which could trigger another retry setup + if it wants to, so we have to unmark the async flag so we + will know if it does a second async reply */ + req->async_states->state &= ~NTVFS_ASYNC_STATE_ASYNC; + + status = pvfs_open(ntvfs, req, io); + if (req->async_states->state & NTVFS_ASYNC_STATE_ASYNC) { + /* the 2nd try also replied async, so we don't send + the reply yet */ + return; + } + + /* re-mark it async, just in case someone up the chain does + paranoid checking */ + req->async_states->state |= NTVFS_ASYNC_STATE_ASYNC; + + /* send the reply up the chain */ + req->async_states->status = status; + req->async_states->send_fn(req); +} + + +/* + special handling for openx DENY_DOS semantics + + This function attempts a reference open using an existing handle. If its allowed, + then it returns NT_STATUS_OK, otherwise it returns any other code and normal + open processing continues. +*/ +static NTSTATUS pvfs_open_deny_dos(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_open *io, + struct pvfs_file *f, struct odb_lock *lck) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + struct pvfs_file *f2; + struct pvfs_filename *name; + NTSTATUS status; + + /* search for an existing open with the right parameters. Note + the magic ntcreatex options flag, which is set in the + generic mapping code. This might look ugly, but its + actually pretty much now w2k does it internally as well. + + If you look at the BASE-DENYDOS test you will see that a + DENY_DOS is a very special case, and in the right + circumstances you actually get the _same_ handle back + twice, rather than a new handle. + */ + for (f2=pvfs->files.list;f2;f2=f2->next) { + if (f2 != f && + f2->ntvfs->session_info == req->session_info && + f2->ntvfs->smbpid == req->smbpid && + (f2->handle->private_flags & + (NTCREATEX_FLAG_DENY_DOS | + NTCREATEX_FLAG_DENY_FCB)) && + (f2->access_mask & SEC_FILE_WRITE_DATA) && + strcasecmp_m(f2->handle->name->original_name, + io->generic.in.fname)==0) { + break; + } + } + + if (!f2) { + return NT_STATUS_SHARING_VIOLATION; + } + + /* quite an insane set of semantics ... */ + if (is_exe_filename(io->generic.in.fname) && + (f2->handle->private_flags & NTCREATEX_FLAG_DENY_DOS)) { + return NT_STATUS_SHARING_VIOLATION; + } + + /* + setup a reference to the existing handle + */ + talloc_free(f->handle); + f->handle = talloc_reference(f, f2->handle); + + talloc_free(lck); + + name = f->handle->name; + + io->generic.out.oplock_level = OPLOCK_NONE; + io->generic.out.file.ntvfs = f->ntvfs; + io->generic.out.create_action = NTCREATEX_ACTION_EXISTED; + io->generic.out.create_time = name->dos.create_time; + io->generic.out.access_time = name->dos.access_time; + io->generic.out.write_time = name->dos.write_time; + io->generic.out.change_time = name->dos.change_time; + io->generic.out.attrib = name->dos.attrib; + io->generic.out.alloc_size = name->dos.alloc_size; + io->generic.out.size = name->st.st_size; + io->generic.out.file_type = FILE_TYPE_DISK; + io->generic.out.ipc_state = 0; + io->generic.out.is_directory = 0; + + status = ntvfs_handle_set_backend_data(f->ntvfs, ntvfs, f); + NT_STATUS_NOT_OK_RETURN(status); + + return NT_STATUS_OK; +} + + + +/* + setup for a open retry after a sharing violation +*/ +static NTSTATUS pvfs_open_setup_retry(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_open *io, + struct pvfs_file *f, + struct odb_lock *lck, + NTSTATUS parent_status) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + NTSTATUS status; + struct timeval end_time; + struct timeval *final_timeout = NULL; + + if (io->generic.in.private_flags & + (NTCREATEX_FLAG_DENY_DOS | NTCREATEX_FLAG_DENY_FCB)) { + /* see if we can satisfy the request using the special DENY_DOS + code */ + status = pvfs_open_deny_dos(ntvfs, req, io, f, lck); + if (NT_STATUS_IS_OK(status)) { + return status; + } + } + + /* the retry should allocate a new file handle */ + talloc_free(f); + + if (NT_STATUS_EQUAL(parent_status, NT_STATUS_SHARING_VIOLATION)) { + end_time = timeval_add(&req->statistics.request_time, + 0, pvfs->sharing_violation_delay); + } else if (NT_STATUS_EQUAL(parent_status, NT_STATUS_OPLOCK_NOT_GRANTED)) { + end_time = timeval_add(&req->statistics.request_time, + pvfs->oplock_break_timeout, 0); + } else if (NT_STATUS_EQUAL(parent_status, STATUS_MORE_ENTRIES)) { + /* + * we got EAGAIN which means a unix application + * has an oplock or share mode + * + * we retry every 4/5 of the sharing violation delay + * to see if the unix application + * has released the oplock or share mode. + */ + final_timeout = talloc(req, struct timeval); + NT_STATUS_HAVE_NO_MEMORY(final_timeout); + *final_timeout = timeval_add(&req->statistics.request_time, + pvfs->oplock_break_timeout, + 0); + end_time = timeval_current_ofs_usec((pvfs->sharing_violation_delay*4)/5); + end_time = timeval_min(final_timeout, &end_time); + } else { + return NT_STATUS_INTERNAL_ERROR; + } + + return pvfs_odb_retry_setup(ntvfs, req, lck, end_time, io, + final_timeout, pvfs_retry_open_sharing); +} + +/* + open a file +*/ +NTSTATUS pvfs_open(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_open *io) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + int flags = 0; + struct pvfs_filename *name; + struct pvfs_file *f; + struct ntvfs_handle *h; + NTSTATUS status; + int fd, count; + struct odb_lock *lck; + uint32_t create_options; + uint32_t create_options_must_ignore_mask; + uint32_t share_access; + uint32_t access_mask; + uint32_t create_action = NTCREATEX_ACTION_EXISTED; + bool del_on_close; + bool stream_existed, stream_truncate=false; + uint32_t oplock_level = OPLOCK_NONE, oplock_granted; + bool allow_level_II_oplock = false; + + /* use the generic mapping code to avoid implementing all the + different open calls. */ + if (io->generic.level != RAW_OPEN_GENERIC && + io->generic.level != RAW_OPEN_NTTRANS_CREATE) { + return ntvfs_map_open(ntvfs, req, io); + } + + ZERO_STRUCT(io->generic.out); + + create_options = io->generic.in.create_options; + share_access = io->generic.in.share_access; + access_mask = io->generic.in.access_mask; + + if (share_access & ~NTCREATEX_SHARE_ACCESS_MASK) { + DEBUG(3,(__location__ ": Invalid share_access 0x%08x for %s\n", + share_access, io->ntcreatex.in.fname)); + return NT_STATUS_INVALID_PARAMETER; + } + + /* + * These options are ignored, + * but we reuse some of them as private values for the generic mapping + */ + create_options_must_ignore_mask = NTCREATEX_OPTIONS_MUST_IGNORE_MASK; + create_options &= ~create_options_must_ignore_mask; + + if (create_options & NTCREATEX_OPTIONS_NOT_SUPPORTED_MASK) { + DEBUG(2,(__location__ " create_options 0x%x not supported\n", + create_options)); + return NT_STATUS_NOT_SUPPORTED; + } + + if (create_options & NTCREATEX_OPTIONS_INVALID_PARAM_MASK) { + DEBUG(3,(__location__ ": Invalid create_options 0x%08x for %s\n", + create_options, io->ntcreatex.in.fname)); + return NT_STATUS_INVALID_PARAMETER; + } + + /* TODO: When we implement HSM, add a hook here not to pull + * the actual file off tape, when this option is passed from + * the client */ + if (create_options & NTCREATEX_OPTIONS_NO_RECALL) { + /* no-op */ + } + + /* TODO: If (unlikely) Linux does a good compressed + * filesystem, we might need an ioctl call for this */ + if (create_options & NTCREATEX_OPTIONS_NO_COMPRESSION) { + /* no-op */ + } + + if (create_options & NTCREATEX_OPTIONS_NO_INTERMEDIATE_BUFFERING) { + create_options |= NTCREATEX_OPTIONS_WRITE_THROUGH; + } + + /* Open the file with sync, if they asked for it, but + 'strict sync = no' turns this client request into a no-op */ + if (create_options & (NTCREATEX_OPTIONS_WRITE_THROUGH) && pvfs->flags & PVFS_FLAG_STRICT_SYNC) { + flags |= O_SYNC; + } + + + /* other create options are not allowed */ + if ((create_options & NTCREATEX_OPTIONS_DELETE_ON_CLOSE) && + !(access_mask & SEC_STD_DELETE)) { + DEBUG(3,(__location__ ": Invalid delete_on_close option 0x%08x with access_mask 0x%08x for %s\n", + create_options, access_mask, io->ntcreatex.in.fname)); + return NT_STATUS_INVALID_PARAMETER; + } + + if (access_mask & SEC_MASK_INVALID) { + return NT_STATUS_ACCESS_DENIED; + } + + /* what does this bit really mean?? */ + if (req->ctx->protocol >= PROTOCOL_SMB2_02 && + access_mask == SEC_STD_SYNCHRONIZE) { + return NT_STATUS_ACCESS_DENIED; + } + + /* cope with non-zero root_fid */ + if (io->ntcreatex.in.root_fid.ntvfs != NULL) { + f = pvfs_find_fd(pvfs, req, io->ntcreatex.in.root_fid.ntvfs); + if (f == NULL) { + return NT_STATUS_INVALID_HANDLE; + } + if (f->handle->fd != -1) { + return NT_STATUS_INVALID_DEVICE_REQUEST; + } + io->ntcreatex.in.fname = talloc_asprintf(req, "%s\\%s", + f->handle->name->original_name, + io->ntcreatex.in.fname); + NT_STATUS_HAVE_NO_MEMORY(io->ntcreatex.in.fname); + } + + if (io->ntcreatex.in.file_attr & (FILE_ATTRIBUTE_DEVICE| + FILE_ATTRIBUTE_VOLUME| + (~FILE_ATTRIBUTE_ALL_MASK))) { + DEBUG(3,(__location__ ": Invalid file_attr 0x%08x for %s\n", + io->ntcreatex.in.file_attr, io->ntcreatex.in.fname)); + return NT_STATUS_INVALID_PARAMETER; + } + + /* we ignore some file_attr bits */ + io->ntcreatex.in.file_attr &= ~(FILE_ATTRIBUTE_NONINDEXED | + FILE_ATTRIBUTE_COMPRESSED | + FILE_ATTRIBUTE_REPARSE_POINT | + FILE_ATTRIBUTE_SPARSE | + FILE_ATTRIBUTE_NORMAL); + + /* resolve the cifs name to a posix name */ + status = pvfs_resolve_name(pvfs, req, io->ntcreatex.in.fname, + PVFS_RESOLVE_STREAMS, &name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* if the client specified that it must not be a directory then + check that it isn't */ + if (name->exists && (name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) && + (io->generic.in.create_options & NTCREATEX_OPTIONS_NON_DIRECTORY_FILE)) { + return NT_STATUS_FILE_IS_A_DIRECTORY; + } + + /* if the client specified that it must be a directory then + check that it is */ + if (name->exists && !(name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) && + (io->generic.in.create_options & NTCREATEX_OPTIONS_DIRECTORY)) { + return NT_STATUS_NOT_A_DIRECTORY; + } + + /* directory opens are handled separately */ + if ((name->exists && (name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY)) || + (io->generic.in.create_options & NTCREATEX_OPTIONS_DIRECTORY)) { + return pvfs_open_directory(pvfs, req, name, io); + } + + /* FILE_ATTRIBUTE_DIRECTORY is ignored if the above test for directory + open doesn't match */ + io->generic.in.file_attr &= ~FILE_ATTRIBUTE_DIRECTORY; + + switch (io->generic.in.open_disposition) { + case NTCREATEX_DISP_SUPERSEDE: + case NTCREATEX_DISP_OVERWRITE_IF: + if (name->stream_name == NULL) { + flags |= O_TRUNC; + } else { + stream_truncate = true; + } + create_action = NTCREATEX_ACTION_TRUNCATED; + break; + + case NTCREATEX_DISP_OPEN: + if (!name->stream_exists) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + break; + + case NTCREATEX_DISP_OVERWRITE: + if (!name->stream_exists) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + if (name->stream_name == NULL) { + flags |= O_TRUNC; + } else { + stream_truncate = true; + } + create_action = NTCREATEX_ACTION_TRUNCATED; + break; + + case NTCREATEX_DISP_CREATE: + if (name->stream_exists) { + return NT_STATUS_OBJECT_NAME_COLLISION; + } + break; + + case NTCREATEX_DISP_OPEN_IF: + break; + + default: + DEBUG(3,(__location__ ": Invalid open disposition 0x%08x for %s\n", + io->generic.in.open_disposition, name->original_name)); + return NT_STATUS_INVALID_PARAMETER; + } + + /* handle creating a new file separately */ + if (!name->exists) { + status = pvfs_create_file(pvfs, req, name, io); + if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) { + return status; + } + + /* we've hit a race - the file was created during this call */ + if (io->generic.in.open_disposition == NTCREATEX_DISP_CREATE) { + return status; + } + + /* try re-resolving the name */ + status = pvfs_resolve_name(pvfs, req, io->ntcreatex.in.fname, 0, &name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + /* fall through to a normal open */ + } + + if ((name->dos.attrib & FILE_ATTRIBUTE_READONLY) && + (create_options & NTCREATEX_OPTIONS_DELETE_ON_CLOSE)) { + return NT_STATUS_CANNOT_DELETE; + } + + /* check the security descriptor */ + status = pvfs_access_check(pvfs, req, name, &access_mask); + NT_STATUS_NOT_OK_RETURN(status); + + if (io->generic.in.query_maximal_access) { + status = pvfs_access_maximal_allowed(pvfs, req, name, + &io->generic.out.maximal_access); + NT_STATUS_NOT_OK_RETURN(status); + } + + if (io->generic.in.query_on_disk_id) { + ZERO_ARRAY(io->generic.out.on_disk_id); + SBVAL(io->generic.out.on_disk_id, 0, name->st.st_ino); + SBVAL(io->generic.out.on_disk_id, 8, name->st.st_dev); + } + + status = ntvfs_handle_new(pvfs->ntvfs, req, &h); + NT_STATUS_NOT_OK_RETURN(status); + + f = talloc(h, struct pvfs_file); + if (f == NULL) { + return NT_STATUS_NO_MEMORY; + } + + f->handle = talloc(f, struct pvfs_file_handle); + if (f->handle == NULL) { + return NT_STATUS_NO_MEMORY; + } + + f->ntvfs = h; + f->pvfs = pvfs; + f->pending_list = NULL; + f->lock_count = 0; + f->share_access = io->generic.in.share_access; + f->access_mask = access_mask; + f->impersonation = io->generic.in.impersonation; + f->notify_buffer = NULL; + f->search = NULL; + + f->handle->pvfs = pvfs; + f->handle->fd = -1; + f->handle->name = talloc_steal(f->handle, name); + f->handle->create_options = io->generic.in.create_options; + f->handle->private_flags = io->generic.in.private_flags; + f->handle->seek_offset = 0; + f->handle->position = 0; + f->handle->mode = 0; + f->handle->oplock = NULL; + f->handle->have_opendb_entry = false; + ZERO_STRUCT(f->handle->write_time); + f->handle->open_completed = false; + + /* form the lock context used for byte range locking and + opendb locking */ + status = pvfs_locking_key(name, f->handle, &f->handle->odb_locking_key); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = pvfs_brl_locking_handle(f, name, h, &f->brl_handle); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* get a lock on this file before the actual open */ + lck = odb_lock(req, pvfs->odb_context, &f->handle->odb_locking_key); + if (lck == NULL) { + DEBUG(0,("pvfs_open: failed to lock file '%s' in opendb\n", + name->full_name)); + /* we were supposed to do a blocking lock, so something + is badly wrong! */ + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + DLIST_ADD(pvfs->files.list, f); + + /* setup a destructor to avoid file descriptor leaks on + abnormal termination */ + talloc_set_destructor(f, pvfs_fnum_destructor); + talloc_set_destructor(f->handle, pvfs_handle_destructor); + + /* + * Only SMB2 takes care of the delete_on_close, + * on existing files + */ + if (create_options & NTCREATEX_OPTIONS_DELETE_ON_CLOSE && + req->ctx->protocol >= PROTOCOL_SMB2_02) { + del_on_close = true; + } else { + del_on_close = false; + } + + if (pvfs->flags & PVFS_FLAG_FAKE_OPLOCKS) { + oplock_level = OPLOCK_NONE; + } else if (io->ntcreatex.in.flags & NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK) { + oplock_level = OPLOCK_BATCH; + } else if (io->ntcreatex.in.flags & NTCREATEX_FLAGS_REQUEST_OPLOCK) { + oplock_level = OPLOCK_EXCLUSIVE; + } + + if (req->client_caps & NTVFS_CLIENT_CAP_LEVEL_II_OPLOCKS) { + allow_level_II_oplock = true; + } + + /* see if we are allowed to open at the same time as existing opens */ + status = odb_can_open(lck, name->stream_id, + share_access, access_mask, del_on_close, + io->generic.in.open_disposition, false); + + /* + * on a sharing violation we need to retry when the file is closed by + * the other user, or after 1 second + * on a non granted oplock we need to retry when the file is closed by + * the other user, or after 30 seconds + */ + if ((NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) || + NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) && + (req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return pvfs_open_setup_retry(ntvfs, req, io, f, lck, status); + } + + if (!NT_STATUS_IS_OK(status)) { + talloc_free(lck); + return status; + } + + if (access_mask & (SEC_FILE_WRITE_DATA | SEC_FILE_APPEND_DATA)) { + flags |= O_RDWR; + } else { + flags |= O_RDONLY; + } + + /* do the actual open */ + fd = pvfs_sys_open(pvfs, f->handle->name->full_name, flags | O_NONBLOCK, 0, name->allow_override); + if (fd == -1) { + status = pvfs_map_errno(f->pvfs, errno); + + DEBUG(0,(__location__ " mapped errno %s for %s (was %d)\n", + nt_errstr(status), f->handle->name->full_name, errno)); + /* + * STATUS_MORE_ENTRIES is EAGAIN or EWOULDBLOCK + */ + if (NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES) && + (req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return pvfs_open_setup_retry(ntvfs, req, io, f, lck, status); + } + + talloc_free(lck); + return status; + } + + f->handle->fd = fd; + + status = brlock_count(f->pvfs->brl_context, f->brl_handle, &count); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(lck); + return status; + } + + if (count != 0) { + oplock_level = OPLOCK_NONE; + } + + /* now really mark the file as open */ + status = odb_open_file(lck, f->handle, name->full_name, + &f->handle->fd, name->dos.write_time, + allow_level_II_oplock, + oplock_level, &oplock_granted); + + if (!NT_STATUS_IS_OK(status)) { + talloc_free(lck); + return status; + } + + f->handle->have_opendb_entry = true; + + if (pvfs->flags & PVFS_FLAG_FAKE_OPLOCKS) { + oplock_granted = OPLOCK_BATCH; + } else if (oplock_granted != OPLOCK_NONE) { + status = pvfs_setup_oplock(f, oplock_granted); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(lck); + return status; + } + } + + stream_existed = name->stream_exists; + + /* if this was a stream create then create the stream as well */ + if (!name->stream_exists) { + status = pvfs_stream_create(pvfs, f->handle->name, fd); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(lck); + return status; + } + if (stream_truncate) { + status = pvfs_stream_truncate(pvfs, f->handle->name, fd, 0); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(lck); + return status; + } + } + } + + /* re-resolve the open fd */ + status = pvfs_resolve_name_fd(f->pvfs, fd, f->handle->name, PVFS_RESOLVE_NO_OPENDB); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(lck); + return status; + } + + if (f->handle->name->stream_id == 0 && + (io->generic.in.open_disposition == NTCREATEX_DISP_OVERWRITE || + io->generic.in.open_disposition == NTCREATEX_DISP_OVERWRITE_IF)) { + /* for overwrite we may need to replace file permissions */ + uint32_t attrib = io->ntcreatex.in.file_attr | FILE_ATTRIBUTE_ARCHIVE; + mode_t mode = pvfs_fileperms(pvfs, attrib); + if (f->handle->name->st.st_mode != mode && + f->handle->name->dos.attrib != attrib && + pvfs_sys_fchmod(pvfs, fd, mode, name->allow_override) == -1) { + talloc_free(lck); + return pvfs_map_errno(pvfs, errno); + } + name->dos.alloc_size = io->ntcreatex.in.alloc_size; + name->dos.attrib = attrib; + status = pvfs_dosattrib_save(pvfs, name, fd); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(lck); + return status; + } + } + + talloc_free(lck); + + status = ntvfs_handle_set_backend_data(h, ntvfs, f); + NT_STATUS_NOT_OK_RETURN(status); + + /* mark the open as having completed fully, so delete on close + can now be used */ + f->handle->open_completed = true; + + io->generic.out.oplock_level = oplock_granted; + io->generic.out.file.ntvfs = h; + io->generic.out.create_action = stream_existed? + create_action:NTCREATEX_ACTION_CREATED; + + io->generic.out.create_time = name->dos.create_time; + io->generic.out.access_time = name->dos.access_time; + io->generic.out.write_time = name->dos.write_time; + io->generic.out.change_time = name->dos.change_time; + io->generic.out.attrib = name->dos.attrib; + io->generic.out.alloc_size = name->dos.alloc_size; + io->generic.out.size = name->st.st_size; + io->generic.out.file_type = FILE_TYPE_DISK; + io->generic.out.ipc_state = 0; + io->generic.out.is_directory = 0; + + return NT_STATUS_OK; +} + + +/* + close a file +*/ +NTSTATUS pvfs_close(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_close *io) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + struct pvfs_file *f; + + if (io->generic.level == RAW_CLOSE_SPLCLOSE) { + return NT_STATUS_DOS(ERRSRV, ERRerror); + } + + if (io->generic.level != RAW_CLOSE_GENERIC) { + return ntvfs_map_close(ntvfs, req, io); + } + + f = pvfs_find_fd(pvfs, req, io->generic.in.file.ntvfs); + if (!f) { + return NT_STATUS_INVALID_HANDLE; + } + + if (!null_time(io->generic.in.write_time)) { + f->handle->write_time.update_forced = false; + f->handle->write_time.update_on_close = true; + unix_to_nt_time(&f->handle->write_time.close_time, io->generic.in.write_time); + } + + if (io->generic.in.flags & SMB2_CLOSE_FLAGS_FULL_INFORMATION) { + struct pvfs_filename *name; + NTSTATUS status; + struct pvfs_file_handle *h = f->handle; + + status = pvfs_resolve_name_handle(pvfs, h); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + name = h->name; + + io->generic.out.flags = SMB2_CLOSE_FLAGS_FULL_INFORMATION; + io->generic.out.create_time = name->dos.create_time; + io->generic.out.access_time = name->dos.access_time; + io->generic.out.write_time = name->dos.write_time; + io->generic.out.change_time = name->dos.change_time; + io->generic.out.alloc_size = name->dos.alloc_size; + io->generic.out.size = name->st.st_size; + io->generic.out.file_attr = name->dos.attrib; + } else { + ZERO_STRUCT(io->generic.out); + } + + talloc_free(f); + + return NT_STATUS_OK; +} + + +/* + logoff - close all file descriptors open by a vuid +*/ +NTSTATUS pvfs_logoff(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + struct pvfs_file *f, *next; + + /* If pvfs is NULL, we never logged on, and no files are open. */ + if(pvfs == NULL) { + return NT_STATUS_OK; + } + + for (f=pvfs->files.list;f;f=next) { + next = f->next; + if (f->ntvfs->session_info == req->session_info) { + talloc_free(f); + } + } + + return NT_STATUS_OK; +} + + +/* + exit - close files for the current pid +*/ +NTSTATUS pvfs_exit(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + struct pvfs_file *f, *next; + + for (f=pvfs->files.list;f;f=next) { + next = f->next; + if (f->ntvfs->session_info == req->session_info && + f->ntvfs->smbpid == req->smbpid) { + talloc_free(f); + } + } + + return NT_STATUS_OK; +} + + +/* + change the delete on close flag on an already open file +*/ +NTSTATUS pvfs_set_delete_on_close(struct pvfs_state *pvfs, + struct ntvfs_request *req, + struct pvfs_file *f, bool del_on_close) +{ + struct odb_lock *lck; + NTSTATUS status; + + if ((f->handle->name->dos.attrib & FILE_ATTRIBUTE_READONLY) && del_on_close) { + return NT_STATUS_CANNOT_DELETE; + } + + if ((f->handle->name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) && + !pvfs_directory_empty(pvfs, f->handle->name)) { + return NT_STATUS_DIRECTORY_NOT_EMPTY; + } + + if (del_on_close) { + f->handle->create_options |= NTCREATEX_OPTIONS_DELETE_ON_CLOSE; + } else { + f->handle->create_options &= ~NTCREATEX_OPTIONS_DELETE_ON_CLOSE; + } + + lck = odb_lock(req, pvfs->odb_context, &f->handle->odb_locking_key); + if (lck == NULL) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + status = odb_set_delete_on_close(lck, del_on_close); + + talloc_free(lck); + + return status; +} + + +/* + determine if a file can be deleted, or if it is prevented by an + already open file +*/ +NTSTATUS pvfs_can_delete(struct pvfs_state *pvfs, + struct ntvfs_request *req, + struct pvfs_filename *name, + struct odb_lock **lckp) +{ + NTSTATUS status; + DATA_BLOB key; + struct odb_lock *lck; + uint32_t share_access; + uint32_t access_mask; + bool delete_on_close; + + status = pvfs_locking_key(name, name, &key); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_NO_MEMORY; + } + + lck = odb_lock(req, pvfs->odb_context, &key); + if (lck == NULL) { + DEBUG(0,("Unable to lock opendb for can_delete\n")); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + access_mask = SEC_STD_DELETE; + delete_on_close = true; + + status = odb_can_open(lck, name->stream_id, + share_access, access_mask, delete_on_close, + NTCREATEX_DISP_OPEN, false); + + if (NT_STATUS_IS_OK(status)) { + status = pvfs_access_check_simple(pvfs, req, name, access_mask); + } + + /* + * if it's a sharing violation or we got no oplock + * only keep the lock if the caller requested access + * to the lock + */ + if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) || + NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) { + if (lckp) { + *lckp = lck; + } else { + talloc_free(lck); + } + } else if (!NT_STATUS_IS_OK(status)) { + talloc_free(lck); + if (lckp) { + *lckp = NULL; + } + } else if (lckp) { + *lckp = lck; + } + + return status; +} + +/* + determine if a file can be renamed, or if it is prevented by an + already open file +*/ +NTSTATUS pvfs_can_rename(struct pvfs_state *pvfs, + struct ntvfs_request *req, + struct pvfs_filename *name, + struct odb_lock **lckp) +{ + NTSTATUS status; + DATA_BLOB key; + struct odb_lock *lck; + uint32_t share_access; + uint32_t access_mask; + bool delete_on_close; + + status = pvfs_locking_key(name, name, &key); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_NO_MEMORY; + } + + lck = odb_lock(req, pvfs->odb_context, &key); + if (lck == NULL) { + DEBUG(0,("Unable to lock opendb for can_stat\n")); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + access_mask = SEC_STD_DELETE; + delete_on_close = false; + + status = odb_can_open(lck, name->stream_id, + share_access, access_mask, delete_on_close, + NTCREATEX_DISP_OPEN, false); + + /* + * if it's a sharing violation or we got no oplock + * only keep the lock if the caller requested access + * to the lock + */ + if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) || + NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) { + if (lckp) { + *lckp = lck; + } else { + talloc_free(lck); + } + } else if (!NT_STATUS_IS_OK(status)) { + talloc_free(lck); + if (lckp) { + *lckp = NULL; + } + } else if (lckp) { + *lckp = lck; + } + + return status; +} + +/* + determine if the file size of a file can be changed, + or if it is prevented by an already open file +*/ +NTSTATUS pvfs_can_update_file_size(struct pvfs_state *pvfs, + struct ntvfs_request *req, + struct pvfs_filename *name, + struct odb_lock **lckp) +{ + NTSTATUS status; + DATA_BLOB key; + struct odb_lock *lck; + uint32_t share_access; + uint32_t access_mask; + bool break_to_none; + bool delete_on_close; + + status = pvfs_locking_key(name, name, &key); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_NO_MEMORY; + } + + lck = odb_lock(req, pvfs->odb_context, &key); + if (lck == NULL) { + DEBUG(0,("Unable to lock opendb for can_stat\n")); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + /* + * this code previous set only SEC_FILE_WRITE_ATTRIBUTE, with + * a comment that this seemed to be wrong, but matched windows + * behaviour. It now appears that this windows behaviour is + * just a bug. + */ + access_mask = SEC_FILE_WRITE_ATTRIBUTE | SEC_FILE_WRITE_DATA | SEC_FILE_APPEND_DATA; + delete_on_close = false; + break_to_none = true; + + status = odb_can_open(lck, name->stream_id, + share_access, access_mask, delete_on_close, + NTCREATEX_DISP_OPEN, break_to_none); + + /* + * if it's a sharing violation or we got no oplock + * only keep the lock if the caller requested access + * to the lock + */ + if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) || + NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) { + if (lckp) { + *lckp = lck; + } else { + talloc_free(lck); + } + } else if (!NT_STATUS_IS_OK(status)) { + talloc_free(lck); + if (lckp) { + *lckp = NULL; + } + } else if (lckp) { + *lckp = lck; + } + + return status; +} + +/* + determine if file meta data can be accessed, or if it is prevented by an + already open file +*/ +NTSTATUS pvfs_can_stat(struct pvfs_state *pvfs, + struct ntvfs_request *req, + struct pvfs_filename *name) +{ + NTSTATUS status; + DATA_BLOB key; + struct odb_lock *lck; + uint32_t share_access; + uint32_t access_mask; + bool delete_on_close; + + status = pvfs_locking_key(name, name, &key); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_NO_MEMORY; + } + + lck = odb_lock(req, pvfs->odb_context, &key); + if (lck == NULL) { + DEBUG(0,("Unable to lock opendb for can_stat\n")); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + access_mask = SEC_FILE_READ_ATTRIBUTE; + delete_on_close = false; + + status = odb_can_open(lck, name->stream_id, + share_access, access_mask, delete_on_close, + NTCREATEX_DISP_OPEN, false); + + if (!NT_STATUS_IS_OK(status)) { + talloc_free(lck); + } + + return status; +} + + +/* + determine if delete on close is set on +*/ +bool pvfs_delete_on_close_set(struct pvfs_state *pvfs, struct pvfs_file_handle *h) +{ + NTSTATUS status; + bool del_on_close; + + status = odb_get_file_infos(pvfs->odb_context, &h->odb_locking_key, + &del_on_close, NULL); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1,("WARNING: unable to determine delete on close status for open file\n")); + return false; + } + + return del_on_close; +} diff --git a/source4/ntvfs/posix/pvfs_oplock.c b/source4/ntvfs/posix/pvfs_oplock.c new file mode 100644 index 0000000..ba5d25d --- /dev/null +++ b/source4/ntvfs/posix/pvfs_oplock.c @@ -0,0 +1,307 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - oplock handling + + Copyright (C) Stefan Metzmacher 2008 + + 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 "lib/messaging/messaging.h" +#include "lib/messaging/irpc.h" +#include "system/time.h" +#include "vfs_posix.h" + + +struct pvfs_oplock { + struct pvfs_file_handle *handle; + struct pvfs_file *file; + uint32_t level; + struct timeval break_to_level_II; + struct timeval break_to_none; + struct imessaging_context *msg_ctx; +}; + +static NTSTATUS pvfs_oplock_release_internal(struct pvfs_file_handle *h, + uint8_t oplock_break) +{ + struct odb_lock *olck; + NTSTATUS status; + + if (h->fd == -1) { + return NT_STATUS_FILE_IS_A_DIRECTORY; + } + + if (!h->have_opendb_entry) { + return NT_STATUS_FOOBAR; + } + + if (!h->oplock) { + return NT_STATUS_FOOBAR; + } + + olck = odb_lock(h, h->pvfs->odb_context, &h->odb_locking_key); + if (olck == NULL) { + DEBUG(0,("Unable to lock opendb for oplock update\n")); + return NT_STATUS_FOOBAR; + } + + if (oplock_break == OPLOCK_BREAK_TO_NONE) { + h->oplock->level = OPLOCK_NONE; + } else if (oplock_break == OPLOCK_BREAK_TO_LEVEL_II) { + h->oplock->level = OPLOCK_LEVEL_II; + } else { + /* fallback to level II in case of a invalid value */ + DEBUG(1,("unexpected oplock break level[0x%02X]\n", oplock_break)); + h->oplock->level = OPLOCK_LEVEL_II; + } + status = odb_update_oplock(olck, h, h->oplock->level); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Unable to update oplock level for '%s' - %s\n", + h->name->full_name, nt_errstr(status))); + talloc_free(olck); + return status; + } + + talloc_free(olck); + + /* after a break to none, we no longer have an oplock attached */ + if (h->oplock->level == OPLOCK_NONE) { + talloc_free(h->oplock); + h->oplock = NULL; + } + + return NT_STATUS_OK; +} + +/* + receive oplock breaks and forward them to the client +*/ +static void pvfs_oplock_break(struct pvfs_oplock *opl, uint8_t level) +{ + NTSTATUS status; + struct pvfs_file *f = opl->file; + struct pvfs_file_handle *h = opl->handle; + struct pvfs_state *pvfs = h->pvfs; + struct timeval cur = timeval_current(); + struct timeval *last = NULL; + struct timeval end; + + switch (level) { + case OPLOCK_BREAK_TO_LEVEL_II: + last = &opl->break_to_level_II; + break; + case OPLOCK_BREAK_TO_NONE: + last = &opl->break_to_none; + break; + } + + if (!last) { + DEBUG(0,("%s: got unexpected level[0x%02X]\n", + __FUNCTION__, level)); + return; + } + + if (timeval_is_zero(last)) { + /* + * this is the first break we for this level + * remember the time + */ + *last = cur; + + DEBUG(5,("%s: sending oplock break level %d for '%s' %p\n", + __FUNCTION__, level, h->name->original_name, h)); + status = ntvfs_send_oplock_break(pvfs->ntvfs, f->ntvfs, level); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("%s: sending oplock break failed: %s\n", + __FUNCTION__, nt_errstr(status))); + } + return; + } + + end = timeval_add(last, pvfs->oplock_break_timeout, 0); + + if (timeval_compare(&cur, &end) < 0) { + /* + * If it's not expired just ignore the break + * as we already sent the break request to the client + */ + DEBUG(0,("%s: do not resend oplock break level %d for '%s' %p\n", + __FUNCTION__, level, h->name->original_name, h)); + return; + } + + /* + * If the client did not send a release within the + * oplock break timeout time frame we auto release + * the oplock + */ + DEBUG(0,("%s: auto release oplock level %d for '%s' %p\n", + __FUNCTION__, level, h->name->original_name, h)); + status = pvfs_oplock_release_internal(h, level); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("%s: failed to auto release the oplock[0x%02X]: %s\n", + __FUNCTION__, level, nt_errstr(status))); + } +} + +static void pvfs_oplock_break_dispatch(struct imessaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id src, + size_t num_fds, + int *fds, + DATA_BLOB *data) +{ + struct pvfs_oplock *opl = talloc_get_type(private_data, + struct pvfs_oplock); + struct opendb_oplock_break opb; + + if (num_fds != 0) { + DBG_WARNING("Received %zu fds, ignoring message\n", num_fds); + return; + } + + ZERO_STRUCT(opb); + + /* we need to check that this one is for us. See + imessaging_send_ptr() for the other side of this. + */ + if (data->length == sizeof(struct opendb_oplock_break)) { + struct opendb_oplock_break *p; + p = (struct opendb_oplock_break *)data->data; + opb = *p; + } else { + DEBUG(0,("%s: ignore oplock break with length[%u]\n", + __location__, (unsigned)data->length)); + return; + } + if (opb.file_handle != opl->handle) { + return; + } + + /* + * maybe we should use ntvfs_setup_async() + */ + pvfs_oplock_break(opl, opb.level); +} + +static int pvfs_oplock_destructor(struct pvfs_oplock *opl) +{ + imessaging_deregister(opl->msg_ctx, MSG_NTVFS_OPLOCK_BREAK, opl); + return 0; +} + +NTSTATUS pvfs_setup_oplock(struct pvfs_file *f, uint32_t oplock_granted) +{ + NTSTATUS status; + struct pvfs_oplock *opl; + uint32_t level = OPLOCK_NONE; + + f->handle->oplock = NULL; + + switch (oplock_granted) { + case EXCLUSIVE_OPLOCK_RETURN: + level = OPLOCK_EXCLUSIVE; + break; + case BATCH_OPLOCK_RETURN: + level = OPLOCK_BATCH; + break; + case LEVEL_II_OPLOCK_RETURN: + level = OPLOCK_LEVEL_II; + break; + } + + if (level == OPLOCK_NONE) { + return NT_STATUS_OK; + } + + opl = talloc_zero(f->handle, struct pvfs_oplock); + NT_STATUS_HAVE_NO_MEMORY(opl); + + opl->handle = f->handle; + opl->file = f; + opl->level = level; + opl->msg_ctx = f->pvfs->ntvfs->ctx->msg_ctx; + + status = imessaging_register(opl->msg_ctx, + opl, + MSG_NTVFS_OPLOCK_BREAK, + pvfs_oplock_break_dispatch); + NT_STATUS_NOT_OK_RETURN(status); + + /* destructor */ + talloc_set_destructor(opl, pvfs_oplock_destructor); + + f->handle->oplock = opl; + + return NT_STATUS_OK; +} + +NTSTATUS pvfs_oplock_release(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_lock *lck) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + struct pvfs_file *f; + uint8_t oplock_break; + NTSTATUS status; + + f = pvfs_find_fd(pvfs, req, lck->lockx.in.file.ntvfs); + if (!f) { + return NT_STATUS_INVALID_HANDLE; + } + + oplock_break = (lck->lockx.in.mode >> 8) & 0xFF; + + status = pvfs_oplock_release_internal(f->handle, oplock_break); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("%s: failed to release the oplock[0x%02X]: %s\n", + __FUNCTION__, oplock_break, nt_errstr(status))); + return status; + } + + return NT_STATUS_OK; +} + +NTSTATUS pvfs_break_level2_oplocks(struct pvfs_file *f) +{ + struct pvfs_file_handle *h = f->handle; + struct odb_lock *olck; + NTSTATUS status; + + if (h->oplock && h->oplock->level != OPLOCK_LEVEL_II) { + return NT_STATUS_OK; + } + + olck = odb_lock(h, h->pvfs->odb_context, &h->odb_locking_key); + if (olck == NULL) { + DEBUG(0,("Unable to lock opendb for oplock update\n")); + return NT_STATUS_FOOBAR; + } + + status = odb_break_oplocks(olck); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Unable to break level2 oplocks to none for '%s' - %s\n", + h->name->full_name, nt_errstr(status))); + talloc_free(olck); + return status; + } + + talloc_free(olck); + + return NT_STATUS_OK; +} diff --git a/source4/ntvfs/posix/pvfs_qfileinfo.c b/source4/ntvfs/posix/pvfs_qfileinfo.c new file mode 100644 index 0000000..be601a2 --- /dev/null +++ b/source4/ntvfs/posix/pvfs_qfileinfo.c @@ -0,0 +1,468 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - read + + Copyright (C) Andrew Tridgell 2004 + + 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 "vfs_posix.h" +#include "librpc/gen_ndr/xattr.h" + + +/* + determine what access bits are needed for a call +*/ +static uint32_t pvfs_fileinfo_access(union smb_fileinfo *info) +{ + uint32_t needed; + + switch (info->generic.level) { + case RAW_FILEINFO_EA_LIST: + case RAW_FILEINFO_ALL_EAS: + needed = SEC_FILE_READ_EA; + break; + + case RAW_FILEINFO_IS_NAME_VALID: + needed = 0; + break; + + case RAW_FILEINFO_ACCESS_INFORMATION: + needed = 0; + break; + + case RAW_FILEINFO_STREAM_INFO: + case RAW_FILEINFO_STREAM_INFORMATION: + needed = 0; + break; + + case RAW_FILEINFO_SEC_DESC: + needed = 0; + if (info->query_secdesc.in.secinfo_flags & (SECINFO_OWNER|SECINFO_GROUP)) { + needed |= SEC_STD_READ_CONTROL; + } + if (info->query_secdesc.in.secinfo_flags & SECINFO_DACL) { + needed |= SEC_STD_READ_CONTROL; + } + if (info->query_secdesc.in.secinfo_flags & SECINFO_SACL) { + needed |= SEC_FLAG_SYSTEM_SECURITY; + } + break; + + default: + needed = SEC_FILE_READ_ATTRIBUTE; + break; + } + + return needed; +} + +/* + reply to a RAW_FILEINFO_EA_LIST call +*/ +NTSTATUS pvfs_query_ea_list(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx, + struct pvfs_filename *name, int fd, + unsigned int num_names, + struct ea_name *names, + struct smb_ea_list *eas) +{ + NTSTATUS status; + int i; + struct xattr_DosEAs *ealist = talloc(mem_ctx, struct xattr_DosEAs); + + ZERO_STRUCTP(eas); + status = pvfs_doseas_load(pvfs, name, fd, ealist); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + eas->eas = talloc_array(mem_ctx, struct ea_struct, num_names); + if (eas->eas == NULL) { + return NT_STATUS_NO_MEMORY; + } + eas->num_eas = num_names; + for (i=0;i<num_names;i++) { + int j; + eas->eas[i].flags = 0; + eas->eas[i].name.s = names[i].name.s; + eas->eas[i].value = data_blob(NULL, 0); + for (j=0;j<ealist->num_eas;j++) { + if (strcasecmp_m(eas->eas[i].name.s, + ealist->eas[j].name) == 0) { + if (ealist->eas[j].value.length == 0) { + continue; + } + eas->eas[i].value = ealist->eas[j].value; + break; + } + } + } + return NT_STATUS_OK; +} + +/* + reply to a RAW_FILEINFO_ALL_EAS call +*/ +static NTSTATUS pvfs_query_all_eas(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx, + struct pvfs_filename *name, int fd, + struct smb_ea_list *eas) +{ + NTSTATUS status; + int i; + struct xattr_DosEAs *ealist = talloc(mem_ctx, struct xattr_DosEAs); + + ZERO_STRUCTP(eas); + status = pvfs_doseas_load(pvfs, name, fd, ealist); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + eas->eas = talloc_array(mem_ctx, struct ea_struct, ealist->num_eas); + if (eas->eas == NULL) { + return NT_STATUS_NO_MEMORY; + } + eas->num_eas = 0; + for (i=0;i<ealist->num_eas;i++) { + eas->eas[eas->num_eas].flags = 0; + eas->eas[eas->num_eas].name.s = ealist->eas[i].name; + if (ealist->eas[i].value.length == 0) { + continue; + } + eas->eas[eas->num_eas].value = ealist->eas[i].value; + eas->num_eas++; + } + return NT_STATUS_OK; +} + +/* + approximately map a struct pvfs_filename to a generic fileinfo struct +*/ +static NTSTATUS pvfs_map_fileinfo(struct pvfs_state *pvfs, + struct ntvfs_request *req, + struct pvfs_filename *name, union smb_fileinfo *info, + int fd) +{ + switch (info->generic.level) { + case RAW_FILEINFO_GETATTR: + info->getattr.out.attrib = name->dos.attrib; + info->getattr.out.size = name->st.st_size; + info->getattr.out.write_time = nt_time_to_unix(name->dos.write_time); + return NT_STATUS_OK; + + case RAW_FILEINFO_GETATTRE: + case RAW_FILEINFO_STANDARD: + info->standard.out.create_time = nt_time_to_unix(name->dos.create_time); + info->standard.out.access_time = nt_time_to_unix(name->dos.access_time); + info->standard.out.write_time = nt_time_to_unix(name->dos.write_time); + info->standard.out.size = name->st.st_size; + info->standard.out.alloc_size = name->dos.alloc_size; + info->standard.out.attrib = name->dos.attrib; + return NT_STATUS_OK; + + case RAW_FILEINFO_EA_SIZE: + info->ea_size.out.create_time = nt_time_to_unix(name->dos.create_time); + info->ea_size.out.access_time = nt_time_to_unix(name->dos.access_time); + info->ea_size.out.write_time = nt_time_to_unix(name->dos.write_time); + info->ea_size.out.size = name->st.st_size; + info->ea_size.out.alloc_size = name->dos.alloc_size; + info->ea_size.out.attrib = name->dos.attrib; + info->ea_size.out.ea_size = name->dos.ea_size; + return NT_STATUS_OK; + + case RAW_FILEINFO_EA_LIST: + return pvfs_query_ea_list(pvfs, req, name, fd, + info->ea_list.in.num_names, + info->ea_list.in.ea_names, + &info->ea_list.out); + + case RAW_FILEINFO_ALL_EAS: + return pvfs_query_all_eas(pvfs, req, name, fd, &info->all_eas.out); + + case RAW_FILEINFO_SMB2_ALL_EAS: { + NTSTATUS status = pvfs_query_all_eas(pvfs, req, name, fd, &info->all_eas.out); + if (NT_STATUS_IS_OK(status) && + info->all_eas.out.num_eas == 0) { + return NT_STATUS_NO_EAS_ON_FILE; + } + return status; + } + + case RAW_FILEINFO_IS_NAME_VALID: + return NT_STATUS_OK; + + case RAW_FILEINFO_BASIC_INFO: + case RAW_FILEINFO_BASIC_INFORMATION: + info->basic_info.out.create_time = name->dos.create_time; + info->basic_info.out.access_time = name->dos.access_time; + info->basic_info.out.write_time = name->dos.write_time; + info->basic_info.out.change_time = name->dos.change_time; + info->basic_info.out.attrib = name->dos.attrib; + return NT_STATUS_OK; + + case RAW_FILEINFO_STANDARD_INFO: + case RAW_FILEINFO_STANDARD_INFORMATION: + info->standard_info.out.alloc_size = name->dos.alloc_size; + info->standard_info.out.size = name->st.st_size; + info->standard_info.out.nlink = name->dos.nlink; + info->standard_info.out.delete_pending = 0; /* only for qfileinfo */ + info->standard_info.out.directory = + (name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY)? 1 : 0; + return NT_STATUS_OK; + + case RAW_FILEINFO_EA_INFO: + case RAW_FILEINFO_EA_INFORMATION: + info->ea_info.out.ea_size = name->dos.ea_size; + return NT_STATUS_OK; + + case RAW_FILEINFO_NAME_INFO: + case RAW_FILEINFO_NAME_INFORMATION: + if (req->ctx->protocol >= PROTOCOL_SMB2_02) { + /* strange that SMB2 doesn't have this */ + return NT_STATUS_NOT_SUPPORTED; + } + info->name_info.out.fname.s = name->original_name; + return NT_STATUS_OK; + + case RAW_FILEINFO_ALL_INFO: + case RAW_FILEINFO_ALL_INFORMATION: + info->all_info.out.create_time = name->dos.create_time; + info->all_info.out.access_time = name->dos.access_time; + info->all_info.out.write_time = name->dos.write_time; + info->all_info.out.change_time = name->dos.change_time; + info->all_info.out.attrib = name->dos.attrib; + info->all_info.out.alloc_size = name->dos.alloc_size; + info->all_info.out.size = name->st.st_size; + info->all_info.out.nlink = name->dos.nlink; + info->all_info.out.delete_pending = 0; /* only set by qfileinfo */ + info->all_info.out.directory = + (name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY)? 1 : 0; + info->all_info.out.ea_size = name->dos.ea_size; + info->all_info.out.fname.s = name->original_name; + return NT_STATUS_OK; + + case RAW_FILEINFO_ALT_NAME_INFO: + case RAW_FILEINFO_ALT_NAME_INFORMATION: + case RAW_FILEINFO_SMB2_ALT_NAME_INFORMATION: + info->name_info.out.fname.s = pvfs_short_name(pvfs, name, name); + return NT_STATUS_OK; + + case RAW_FILEINFO_STREAM_INFO: + case RAW_FILEINFO_STREAM_INFORMATION: + return pvfs_stream_information(pvfs, req, name, fd, &info->stream_info.out); + + case RAW_FILEINFO_COMPRESSION_INFO: + case RAW_FILEINFO_COMPRESSION_INFORMATION: + info->compression_info.out.compressed_size = name->st.st_size; + info->compression_info.out.format = 0; + info->compression_info.out.unit_shift = 0; + info->compression_info.out.chunk_shift = 0; + info->compression_info.out.cluster_shift = 0; + return NT_STATUS_OK; + + case RAW_FILEINFO_INTERNAL_INFORMATION: + info->internal_information.out.file_id = name->dos.file_id; + return NT_STATUS_OK; + + case RAW_FILEINFO_ACCESS_INFORMATION: + info->access_information.out.access_flags = 0; /* only set by qfileinfo */ + return NT_STATUS_OK; + + case RAW_FILEINFO_POSITION_INFORMATION: + info->position_information.out.position = 0; /* only set by qfileinfo */ + return NT_STATUS_OK; + + case RAW_FILEINFO_MODE_INFORMATION: + info->mode_information.out.mode = 0; /* only set by qfileinfo */ + return NT_STATUS_OK; + + case RAW_FILEINFO_ALIGNMENT_INFORMATION: + info->alignment_information.out.alignment_requirement = 0; + return NT_STATUS_OK; + + case RAW_FILEINFO_NETWORK_OPEN_INFORMATION: + info->network_open_information.out.create_time = name->dos.create_time; + info->network_open_information.out.access_time = name->dos.access_time; + info->network_open_information.out.write_time = name->dos.write_time; + info->network_open_information.out.change_time = name->dos.change_time; + info->network_open_information.out.alloc_size = name->dos.alloc_size; + info->network_open_information.out.size = name->st.st_size; + info->network_open_information.out.attrib = name->dos.attrib; + return NT_STATUS_OK; + + case RAW_FILEINFO_ATTRIBUTE_TAG_INFORMATION: + info->attribute_tag_information.out.attrib = name->dos.attrib; + info->attribute_tag_information.out.reparse_tag = 0; + return NT_STATUS_OK; + + case RAW_FILEINFO_SEC_DESC: + return pvfs_acl_query(pvfs, req, name, fd, info); + + case RAW_FILEINFO_SMB2_ALL_INFORMATION: + info->all_info2.out.create_time = name->dos.create_time; + info->all_info2.out.access_time = name->dos.access_time; + info->all_info2.out.write_time = name->dos.write_time; + info->all_info2.out.change_time = name->dos.change_time; + info->all_info2.out.attrib = name->dos.attrib; + info->all_info2.out.unknown1 = 0; + info->all_info2.out.alloc_size = name->dos.alloc_size; + info->all_info2.out.size = name->st.st_size; + info->all_info2.out.nlink = name->dos.nlink; + info->all_info2.out.delete_pending = 0; /* only set by qfileinfo */ + info->all_info2.out.directory = + (name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY)? 1 : 0; + info->all_info2.out.file_id = name->dos.file_id; + info->all_info2.out.ea_size = name->dos.ea_size; + info->all_info2.out.access_mask = 0; /* only set by qfileinfo */ + info->all_info2.out.position = 0; /* only set by qfileinfo */ + info->all_info2.out.mode = 0; /* only set by qfileinfo */ + info->all_info2.out.alignment_requirement = 0; + /* windows wants the full path on disk for this + result, but I really don't want to expose that on + the wire, so I'll give the path with a share + prefix, which is a good approximation */ + info->all_info2.out.fname.s = talloc_asprintf(req, "\\%s\\%s", + pvfs->share_name, + name->original_name); + NT_STATUS_HAVE_NO_MEMORY(info->all_info2.out.fname.s); + return NT_STATUS_OK; + + case RAW_FILEINFO_GENERIC: + case RAW_FILEINFO_UNIX_BASIC: + case RAW_FILEINFO_UNIX_INFO2: + case RAW_FILEINFO_UNIX_LINK: + return NT_STATUS_INVALID_LEVEL; + case RAW_FILEINFO_NORMALIZED_NAME_INFORMATION: + return NT_STATUS_NOT_SUPPORTED; + } + + return NT_STATUS_INVALID_LEVEL; +} + +/* + return info on a pathname +*/ +NTSTATUS pvfs_qpathinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_fileinfo *info) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + struct pvfs_filename *name; + NTSTATUS status; + + /* resolve the cifs name to a posix name */ + status = pvfs_resolve_name(pvfs, req, info->generic.in.file.path, PVFS_RESOLVE_STREAMS, &name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (!name->stream_exists) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + status = pvfs_can_stat(pvfs, req, name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = pvfs_access_check_simple(pvfs, req, name, + pvfs_fileinfo_access(info)); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = pvfs_map_fileinfo(pvfs, req, name, info, -1); + + return status; +} + +/* + query info on a open file +*/ +NTSTATUS pvfs_qfileinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_fileinfo *info) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + struct pvfs_file *f; + struct pvfs_file_handle *h; + NTSTATUS status; + uint32_t access_needed; + + f = pvfs_find_fd(pvfs, req, info->generic.in.file.ntvfs); + if (!f) { + return NT_STATUS_INVALID_HANDLE; + } + h = f->handle; + + access_needed = pvfs_fileinfo_access(info); + if ((f->access_mask & access_needed) != access_needed) { + return NT_STATUS_ACCESS_DENIED; + } + + /* update the file information */ + status = pvfs_resolve_name_handle(pvfs, h); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = pvfs_map_fileinfo(pvfs, req, h->name, info, h->fd); + + /* a qfileinfo can fill in a bit more info than a qpathinfo - + now modify the levels that need to be fixed up */ + switch (info->generic.level) { + case RAW_FILEINFO_STANDARD_INFO: + case RAW_FILEINFO_STANDARD_INFORMATION: + if (pvfs_delete_on_close_set(pvfs, h)) { + info->standard_info.out.delete_pending = 1; + info->standard_info.out.nlink--; + } + break; + + case RAW_FILEINFO_ALL_INFO: + case RAW_FILEINFO_ALL_INFORMATION: + if (pvfs_delete_on_close_set(pvfs, h)) { + info->all_info.out.delete_pending = 1; + info->all_info.out.nlink--; + } + break; + + case RAW_FILEINFO_POSITION_INFORMATION: + info->position_information.out.position = h->position; + break; + + case RAW_FILEINFO_ACCESS_INFORMATION: + info->access_information.out.access_flags = f->access_mask; + break; + + case RAW_FILEINFO_MODE_INFORMATION: + info->mode_information.out.mode = h->mode; + break; + + case RAW_FILEINFO_SMB2_ALL_INFORMATION: + if (pvfs_delete_on_close_set(pvfs, h)) { + info->all_info2.out.delete_pending = 1; + info->all_info2.out.nlink--; + } + info->all_info2.out.position = h->position; + info->all_info2.out.access_mask = f->access_mask; + info->all_info2.out.mode = h->mode; + break; + + default: + break; + } + + return status; +} diff --git a/source4/ntvfs/posix/pvfs_read.c b/source4/ntvfs/posix/pvfs_read.c new file mode 100644 index 0000000..09eedd5 --- /dev/null +++ b/source4/ntvfs/posix/pvfs_read.c @@ -0,0 +1,103 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - read + + Copyright (C) Andrew Tridgell 2004 + + 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 "vfs_posix.h" +#include "lib/events/events.h" + +/* + read from a file +*/ +NTSTATUS pvfs_read(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_read *rd) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + ssize_t ret; + struct pvfs_file *f; + NTSTATUS status; + uint32_t maxcnt; + uint32_t mask; + + if (rd->generic.level != RAW_READ_READX) { + return ntvfs_map_read(ntvfs, req, rd); + } + + f = pvfs_find_fd(pvfs, req, rd->readx.in.file.ntvfs); + if (!f) { + return NT_STATUS_INVALID_HANDLE; + } + + if (f->handle->fd == -1) { + return NT_STATUS_INVALID_DEVICE_REQUEST; + } + + mask = SEC_FILE_READ_DATA; + if (rd->readx.in.read_for_execute) { + mask |= SEC_FILE_EXECUTE; + } + if (!(f->access_mask & mask)) { + return NT_STATUS_ACCESS_DENIED; + } + + maxcnt = rd->readx.in.maxcnt; + if (maxcnt > 2*UINT16_MAX && req->ctx->protocol < PROTOCOL_SMB2_02) { + DEBUG(3,(__location__ ": Invalid SMB maxcnt 0x%x\n", maxcnt)); + return NT_STATUS_INVALID_PARAMETER; + } + + status = pvfs_check_lock(pvfs, f, req->smbpid, + rd->readx.in.offset, + maxcnt, + READ_LOCK); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (f->handle->name->stream_name) { + ret = pvfs_stream_read(pvfs, f->handle, + rd->readx.out.data, maxcnt, rd->readx.in.offset); + } else { + ret = pread(f->handle->fd, + rd->readx.out.data, + maxcnt, + rd->readx.in.offset); + } + if (ret == -1) { + return pvfs_map_errno(pvfs, errno); + } + + /* only SMB2 honors mincnt */ + if (req->ctx->protocol >= PROTOCOL_SMB2_02) { + if (rd->readx.in.mincnt > ret || + (ret == 0 && maxcnt > 0)) { + return NT_STATUS_END_OF_FILE; + } + } + + f->handle->position = f->handle->seek_offset = rd->readx.in.offset + ret; + + rd->readx.out.nread = ret; + rd->readx.out.remaining = 0xFFFF; + rd->readx.out.compaction_mode = 0; + + return NT_STATUS_OK; +} diff --git a/source4/ntvfs/posix/pvfs_rename.c b/source4/ntvfs/posix/pvfs_rename.c new file mode 100644 index 0000000..4327161 --- /dev/null +++ b/source4/ntvfs/posix/pvfs_rename.c @@ -0,0 +1,675 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - rename + + Copyright (C) Andrew Tridgell 2004 + + 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 "vfs_posix.h" +#include "librpc/gen_ndr/security.h" +#include "param/param.h" + + +/* + do a file rename, and send any notify triggers +*/ +NTSTATUS pvfs_do_rename(struct pvfs_state *pvfs, + struct odb_lock *lck, + const struct pvfs_filename *name1, + const char *name2) +{ + const char *r1, *r2; + uint32_t mask; + NTSTATUS status; + + if (pvfs_sys_rename(pvfs, name1->full_name, name2, + name1->allow_override) == -1) { + return pvfs_map_errno(pvfs, errno); + } + + status = odb_rename(lck, name2); + NT_STATUS_NOT_OK_RETURN(status); + + if (name1->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) { + mask = FILE_NOTIFY_CHANGE_DIR_NAME; + } else { + mask = FILE_NOTIFY_CHANGE_FILE_NAME; + } + /* + renames to the same directory cause a OLD_NAME->NEW_NAME notify. + renames to a different directory are considered a remove/add + */ + r1 = strrchr_m(name1->full_name, '/'); + r2 = strrchr_m(name2, '/'); + + if ((r1-name1->full_name) != (r2-name2) || + strncmp(name1->full_name, name2, r1-name1->full_name) != 0) { + notify_trigger(pvfs->notify_context, + NOTIFY_ACTION_REMOVED, + mask, + name1->full_name); + notify_trigger(pvfs->notify_context, + NOTIFY_ACTION_ADDED, + mask, + name2); + } else { + notify_trigger(pvfs->notify_context, + NOTIFY_ACTION_OLD_NAME, + mask, + name1->full_name); + notify_trigger(pvfs->notify_context, + NOTIFY_ACTION_NEW_NAME, + mask, + name2); + } + + /* this is a strange one. w2k3 gives an additional event for CHANGE_ATTRIBUTES + and CHANGE_CREATION on the new file when renaming files, but not + directories */ + if ((name1->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) == 0) { + notify_trigger(pvfs->notify_context, + NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_ATTRIBUTES|FILE_NOTIFY_CHANGE_CREATION, + name2); + } + + return NT_STATUS_OK; +} + + +/* + resolve a wildcard rename pattern. This works on one component of the name +*/ +static const char *pvfs_resolve_wildcard_component(TALLOC_CTX *mem_ctx, + const char *fname, + const char *pattern) +{ + const char *p1, *p2; + char *dest, *d; + + /* the length is bounded by the length of the two strings combined */ + dest = talloc_array(mem_ctx, char, strlen(fname) + strlen(pattern) + 1); + if (dest == NULL) { + return NULL; + } + + p1 = fname; + p2 = pattern; + d = dest; + + while (*p2) { + codepoint_t c1, c2; + size_t c_size1, c_size2; + c1 = next_codepoint(p1, &c_size1); + c2 = next_codepoint(p2, &c_size2); + if (c2 == '?') { + d += push_codepoint(d, c1); + } else if (c2 == '*') { + memcpy(d, p1, strlen(p1)); + d += strlen(p1); + break; + } else { + d += push_codepoint(d, c2); + } + + p1 += c_size1; + p2 += c_size2; + } + + *d = 0; + + talloc_set_name_const(dest, dest); + + return dest; +} + +/* + resolve a wildcard rename pattern. +*/ +static const char *pvfs_resolve_wildcard(TALLOC_CTX *mem_ctx, + const char *fname, + const char *pattern) +{ + const char *base1, *base2; + const char *ext1, *ext2; + char *p; + + /* break into base part plus extension */ + p = strrchr_m(fname, '.'); + if (p == NULL) { + ext1 = ""; + base1 = fname; + } else { + ext1 = talloc_strdup(mem_ctx, p+1); + base1 = talloc_strndup(mem_ctx, fname, p-fname); + } + if (ext1 == NULL || base1 == NULL) { + return NULL; + } + + p = strrchr_m(pattern, '.'); + if (p == NULL) { + ext2 = ""; + base2 = fname; + } else { + ext2 = talloc_strdup(mem_ctx, p+1); + base2 = talloc_strndup(mem_ctx, pattern, p-pattern); + } + if (ext2 == NULL || base2 == NULL) { + return NULL; + } + + base1 = pvfs_resolve_wildcard_component(mem_ctx, base1, base2); + ext1 = pvfs_resolve_wildcard_component(mem_ctx, ext1, ext2); + if (base1 == NULL || ext1 == NULL) { + return NULL; + } + + if (*ext1 == 0) { + return base1; + } + + return talloc_asprintf(mem_ctx, "%s.%s", base1, ext1); +} + +/* + retry an rename after a sharing violation +*/ +static void pvfs_retry_rename(struct pvfs_odb_retry *r, + struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + void *_io, + void *private_data, + enum pvfs_wait_notice reason) +{ + union smb_rename *io = talloc_get_type(_io, union smb_rename); + NTSTATUS status = NT_STATUS_INTERNAL_ERROR; + + talloc_free(r); + + switch (reason) { + case PVFS_WAIT_CANCEL: +/*TODO*/ + status = NT_STATUS_CANCELLED; + break; + case PVFS_WAIT_TIMEOUT: + /* if it timed out, then give the failure + immediately */ +/*TODO*/ + status = NT_STATUS_SHARING_VIOLATION; + break; + case PVFS_WAIT_EVENT: + + /* try the open again, which could trigger another retry setup + if it wants to, so we have to unmark the async flag so we + will know if it does a second async reply */ + req->async_states->state &= ~NTVFS_ASYNC_STATE_ASYNC; + + status = pvfs_rename(ntvfs, req, io); + if (req->async_states->state & NTVFS_ASYNC_STATE_ASYNC) { + /* the 2nd try also replied async, so we don't send + the reply yet */ + return; + } + + /* re-mark it async, just in case someone up the chain does + paranoid checking */ + req->async_states->state |= NTVFS_ASYNC_STATE_ASYNC; + break; + } + + /* send the reply up the chain */ + req->async_states->status = status; + req->async_states->send_fn(req); +} + +/* + setup for a rename retry after a sharing violation + or a non granted oplock +*/ +static NTSTATUS pvfs_rename_setup_retry(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_rename *io, + struct odb_lock *lck, + NTSTATUS status) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + struct timeval end_time; + + if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) { + end_time = timeval_add(&req->statistics.request_time, + 0, pvfs->sharing_violation_delay); + } else if (NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) { + end_time = timeval_add(&req->statistics.request_time, + pvfs->oplock_break_timeout, 0); + } else { + return NT_STATUS_INTERNAL_ERROR; + } + + return pvfs_odb_retry_setup(ntvfs, req, lck, end_time, io, NULL, + pvfs_retry_rename); +} + +/* + rename one file from a wildcard set +*/ +static NTSTATUS pvfs_rename_one(struct pvfs_state *pvfs, + struct ntvfs_request *req, + const char *dir_path, + const char *fname1, + const char *fname2, + uint16_t attrib) +{ + struct pvfs_filename *name1, *name2; + TALLOC_CTX *mem_ctx = talloc_new(req); + struct odb_lock *lck = NULL; + NTSTATUS status; + + /* resolve the wildcard pattern for this name */ + fname2 = pvfs_resolve_wildcard(mem_ctx, fname1, fname2); + if (fname2 == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* get a pvfs_filename source object */ + status = pvfs_resolve_partial(pvfs, mem_ctx, + dir_path, fname1, + PVFS_RESOLVE_NO_OPENDB, + &name1); + if (!NT_STATUS_IS_OK(status)) { + goto failed; + } + + /* make sure its matches the given attributes */ + status = pvfs_match_attrib(pvfs, name1, attrib, 0); + if (!NT_STATUS_IS_OK(status)) { + goto failed; + } + + status = pvfs_can_rename(pvfs, req, name1, &lck); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(lck); + goto failed; + } + + /* get a pvfs_filename dest object */ + status = pvfs_resolve_partial(pvfs, mem_ctx, + dir_path, fname2, + PVFS_RESOLVE_NO_OPENDB, + &name2); + if (NT_STATUS_IS_OK(status)) { + status = pvfs_can_delete(pvfs, req, name2, NULL); + if (!NT_STATUS_IS_OK(status)) { + goto failed; + } + } + + status = NT_STATUS_OK; + + fname2 = talloc_asprintf(mem_ctx, "%s/%s", dir_path, fname2); + if (fname2 == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = pvfs_do_rename(pvfs, lck, name1, fname2); + +failed: + talloc_free(mem_ctx); + return status; +} + + +/* + rename a set of files with wildcards +*/ +static NTSTATUS pvfs_rename_wildcard(struct pvfs_state *pvfs, + struct ntvfs_request *req, + union smb_rename *ren, + struct pvfs_filename *name1, + struct pvfs_filename *name2) +{ + struct pvfs_dir *dir; + NTSTATUS status; + off_t ofs = 0; + const char *fname, *fname2, *dir_path; + uint16_t attrib = ren->rename.in.attrib; + int total_renamed = 0; + + /* get list of matching files */ + status = pvfs_list_start(pvfs, name1, req, &dir); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = NT_STATUS_NO_SUCH_FILE; + + dir_path = pvfs_list_unix_path(dir); + + /* only allow wildcard renames within a directory */ + if (strncmp(dir_path, name2->full_name, strlen(dir_path)) != 0 || + name2->full_name[strlen(dir_path)] != '/' || + strchr(name2->full_name + strlen(dir_path) + 1, '/')) { + DEBUG(3,(__location__ ": Invalid rename for %s -> %s\n", + name1->original_name, name2->original_name)); + return NT_STATUS_INVALID_PARAMETER; + } + + fname2 = talloc_strdup(name2, name2->full_name + strlen(dir_path) + 1); + if (fname2 == NULL) { + return NT_STATUS_NO_MEMORY; + } + + while ((fname = pvfs_list_next(dir, &ofs))) { + status = pvfs_rename_one(pvfs, req, + dir_path, + fname, fname2, attrib); + if (NT_STATUS_IS_OK(status)) { + total_renamed++; + } + } + + if (total_renamed == 0) { + return status; + } + + return NT_STATUS_OK; +} + +/* + rename a set of files - SMBmv interface +*/ +static NTSTATUS pvfs_rename_mv(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_rename *ren) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + NTSTATUS status; + struct pvfs_filename *name1, *name2; + struct odb_lock *lck = NULL; + + /* resolve the cifs name to a posix name */ + status = pvfs_resolve_name(pvfs, req, ren->rename.in.pattern1, + PVFS_RESOLVE_WILDCARD, &name1); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = pvfs_resolve_name(pvfs, req, ren->rename.in.pattern2, + PVFS_RESOLVE_WILDCARD, &name2); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (name1->has_wildcard || name2->has_wildcard) { + return pvfs_rename_wildcard(pvfs, req, ren, name1, name2); + } + + if (!name1->exists) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + if (strcmp(name1->full_name, name2->full_name) == 0) { + return NT_STATUS_OK; + } + + if (name2->exists) { + return NT_STATUS_OBJECT_NAME_COLLISION; + } + + status = pvfs_match_attrib(pvfs, name1, ren->rename.in.attrib, 0); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = pvfs_access_check_parent(pvfs, req, name2, SEC_DIR_ADD_FILE); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = pvfs_can_rename(pvfs, req, name1, &lck); + /* + * on a sharing violation we need to retry when the file is closed by + * the other user, or after 1 second + * on a non granted oplock we need to retry when the file is closed by + * the other user, or after 30 seconds + */ + if ((NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) || + NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) && + (req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return pvfs_rename_setup_retry(pvfs->ntvfs, req, ren, lck, status); + } + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = pvfs_do_rename(pvfs, lck, name1, name2->full_name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + + +/* + rename a stream +*/ +static NTSTATUS pvfs_rename_stream(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_rename *ren, + struct pvfs_filename *name1) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + NTSTATUS status; + struct odb_lock *lck = NULL; + + if (name1->has_wildcard) { + DEBUG(3,(__location__ ": Invalid wildcard rename for %s\n", + name1->original_name)); + return NT_STATUS_INVALID_PARAMETER; + } + + if (ren->ntrename.in.new_name[0] != ':') { + DEBUG(3,(__location__ ": Invalid rename for %s\n", + ren->ntrename.in.new_name)); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!name1->exists) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + if (ren->ntrename.in.flags != RENAME_FLAG_RENAME) { + DEBUG(3,(__location__ ": Invalid rename flags 0x%x for %s\n", + ren->ntrename.in.flags, ren->ntrename.in.new_name)); + return NT_STATUS_INVALID_PARAMETER; + } + + status = pvfs_can_rename(pvfs, req, name1, &lck); + /* + * on a sharing violation we need to retry when the file is closed by + * the other user, or after 1 second + * on a non granted oplock we need to retry when the file is closed by + * the other user, or after 30 seconds + */ + if ((NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) || + NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) && + (req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return pvfs_rename_setup_retry(pvfs->ntvfs, req, ren, lck, status); + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = pvfs_access_check_simple(pvfs, req, name1, SEC_FILE_WRITE_ATTRIBUTE); + NT_STATUS_NOT_OK_RETURN(status); + + status = pvfs_stream_rename(pvfs, name1, -1, + ren->ntrename.in.new_name+1, + true); + NT_STATUS_NOT_OK_RETURN(status); + + return NT_STATUS_OK; +} + +/* + rename a set of files - ntrename interface +*/ +static NTSTATUS pvfs_rename_nt(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_rename *ren) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + NTSTATUS status; + struct pvfs_filename *name1, *name2; + struct odb_lock *lck = NULL; + + switch (ren->ntrename.in.flags) { + case RENAME_FLAG_RENAME: + case RENAME_FLAG_HARD_LINK: + case RENAME_FLAG_COPY: + case RENAME_FLAG_MOVE_CLUSTER_INFORMATION: + break; + default: + return NT_STATUS_ACCESS_DENIED; + } + + /* resolve the cifs name to a posix name */ + status = pvfs_resolve_name(pvfs, req, ren->ntrename.in.old_name, + PVFS_RESOLVE_WILDCARD | PVFS_RESOLVE_STREAMS, &name1); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (name1->stream_name) { + /* stream renames need to be handled separately */ + return pvfs_rename_stream(ntvfs, req, ren, name1); + } + + status = pvfs_resolve_name(pvfs, req, ren->ntrename.in.new_name, + PVFS_RESOLVE_WILDCARD, &name2); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (name1->has_wildcard || name2->has_wildcard) { + return NT_STATUS_OBJECT_PATH_SYNTAX_BAD; + } + + if (!name1->exists) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + if (strcmp(name1->full_name, name2->full_name) == 0) { + return NT_STATUS_OK; + } + + if (name2->exists) { + return NT_STATUS_OBJECT_NAME_COLLISION; + } + + status = pvfs_match_attrib(pvfs, name1, ren->ntrename.in.attrib, 0); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = pvfs_can_rename(pvfs, req, name1, &lck); + /* + * on a sharing violation we need to retry when the file is closed by + * the other user, or after 1 second + * on a non granted oplock we need to retry when the file is closed by + * the other user, or after 30 seconds + */ + if ((NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) || + NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) && + (req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return pvfs_rename_setup_retry(pvfs->ntvfs, req, ren, lck, status); + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + switch (ren->ntrename.in.flags) { + case RENAME_FLAG_RENAME: + status = pvfs_access_check_parent(pvfs, req, name2, SEC_DIR_ADD_FILE); + NT_STATUS_NOT_OK_RETURN(status); + status = pvfs_do_rename(pvfs, lck, name1, name2->full_name); + NT_STATUS_NOT_OK_RETURN(status); + break; + + case RENAME_FLAG_HARD_LINK: + status = pvfs_access_check_parent(pvfs, req, name2, SEC_DIR_ADD_FILE); + NT_STATUS_NOT_OK_RETURN(status); + if (link(name1->full_name, name2->full_name) == -1) { + return pvfs_map_errno(pvfs, errno); + } + break; + + case RENAME_FLAG_COPY: + status = pvfs_access_check_parent(pvfs, req, name2, SEC_DIR_ADD_FILE); + NT_STATUS_NOT_OK_RETURN(status); + return pvfs_copy_file(pvfs, name1, name2, name1->allow_override && name2->allow_override); + + case RENAME_FLAG_MOVE_CLUSTER_INFORMATION: + DEBUG(3,(__location__ ": Invalid rename cluster for %s\n", + name1->original_name)); + return NT_STATUS_INVALID_PARAMETER; + + default: + return NT_STATUS_ACCESS_DENIED; + } + + + return NT_STATUS_OK; +} + +/* + rename a set of files - ntrename interface +*/ +NTSTATUS pvfs_rename(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_rename *ren) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + struct pvfs_file *f; + + switch (ren->generic.level) { + case RAW_RENAME_RENAME: + return pvfs_rename_mv(ntvfs, req, ren); + + case RAW_RENAME_NTRENAME: + return pvfs_rename_nt(ntvfs, req, ren); + + case RAW_RENAME_NTTRANS: + f = pvfs_find_fd(pvfs, req, ren->nttrans.in.file.ntvfs); + if (!f) { + return NT_STATUS_INVALID_HANDLE; + } + + /* wk23 ignores the request */ + return NT_STATUS_OK; + + default: + break; + } + + return NT_STATUS_INVALID_LEVEL; +} + diff --git a/source4/ntvfs/posix/pvfs_resolve.c b/source4/ntvfs/posix/pvfs_resolve.c new file mode 100644 index 0000000..fbd8c7d --- /dev/null +++ b/source4/ntvfs/posix/pvfs_resolve.c @@ -0,0 +1,826 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - filename resolution + + Copyright (C) Andrew Tridgell 2004 + + 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/>. +*/ + +/* + this is the core code for converting a filename from the format as + given by a client to a posix filename, including any case-matching + required, and checks for legal characters +*/ + + +#include "includes.h" +#include "vfs_posix.h" +#include "system/dir.h" +#include "param/param.h" + +/** + compare two filename components. This is where the name mangling hook will go +*/ +static int component_compare(struct pvfs_state *pvfs, const char *comp, const char *name) +{ + int ret; + + ret = strcasecmp_m(comp, name); + + if (ret != 0) { + char *shortname = pvfs_short_name_component(pvfs, name); + if (shortname) { + ret = strcasecmp_m(comp, shortname); + talloc_free(shortname); + } + } + + return ret; +} + +/* + search for a filename in a case insensitive fashion + + TODO: add a cache for previously resolved case-insensitive names + TODO: add mangled name support +*/ +static NTSTATUS pvfs_case_search(struct pvfs_state *pvfs, + struct pvfs_filename *name, + unsigned int flags) +{ + /* break into a series of components */ + size_t num_components; + char **components; + char *p, *partial_name; + size_t i; + + /* break up the full name into pathname components */ + num_components=2; + p = name->full_name + strlen(pvfs->base_directory) + 1; + + for (;*p;p++) { + if (*p == '/') { + num_components++; + } + } + + components = talloc_array(name, char *, num_components); + p = name->full_name + strlen(pvfs->base_directory); + *p++ = 0; + + components[0] = name->full_name; + + for (i=1;i<num_components;i++) { + components[i] = p; + p = strchr(p, '/'); + if (p) *p++ = 0; + if (pvfs_is_reserved_name(pvfs, components[i])) { + return NT_STATUS_ACCESS_DENIED; + } + } + + partial_name = talloc_strdup(name, components[0]); + if (!partial_name) { + return NT_STATUS_NO_MEMORY; + } + + /* for each component, check if it exists as-is, and if not then + do a directory scan */ + for (i=1;i<num_components;i++) { + char *test_name; + DIR *dir; + struct dirent *de; + char *long_component; + + /* possibly remap from the short name cache */ + long_component = pvfs_mangled_lookup(pvfs, name, components[i]); + if (long_component) { + components[i] = long_component; + } + + test_name = talloc_asprintf(name, "%s/%s", partial_name, components[i]); + if (!test_name) { + return NT_STATUS_NO_MEMORY; + } + + /* check if this component exists as-is */ + if (stat(test_name, &name->st) == 0) { + if (i<num_components-1 && !S_ISDIR(name->st.st_mode)) { + return NT_STATUS_OBJECT_PATH_NOT_FOUND; + } + talloc_free(partial_name); + partial_name = test_name; + if (i == num_components - 1) { + name->exists = true; + } + continue; + } + + /* the filesystem might be case insensitive, in which + case a search is pointless unless the name is + mangled */ + if ((pvfs->flags & PVFS_FLAG_CI_FILESYSTEM) && + !pvfs_is_mangled_component(pvfs, components[i])) { + if (i < num_components-1) { + return NT_STATUS_OBJECT_PATH_NOT_FOUND; + } + partial_name = test_name; + continue; + } + + dir = opendir(partial_name); + if (!dir) { + return pvfs_map_errno(pvfs, errno); + } + + while ((de = readdir(dir))) { + if (component_compare(pvfs, components[i], de->d_name) == 0) { + break; + } + } + + if (!de) { + if (i < num_components-1) { + closedir(dir); + return NT_STATUS_OBJECT_PATH_NOT_FOUND; + } + } else { + components[i] = talloc_strdup(name, de->d_name); + } + test_name = talloc_asprintf(name, "%s/%s", partial_name, components[i]); + talloc_free(partial_name); + partial_name = test_name; + + closedir(dir); + } + + if (!name->exists) { + if (stat(partial_name, &name->st) == 0) { + name->exists = true; + } + } + + talloc_free(name->full_name); + name->full_name = partial_name; + + if (name->exists) { + return pvfs_fill_dos_info(pvfs, name, flags, -1); + } + + return NT_STATUS_OK; +} + +/* + parse a alternate data stream name +*/ +static NTSTATUS parse_stream_name(struct pvfs_filename *name, + const char *s) +{ + char *p, *stream_name; + if (s[1] == '\0') { + return NT_STATUS_OBJECT_NAME_INVALID; + } + name->stream_name = stream_name = talloc_strdup(name, s+1); + if (name->stream_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + p = stream_name; + + while (*p) { + size_t c_size; + codepoint_t c = next_codepoint(p, &c_size); + + switch (c) { + case '/': + case '\\': + return NT_STATUS_OBJECT_NAME_INVALID; + case ':': + *p= 0; + p++; + if (*p == '\0') { + return NT_STATUS_OBJECT_NAME_INVALID; + } + if (strcasecmp_m(p, "$DATA") != 0) { + if (strchr_m(p, ':')) { + return NT_STATUS_OBJECT_NAME_INVALID; + } + return NT_STATUS_INVALID_PARAMETER; + } + c_size = 0; + p--; + break; + } + + p += c_size; + } + + if (strcmp(name->stream_name, "") == 0) { + /* + * we don't set stream_name to NULL, here + * as this would be wrong for directories + * + * pvfs_fill_dos_info() will set it to NULL + * if it's not a directory. + */ + name->stream_id = 0; + } else { + name->stream_id = pvfs_name_hash(name->stream_name, + strlen(name->stream_name)); + } + + return NT_STATUS_OK; +} + + +/* + convert a CIFS pathname to a unix pathname. Note that this does NOT + take into account case insensitivity, and in fact does not access + the filesystem at all. It is merely a reformatting and charset + checking routine. + + errors are returned if the filename is illegal given the flags +*/ +static NTSTATUS pvfs_unix_path(struct pvfs_state *pvfs, const char *cifs_name, + unsigned int flags, struct pvfs_filename *name) +{ + char *ret, *p, *p_start; + NTSTATUS status; + + name->original_name = talloc_strdup(name, cifs_name); + + /* remove any :$DATA */ + p = strrchr(name->original_name, ':'); + if (p && strcasecmp_m(p, ":$DATA") == 0) { + if (p > name->original_name && p[-1] == ':') { + p--; + } + *p = 0; + } + + name->stream_name = NULL; + name->stream_id = 0; + name->has_wildcard = false; + + while (*cifs_name == '\\') { + cifs_name++; + } + + if (*cifs_name == 0) { + name->full_name = talloc_asprintf(name, "%s/.", pvfs->base_directory); + if (name->full_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; + } + + ret = talloc_asprintf(name, "%s/%s", pvfs->base_directory, cifs_name); + if (ret == NULL) { + return NT_STATUS_NO_MEMORY; + } + + p = ret + strlen(pvfs->base_directory) + 1; + + /* now do an in-place conversion of '\' to '/', checking + for legal characters */ + p_start = p; + + while (*p) { + size_t c_size; + codepoint_t c = next_codepoint(p, &c_size); + + if (c <= 0x1F) { + return NT_STATUS_OBJECT_NAME_INVALID; + } + + switch (c) { + case '\\': + if (name->has_wildcard) { + /* wildcards are only allowed in the last part + of a name */ + return NT_STATUS_OBJECT_NAME_INVALID; + } + if (p > p_start && (p[1] == '\\' || p[1] == '\0')) { + /* see if it is definitely a "\\" or + * a trailing "\". If it is then fail here, + * and let the next layer up try again after + * pvfs_reduce_name() if it wants to. This is + * much more efficient on average than always + * scanning for these separately + */ + return NT_STATUS_OBJECT_PATH_SYNTAX_BAD; + } else { + *p = '/'; + } + break; + case ':': + if (!(flags & PVFS_RESOLVE_STREAMS)) { + return NT_STATUS_OBJECT_NAME_INVALID; + } + if (name->has_wildcard) { + return NT_STATUS_OBJECT_NAME_INVALID; + } + status = parse_stream_name(name, p); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + *p-- = 0; + break; + case '*': + case '>': + case '<': + case '?': + case '"': + if (!(flags & PVFS_RESOLVE_WILDCARD)) { + return NT_STATUS_OBJECT_NAME_INVALID; + } + name->has_wildcard = true; + break; + case '/': + case '|': + return NT_STATUS_OBJECT_NAME_INVALID; + case '.': + /* see if it is definitely a .. or + . component. If it is then fail here, and + let the next layer up try again after + pvfs_reduce_name() if it wants to. This is + much more efficient on average than always + scanning for these separately */ + if (p[1] == '.' && + (p[2] == 0 || p[2] == '\\') && + (p == p_start || p[-1] == '/')) { + return NT_STATUS_OBJECT_PATH_SYNTAX_BAD; + } + if ((p[1] == 0 || p[1] == '\\') && + (p == p_start || p[-1] == '/')) { + return NT_STATUS_OBJECT_PATH_SYNTAX_BAD; + } + break; + } + + p += c_size; + } + + name->full_name = ret; + + return NT_STATUS_OK; +} + + +/* + reduce a name that contains .. components or repeated \ separators + return NULL if it can't be reduced +*/ +static NTSTATUS pvfs_reduce_name(TALLOC_CTX *mem_ctx, + const char **fname, unsigned int flags) +{ + codepoint_t c; + size_t c_size, len; + size_t i, num_components, err_count; + char **components; + char *p, *s, *ret; + + s = talloc_strdup(mem_ctx, *fname); + if (s == NULL) return NT_STATUS_NO_MEMORY; + + for (num_components=1, p=s; *p; p += c_size) { + c = next_codepoint(p, &c_size); + if (c == '\\') num_components++; + } + + components = talloc_array(s, char *, num_components+1); + if (components == NULL) { + talloc_free(s); + return NT_STATUS_NO_MEMORY; + } + + components[0] = s; + for (i=0, p=s; *p; p += c_size) { + c = next_codepoint(p, &c_size); + if (c == '\\') { + *p = 0; + components[++i] = p+1; + } + } + components[i+1] = NULL; + + /* + rather bizarre! + + '.' components are not allowed, but the rules for what error + code to give don't seem to make sense. This is a close + approximation. + */ + for (err_count=i=0;components[i];i++) { + if (strcmp(components[i], "") == 0) { + continue; + } + if (ISDOT(components[i]) || err_count) { + err_count++; + } + } + if (err_count > 0) { + if (flags & PVFS_RESOLVE_WILDCARD) err_count--; + + if (err_count==1) { + return NT_STATUS_OBJECT_NAME_INVALID; + } else { + return NT_STATUS_OBJECT_PATH_NOT_FOUND; + } + } + + /* remove any null components */ + for (i=0;components[i];i++) { + if (strcmp(components[i], "") == 0) { + memmove(&components[i], &components[i+1], + sizeof(char *)*(num_components-i)); + i--; + continue; + } + if (ISDOTDOT(components[i])) { + if (i < 1) return NT_STATUS_OBJECT_PATH_SYNTAX_BAD; + memmove(&components[i-1], &components[i+1], + sizeof(char *)*(num_components-i)); + i -= 2; + continue; + } + } + + if (components[0] == NULL) { + talloc_free(s); + *fname = talloc_strdup(mem_ctx, "\\"); + return NT_STATUS_OK; + } + + for (len=i=0;components[i];i++) { + len += strlen(components[i]) + 1; + } + + /* rebuild the name */ + ret = talloc_array(mem_ctx, char, len+1); + if (ret == NULL) { + talloc_free(s); + return NT_STATUS_NO_MEMORY; + } + + for (len=0,i=0;components[i];i++) { + size_t len1 = strlen(components[i]); + ret[len] = '\\'; + memcpy(ret+len+1, components[i], len1); + len += len1 + 1; + } + ret[len] = 0; + + talloc_set_name_const(ret, ret); + + talloc_free(s); + + *fname = ret; + + return NT_STATUS_OK; +} + + +/* + resolve a name from relative client format to a struct pvfs_filename + the memory for the filename is made as a talloc child of 'name' + + flags include: + PVFS_RESOLVE_NO_WILDCARD = wildcards are considered illegal characters + PVFS_RESOLVE_STREAMS = stream names are allowed + + TODO: ../ collapsing, and outside share checking +*/ +NTSTATUS pvfs_resolve_name(struct pvfs_state *pvfs, + struct ntvfs_request *req, + const char *cifs_name, + unsigned int flags, struct pvfs_filename **name) +{ + NTSTATUS status; + + *name = talloc(req, struct pvfs_filename); + if (*name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + (*name)->exists = false; + (*name)->stream_exists = false; + (*name)->allow_override = false; + + if (!(pvfs->fs_attribs & FS_ATTR_NAMED_STREAMS)) { + flags &= ~PVFS_RESOLVE_STREAMS; + } + + /* SMB2 doesn't allow a leading slash */ + if (req->ctx->protocol >= PROTOCOL_SMB2_02 && + *cifs_name == '\\') { + return NT_STATUS_INVALID_PARAMETER; + } + + /* do the basic conversion to a unix formatted path, + also checking for allowable characters */ + status = pvfs_unix_path(pvfs, cifs_name, flags, *name); + + if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_SYNTAX_BAD)) { + /* it might contain .. components which need to be reduced */ + status = pvfs_reduce_name(*name, &cifs_name, flags); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + status = pvfs_unix_path(pvfs, cifs_name, flags, *name); + } + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* if it has a wildcard then no point doing a stat() of the + full name. Instead We need check if the directory exists + */ + if ((*name)->has_wildcard) { + const char *p; + char *dir_name, *saved_name; + p = strrchr((*name)->full_name, '/'); + if (p == NULL) { + /* root directory wildcard is OK */ + return NT_STATUS_OK; + } + dir_name = talloc_strndup(*name, (*name)->full_name, (p-(*name)->full_name)); + if (stat(dir_name, &(*name)->st) == 0) { + talloc_free(dir_name); + return NT_STATUS_OK; + } + /* we need to search for a matching name */ + saved_name = (*name)->full_name; + (*name)->full_name = dir_name; + status = pvfs_case_search(pvfs, *name, flags); + if (!NT_STATUS_IS_OK(status)) { + /* the directory doesn't exist */ + (*name)->full_name = saved_name; + return status; + } + /* it does exist, but might need a case change */ + if (dir_name != (*name)->full_name) { + (*name)->full_name = talloc_asprintf(*name, "%s%s", + (*name)->full_name, p); + NT_STATUS_HAVE_NO_MEMORY((*name)->full_name); + } else { + (*name)->full_name = saved_name; + talloc_free(dir_name); + } + return NT_STATUS_OK; + } + + /* if we can stat() the full name now then we are done */ + if (stat((*name)->full_name, &(*name)->st) == 0) { + (*name)->exists = true; + return pvfs_fill_dos_info(pvfs, *name, flags, -1); + } + + /* search for a matching filename */ + status = pvfs_case_search(pvfs, *name, flags); + + return status; +} + + +/* + do a partial resolve, returning a pvfs_filename structure given a + base path and a relative component. It is an error if the file does + not exist. No case-insensitive matching is done. + + this is used in places like directory searching where we need a pvfs_filename + to pass to a function, but already know the unix base directory and component +*/ +NTSTATUS pvfs_resolve_partial(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx, + const char *unix_dir, const char *fname, + unsigned int flags, struct pvfs_filename **name) +{ + NTSTATUS status; + + *name = talloc(mem_ctx, struct pvfs_filename); + if (*name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + (*name)->full_name = talloc_asprintf(*name, "%s/%s", unix_dir, fname); + if ((*name)->full_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (stat((*name)->full_name, &(*name)->st) == -1) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + (*name)->exists = true; + (*name)->stream_exists = true; + (*name)->has_wildcard = false; + (*name)->original_name = talloc_strdup(*name, fname); + (*name)->stream_name = NULL; + (*name)->stream_id = 0; + (*name)->allow_override = false; + + status = pvfs_fill_dos_info(pvfs, *name, flags, -1); + + return status; +} + + +/* + fill in the pvfs_filename info for an open file, given the current + info for a (possibly) non-open file. This is used by places that need + to update the pvfs_filename stat information, and by pvfs_open() +*/ +NTSTATUS pvfs_resolve_name_fd(struct pvfs_state *pvfs, int fd, + struct pvfs_filename *name, unsigned int flags) +{ + dev_t device = (dev_t)0; + ino_t inode = 0; + + if (name->exists) { + device = name->st.st_dev; + inode = name->st.st_ino; + } + + if (fd == -1) { + if (stat(name->full_name, &name->st) == -1) { + return NT_STATUS_INVALID_HANDLE; + } + } else { + if (fstat(fd, &name->st) == -1) { + return NT_STATUS_INVALID_HANDLE; + } + } + + if (name->exists && + (device != name->st.st_dev || inode != name->st.st_ino)) { + /* the file we are looking at has changed! this could + be someone trying to exploit a race + condition. Certainly we don't want to continue + operating on this file */ + DEBUG(0,("pvfs: WARNING: file '%s' changed during resolve - failing\n", + name->full_name)); + return NT_STATUS_UNEXPECTED_IO_ERROR; + } + + name->exists = true; + + return pvfs_fill_dos_info(pvfs, name, flags, fd); +} + +/* + fill in the pvfs_filename info for an open file, given the current + info for a (possibly) non-open file. This is used by places that need + to update the pvfs_filename stat information, and the path + after a possible rename on a different handle. +*/ +NTSTATUS pvfs_resolve_name_handle(struct pvfs_state *pvfs, + struct pvfs_file_handle *h) +{ + NTSTATUS status; + + if (h->have_opendb_entry) { + struct odb_lock *lck; + const char *name = NULL; + + lck = odb_lock(h, h->pvfs->odb_context, &h->odb_locking_key); + if (lck == NULL) { + DEBUG(0,("%s: failed to lock file '%s' in opendb\n", + __FUNCTION__, h->name->full_name)); + /* we were supposed to do a blocking lock, so something + is badly wrong! */ + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + status = odb_get_path(lck, &name); + if (NT_STATUS_IS_OK(status)) { + /* + * This relies on the fact that + * renames of open files are only + * allowed by setpathinfo() and setfileinfo() + * and there're only renames within the same + * directory supported + */ + if (strcmp(h->name->full_name, name) != 0) { + const char *orig_dir; + const char *new_file; + char *new_orig; + char *delim; + char *full_name = discard_const_p(char, name); + + delim = strrchr(name, '/'); + if (!delim) { + talloc_free(lck); + return NT_STATUS_INTERNAL_ERROR; + } + + new_file = delim + 1; + delim = strrchr(h->name->original_name, '\\'); + if (delim) { + delim[0] = '\0'; + orig_dir = h->name->original_name; + new_orig = talloc_asprintf(h->name, "%s\\%s", + orig_dir, new_file); + if (!new_orig) { + talloc_free(lck); + return NT_STATUS_NO_MEMORY; + } + } else { + new_orig = talloc_strdup(h->name, new_file); + if (!new_orig) { + talloc_free(lck); + return NT_STATUS_NO_MEMORY; + } + } + + talloc_free(h->name->original_name); + talloc_free(h->name->full_name); + h->name->full_name = talloc_steal(h->name, full_name); + h->name->original_name = new_orig; + } + } + + talloc_free(lck); + } + + /* + * TODO: pass PVFS_RESOLVE_NO_OPENDB and get + * the write time from odb_lock() above. + */ + status = pvfs_resolve_name_fd(pvfs, h->fd, h->name, 0); + NT_STATUS_NOT_OK_RETURN(status); + + if (!null_nttime(h->write_time.close_time)) { + h->name->dos.write_time = h->write_time.close_time; + } + + return NT_STATUS_OK; +} + + +/* + resolve the parent of a given name +*/ +NTSTATUS pvfs_resolve_parent(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx, + const struct pvfs_filename *child, + struct pvfs_filename **name) +{ + NTSTATUS status; + char *p; + + *name = talloc(mem_ctx, struct pvfs_filename); + if (*name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + (*name)->full_name = talloc_strdup(*name, child->full_name); + if ((*name)->full_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + p = strrchr_m((*name)->full_name, '/'); + if (p == NULL) { + return NT_STATUS_OBJECT_PATH_SYNTAX_BAD; + } + + /* this handles the root directory */ + if (p == (*name)->full_name) { + p[1] = 0; + } else { + p[0] = 0; + } + + if (stat((*name)->full_name, &(*name)->st) == -1) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + (*name)->exists = true; + (*name)->stream_exists = true; + (*name)->has_wildcard = false; + /* we can't get the correct 'original_name', but for the purposes + of this call this is close enough */ + (*name)->original_name = talloc_strdup(*name, child->original_name); + if ((*name)->original_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + (*name)->stream_name = NULL; + (*name)->stream_id = 0; + (*name)->allow_override = false; + + status = pvfs_fill_dos_info(pvfs, *name, PVFS_RESOLVE_NO_OPENDB, -1); + + return status; +} diff --git a/source4/ntvfs/posix/pvfs_search.c b/source4/ntvfs/posix/pvfs_search.c new file mode 100644 index 0000000..f352a6a --- /dev/null +++ b/source4/ntvfs/posix/pvfs_search.c @@ -0,0 +1,865 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - directory search functions + + Copyright (C) Andrew Tridgell 2004 + + 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 "vfs_posix.h" +#include "system/time.h" +#include "librpc/gen_ndr/security.h" +#include "samba/service_stream.h" +#include "lib/events/events.h" +#include "../lib/util/dlinklist.h" +#include "lib/util/idtree.h" + +/* place a reasonable limit on old-style searches as clients tend to + not send search close requests */ +#define MAX_OLD_SEARCHES 2000 +#define MAX_SEARCH_HANDLES (UINT16_MAX - 1) +#define INVALID_SEARCH_HANDLE UINT16_MAX + +/* + destroy an open search +*/ +static int pvfs_search_destructor(struct pvfs_search_state *search) +{ + DLIST_REMOVE(search->pvfs->search.list, search); + idr_remove(search->pvfs->search.idtree, search->handle); + return 0; +} + +/* + called when a search timer goes off +*/ +static void pvfs_search_timer(struct tevent_context *ev, struct tevent_timer *te, + struct timeval t, void *ptr) +{ + struct pvfs_search_state *search = talloc_get_type(ptr, struct pvfs_search_state); + talloc_free(search); +} + +/* + setup a timer to destroy a open search after a inactivity period +*/ +static void pvfs_search_setup_timer(struct pvfs_search_state *search) +{ + struct tevent_context *ev = search->pvfs->ntvfs->ctx->event_ctx; + if (search->handle == INVALID_SEARCH_HANDLE) return; + talloc_free(search->te); + search->te = tevent_add_timer(ev, search, + timeval_current_ofs(search->pvfs->search.inactivity_time, 0), + pvfs_search_timer, search); +} + +/* + fill in a single search result for a given info level +*/ +static NTSTATUS fill_search_info(struct pvfs_state *pvfs, + enum smb_search_data_level level, + const char *unix_path, + const char *fname, + struct pvfs_search_state *search, + off_t dir_offset, + union smb_search_data *file) +{ + struct pvfs_filename *name; + NTSTATUS status; + const char *shortname; + uint32_t dir_index = (uint32_t)dir_offset; /* truncated - see the code + in pvfs_list_seek_ofs() for + how we cope with this */ + + status = pvfs_resolve_partial(pvfs, file, unix_path, fname, 0, &name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = pvfs_match_attrib(pvfs, name, search->search_attrib, search->must_attrib); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + switch (level) { + case RAW_SEARCH_DATA_SEARCH: + shortname = pvfs_short_name(pvfs, name, name); + file->search.attrib = name->dos.attrib; + file->search.write_time = nt_time_to_unix(name->dos.write_time); + file->search.size = name->st.st_size; + file->search.name = shortname; + file->search.id.reserved = search->handle >> 8; + memset(file->search.id.name, ' ', sizeof(file->search.id.name)); + memcpy(file->search.id.name, shortname, + MIN(strlen(shortname)+1, sizeof(file->search.id.name))); + file->search.id.handle = search->handle & 0xFF; + file->search.id.server_cookie = dir_index; + file->search.id.client_cookie = 0; + return NT_STATUS_OK; + + case RAW_SEARCH_DATA_STANDARD: + file->standard.resume_key = dir_index; + file->standard.create_time = nt_time_to_unix(name->dos.create_time); + file->standard.access_time = nt_time_to_unix(name->dos.access_time); + file->standard.write_time = nt_time_to_unix(name->dos.write_time); + file->standard.size = name->st.st_size; + file->standard.alloc_size = name->dos.alloc_size; + file->standard.attrib = name->dos.attrib; + file->standard.name.s = fname; + return NT_STATUS_OK; + + case RAW_SEARCH_DATA_EA_SIZE: + file->ea_size.resume_key = dir_index; + file->ea_size.create_time = nt_time_to_unix(name->dos.create_time); + file->ea_size.access_time = nt_time_to_unix(name->dos.access_time); + file->ea_size.write_time = nt_time_to_unix(name->dos.write_time); + file->ea_size.size = name->st.st_size; + file->ea_size.alloc_size = name->dos.alloc_size; + file->ea_size.attrib = name->dos.attrib; + file->ea_size.ea_size = name->dos.ea_size; + file->ea_size.name.s = fname; + return NT_STATUS_OK; + + case RAW_SEARCH_DATA_EA_LIST: + file->ea_list.resume_key = dir_index; + file->ea_list.create_time = nt_time_to_unix(name->dos.create_time); + file->ea_list.access_time = nt_time_to_unix(name->dos.access_time); + file->ea_list.write_time = nt_time_to_unix(name->dos.write_time); + file->ea_list.size = name->st.st_size; + file->ea_list.alloc_size = name->dos.alloc_size; + file->ea_list.attrib = name->dos.attrib; + file->ea_list.name.s = fname; + return pvfs_query_ea_list(pvfs, file, name, -1, + search->num_ea_names, + search->ea_names, + &file->ea_list.eas); + + case RAW_SEARCH_DATA_DIRECTORY_INFO: + file->directory_info.file_index = dir_index; + file->directory_info.create_time = name->dos.create_time; + file->directory_info.access_time = name->dos.access_time; + file->directory_info.write_time = name->dos.write_time; + file->directory_info.change_time = name->dos.change_time; + file->directory_info.size = name->st.st_size; + file->directory_info.alloc_size = name->dos.alloc_size; + file->directory_info.attrib = name->dos.attrib; + file->directory_info.name.s = fname; + return NT_STATUS_OK; + + case RAW_SEARCH_DATA_FULL_DIRECTORY_INFO: + file->full_directory_info.file_index = dir_index; + file->full_directory_info.create_time = name->dos.create_time; + file->full_directory_info.access_time = name->dos.access_time; + file->full_directory_info.write_time = name->dos.write_time; + file->full_directory_info.change_time = name->dos.change_time; + file->full_directory_info.size = name->st.st_size; + file->full_directory_info.alloc_size = name->dos.alloc_size; + file->full_directory_info.attrib = name->dos.attrib; + file->full_directory_info.ea_size = name->dos.ea_size; + file->full_directory_info.name.s = fname; + return NT_STATUS_OK; + + case RAW_SEARCH_DATA_NAME_INFO: + file->name_info.file_index = dir_index; + file->name_info.name.s = fname; + return NT_STATUS_OK; + + case RAW_SEARCH_DATA_BOTH_DIRECTORY_INFO: + file->both_directory_info.file_index = dir_index; + file->both_directory_info.create_time = name->dos.create_time; + file->both_directory_info.access_time = name->dos.access_time; + file->both_directory_info.write_time = name->dos.write_time; + file->both_directory_info.change_time = name->dos.change_time; + file->both_directory_info.size = name->st.st_size; + file->both_directory_info.alloc_size = name->dos.alloc_size; + file->both_directory_info.attrib = name->dos.attrib; + file->both_directory_info.ea_size = name->dos.ea_size; + file->both_directory_info.short_name.s = pvfs_short_name(pvfs, file, name); + file->both_directory_info.name.s = fname; + return NT_STATUS_OK; + + case RAW_SEARCH_DATA_ID_FULL_DIRECTORY_INFO: + file->id_full_directory_info.file_index = dir_index; + file->id_full_directory_info.create_time = name->dos.create_time; + file->id_full_directory_info.access_time = name->dos.access_time; + file->id_full_directory_info.write_time = name->dos.write_time; + file->id_full_directory_info.change_time = name->dos.change_time; + file->id_full_directory_info.size = name->st.st_size; + file->id_full_directory_info.alloc_size = name->dos.alloc_size; + file->id_full_directory_info.attrib = name->dos.attrib; + file->id_full_directory_info.ea_size = name->dos.ea_size; + file->id_full_directory_info.file_id = name->dos.file_id; + file->id_full_directory_info.name.s = fname; + return NT_STATUS_OK; + + case RAW_SEARCH_DATA_ID_BOTH_DIRECTORY_INFO: + file->id_both_directory_info.file_index = dir_index; + file->id_both_directory_info.create_time = name->dos.create_time; + file->id_both_directory_info.access_time = name->dos.access_time; + file->id_both_directory_info.write_time = name->dos.write_time; + file->id_both_directory_info.change_time = name->dos.change_time; + file->id_both_directory_info.size = name->st.st_size; + file->id_both_directory_info.alloc_size = name->dos.alloc_size; + file->id_both_directory_info.attrib = name->dos.attrib; + file->id_both_directory_info.ea_size = name->dos.ea_size; + file->id_both_directory_info.file_id = name->dos.file_id; + file->id_both_directory_info.short_name.s = pvfs_short_name(pvfs, file, name); + file->id_both_directory_info.name.s = fname; + return NT_STATUS_OK; + + case RAW_SEARCH_DATA_GENERIC: + case RAW_SEARCH_DATA_UNIX_INFO: + case RAW_SEARCH_DATA_UNIX_INFO2: + return NT_STATUS_INVALID_LEVEL; + } + + return NT_STATUS_INVALID_LEVEL; +} + + +/* + the search fill loop +*/ +static NTSTATUS pvfs_search_fill(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx, + unsigned int max_count, + struct pvfs_search_state *search, + enum smb_search_data_level level, + unsigned int *reply_count, + void *search_private, + bool (*callback)(void *, const union smb_search_data *)) +{ + struct pvfs_dir *dir = search->dir; + NTSTATUS status; + + *reply_count = 0; + + if (max_count == 0) { + max_count = 1; + } + + while ((*reply_count) < max_count) { + union smb_search_data *file; + const char *name; + off_t ofs = search->current_index; + + name = pvfs_list_next(dir, &search->current_index); + if (name == NULL) break; + + file = talloc(mem_ctx, union smb_search_data); + if (!file) { + return NT_STATUS_NO_MEMORY; + } + + status = fill_search_info(pvfs, level, + pvfs_list_unix_path(dir), name, + search, search->current_index, file); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(file); + continue; + } + + if (!callback(search_private, file)) { + talloc_free(file); + search->current_index = ofs; + break; + } + + (*reply_count)++; + talloc_free(file); + } + + pvfs_search_setup_timer(search); + + return NT_STATUS_OK; +} + +/* + we've run out of search handles - cleanup those that the client forgot + to close +*/ +static void pvfs_search_cleanup(struct pvfs_state *pvfs) +{ + int i; + time_t t = time_mono(NULL); + + for (i=0;i<MAX_OLD_SEARCHES;i++) { + struct pvfs_search_state *search; + void *p = idr_find(pvfs->search.idtree, i); + + if (p == NULL) return; + + search = talloc_get_type(p, struct pvfs_search_state); + if (pvfs_list_eos(search->dir, search->current_index) && + search->last_used != 0 && + t > search->last_used + 30) { + /* its almost certainly been forgotten + about */ + talloc_free(search); + } + } +} + + +/* + list files in a directory matching a wildcard pattern - old SMBsearch interface +*/ +static NTSTATUS pvfs_search_first_old(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_search_first *io, + void *search_private, + bool (*callback)(void *, const union smb_search_data *)) +{ + struct pvfs_dir *dir; + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + struct pvfs_search_state *search; + unsigned int reply_count; + uint16_t search_attrib; + const char *pattern; + NTSTATUS status; + struct pvfs_filename *name; + int id; + + search_attrib = io->search_first.in.search_attrib; + pattern = io->search_first.in.pattern; + + /* resolve the cifs name to a posix name */ + status = pvfs_resolve_name(pvfs, req, pattern, PVFS_RESOLVE_WILDCARD, &name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (!name->has_wildcard && !name->exists) { + return STATUS_NO_MORE_FILES; + } + + status = pvfs_access_check_parent(pvfs, req, name, SEC_DIR_TRAVERSE | SEC_DIR_LIST); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* we initially make search a child of the request, then if we + need to keep it long term we steal it for the private + structure */ + search = talloc(req, struct pvfs_search_state); + if (!search) { + return NT_STATUS_NO_MEMORY; + } + + /* do the actual directory listing */ + status = pvfs_list_start(pvfs, name, search, &dir); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* we need to give a handle back to the client so it + can continue a search */ + id = idr_get_new(pvfs->search.idtree, search, MAX_OLD_SEARCHES); + if (id == -1) { + pvfs_search_cleanup(pvfs); + id = idr_get_new(pvfs->search.idtree, search, MAX_OLD_SEARCHES); + } + if (id == -1) { + return NT_STATUS_INSUFFICIENT_RESOURCES; + } + + search->pvfs = pvfs; + search->handle = id; + search->dir = dir; + search->current_index = 0; + search->search_attrib = search_attrib & 0xFF; + search->must_attrib = (search_attrib>>8) & 0xFF; + search->last_used = time_mono(NULL); + search->te = NULL; + + DLIST_ADD(pvfs->search.list, search); + + talloc_set_destructor(search, pvfs_search_destructor); + + status = pvfs_search_fill(pvfs, req, io->search_first.in.max_count, search, io->generic.data_level, + &reply_count, search_private, callback); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + io->search_first.out.count = reply_count; + + /* not matching any entries is an error */ + if (reply_count == 0) { + return STATUS_NO_MORE_FILES; + } + + talloc_steal(pvfs, search); + + return NT_STATUS_OK; +} + +/* continue a old style search */ +static NTSTATUS pvfs_search_next_old(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_search_next *io, + void *search_private, + bool (*callback)(void *, const union smb_search_data *)) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + void *p; + struct pvfs_search_state *search; + struct pvfs_dir *dir; + unsigned int reply_count, max_count; + uint16_t handle; + NTSTATUS status; + + handle = io->search_next.in.id.handle | (io->search_next.in.id.reserved<<8); + max_count = io->search_next.in.max_count; + + p = idr_find(pvfs->search.idtree, handle); + if (p == NULL) { + /* we didn't find the search handle */ + return NT_STATUS_INVALID_HANDLE; + } + + search = talloc_get_type(p, struct pvfs_search_state); + + dir = search->dir; + + status = pvfs_list_seek_ofs(dir, io->search_next.in.id.server_cookie, + &search->current_index); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + search->last_used = time_mono(NULL); + + status = pvfs_search_fill(pvfs, req, max_count, search, io->generic.data_level, + &reply_count, search_private, callback); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + io->search_next.out.count = reply_count; + + /* not matching any entries means end of search */ + if (reply_count == 0) { + talloc_free(search); + } + + return NT_STATUS_OK; +} + +/* + list files in a directory matching a wildcard pattern +*/ +static NTSTATUS pvfs_search_first_trans2(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_search_first *io, + void *search_private, + bool (*callback)(void *, const union smb_search_data *)) +{ + struct pvfs_dir *dir; + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + struct pvfs_search_state *search; + unsigned int reply_count; + uint16_t search_attrib, max_count; + const char *pattern; + NTSTATUS status; + struct pvfs_filename *name; + int id; + + search_attrib = io->t2ffirst.in.search_attrib; + pattern = io->t2ffirst.in.pattern; + max_count = io->t2ffirst.in.max_count; + + /* resolve the cifs name to a posix name */ + status = pvfs_resolve_name(pvfs, req, pattern, PVFS_RESOLVE_WILDCARD, &name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (!name->has_wildcard && !name->exists) { + return NT_STATUS_NO_SUCH_FILE; + } + + status = pvfs_access_check_parent(pvfs, req, name, SEC_DIR_TRAVERSE | SEC_DIR_LIST); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* we initially make search a child of the request, then if we + need to keep it long term we steal it for the private + structure */ + search = talloc(req, struct pvfs_search_state); + if (!search) { + return NT_STATUS_NO_MEMORY; + } + + /* do the actual directory listing */ + status = pvfs_list_start(pvfs, name, search, &dir); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + id = idr_get_new(pvfs->search.idtree, search, MAX_SEARCH_HANDLES); + if (id == -1) { + return NT_STATUS_INSUFFICIENT_RESOURCES; + } + + search->pvfs = pvfs; + search->handle = id; + search->dir = dir; + search->current_index = 0; + search->search_attrib = search_attrib; + search->must_attrib = 0; + search->last_used = 0; + search->num_ea_names = io->t2ffirst.in.num_names; + search->ea_names = io->t2ffirst.in.ea_names; + search->te = NULL; + + DLIST_ADD(pvfs->search.list, search); + talloc_set_destructor(search, pvfs_search_destructor); + + status = pvfs_search_fill(pvfs, req, max_count, search, io->generic.data_level, + &reply_count, search_private, callback); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* not matching any entries is an error */ + if (reply_count == 0) { + return NT_STATUS_NO_SUCH_FILE; + } + + io->t2ffirst.out.count = reply_count; + io->t2ffirst.out.handle = search->handle; + io->t2ffirst.out.end_of_search = pvfs_list_eos(dir, search->current_index) ? 1 : 0; + + /* work out if we are going to keep the search state + and allow for a search continue */ + if ((io->t2ffirst.in.flags & FLAG_TRANS2_FIND_CLOSE) || + ((io->t2ffirst.in.flags & FLAG_TRANS2_FIND_CLOSE_IF_END) && + io->t2ffirst.out.end_of_search)) { + talloc_free(search); + } else { + talloc_steal(pvfs, search); + } + + return NT_STATUS_OK; +} + +/* continue a search */ +static NTSTATUS pvfs_search_next_trans2(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_search_next *io, + void *search_private, + bool (*callback)(void *, const union smb_search_data *)) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + void *p; + struct pvfs_search_state *search; + struct pvfs_dir *dir; + unsigned int reply_count; + uint16_t handle; + NTSTATUS status; + + handle = io->t2fnext.in.handle; + + p = idr_find(pvfs->search.idtree, handle); + if (p == NULL) { + /* we didn't find the search handle */ + return NT_STATUS_INVALID_HANDLE; + } + + search = talloc_get_type(p, struct pvfs_search_state); + + dir = search->dir; + + status = NT_STATUS_OK; + + /* work out what type of continuation is being used */ + if (io->t2fnext.in.last_name && *io->t2fnext.in.last_name) { + status = pvfs_list_seek(dir, io->t2fnext.in.last_name, &search->current_index); + if (!NT_STATUS_IS_OK(status) && io->t2fnext.in.resume_key) { + status = pvfs_list_seek_ofs(dir, io->t2fnext.in.resume_key, + &search->current_index); + } + } else if (!(io->t2fnext.in.flags & FLAG_TRANS2_FIND_CONTINUE)) { + status = pvfs_list_seek_ofs(dir, io->t2fnext.in.resume_key, + &search->current_index); + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + search->num_ea_names = io->t2fnext.in.num_names; + search->ea_names = io->t2fnext.in.ea_names; + + status = pvfs_search_fill(pvfs, req, io->t2fnext.in.max_count, search, io->generic.data_level, + &reply_count, search_private, callback); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + io->t2fnext.out.count = reply_count; + io->t2fnext.out.end_of_search = pvfs_list_eos(dir, search->current_index) ? 1 : 0; + + /* work out if we are going to keep the search state */ + if ((io->t2fnext.in.flags & FLAG_TRANS2_FIND_CLOSE) || + ((io->t2fnext.in.flags & FLAG_TRANS2_FIND_CLOSE_IF_END) && + io->t2fnext.out.end_of_search)) { + talloc_free(search); + } + + return NT_STATUS_OK; +} + +static NTSTATUS pvfs_search_first_smb2(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, const struct smb2_find *io, + void *search_private, + bool (*callback)(void *, const union smb_search_data *)) +{ + struct pvfs_dir *dir; + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + struct pvfs_search_state *search; + unsigned int reply_count; + uint16_t max_count; + const char *pattern; + NTSTATUS status; + struct pvfs_filename *name; + struct pvfs_file *f; + + f = pvfs_find_fd(pvfs, req, io->in.file.ntvfs); + if (!f) { + return NT_STATUS_FILE_CLOSED; + } + + /* its only valid for directories */ + if (f->handle->fd != -1) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (!(f->access_mask & SEC_DIR_LIST)) { + return NT_STATUS_ACCESS_DENIED; + } + + if (f->search) { + talloc_free(f->search); + f->search = NULL; + } + + if (strequal(io->in.pattern, "")) { + return NT_STATUS_OBJECT_NAME_INVALID; + } + if (strchr_m(io->in.pattern, '\\')) { + return NT_STATUS_OBJECT_NAME_INVALID; + } + if (strchr_m(io->in.pattern, '/')) { + return NT_STATUS_OBJECT_NAME_INVALID; + } + + if (strequal("", f->handle->name->original_name)) { + pattern = talloc_asprintf(req, "%s", io->in.pattern); + NT_STATUS_HAVE_NO_MEMORY(pattern); + } else { + pattern = talloc_asprintf(req, "%s\\%s", + f->handle->name->original_name, + io->in.pattern); + NT_STATUS_HAVE_NO_MEMORY(pattern); + } + + /* resolve the cifs name to a posix name */ + status = pvfs_resolve_name(pvfs, req, pattern, PVFS_RESOLVE_WILDCARD, &name); + NT_STATUS_NOT_OK_RETURN(status); + + if (!name->has_wildcard && !name->exists) { + return NT_STATUS_NO_SUCH_FILE; + } + + /* we initially make search a child of the request, then if we + need to keep it long term we steal it for the private + structure */ + search = talloc(req, struct pvfs_search_state); + NT_STATUS_HAVE_NO_MEMORY(search); + + /* do the actual directory listing */ + status = pvfs_list_start(pvfs, name, search, &dir); + NT_STATUS_NOT_OK_RETURN(status); + + search->pvfs = pvfs; + search->handle = INVALID_SEARCH_HANDLE; + search->dir = dir; + search->current_index = 0; + search->search_attrib = 0x0000FFFF; + search->must_attrib = 0; + search->last_used = 0; + search->num_ea_names = 0; + search->ea_names = NULL; + search->te = NULL; + + if (io->in.continue_flags & SMB2_CONTINUE_FLAG_SINGLE) { + max_count = 1; + } else { + max_count = UINT16_MAX; + } + + status = pvfs_search_fill(pvfs, req, max_count, search, io->data_level, + &reply_count, search_private, callback); + NT_STATUS_NOT_OK_RETURN(status); + + /* not matching any entries is an error */ + if (reply_count == 0) { + return NT_STATUS_NO_SUCH_FILE; + } + + f->search = talloc_steal(f, search); + + return NT_STATUS_OK; +} + +static NTSTATUS pvfs_search_next_smb2(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, const struct smb2_find *io, + void *search_private, + bool (*callback)(void *, const union smb_search_data *)) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + struct pvfs_search_state *search; + unsigned int reply_count; + uint16_t max_count; + NTSTATUS status; + struct pvfs_file *f; + + f = pvfs_find_fd(pvfs, req, io->in.file.ntvfs); + if (!f) { + return NT_STATUS_FILE_CLOSED; + } + + /* its only valid for directories */ + if (f->handle->fd != -1) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* if there's no search started on the dir handle, it's like a search_first */ + search = f->search; + if (!search) { + return pvfs_search_first_smb2(ntvfs, req, io, search_private, callback); + } + + if (io->in.continue_flags & SMB2_CONTINUE_FLAG_RESTART) { + search->current_index = 0; + } + + if (io->in.continue_flags & SMB2_CONTINUE_FLAG_SINGLE) { + max_count = 1; + } else { + max_count = UINT16_MAX; + } + + status = pvfs_search_fill(pvfs, req, max_count, search, io->data_level, + &reply_count, search_private, callback); + NT_STATUS_NOT_OK_RETURN(status); + + /* not matching any entries is an error */ + if (reply_count == 0) { + return STATUS_NO_MORE_FILES; + } + + return NT_STATUS_OK; +} + +/* + list files in a directory matching a wildcard pattern +*/ +NTSTATUS pvfs_search_first(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_search_first *io, + void *search_private, + bool (*callback)(void *, const union smb_search_data *)) +{ + switch (io->generic.level) { + case RAW_SEARCH_SEARCH: + case RAW_SEARCH_FFIRST: + case RAW_SEARCH_FUNIQUE: + return pvfs_search_first_old(ntvfs, req, io, search_private, callback); + + case RAW_SEARCH_TRANS2: + return pvfs_search_first_trans2(ntvfs, req, io, search_private, callback); + + case RAW_SEARCH_SMB2: + return pvfs_search_first_smb2(ntvfs, req, &io->smb2, search_private, callback); + } + + return NT_STATUS_INVALID_LEVEL; +} + +/* continue a search */ +NTSTATUS pvfs_search_next(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_search_next *io, + void *search_private, + bool (*callback)(void *, const union smb_search_data *)) +{ + switch (io->generic.level) { + case RAW_SEARCH_SEARCH: + case RAW_SEARCH_FFIRST: + return pvfs_search_next_old(ntvfs, req, io, search_private, callback); + + case RAW_SEARCH_FUNIQUE: + return NT_STATUS_INVALID_LEVEL; + + case RAW_SEARCH_TRANS2: + return pvfs_search_next_trans2(ntvfs, req, io, search_private, callback); + + case RAW_SEARCH_SMB2: + return pvfs_search_next_smb2(ntvfs, req, &io->smb2, search_private, callback); + } + + return NT_STATUS_INVALID_LEVEL; +} + + +/* close a search */ +NTSTATUS pvfs_search_close(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_search_close *io) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + void *p; + struct pvfs_search_state *search; + uint16_t handle = INVALID_SEARCH_HANDLE; + + switch (io->generic.level) { + case RAW_FINDCLOSE_GENERIC: + return NT_STATUS_INVALID_LEVEL; + + case RAW_FINDCLOSE_FCLOSE: + handle = io->fclose.in.id.handle; + break; + + case RAW_FINDCLOSE_FINDCLOSE: + handle = io->findclose.in.handle; + break; + } + + p = idr_find(pvfs->search.idtree, handle); + if (p == NULL) { + /* we didn't find the search handle */ + return NT_STATUS_INVALID_HANDLE; + } + + search = talloc_get_type(p, struct pvfs_search_state); + + talloc_free(search); + + return NT_STATUS_OK; +} + diff --git a/source4/ntvfs/posix/pvfs_seek.c b/source4/ntvfs/posix/pvfs_seek.c new file mode 100644 index 0000000..2cd3410 --- /dev/null +++ b/source4/ntvfs/posix/pvfs_seek.c @@ -0,0 +1,65 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - seek + + Copyright (C) Andrew Tridgell 2004 + + 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 "vfs_posix.h" + +/* + seek in a file +*/ +NTSTATUS pvfs_seek(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_seek *io) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + struct pvfs_file *f; + struct pvfs_file_handle *h; + NTSTATUS status; + + f = pvfs_find_fd(pvfs, req, io->lseek.in.file.ntvfs); + if (!f) { + return NT_STATUS_INVALID_HANDLE; + } + h = f->handle; + + status = NT_STATUS_OK; + + switch (io->lseek.in.mode) { + case SEEK_MODE_START: + h->seek_offset = io->lseek.in.offset; + break; + + case SEEK_MODE_CURRENT: + h->seek_offset += io->lseek.in.offset; + break; + + case SEEK_MODE_END: + status = pvfs_resolve_name_fd(pvfs, h->fd, h->name, PVFS_RESOLVE_NO_OPENDB); + h->seek_offset = h->name->st.st_size + io->lseek.in.offset; + break; + } + + io->lseek.out.offset = h->seek_offset; + + return status; +} + diff --git a/source4/ntvfs/posix/pvfs_setfileinfo.c b/source4/ntvfs/posix/pvfs_setfileinfo.c new file mode 100644 index 0000000..7fd4e35 --- /dev/null +++ b/source4/ntvfs/posix/pvfs_setfileinfo.c @@ -0,0 +1,884 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - setfileinfo + + Copyright (C) Andrew Tridgell 2004 + + 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 "vfs_posix.h" +#include "system/time.h" +#include "librpc/gen_ndr/xattr.h" + + +/* + determine what access bits are needed for a call +*/ +static uint32_t pvfs_setfileinfo_access(union smb_setfileinfo *info) +{ + uint32_t needed; + + switch (info->generic.level) { + case RAW_SFILEINFO_EA_SET: + needed = SEC_FILE_WRITE_EA; + break; + + case RAW_SFILEINFO_DISPOSITION_INFO: + case RAW_SFILEINFO_DISPOSITION_INFORMATION: + needed = SEC_STD_DELETE; + break; + + case RAW_SFILEINFO_END_OF_FILE_INFO: + needed = SEC_FILE_WRITE_DATA; + break; + + case RAW_SFILEINFO_POSITION_INFORMATION: + needed = 0; + break; + + case RAW_SFILEINFO_SEC_DESC: + needed = 0; + if (info->set_secdesc.in.secinfo_flags & (SECINFO_OWNER|SECINFO_GROUP)) { + needed |= SEC_STD_WRITE_OWNER; + } + if (info->set_secdesc.in.secinfo_flags & SECINFO_DACL) { + needed |= SEC_STD_WRITE_DAC; + } + if (info->set_secdesc.in.secinfo_flags & SECINFO_SACL) { + needed |= SEC_FLAG_SYSTEM_SECURITY; + } + break; + + case RAW_SFILEINFO_RENAME_INFORMATION: + case RAW_SFILEINFO_RENAME_INFORMATION_SMB2: + needed = SEC_STD_DELETE; + break; + + default: + needed = SEC_FILE_WRITE_ATTRIBUTE; + break; + } + + return needed; +} + +/* + rename_information level for streams +*/ +static NTSTATUS pvfs_setfileinfo_rename_stream(struct pvfs_state *pvfs, + struct ntvfs_request *req, + struct pvfs_filename *name, + int fd, + DATA_BLOB *odb_locking_key, + union smb_setfileinfo *info) +{ + NTSTATUS status; + struct odb_lock *lck = NULL; + + /* strangely, this gives a sharing violation, not invalid + parameter */ + if (info->rename_information.in.new_name[0] != ':') { + return NT_STATUS_SHARING_VIOLATION; + } + + status = pvfs_access_check_simple(pvfs, req, name, SEC_FILE_WRITE_ATTRIBUTE); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + lck = odb_lock(req, pvfs->odb_context, odb_locking_key); + if (lck == NULL) { + DEBUG(0,("Unable to lock opendb for can_stat\n")); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + + status = pvfs_stream_rename(pvfs, name, fd, + info->rename_information.in.new_name+1, + info->rename_information.in.overwrite); + return status; +} + +/* + rename_information level +*/ +static NTSTATUS pvfs_setfileinfo_rename(struct pvfs_state *pvfs, + struct ntvfs_request *req, + struct pvfs_filename *name, + int fd, + DATA_BLOB *odb_locking_key, + union smb_setfileinfo *info) +{ + NTSTATUS status; + struct pvfs_filename *name2; + char *new_name, *p; + struct odb_lock *lck = NULL; + + /* renames are only allowed within a directory */ + if (strchr_m(info->rename_information.in.new_name, '\\') && + (req->ctx->protocol < PROTOCOL_SMB2_02)) { + return NT_STATUS_NOT_SUPPORTED; + } + + /* handle stream renames specially */ + if (name->stream_name) { + return pvfs_setfileinfo_rename_stream(pvfs, req, name, fd, + odb_locking_key, info); + } + + /* w2k3 does not appear to allow relative rename. On SMB2, vista sends it sometimes, + but I suspect it is just uninitialised memory */ + if (info->rename_information.in.root_fid != 0 && + (req->ctx->protocol < PROTOCOL_SMB2_02)) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* construct the fully qualified windows name for the new file name */ + if (req->ctx->protocol >= PROTOCOL_SMB2_02) { + /* SMB2 sends the full path of the new name */ + new_name = talloc_asprintf(req, "\\%s", info->rename_information.in.new_name); + } else { + new_name = talloc_strdup(req, name->original_name); + if (new_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + p = strrchr_m(new_name, '\\'); + if (p == NULL) { + return NT_STATUS_OBJECT_NAME_INVALID; + } else { + *p = 0; + } + + new_name = talloc_asprintf(req, "%s\\%s", new_name, + info->rename_information.in.new_name); + } + if (new_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* resolve the new name */ + status = pvfs_resolve_name(pvfs, req, new_name, 0, &name2); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* if the destination exists, then check the rename is allowed */ + if (name2->exists) { + if (strcmp(name2->full_name, name->full_name) == 0) { + /* rename to same name is null-op */ + return NT_STATUS_OK; + } + + if (!info->rename_information.in.overwrite) { + return NT_STATUS_OBJECT_NAME_COLLISION; + } + + status = pvfs_can_delete(pvfs, req, name2, NULL); + if (NT_STATUS_EQUAL(status, NT_STATUS_DELETE_PENDING)) { + return NT_STATUS_ACCESS_DENIED; + } + if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) { + return NT_STATUS_ACCESS_DENIED; + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + status = pvfs_access_check_parent(pvfs, req, name2, SEC_DIR_ADD_FILE); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + lck = odb_lock(req, pvfs->odb_context, odb_locking_key); + if (lck == NULL) { + DEBUG(0,("Unable to lock opendb for can_stat\n")); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + status = pvfs_do_rename(pvfs, lck, name, name2->full_name); + talloc_free(lck); + NT_STATUS_NOT_OK_RETURN(status); + if (NT_STATUS_IS_OK(status)) { + name->full_name = talloc_steal(name, name2->full_name); + name->original_name = talloc_steal(name, name2->original_name); + } + + return NT_STATUS_OK; +} + +/* + add a single DOS EA +*/ +NTSTATUS pvfs_setfileinfo_ea_set(struct pvfs_state *pvfs, + struct pvfs_filename *name, + int fd, uint16_t num_eas, + struct ea_struct *eas) +{ + struct xattr_DosEAs *ealist; + int i, j; + NTSTATUS status; + + if (num_eas == 0) { + return NT_STATUS_OK; + } + + if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE)) { + return NT_STATUS_NOT_SUPPORTED; + } + + ealist = talloc(name, struct xattr_DosEAs); + + /* load the current list */ + status = pvfs_doseas_load(pvfs, name, fd, ealist); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + for (j=0;j<num_eas;j++) { + struct ea_struct *ea = &eas[j]; + /* see if its already there */ + for (i=0;i<ealist->num_eas;i++) { + if (strcasecmp_m(ealist->eas[i].name, ea->name.s) == 0) { + ealist->eas[i].value = ea->value; + break; + } + } + + if (i==ealist->num_eas) { + /* add it */ + ealist->eas = talloc_realloc(ealist, ealist->eas, + struct xattr_EA, + ealist->num_eas+1); + if (ealist->eas == NULL) { + return NT_STATUS_NO_MEMORY; + } + ealist->eas[i].name = ea->name.s; + ealist->eas[i].value = ea->value; + ealist->num_eas++; + } + } + + /* pull out any null EAs */ + for (i=0;i<ealist->num_eas;i++) { + if (ealist->eas[i].value.length == 0) { + memmove(&ealist->eas[i], + &ealist->eas[i+1], + (ealist->num_eas-(i+1)) * sizeof(ealist->eas[i])); + ealist->num_eas--; + i--; + } + } + + status = pvfs_doseas_save(pvfs, name, fd, ealist); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + notify_trigger(pvfs->notify_context, + NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_EA, + name->full_name); + + name->dos.ea_size = 4; + for (i=0;i<ealist->num_eas;i++) { + name->dos.ea_size += 4 + strlen(ealist->eas[i].name)+1 + + ealist->eas[i].value.length; + } + + /* update the ea_size attrib */ + return pvfs_dosattrib_save(pvfs, name, fd); +} + +/* + set info on a open file +*/ +NTSTATUS pvfs_setfileinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_setfileinfo *info) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + struct pvfs_file *f; + struct pvfs_file_handle *h; + struct pvfs_filename newstats; + NTSTATUS status; + uint32_t access_needed; + uint32_t change_mask = 0; + + f = pvfs_find_fd(pvfs, req, info->generic.in.file.ntvfs); + if (!f) { + return NT_STATUS_INVALID_HANDLE; + } + + h = f->handle; + + access_needed = pvfs_setfileinfo_access(info); + if ((f->access_mask & access_needed) != access_needed) { + return NT_STATUS_ACCESS_DENIED; + } + + /* update the file information */ + status = pvfs_resolve_name_handle(pvfs, h); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* we take a copy of the current file stats, then update + newstats in each of the elements below. At the end we + compare, and make any changes needed */ + newstats = *h->name; + + switch (info->generic.level) { + case RAW_SFILEINFO_SETATTR: + if (!null_time(info->setattr.in.write_time)) { + unix_to_nt_time(&newstats.dos.write_time, info->setattr.in.write_time); + } + if (info->setattr.in.attrib != FILE_ATTRIBUTE_NORMAL) { + newstats.dos.attrib = info->setattr.in.attrib; + } + break; + + case RAW_SFILEINFO_SETATTRE: + case RAW_SFILEINFO_STANDARD: + if (!null_time(info->setattre.in.create_time)) { + unix_to_nt_time(&newstats.dos.create_time, info->setattre.in.create_time); + } + if (!null_time(info->setattre.in.access_time)) { + unix_to_nt_time(&newstats.dos.access_time, info->setattre.in.access_time); + } + if (!null_time(info->setattre.in.write_time)) { + unix_to_nt_time(&newstats.dos.write_time, info->setattre.in.write_time); + } + break; + + case RAW_SFILEINFO_EA_SET: + return pvfs_setfileinfo_ea_set(pvfs, h->name, h->fd, + info->ea_set.in.num_eas, + info->ea_set.in.eas); + + case RAW_SFILEINFO_BASIC_INFO: + case RAW_SFILEINFO_BASIC_INFORMATION: + if (!null_nttime(info->basic_info.in.create_time)) { + newstats.dos.create_time = info->basic_info.in.create_time; + } + if (!null_nttime(info->basic_info.in.access_time)) { + newstats.dos.access_time = info->basic_info.in.access_time; + } + if (!null_nttime(info->basic_info.in.write_time)) { + newstats.dos.write_time = info->basic_info.in.write_time; + } + if (!null_nttime(info->basic_info.in.change_time)) { + newstats.dos.change_time = info->basic_info.in.change_time; + } + if (info->basic_info.in.attrib != 0) { + newstats.dos.attrib = info->basic_info.in.attrib; + } + break; + + case RAW_SFILEINFO_DISPOSITION_INFO: + case RAW_SFILEINFO_DISPOSITION_INFORMATION: + return pvfs_set_delete_on_close(pvfs, req, f, + info->disposition_info.in.delete_on_close); + + case RAW_SFILEINFO_ALLOCATION_INFO: + case RAW_SFILEINFO_ALLOCATION_INFORMATION: + status = pvfs_break_level2_oplocks(f); + NT_STATUS_NOT_OK_RETURN(status); + + newstats.dos.alloc_size = info->allocation_info.in.alloc_size; + if (newstats.dos.alloc_size < newstats.st.st_size) { + newstats.st.st_size = newstats.dos.alloc_size; + } + newstats.dos.alloc_size = pvfs_round_alloc_size(pvfs, + newstats.dos.alloc_size); + break; + + case RAW_SFILEINFO_END_OF_FILE_INFO: + case RAW_SFILEINFO_END_OF_FILE_INFORMATION: + status = pvfs_break_level2_oplocks(f); + NT_STATUS_NOT_OK_RETURN(status); + + newstats.st.st_size = info->end_of_file_info.in.size; + break; + + case RAW_SFILEINFO_POSITION_INFORMATION: + h->position = info->position_information.in.position; + break; + + case RAW_SFILEINFO_FULL_EA_INFORMATION: + return pvfs_setfileinfo_ea_set(pvfs, h->name, h->fd, + info->full_ea_information.in.eas.num_eas, + info->full_ea_information.in.eas.eas); + + case RAW_SFILEINFO_MODE_INFORMATION: + /* this one is a puzzle */ + if (info->mode_information.in.mode != 0 && + info->mode_information.in.mode != 2 && + info->mode_information.in.mode != 4 && + info->mode_information.in.mode != 6) { + return NT_STATUS_INVALID_PARAMETER; + } + h->mode = info->mode_information.in.mode; + break; + + case RAW_SFILEINFO_RENAME_INFORMATION: + case RAW_SFILEINFO_RENAME_INFORMATION_SMB2: + return pvfs_setfileinfo_rename(pvfs, req, h->name, f->handle->fd, + &h->odb_locking_key, + info); + + case RAW_SFILEINFO_SEC_DESC: + notify_trigger(pvfs->notify_context, + NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_SECURITY, + h->name->full_name); + return pvfs_acl_set(pvfs, req, h->name, h->fd, f->access_mask, info); + + default: + return NT_STATUS_INVALID_LEVEL; + } + + /* possibly change the file size */ + if (newstats.st.st_size != h->name->st.st_size) { + if (h->name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) { + return NT_STATUS_FILE_IS_A_DIRECTORY; + } + if (h->name->stream_name) { + status = pvfs_stream_truncate(pvfs, h->name, h->fd, newstats.st.st_size); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + change_mask |= FILE_NOTIFY_CHANGE_STREAM_SIZE; + } else { + int ret; + if (f->access_mask & + (SEC_FILE_WRITE_DATA|SEC_FILE_APPEND_DATA)) { + ret = ftruncate(h->fd, newstats.st.st_size); + } else { + ret = truncate(h->name->full_name, newstats.st.st_size); + } + if (ret == -1) { + return pvfs_map_errno(pvfs, errno); + } + change_mask |= FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_ATTRIBUTES; + } + } + + /* possibly change the file timestamps */ + if (newstats.dos.create_time != h->name->dos.create_time) { + change_mask |= FILE_NOTIFY_CHANGE_CREATION; + } + if (newstats.dos.access_time != h->name->dos.access_time) { + change_mask |= FILE_NOTIFY_CHANGE_LAST_ACCESS; + } + if (newstats.dos.write_time != h->name->dos.write_time) { + change_mask |= FILE_NOTIFY_CHANGE_LAST_WRITE; + } + if ((change_mask & FILE_NOTIFY_CHANGE_LAST_ACCESS) || + (change_mask & FILE_NOTIFY_CHANGE_LAST_WRITE)) { + struct timeval tv[2]; + + nttime_to_timeval(&tv[0], newstats.dos.access_time); + nttime_to_timeval(&tv[1], newstats.dos.write_time); + + if (!timeval_is_zero(&tv[0]) || !timeval_is_zero(&tv[1])) { + if (utimes(h->name->full_name, tv) == -1) { + DEBUG(0,("pvfs_setfileinfo: utimes() failed '%s' - %s\n", + h->name->full_name, strerror(errno))); + return pvfs_map_errno(pvfs, errno); + } + } + } + if (change_mask & FILE_NOTIFY_CHANGE_LAST_WRITE) { + struct odb_lock *lck; + + lck = odb_lock(req, h->pvfs->odb_context, &h->odb_locking_key); + if (lck == NULL) { + DEBUG(0,("Unable to lock opendb for write time update\n")); + return NT_STATUS_INTERNAL_ERROR; + } + + status = odb_set_write_time(lck, newstats.dos.write_time, true); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Unable to update write time: %s\n", + nt_errstr(status))); + talloc_free(lck); + return status; + } + + talloc_free(lck); + + h->write_time.update_forced = true; + h->write_time.update_on_close = false; + talloc_free(h->write_time.update_event); + h->write_time.update_event = NULL; + } + + /* possibly change the attribute */ + if (newstats.dos.attrib != h->name->dos.attrib) { + mode_t mode; + if ((newstats.dos.attrib & FILE_ATTRIBUTE_DIRECTORY) && + !(h->name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY)) { + return NT_STATUS_INVALID_PARAMETER; + } + mode = pvfs_fileperms(pvfs, newstats.dos.attrib); + if (!(h->name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY)) { + if (pvfs_sys_fchmod(pvfs, h->fd, mode, h->name->allow_override) == -1) { + return pvfs_map_errno(pvfs, errno); + } + } + change_mask |= FILE_NOTIFY_CHANGE_ATTRIBUTES; + } + + *h->name = newstats; + + notify_trigger(pvfs->notify_context, + NOTIFY_ACTION_MODIFIED, + change_mask, + h->name->full_name); + + return pvfs_dosattrib_save(pvfs, h->name, h->fd); +} + +/* + retry an open after a sharing violation +*/ +static void pvfs_retry_setpathinfo(struct pvfs_odb_retry *r, + struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + void *_info, + void *private_data, + enum pvfs_wait_notice reason) +{ + union smb_setfileinfo *info = talloc_get_type(_info, + union smb_setfileinfo); + NTSTATUS status = NT_STATUS_INTERNAL_ERROR; + + talloc_free(r); + + switch (reason) { + case PVFS_WAIT_CANCEL: +/*TODO*/ + status = NT_STATUS_CANCELLED; + break; + case PVFS_WAIT_TIMEOUT: + /* if it timed out, then give the failure + immediately */ +/*TODO*/ + status = NT_STATUS_SHARING_VIOLATION; + break; + case PVFS_WAIT_EVENT: + + /* try the open again, which could trigger another retry setup + if it wants to, so we have to unmark the async flag so we + will know if it does a second async reply */ + req->async_states->state &= ~NTVFS_ASYNC_STATE_ASYNC; + + status = pvfs_setpathinfo(ntvfs, req, info); + if (req->async_states->state & NTVFS_ASYNC_STATE_ASYNC) { + /* the 2nd try also replied async, so we don't send + the reply yet */ + return; + } + + /* re-mark it async, just in case someone up the chain does + paranoid checking */ + req->async_states->state |= NTVFS_ASYNC_STATE_ASYNC; + break; + } + + /* send the reply up the chain */ + req->async_states->status = status; + req->async_states->send_fn(req); +} + +/* + setup for a unlink retry after a sharing violation + or a non granted oplock +*/ +static NTSTATUS pvfs_setpathinfo_setup_retry(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_setfileinfo *info, + struct odb_lock *lck, + NTSTATUS status) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + struct timeval end_time; + + if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) { + end_time = timeval_add(&req->statistics.request_time, + 0, pvfs->sharing_violation_delay); + } else if (NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) { + end_time = timeval_add(&req->statistics.request_time, + pvfs->oplock_break_timeout, 0); + } else { + return NT_STATUS_INTERNAL_ERROR; + } + + return pvfs_odb_retry_setup(ntvfs, req, lck, end_time, info, NULL, + pvfs_retry_setpathinfo); +} + +/* + set info on a pathname +*/ +NTSTATUS pvfs_setpathinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_setfileinfo *info) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + struct pvfs_filename *name; + struct pvfs_filename newstats; + NTSTATUS status; + uint32_t access_needed; + uint32_t change_mask = 0; + struct odb_lock *lck = NULL; + DATA_BLOB odb_locking_key; + + /* resolve the cifs name to a posix name */ + status = pvfs_resolve_name(pvfs, req, info->generic.in.file.path, + PVFS_RESOLVE_STREAMS, &name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (!name->exists) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + access_needed = pvfs_setfileinfo_access(info); + status = pvfs_access_check_simple(pvfs, req, name, access_needed); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* we take a copy of the current file stats, then update + newstats in each of the elements below. At the end we + compare, and make any changes needed */ + newstats = *name; + + switch (info->generic.level) { + case RAW_SFILEINFO_SETATTR: + if (!null_time(info->setattr.in.write_time)) { + unix_to_nt_time(&newstats.dos.write_time, info->setattr.in.write_time); + } + if (info->setattr.in.attrib == 0) { + newstats.dos.attrib = FILE_ATTRIBUTE_NORMAL; + } else if (info->setattr.in.attrib != FILE_ATTRIBUTE_NORMAL) { + newstats.dos.attrib = info->setattr.in.attrib; + } + break; + + case RAW_SFILEINFO_SETATTRE: + case RAW_SFILEINFO_STANDARD: + if (!null_time(info->setattre.in.create_time)) { + unix_to_nt_time(&newstats.dos.create_time, info->setattre.in.create_time); + } + if (!null_time(info->setattre.in.access_time)) { + unix_to_nt_time(&newstats.dos.access_time, info->setattre.in.access_time); + } + if (!null_time(info->setattre.in.write_time)) { + unix_to_nt_time(&newstats.dos.write_time, info->setattre.in.write_time); + } + break; + + case RAW_SFILEINFO_EA_SET: + return pvfs_setfileinfo_ea_set(pvfs, name, -1, + info->ea_set.in.num_eas, + info->ea_set.in.eas); + + case RAW_SFILEINFO_BASIC_INFO: + case RAW_SFILEINFO_BASIC_INFORMATION: + if (!null_nttime(info->basic_info.in.create_time)) { + newstats.dos.create_time = info->basic_info.in.create_time; + } + if (!null_nttime(info->basic_info.in.access_time)) { + newstats.dos.access_time = info->basic_info.in.access_time; + } + if (!null_nttime(info->basic_info.in.write_time)) { + newstats.dos.write_time = info->basic_info.in.write_time; + } + if (!null_nttime(info->basic_info.in.change_time)) { + newstats.dos.change_time = info->basic_info.in.change_time; + } + if (info->basic_info.in.attrib != 0) { + newstats.dos.attrib = info->basic_info.in.attrib; + } + break; + + case RAW_SFILEINFO_ALLOCATION_INFO: + case RAW_SFILEINFO_ALLOCATION_INFORMATION: + status = pvfs_can_update_file_size(pvfs, req, name, &lck); + /* + * on a sharing violation we need to retry when the file is closed by + * the other user, or after 1 second + * on a non granted oplock we need to retry when the file is closed by + * the other user, or after 30 seconds + */ + if ((NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) || + NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) && + (req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return pvfs_setpathinfo_setup_retry(pvfs->ntvfs, req, info, lck, status); + } + NT_STATUS_NOT_OK_RETURN(status); + + if (info->allocation_info.in.alloc_size > newstats.dos.alloc_size) { + /* strange. Increasing the allocation size via setpathinfo + should be silently ignored */ + break; + } + newstats.dos.alloc_size = info->allocation_info.in.alloc_size; + if (newstats.dos.alloc_size < newstats.st.st_size) { + newstats.st.st_size = newstats.dos.alloc_size; + } + newstats.dos.alloc_size = pvfs_round_alloc_size(pvfs, + newstats.dos.alloc_size); + break; + + case RAW_SFILEINFO_END_OF_FILE_INFO: + case RAW_SFILEINFO_END_OF_FILE_INFORMATION: + status = pvfs_can_update_file_size(pvfs, req, name, &lck); + /* + * on a sharing violation we need to retry when the file is closed by + * the other user, or after 1 second + * on a non granted oplock we need to retry when the file is closed by + * the other user, or after 30 seconds + */ + if ((NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) || + NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) && + (req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return pvfs_setpathinfo_setup_retry(pvfs->ntvfs, req, info, lck, status); + } + NT_STATUS_NOT_OK_RETURN(status); + + newstats.st.st_size = info->end_of_file_info.in.size; + break; + + case RAW_SFILEINFO_MODE_INFORMATION: + if (info->mode_information.in.mode != 0 && + info->mode_information.in.mode != 2 && + info->mode_information.in.mode != 4 && + info->mode_information.in.mode != 6) { + return NT_STATUS_INVALID_PARAMETER; + } + return NT_STATUS_OK; + + case RAW_SFILEINFO_RENAME_INFORMATION: + case RAW_SFILEINFO_RENAME_INFORMATION_SMB2: + status = pvfs_locking_key(name, name, &odb_locking_key); + NT_STATUS_NOT_OK_RETURN(status); + status = pvfs_setfileinfo_rename(pvfs, req, name, -1, + &odb_locking_key, info); + NT_STATUS_NOT_OK_RETURN(status); + return NT_STATUS_OK; + + case RAW_SFILEINFO_DISPOSITION_INFO: + case RAW_SFILEINFO_DISPOSITION_INFORMATION: + case RAW_SFILEINFO_POSITION_INFORMATION: + return NT_STATUS_OK; + + default: + return NT_STATUS_INVALID_LEVEL; + } + + /* possibly change the file size */ + if (newstats.st.st_size != name->st.st_size) { + if (name->stream_name) { + status = pvfs_stream_truncate(pvfs, name, -1, newstats.st.st_size); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } else if (truncate(name->full_name, newstats.st.st_size) == -1) { + return pvfs_map_errno(pvfs, errno); + } + change_mask |= FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_ATTRIBUTES; + } + + /* possibly change the file timestamps */ + if (newstats.dos.create_time != name->dos.create_time) { + change_mask |= FILE_NOTIFY_CHANGE_CREATION; + } + if (newstats.dos.access_time != name->dos.access_time) { + change_mask |= FILE_NOTIFY_CHANGE_LAST_ACCESS; + } + if (newstats.dos.write_time != name->dos.write_time) { + change_mask |= FILE_NOTIFY_CHANGE_LAST_WRITE; + } + if ((change_mask & FILE_NOTIFY_CHANGE_LAST_ACCESS) || + (change_mask & FILE_NOTIFY_CHANGE_LAST_WRITE)) { + struct timeval tv[2]; + + nttime_to_timeval(&tv[0], newstats.dos.access_time); + nttime_to_timeval(&tv[1], newstats.dos.write_time); + + if (!timeval_is_zero(&tv[0]) || !timeval_is_zero(&tv[1])) { + if (utimes(name->full_name, tv) == -1) { + DEBUG(0,("pvfs_setpathinfo: utimes() failed '%s' - %s\n", + name->full_name, strerror(errno))); + return pvfs_map_errno(pvfs, errno); + } + } + } + if (change_mask & FILE_NOTIFY_CHANGE_LAST_WRITE) { + if (lck == NULL) { + DATA_BLOB lkey; + status = pvfs_locking_key(name, name, &lkey); + NT_STATUS_NOT_OK_RETURN(status); + + lck = odb_lock(req, pvfs->odb_context, &lkey); + data_blob_free(&lkey); + if (lck == NULL) { + DEBUG(0,("Unable to lock opendb for write time update\n")); + return NT_STATUS_INTERNAL_ERROR; + } + } + + status = odb_set_write_time(lck, newstats.dos.write_time, true); + if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + /* it could be that nobody has opened the file */ + } else if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Unable to update write time: %s\n", + nt_errstr(status))); + return status; + } + } + + /* possibly change the attribute */ + newstats.dos.attrib |= (name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY); + if (newstats.dos.attrib != name->dos.attrib) { + mode_t mode = pvfs_fileperms(pvfs, newstats.dos.attrib); + if (pvfs_sys_chmod(pvfs, name->full_name, mode, name->allow_override) == -1) { + return pvfs_map_errno(pvfs, errno); + } + change_mask |= FILE_NOTIFY_CHANGE_ATTRIBUTES; + } + + *name = newstats; + + if (change_mask != 0) { + notify_trigger(pvfs->notify_context, + NOTIFY_ACTION_MODIFIED, + change_mask, + name->full_name); + } + + return pvfs_dosattrib_save(pvfs, name, -1); +} + diff --git a/source4/ntvfs/posix/pvfs_shortname.c b/source4/ntvfs/posix/pvfs_shortname.c new file mode 100644 index 0000000..9e3cf5f --- /dev/null +++ b/source4/ntvfs/posix/pvfs_shortname.c @@ -0,0 +1,699 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - 8.3 name routines + + Copyright (C) Andrew Tridgell 2004 + + 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/locale.h" +#include "vfs_posix.h" +#include "param/param.h" + +#undef strcasecmp + +/* + this mangling scheme uses the following format + + Annnn~n.AAA + + where nnnnn is a base 36 hash, and A represents characters from the original string + + The hash is taken of the leading part of the long filename, in uppercase + + for simplicity, we only allow ascii characters in 8.3 names +*/ + +/* + =============================================================================== + NOTE NOTE NOTE!!! + + This file deliberately uses non-multibyte string functions in many places. This + is *not* a mistake. This code is multi-byte safe, but it gets this property + through some very subtle knowledge of the way multi-byte strings are encoded + and the fact that this mangling algorithm only supports ascii characters in + 8.3 names. + + please don't convert this file to use the *_m() functions!! + =============================================================================== +*/ + + +#if 1 +#define M_DEBUG(level, x) DEBUG(level, x) +#else +#define M_DEBUG(level, x) +#endif + +/* these flags are used to mark characters in as having particular + properties */ +#define FLAG_BASECHAR 1 +#define FLAG_ASCII 2 +#define FLAG_ILLEGAL 4 +#define FLAG_WILDCARD 8 + +/* the "possible" flags are used as a fast way to find possible DOS + reserved filenames */ +#define FLAG_POSSIBLE1 16 +#define FLAG_POSSIBLE2 32 +#define FLAG_POSSIBLE3 64 +#define FLAG_POSSIBLE4 128 + +#define DEFAULT_MANGLE_PREFIX 4 + +#define MANGLE_BASECHARS "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + +#define FLAG_CHECK(c, flag) (ctx->char_flags[(uint8_t)(c)] & (flag)) + +static const char *reserved_names[] = +{ "AUX", "CON", + "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", + "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", + "NUL", "PRN", NULL }; + + +struct pvfs_mangle_context { + uint8_t char_flags[256]; + /* + this determines how many characters are used from the original + filename in the 8.3 mangled name. A larger value leads to a weaker + hash and more collisions. The largest possible value is 6. + */ + int mangle_prefix; + uint32_t mangle_modulus; + + /* we will use a very simple direct mapped prefix cache. The big + advantage of this cache structure is speed and low memory usage + + The cache is indexed by the low-order bits of the hash, and confirmed by + hashing the resulting cache entry to match the known hash + */ + uint32_t cache_size; + char **prefix_cache; + uint32_t *prefix_cache_hashes; + + /* this is used to reverse the base 36 mapping */ + unsigned char base_reverse[256]; +}; + + +/* + hash a string of the specified length. The string does not need to be + null terminated + + this hash needs to be fast with a low collision rate (what hash doesn't?) +*/ +static uint32_t mangle_hash(struct pvfs_mangle_context *ctx, + const char *key, size_t length) +{ + return pvfs_name_hash(key, length) % ctx->mangle_modulus; +} + +/* + insert an entry into the prefix cache. The string might not be null + terminated */ +static void cache_insert(struct pvfs_mangle_context *ctx, + const char *prefix, int length, uint32_t hash) +{ + int i = hash % ctx->cache_size; + + if (ctx->prefix_cache[i]) { + talloc_free(ctx->prefix_cache[i]); + } + + ctx->prefix_cache[i] = talloc_strndup(ctx->prefix_cache, prefix, length); + ctx->prefix_cache_hashes[i] = hash; +} + +/* + lookup an entry in the prefix cache. Return NULL if not found. +*/ +static const char *cache_lookup(struct pvfs_mangle_context *ctx, uint32_t hash) +{ + int i = hash % ctx->cache_size; + + + if (!ctx->prefix_cache[i] || hash != ctx->prefix_cache_hashes[i]) { + return NULL; + } + + /* yep, it matched */ + return ctx->prefix_cache[i]; +} + + +/* + determine if a string is possibly in a mangled format, ignoring + case + + In this algorithm, mangled names use only pure ascii characters (no + multi-byte) so we can avoid doing a UCS2 conversion + */ +static bool is_mangled_component(struct pvfs_mangle_context *ctx, + const char *name, size_t len) +{ + unsigned int i; + + M_DEBUG(10,("is_mangled_component %s (len %u) ?\n", name, (unsigned int)len)); + + /* check the length */ + if (len > 12 || len < 8) + return false; + + /* the best distinguishing characteristic is the ~ */ + if (name[6] != '~') + return false; + + /* check extension */ + if (len > 8) { + if (name[8] != '.') + return false; + for (i=9; name[i] && i < len; i++) { + if (! FLAG_CHECK(name[i], FLAG_ASCII)) { + return false; + } + } + } + + /* check lead characters */ + for (i=0;i<ctx->mangle_prefix;i++) { + if (! FLAG_CHECK(name[i], FLAG_ASCII)) { + return false; + } + } + + /* check rest of hash */ + if (! FLAG_CHECK(name[7], FLAG_BASECHAR)) { + return false; + } + for (i=ctx->mangle_prefix;i<6;i++) { + if (! FLAG_CHECK(name[i], FLAG_BASECHAR)) { + return false; + } + } + + M_DEBUG(10,("is_mangled_component %s (len %u) -> yes\n", name, (unsigned int)len)); + + return true; +} + + + +/* + determine if a string is possibly in a mangled format, ignoring + case + + In this algorithm, mangled names use only pure ascii characters (no + multi-byte) so we can avoid doing a UCS2 conversion + + NOTE! This interface must be able to handle a path with unix + directory separators. It should return true if any component is + mangled + */ +static bool is_mangled(struct pvfs_mangle_context *ctx, const char *name) +{ + const char *p; + const char *s; + + M_DEBUG(10,("is_mangled %s ?\n", name)); + + for (s=name; (p=strchr(s, '/')); s=p+1) { + if (is_mangled_component(ctx, s, PTR_DIFF(p, s))) { + return true; + } + } + + /* and the last part ... */ + return is_mangled_component(ctx, s, strlen(s)); +} + + +/* + see if a filename is an allowable 8.3 name. + + we are only going to allow ascii characters in 8.3 names, as this + simplifies things greatly (it means that we know the string won't + get larger when converted from UNIX to DOS formats) +*/ +static bool is_8_3(struct pvfs_mangle_context *ctx, + const char *name, bool check_case, bool allow_wildcards) +{ + int len, i; + char *dot_p; + + /* as a special case, the names '.' and '..' are allowable 8.3 names */ + if (name[0] == '.') { + if (!name[1] || (name[1] == '.' && !name[2])) { + return true; + } + } + + /* the simplest test is on the overall length of the + filename. Note that we deliberately use the ascii string + length (not the multi-byte one) as it is faster, and gives us + the result we need in this case. Using strlen_m would not + only be slower, it would be incorrect */ + len = strlen(name); + if (len > 12) + return false; + + /* find the '.'. Note that once again we use the non-multibyte + function */ + dot_p = strchr(name, '.'); + + if (!dot_p) { + /* if the name doesn't contain a '.' then its length + must be less than 8 */ + if (len > 8) { + return false; + } + } else { + int prefix_len, suffix_len; + + /* if it does contain a dot then the prefix must be <= + 8 and the suffix <= 3 in length */ + prefix_len = PTR_DIFF(dot_p, name); + suffix_len = len - (prefix_len+1); + + if (prefix_len > 8 || suffix_len > 3 || suffix_len == 0) { + return false; + } + + /* a 8.3 name cannot contain more than 1 '.' */ + if (strchr(dot_p+1, '.')) { + return false; + } + } + + /* the length are all OK. Now check to see if the characters themselves are OK */ + for (i=0; name[i]; i++) { + /* note that we may allow wildcard petterns! */ + if (!FLAG_CHECK(name[i], FLAG_ASCII|(allow_wildcards ? FLAG_WILDCARD : 0)) && + name[i] != '.') { + return false; + } + } + + /* it is a good 8.3 name */ + return true; +} + + +/* + try to find a 8.3 name in the cache, and if found then + return the original long name. +*/ +static char *check_cache(struct pvfs_mangle_context *ctx, + TALLOC_CTX *mem_ctx, const char *name) +{ + uint32_t hash, multiplier; + unsigned int i; + const char *prefix; + char extension[4]; + + /* make sure that this is a mangled name from this cache */ + if (!is_mangled(ctx, name)) { + M_DEBUG(10,("check_cache: %s -> not mangled\n", name)); + return NULL; + } + + /* we need to extract the hash from the 8.3 name */ + hash = ctx->base_reverse[(unsigned char)name[7]]; + for (multiplier=36, i=5;i>=ctx->mangle_prefix;i--) { + uint32_t v = ctx->base_reverse[(unsigned char)name[i]]; + hash += multiplier * v; + multiplier *= 36; + } + + /* now look in the prefix cache for that hash */ + prefix = cache_lookup(ctx, hash); + if (!prefix) { + M_DEBUG(10,("check_cache: %s -> %08X -> not found\n", name, hash)); + return NULL; + } + + /* we found it - construct the full name */ + if (name[8] == '.') { + strncpy(extension, name+9, 3); + extension[3] = 0; + } else { + extension[0] = 0; + } + + if (extension[0]) { + return talloc_asprintf(mem_ctx, "%s.%s", prefix, extension); + } + + return talloc_strdup(mem_ctx, prefix); +} + + +/* + look for a DOS reserved name +*/ +static bool is_reserved_name(struct pvfs_mangle_context *ctx, const char *name) +{ + if (FLAG_CHECK(name[0], FLAG_POSSIBLE1) && + FLAG_CHECK(name[1], FLAG_POSSIBLE2) && + FLAG_CHECK(name[2], FLAG_POSSIBLE3) && + FLAG_CHECK(name[3], FLAG_POSSIBLE4)) { + /* a likely match, scan the lot */ + int i; + for (i=0; reserved_names[i]; i++) { + if (strcasecmp(name, reserved_names[i]) == 0) { + return true; + } + } + } + + return false; +} + + +/* + See if a filename is a legal long filename. + A filename ending in a '.' is not legal unless it's "." or "..". JRA. +*/ +static bool is_legal_name(struct pvfs_mangle_context *ctx, const char *name) +{ + while (*name) { + size_t c_size; + codepoint_t c = next_codepoint(name, &c_size); + if (c == INVALID_CODEPOINT) { + return false; + } + /* all high chars are OK */ + if (c >= 128) { + name += c_size; + continue; + } + if (FLAG_CHECK(c, FLAG_ILLEGAL)) { + return false; + } + name += c_size; + } + + return true; +} + +/* + the main forward mapping function, which converts a long filename to + a 8.3 name + + if need83 is not set then we only do the mangling if the name is illegal + as a long name + + if cache83 is not set then we don't cache the result + + return NULL if we don't need to do any conversion +*/ +static char *name_map(struct pvfs_mangle_context *ctx, + const char *name, bool need83, bool cache83) +{ + char *dot_p; + char lead_chars[7]; + char extension[4]; + unsigned int extension_length, i; + unsigned int prefix_len; + uint32_t hash, v; + char *new_name; + const char *basechars = MANGLE_BASECHARS; + + /* reserved names are handled specially */ + if (!is_reserved_name(ctx, name)) { + /* if the name is already a valid 8.3 name then we don't need to + do anything */ + if (is_8_3(ctx, name, false, false)) { + return NULL; + } + + /* if the caller doesn't strictly need 8.3 then just check for illegal + filenames */ + if (!need83 && is_legal_name(ctx, name)) { + return NULL; + } + } + + /* find the '.' if any */ + dot_p = strrchr(name, '.'); + + if (dot_p) { + /* if the extension contains any illegal characters or + is too long or zero length then we treat it as part + of the prefix */ + for (i=0; i<4 && dot_p[i+1]; i++) { + if (! FLAG_CHECK(dot_p[i+1], FLAG_ASCII)) { + dot_p = NULL; + break; + } + } + if (i == 0 || i == 4) dot_p = NULL; + } + + /* the leading characters in the mangled name is taken from + the first characters of the name, if they are ascii otherwise + '_' is used + */ + for (i=0;i<ctx->mangle_prefix && name[i];i++) { + lead_chars[i] = name[i]; + if (! FLAG_CHECK(lead_chars[i], FLAG_ASCII)) { + lead_chars[i] = '_'; + } + lead_chars[i] = toupper((unsigned char)lead_chars[i]); + } + for (;i<ctx->mangle_prefix;i++) { + lead_chars[i] = '_'; + } + + /* the prefix is anything up to the first dot */ + if (dot_p) { + prefix_len = PTR_DIFF(dot_p, name); + } else { + prefix_len = strlen(name); + } + + /* the extension of the mangled name is taken from the first 3 + ascii chars after the dot */ + extension_length = 0; + if (dot_p) { + for (i=1; extension_length < 3 && dot_p[i]; i++) { + unsigned char c = dot_p[i]; + if (FLAG_CHECK(c, FLAG_ASCII)) { + extension[extension_length++] = toupper(c); + } + } + } + + /* find the hash for this prefix */ + v = hash = mangle_hash(ctx, name, prefix_len); + + new_name = talloc_array(ctx, char, 13); + if (new_name == NULL) { + return NULL; + } + + /* now form the mangled name. */ + for (i=0;i<ctx->mangle_prefix;i++) { + new_name[i] = lead_chars[i]; + } + new_name[7] = basechars[v % 36]; + new_name[6] = '~'; + for (i=5; i>=ctx->mangle_prefix; i--) { + v = v / 36; + new_name[i] = basechars[v % 36]; + } + + /* add the extension */ + if (extension_length) { + new_name[8] = '.'; + memcpy(&new_name[9], extension, extension_length); + new_name[9+extension_length] = 0; + } else { + new_name[8] = 0; + } + + if (cache83) { + /* put it in the cache */ + cache_insert(ctx, name, prefix_len, hash); + } + + M_DEBUG(10,("name_map: %s -> %08X -> %s (cache=%d)\n", + name, hash, new_name, cache83)); + + return new_name; +} + + +/* initialise the flags table + + we allow only a very restricted set of characters as 'ascii' in this + mangling backend. This isn't a significant problem as modern clients + use the 'long' filenames anyway, and those don't have these + restrictions. +*/ +static void init_tables(struct pvfs_mangle_context *ctx) +{ + const char *basechars = MANGLE_BASECHARS; + int i; + /* the list of reserved dos names - all of these are illegal */ + + ZERO_STRUCT(ctx->char_flags); + + for (i=1;i<128;i++) { + if ((i >= '0' && i <= '9') || + (i >= 'a' && i <= 'z') || + (i >= 'A' && i <= 'Z')) { + ctx->char_flags[i] |= (FLAG_ASCII | FLAG_BASECHAR); + } + if (strchr("_-$~", i)) { + ctx->char_flags[i] |= FLAG_ASCII; + } + + if (strchr("*\\/?<>|\":", i)) { + ctx->char_flags[i] |= FLAG_ILLEGAL; + } + + if (strchr("*?\"<>", i)) { + ctx->char_flags[i] |= FLAG_WILDCARD; + } + } + + ZERO_STRUCT(ctx->base_reverse); + for (i=0;i<36;i++) { + ctx->base_reverse[(uint8_t)basechars[i]] = i; + } + + /* fill in the reserved names flags. These are used as a very + fast filter for finding possible DOS reserved filenames */ + for (i=0; reserved_names[i]; i++) { + unsigned char c1, c2, c3, c4; + + c1 = (unsigned char)reserved_names[i][0]; + c2 = (unsigned char)reserved_names[i][1]; + c3 = (unsigned char)reserved_names[i][2]; + c4 = (unsigned char)reserved_names[i][3]; + + ctx->char_flags[c1] |= FLAG_POSSIBLE1; + ctx->char_flags[c2] |= FLAG_POSSIBLE2; + ctx->char_flags[c3] |= FLAG_POSSIBLE3; + ctx->char_flags[c4] |= FLAG_POSSIBLE4; + ctx->char_flags[tolower(c1)] |= FLAG_POSSIBLE1; + ctx->char_flags[tolower(c2)] |= FLAG_POSSIBLE2; + ctx->char_flags[tolower(c3)] |= FLAG_POSSIBLE3; + ctx->char_flags[tolower(c4)] |= FLAG_POSSIBLE4; + + ctx->char_flags[(unsigned char)'.'] |= FLAG_POSSIBLE4; + } + + ctx->mangle_modulus = 1; + for (i=0;i<(7-ctx->mangle_prefix);i++) { + ctx->mangle_modulus *= 36; + } +} + +/* + initialise the mangling code + */ +NTSTATUS pvfs_mangle_init(struct pvfs_state *pvfs) +{ + struct pvfs_mangle_context *ctx; + + ctx = talloc(pvfs, struct pvfs_mangle_context); + if (ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* by default have a max of 512 entries in the cache. */ + ctx->cache_size = lpcfg_parm_int(pvfs->ntvfs->ctx->lp_ctx, NULL, "mangle", "cachesize", 512); + + ctx->prefix_cache = talloc_array(ctx, char *, ctx->cache_size); + if (ctx->prefix_cache == NULL) { + return NT_STATUS_NO_MEMORY; + } + ctx->prefix_cache_hashes = talloc_array(ctx, uint32_t, ctx->cache_size); + if (ctx->prefix_cache_hashes == NULL) { + return NT_STATUS_NO_MEMORY; + } + + memset(ctx->prefix_cache, 0, sizeof(char *) * ctx->cache_size); + memset(ctx->prefix_cache_hashes, 0, sizeof(uint32_t) * ctx->cache_size); + + ctx->mangle_prefix = lpcfg_parm_int(pvfs->ntvfs->ctx->lp_ctx, NULL, "mangle", "prefix", -1); + if (ctx->mangle_prefix < 0 || ctx->mangle_prefix > 6) { + ctx->mangle_prefix = DEFAULT_MANGLE_PREFIX; + } + + init_tables(ctx); + + pvfs->mangle_ctx = ctx; + + return NT_STATUS_OK; +} + + +/* + return the short name for a component of a full name +*/ +char *pvfs_short_name_component(struct pvfs_state *pvfs, const char *name) +{ + return name_map(pvfs->mangle_ctx, name, true, true); +} + + +/* + return the short name for a given entry in a directory +*/ +const char *pvfs_short_name(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx, + struct pvfs_filename *name) +{ + char *p = strrchr(name->full_name, '/'); + char *ret = pvfs_short_name_component(pvfs, p+1); + if (ret == NULL) { + return p+1; + } + talloc_steal(mem_ctx, ret); + return ret; +} + +/* + lookup a mangled name, returning the original long name if present + in the cache +*/ +char *pvfs_mangled_lookup(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx, + const char *name) +{ + return check_cache(pvfs->mangle_ctx, mem_ctx, name); +} + + +/* + look for a DOS reserved name +*/ +bool pvfs_is_reserved_name(struct pvfs_state *pvfs, const char *name) +{ + return is_reserved_name(pvfs->mangle_ctx, name); +} + + +/* + see if a component of a filename could be a mangled name from our + mangling code +*/ +bool pvfs_is_mangled_component(struct pvfs_state *pvfs, const char *name) +{ + return is_mangled_component(pvfs->mangle_ctx, name, strlen(name)); +} diff --git a/source4/ntvfs/posix/pvfs_streams.c b/source4/ntvfs/posix/pvfs_streams.c new file mode 100644 index 0000000..9210237 --- /dev/null +++ b/source4/ntvfs/posix/pvfs_streams.c @@ -0,0 +1,556 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - alternate data streams + + Copyright (C) Andrew Tridgell 2004 + + 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 "vfs_posix.h" +#include "librpc/gen_ndr/xattr.h" + +/* + normalise a stream name, removing a :$DATA suffix if there is one + Note: this returns the existing pointer to the name if the name does + not need normalising + */ +static const char *stream_name_normalise(TALLOC_CTX *ctx, const char *name) +{ + const char *c = strchr_m(name, ':'); + if (c == NULL || strcasecmp_m(c, ":$DATA") != 0) { + return name; + } + return talloc_strndup(ctx, name, c-name); +} + +/* + compare two stream names, taking account of the default $DATA extension + */ +static int stream_name_cmp(const char *name1, const char *name2) +{ + const char *c1, *c2; + int l1, l2, ret; + c1 = strchr_m(name1, ':'); + c2 = strchr_m(name2, ':'); + + /* check the first part is the same */ + l1 = c1?(c1 - name1):strlen(name1); + l2 = c2?(c2 - name2):strlen(name2); + if (l1 != l2) { + return l1 - l2; + } + ret = strncasecmp_m(name1, name2, l1); + if (ret != 0) { + return ret; + } + + /* the first parts are the same, check the suffix */ + if (c1 && c2) { + return strcasecmp_m(c1, c2); + } + + if (c1) { + return strcasecmp_m(c1, ":$DATA"); + } + if (c2) { + return strcasecmp_m(c2, ":$DATA"); + } + + /* neither names have a suffix */ + return 0; +} + + +/* + return the list of file streams for RAW_FILEINFO_STREAM_INFORMATION +*/ +NTSTATUS pvfs_stream_information(struct pvfs_state *pvfs, + TALLOC_CTX *mem_ctx, + struct pvfs_filename *name, int fd, + struct stream_information *info) +{ + struct xattr_DosStreams *streams; + int i; + NTSTATUS status; + + /* directories don't have streams */ + if (name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) { + info->num_streams = 0; + info->streams = NULL; + return NT_STATUS_OK; + } + + streams = talloc(mem_ctx, struct xattr_DosStreams); + if (streams == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = pvfs_streams_load(pvfs, name, fd, streams); + if (!NT_STATUS_IS_OK(status)) { + ZERO_STRUCTP(streams); + } + + info->num_streams = streams->num_streams+1; + info->streams = talloc_array(mem_ctx, struct stream_struct, info->num_streams); + if (!info->streams) { + return NT_STATUS_NO_MEMORY; + } + + info->streams[0].size = name->st.st_size; + info->streams[0].alloc_size = name->dos.alloc_size; + info->streams[0].stream_name.s = talloc_strdup(info->streams, "::$DATA"); + + for (i=0;i<streams->num_streams;i++) { + info->streams[i+1].size = streams->streams[i].size; + info->streams[i+1].alloc_size = streams->streams[i].alloc_size; + if (strchr(streams->streams[i].name, ':') == NULL) { + info->streams[i+1].stream_name.s = talloc_asprintf(streams->streams, + ":%s:$DATA", + streams->streams[i].name); + } else { + info->streams[i+1].stream_name.s = talloc_strdup(streams->streams, + streams->streams[i].name); + } + } + + return NT_STATUS_OK; +} + + +/* + fill in the stream information for a name +*/ +NTSTATUS pvfs_stream_info(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd) +{ + struct xattr_DosStreams *streams; + int i; + NTSTATUS status; + + /* the NULL stream always exists */ + if (name->stream_name == NULL) { + name->stream_exists = true; + return NT_STATUS_OK; + } + + streams = talloc(name, struct xattr_DosStreams); + if (streams == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = pvfs_streams_load(pvfs, name, fd, streams); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(streams); + return status; + } + + for (i=0;i<streams->num_streams;i++) { + struct xattr_DosStream *s = &streams->streams[i]; + if (stream_name_cmp(s->name, name->stream_name) == 0) { + name->dos.alloc_size = pvfs_round_alloc_size(pvfs, s->alloc_size); + name->st.st_size = s->size; + name->stream_exists = true; + talloc_free(streams); + return NT_STATUS_OK; + } + } + + talloc_free(streams); + + name->dos.alloc_size = 0; + name->st.st_size = 0; + name->stream_exists = false; + + return NT_STATUS_OK; +} + + +/* + update size information for a stream +*/ +static NTSTATUS pvfs_stream_update_size(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd, + off_t size) +{ + struct xattr_DosStreams *streams; + int i; + NTSTATUS status; + + streams = talloc(name, struct xattr_DosStreams); + if (streams == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = pvfs_streams_load(pvfs, name, fd, streams); + if (!NT_STATUS_IS_OK(status)) { + ZERO_STRUCTP(streams); + } + + for (i=0;i<streams->num_streams;i++) { + struct xattr_DosStream *s = &streams->streams[i]; + if (stream_name_cmp(s->name, name->stream_name) == 0) { + s->size = size; + s->alloc_size = pvfs_round_alloc_size(pvfs, size); + break; + } + } + + if (i == streams->num_streams) { + struct xattr_DosStream *s; + streams->streams = talloc_realloc(streams, streams->streams, + struct xattr_DosStream, + streams->num_streams+1); + if (streams->streams == NULL) { + talloc_free(streams); + return NT_STATUS_NO_MEMORY; + } + streams->num_streams++; + s = &streams->streams[i]; + + s->flags = XATTR_STREAM_FLAG_INTERNAL; + s->size = size; + s->alloc_size = pvfs_round_alloc_size(pvfs, size); + s->name = stream_name_normalise(streams, name->stream_name); + if (s->name == NULL) { + talloc_free(streams); + return NT_STATUS_NO_MEMORY; + } + } + + status = pvfs_streams_save(pvfs, name, fd, streams); + talloc_free(streams); + + return status; +} + + +/* + rename a stream +*/ +NTSTATUS pvfs_stream_rename(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd, + const char *new_name, bool overwrite) +{ + struct xattr_DosStreams *streams; + int i, found_old, found_new; + NTSTATUS status; + + streams = talloc(name, struct xattr_DosStreams); + if (streams == NULL) { + return NT_STATUS_NO_MEMORY; + } + + new_name = stream_name_normalise(streams, new_name); + if (new_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = pvfs_streams_load(pvfs, name, fd, streams); + if (!NT_STATUS_IS_OK(status)) { + ZERO_STRUCTP(streams); + } + + /* the default stream always exists */ + if (strcmp(new_name, "") == 0 || + strcasecmp_m(new_name, ":$DATA") == 0) { + return NT_STATUS_OBJECT_NAME_COLLISION; + } + + /* try to find the old/new names in the list */ + found_old = found_new = -1; + for (i=0;i<streams->num_streams;i++) { + struct xattr_DosStream *s = &streams->streams[i]; + if (stream_name_cmp(s->name, new_name) == 0) { + found_new = i; + } + if (stream_name_cmp(s->name, name->stream_name) == 0) { + found_old = i; + } + } + + if (found_old == -1) { + talloc_free(streams); + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + if (found_new == -1) { + /* a simple rename */ + struct xattr_DosStream *s = &streams->streams[found_old]; + s->name = new_name; + } else { + if (!overwrite) { + return NT_STATUS_OBJECT_NAME_COLLISION; + } + if (found_old != found_new) { + /* remove the old one and replace with the new one */ + streams->streams[found_old].name = new_name; + memmove(&streams->streams[found_new], + &streams->streams[found_new+1], + sizeof(streams->streams[0]) * + (streams->num_streams - (found_new+1))); + streams->num_streams--; + } + } + + status = pvfs_streams_save(pvfs, name, fd, streams); + + if (NT_STATUS_IS_OK(status)) { + + /* update the in-memory copy of the name of the open file */ + talloc_free(name->stream_name); + name->stream_name = talloc_strdup(name, new_name); + + talloc_free(streams); + } + + return status; +} + + +/* + create the xattr for a alternate data stream +*/ +NTSTATUS pvfs_stream_create(struct pvfs_state *pvfs, + struct pvfs_filename *name, + int fd) +{ + NTSTATUS status; + status = pvfs_xattr_create(pvfs, name->full_name, fd, + XATTR_DOSSTREAM_PREFIX, name->stream_name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + return pvfs_stream_update_size(pvfs, name, fd, 0); +} + +/* + delete the xattr for a alternate data stream +*/ +NTSTATUS pvfs_stream_delete(struct pvfs_state *pvfs, + struct pvfs_filename *name, + int fd) +{ + NTSTATUS status; + struct xattr_DosStreams *streams; + int i; + + status = pvfs_xattr_delete(pvfs, name->full_name, fd, + XATTR_DOSSTREAM_PREFIX, name->stream_name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + streams = talloc(name, struct xattr_DosStreams); + if (streams == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = pvfs_streams_load(pvfs, name, fd, streams); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(streams); + return status; + } + + for (i=0;i<streams->num_streams;i++) { + struct xattr_DosStream *s = &streams->streams[i]; + if (stream_name_cmp(s->name, name->stream_name) == 0) { + memmove(s, s+1, (streams->num_streams - (i+1)) * sizeof(*s)); + streams->num_streams--; + break; + } + } + + status = pvfs_streams_save(pvfs, name, fd, streams); + talloc_free(streams); + + return status; +} + +/* + load a stream into a blob +*/ +static NTSTATUS pvfs_stream_load(struct pvfs_state *pvfs, + TALLOC_CTX *mem_ctx, + struct pvfs_filename *name, + int fd, + size_t estimated_size, + DATA_BLOB *blob) +{ + NTSTATUS status; + + status = pvfs_xattr_load(pvfs, mem_ctx, name->full_name, fd, + XATTR_DOSSTREAM_PREFIX, + name->stream_name, estimated_size, blob); + + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + /* try with a case insensitive match */ + struct xattr_DosStreams *streams; + int i; + + streams = talloc(mem_ctx, struct xattr_DosStreams); + if (streams == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = pvfs_streams_load(pvfs, name, fd, streams); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(streams); + return NT_STATUS_NOT_FOUND; + } + for (i=0;i<streams->num_streams;i++) { + struct xattr_DosStream *s = &streams->streams[i]; + if (stream_name_cmp(s->name, name->stream_name) == 0) { + status = pvfs_xattr_load(pvfs, mem_ctx, name->full_name, fd, + XATTR_DOSSTREAM_PREFIX, + s->name, estimated_size, blob); + talloc_free(streams); + return status; + } + } + talloc_free(streams); + return NT_STATUS_NOT_FOUND; + } + + return status; +} + +/* + the equivalent of pread() on a stream +*/ +ssize_t pvfs_stream_read(struct pvfs_state *pvfs, + struct pvfs_file_handle *h, void *data, size_t count, off_t offset) +{ + NTSTATUS status; + DATA_BLOB blob; + if (count == 0) { + return 0; + } + status = pvfs_stream_load(pvfs, h, h->name, h->fd, offset+count, &blob); + if (!NT_STATUS_IS_OK(status)) { + errno = EIO; + return -1; + } + if (offset >= blob.length) { + data_blob_free(&blob); + return 0; + } + if (count > blob.length - offset) { + count = blob.length - offset; + } + memcpy(data, blob.data + offset, count); + data_blob_free(&blob); + return count; +} + + +/* + the equivalent of pwrite() on a stream +*/ +ssize_t pvfs_stream_write(struct pvfs_state *pvfs, + struct pvfs_file_handle *h, const void *data, size_t count, off_t offset) +{ + NTSTATUS status; + DATA_BLOB blob; + if (count == 0) { + return 0; + } + + if (count+offset > XATTR_MAX_STREAM_SIZE) { + if (!pvfs->ea_db || count+offset > XATTR_MAX_STREAM_SIZE_TDB) { + errno = ENOSPC; + return -1; + } + } + + /* we have to load the existing stream, then modify, then save */ + status = pvfs_stream_load(pvfs, h, h->name, h->fd, offset+count, &blob); + if (!NT_STATUS_IS_OK(status)) { + blob = data_blob(NULL, 0); + } + if (count+offset > blob.length) { + blob.data = talloc_realloc(blob.data, blob.data, uint8_t, count+offset); + if (blob.data == NULL) { + errno = ENOMEM; + return -1; + } + if (offset > blob.length) { + memset(blob.data+blob.length, 0, offset - blob.length); + } + blob.length = count+offset; + } + memcpy(blob.data + offset, data, count); + + status = pvfs_xattr_save(pvfs, h->name->full_name, h->fd, XATTR_DOSSTREAM_PREFIX, + h->name->stream_name, &blob); + if (!NT_STATUS_IS_OK(status)) { + data_blob_free(&blob); + /* getting this error mapping right is probably + not worth it */ + errno = ENOSPC; + return -1; + } + + status = pvfs_stream_update_size(pvfs, h->name, h->fd, blob.length); + + data_blob_free(&blob); + + if (!NT_STATUS_IS_OK(status)) { + errno = EIO; + return -1; + } + + return count; +} + +/* + the equivalent of truncate() on a stream +*/ +NTSTATUS pvfs_stream_truncate(struct pvfs_state *pvfs, + struct pvfs_filename *name, int fd, off_t length) +{ + NTSTATUS status; + DATA_BLOB blob; + + if (length > XATTR_MAX_STREAM_SIZE) { + if (!pvfs->ea_db || length > XATTR_MAX_STREAM_SIZE_TDB) { + return NT_STATUS_DISK_FULL; + } + } + + /* we have to load the existing stream, then modify, then save */ + status = pvfs_stream_load(pvfs, name, name, fd, length, &blob); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (length <= blob.length) { + blob.length = length; + } else if (length > blob.length) { + blob.data = talloc_realloc(blob.data, blob.data, uint8_t, length); + if (blob.data == NULL) { + return NT_STATUS_NO_MEMORY; + } + memset(blob.data+blob.length, 0, length - blob.length); + blob.length = length; + } + + status = pvfs_xattr_save(pvfs, name->full_name, fd, XATTR_DOSSTREAM_PREFIX, + name->stream_name, &blob); + + if (NT_STATUS_IS_OK(status)) { + status = pvfs_stream_update_size(pvfs, name, fd, blob.length); + } + data_blob_free(&blob); + + return status; +} diff --git a/source4/ntvfs/posix/pvfs_sys.c b/source4/ntvfs/posix/pvfs_sys.c new file mode 100644 index 0000000..32770c1 --- /dev/null +++ b/source4/ntvfs/posix/pvfs_sys.c @@ -0,0 +1,662 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - pvfs_sys wrappers + + Copyright (C) Andrew Tridgell 2010 + Copyright (C) Andrew Bartlett 2010 + + 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 "vfs_posix.h" +#include "../lib/util/unix_privs.h" + +/* + these wrapper functions must only be called when the appropriate ACL + has already been checked. The wrappers will override a EACCES result + by gaining root privileges if the 'pvfs:perm override' is set on the + share (it is enabled by default) + + Careful use of O_NOFOLLOW and O_DIRECTORY is used to prevent + security attacks via symlinks + */ + + +struct pvfs_sys_ctx { + struct pvfs_state *pvfs; + void *privs; + const char *old_wd; + struct stat st_orig; +}; + + +/* + we create PVFS_NOFOLLOW and PVFS_DIRECTORY as aliases for O_NOFOLLOW + and O_DIRECTORY on systems that have them. On systems that don't + have O_NOFOLLOW we are less safe, but the root override code is off + by default. + */ +#ifdef O_NOFOLLOW +#define PVFS_NOFOLLOW O_NOFOLLOW +#else +#define PVFS_NOFOLLOW 0 +#endif +#ifdef O_DIRECTORY +#define PVFS_DIRECTORY O_DIRECTORY +#else +#define PVFS_DIRECTORY 0 +#endif + +/* + return to original directory when context is destroyed + */ +static int pvfs_sys_pushdir_destructor(struct pvfs_sys_ctx *ctx) +{ + struct stat st; + + if (ctx->old_wd == NULL) { + return 0; + } + + if (chdir(ctx->old_wd) != 0) { + smb_panic("Failed to restore working directory"); + } + if (stat(".", &st) != 0) { + smb_panic("Failed to stat working directory"); + } + if (st.st_ino != ctx->st_orig.st_ino || + st.st_dev != ctx->st_orig.st_dev) { + smb_panic("Working directory changed during call"); + } + + return 0; +} + + +/* + chdir() to the directory part of a pathname, but disallow any + component with a symlink + + Note that we can't use O_NOFOLLOW on the whole path as that only + prevents links in the final component of the path + */ +static int pvfs_sys_chdir_nosymlink(struct pvfs_sys_ctx *ctx, const char *pathname) +{ + char *p, *path; + size_t base_len = strlen(ctx->pvfs->base_directory); + + /* don't check for symlinks in the base directory of the share */ + if (strncmp(ctx->pvfs->base_directory, pathname, base_len) == 0 && + pathname[base_len] == '/') { + if (chdir(ctx->pvfs->base_directory) != 0) { + return -1; + } + pathname += base_len + 1; + } + + path = talloc_strdup(ctx, pathname); + if (path == NULL) { + return -1; + } + while ((p = strchr(path, '/'))) { + int fd; + struct stat st1, st2; + *p = 0; + fd = open(path, PVFS_NOFOLLOW | PVFS_DIRECTORY | O_RDONLY); + if (fd == -1) { + return -1; + } + if (chdir(path) != 0) { + close(fd); + return -1; + } + if (stat(".", &st1) != 0 || + fstat(fd, &st2) != 0) { + close(fd); + return -1; + } + close(fd); + if (st1.st_ino != st2.st_ino || + st1.st_dev != st2.st_dev) { + DEBUG(0,(__location__ ": Inode changed during chdir in '%s' - symlink attack?\n", + pathname)); + return -1; + } + path = p + 1; + } + + return 0; +} + + +/* + become root, and change directory to the directory component of a + path. Return a talloc context which when freed will move us back + to the original directory, and return us to the original uid + + change the pathname argument to contain just the base component of + the path + + return NULL on error, which could include an attempt to subvert + security using symlink tricks + */ +static struct pvfs_sys_ctx *pvfs_sys_pushdir(struct pvfs_state *pvfs, + const char **pathname) +{ + struct pvfs_sys_ctx *ctx; + char *cwd, *p, *dirname; + int ret; + + ctx = talloc_zero(pvfs, struct pvfs_sys_ctx); + if (ctx == NULL) { + return NULL; + } + ctx->pvfs = pvfs; + ctx->privs = root_privileges(); + if (ctx->privs == NULL) { + talloc_free(ctx); + return NULL; + } + + talloc_steal(ctx, ctx->privs); + + if (!pathname) { + /* no pathname needed */ + return ctx; + } + + p = strrchr(*pathname, '/'); + if (p == NULL) { + /* we don't need to change directory */ + return ctx; + } + + /* we keep the old st around, so we can tell that + we have come back to the right directory */ + if (stat(".", &ctx->st_orig) != 0) { + talloc_free(ctx); + return NULL; + } + + cwd = get_current_dir_name(); + if (cwd == NULL) { + talloc_free(ctx); + return NULL; + } + + ctx->old_wd = talloc_strdup(ctx, cwd); + free(cwd); + + if (ctx->old_wd == NULL) { + talloc_free(ctx); + return NULL; + } + + dirname = talloc_strndup(ctx, *pathname, (p - *pathname)); + if (dirname == NULL) { + talloc_free(ctx); + return NULL; + } + + ret = pvfs_sys_chdir_nosymlink(ctx, *pathname); + if (ret == -1) { + talloc_free(ctx); + return NULL; + } + + talloc_set_destructor(ctx, pvfs_sys_pushdir_destructor); + + /* return the basename as the filename that should be operated on */ + (*pathname) = talloc_strdup(ctx, p+1); + if (! *pathname) { + talloc_free(ctx); + return NULL; + } + + return ctx; +} + + +/* + chown a file that we created with a root privileges override + */ +static int pvfs_sys_fchown(struct pvfs_state *pvfs, struct pvfs_sys_ctx *ctx, int fd) +{ + return fchown(fd, root_privileges_original_uid(ctx->privs), -1); +} + +/* + chown a directory that we created with a root privileges override + */ +static int pvfs_sys_chown(struct pvfs_state *pvfs, struct pvfs_sys_ctx *ctx, const char *name) +{ + /* to avoid symlink hacks, we need to use fchown() on a directory fd */ + int ret, fd; + fd = open(name, PVFS_DIRECTORY | PVFS_NOFOLLOW | O_RDONLY); + if (fd == -1) { + return -1; + } + ret = pvfs_sys_fchown(pvfs, ctx, fd); + close(fd); + return ret; +} + + +/* + wrap open for system override +*/ +int pvfs_sys_open(struct pvfs_state *pvfs, const char *filename, int flags, mode_t mode, bool allow_override) +{ + int fd, ret; + struct pvfs_sys_ctx *ctx; + int saved_errno, orig_errno; + int retries = 5; + + orig_errno = errno; + + fd = open(filename, flags, mode); + if (fd != -1 || + !allow_override || + errno != EACCES) { + return fd; + } + + saved_errno = errno; + ctx = pvfs_sys_pushdir(pvfs, &filename); + if (ctx == NULL) { + errno = saved_errno; + return -1; + } + + /* don't allow permission overrides to follow links */ + flags |= PVFS_NOFOLLOW; + + /* + if O_CREAT was specified and O_EXCL was not specified + then initially do the open without O_CREAT, as in that case + we know that we did not create the file, so we don't have + to fchown it + */ + if ((flags & O_CREAT) && !(flags & O_EXCL)) { + try_again: + fd = open(filename, flags & ~O_CREAT, mode); + /* if this open succeeded, or if it failed + with anything other than ENOENT, then we return the + open result, with the original errno */ + if (fd == -1 && errno != ENOENT) { + talloc_free(ctx); + errno = saved_errno; + return -1; + } + if (fd != -1) { + /* the file already existed and we opened it */ + talloc_free(ctx); + errno = orig_errno; + return fd; + } + + fd = open(filename, flags | O_EXCL, mode); + if (fd == -1 && errno != EEXIST) { + talloc_free(ctx); + errno = saved_errno; + return -1; + } + if (fd != -1) { + /* we created the file, we need to set the + right ownership on it */ + ret = pvfs_sys_fchown(pvfs, ctx, fd); + if (ret == -1) { + close(fd); + unlink(filename); + talloc_free(ctx); + errno = saved_errno; + return -1; + } + talloc_free(ctx); + errno = orig_errno; + return fd; + } + + /* the file got created between the two times + we tried to open it! Try again */ + if (retries-- > 0) { + goto try_again; + } + + talloc_free(ctx); + errno = saved_errno; + return -1; + } + + fd = open(filename, flags, mode); + if (fd == -1) { + talloc_free(ctx); + errno = saved_errno; + return -1; + } + + /* if we have created a file then fchown it */ + if (flags & O_CREAT) { + ret = pvfs_sys_fchown(pvfs, ctx, fd); + if (ret == -1) { + close(fd); + unlink(filename); + talloc_free(ctx); + errno = saved_errno; + return -1; + } + } + + talloc_free(ctx); + return fd; +} + + +/* + wrap unlink for system override +*/ +int pvfs_sys_unlink(struct pvfs_state *pvfs, const char *filename, bool allow_override) +{ + int ret; + struct pvfs_sys_ctx *ctx; + int saved_errno, orig_errno; + + orig_errno = errno; + + ret = unlink(filename); + if (ret != -1 || + !allow_override || + errno != EACCES) { + return ret; + } + + saved_errno = errno; + + ctx = pvfs_sys_pushdir(pvfs, &filename); + if (ctx == NULL) { + errno = saved_errno; + return -1; + } + + ret = unlink(filename); + if (ret == -1) { + talloc_free(ctx); + errno = saved_errno; + return -1; + } + + talloc_free(ctx); + errno = orig_errno; + return ret; +} + + +static bool contains_symlink(const char *path) +{ + int fd = open(path, PVFS_NOFOLLOW | O_RDONLY); + int posix_errno = errno; + if (fd != -1) { + close(fd); + return false; + } + +#if defined(ENOTSUP) && defined(OSF1) + /* handle special Tru64 errno */ + if (errno == ENOTSUP) { + posix_errno = ELOOP; + } +#endif /* ENOTSUP */ + +#ifdef EFTYPE + /* fix broken NetBSD errno */ + if (errno == EFTYPE) { + posix_errno = ELOOP; + } +#endif /* EFTYPE */ + + /* fix broken FreeBSD errno */ + if (errno == EMLINK) { + posix_errno = ELOOP; + } + + return (posix_errno == ELOOP); +} + +/* + wrap rename for system override +*/ +int pvfs_sys_rename(struct pvfs_state *pvfs, const char *name1, const char *name2, bool allow_override) +{ + int ret; + struct pvfs_sys_ctx *ctx; + int saved_errno, orig_errno; + + orig_errno = errno; + + ret = rename(name1, name2); + if (ret != -1 || + !allow_override || + errno != EACCES) { + return ret; + } + + saved_errno = errno; + + ctx = pvfs_sys_pushdir(pvfs, &name1); + if (ctx == NULL) { + errno = saved_errno; + return -1; + } + + /* we need the destination as an absolute path */ + if (name2[0] != '/') { + name2 = talloc_asprintf(ctx, "%s/%s", ctx->old_wd, name2); + if (name2 == NULL) { + talloc_free(ctx); + errno = saved_errno; + return -1; + } + } + + /* make sure the destination isn't a symlink beforehand */ + if (contains_symlink(name2)) { + talloc_free(ctx); + errno = saved_errno; + return -1; + } + + ret = rename(name1, name2); + if (ret == -1) { + talloc_free(ctx); + errno = saved_errno; + return -1; + } + + /* make sure the destination isn't a symlink afterwards */ + if (contains_symlink(name2)) { + DEBUG(0,(__location__ ": Possible symlink attack in rename to '%s' - unlinking\n", name2)); + unlink(name2); + talloc_free(ctx); + errno = saved_errno; + return -1; + } + + talloc_free(ctx); + errno = orig_errno; + return ret; +} + + +/* + wrap mkdir for system override +*/ +int pvfs_sys_mkdir(struct pvfs_state *pvfs, const char *dirname, mode_t mode, bool allow_override) +{ + int ret; + struct pvfs_sys_ctx *ctx; + int saved_errno, orig_errno; + + orig_errno = errno; + + ret = mkdir(dirname, mode); + if (ret != -1 || + !allow_override || + errno != EACCES) { + return ret; + } + + saved_errno = errno; + ctx = pvfs_sys_pushdir(pvfs, &dirname); + if (ctx == NULL) { + errno = saved_errno; + return -1; + } + + ret = mkdir(dirname, mode); + if (ret == -1) { + talloc_free(ctx); + errno = saved_errno; + return -1; + } + + ret = pvfs_sys_chown(pvfs, ctx, dirname); + if (ret == -1) { + rmdir(dirname); + talloc_free(ctx); + errno = saved_errno; + return -1; + } + + talloc_free(ctx); + errno = orig_errno; + return ret; +} + + +/* + wrap rmdir for system override +*/ +int pvfs_sys_rmdir(struct pvfs_state *pvfs, const char *dirname, bool allow_override) +{ + int ret; + struct pvfs_sys_ctx *ctx; + int saved_errno, orig_errno; + + orig_errno = errno; + + ret = rmdir(dirname); + if (ret != -1 || + !allow_override || + errno != EACCES) { + return ret; + } + + saved_errno = errno; + + ctx = pvfs_sys_pushdir(pvfs, &dirname); + if (ctx == NULL) { + errno = saved_errno; + return -1; + } + + ret = rmdir(dirname); + if (ret == -1) { + talloc_free(ctx); + errno = saved_errno; + return -1; + } + + talloc_free(ctx); + errno = orig_errno; + return ret; +} + +/* + wrap fchmod for system override +*/ +int pvfs_sys_fchmod(struct pvfs_state *pvfs, int fd, mode_t mode, bool allow_override) +{ + int ret; + struct pvfs_sys_ctx *ctx; + int saved_errno, orig_errno; + + orig_errno = errno; + + ret = fchmod(fd, mode); + if (ret != -1 || + !allow_override || + errno != EACCES) { + return ret; + } + + saved_errno = errno; + + ctx = pvfs_sys_pushdir(pvfs, NULL); + if (ctx == NULL) { + errno = saved_errno; + return -1; + } + + ret = fchmod(fd, mode); + if (ret == -1) { + talloc_free(ctx); + errno = saved_errno; + return -1; + } + + talloc_free(ctx); + errno = orig_errno; + return ret; +} + + +/* + wrap chmod for system override +*/ +int pvfs_sys_chmod(struct pvfs_state *pvfs, const char *filename, mode_t mode, bool allow_override) +{ + int ret; + struct pvfs_sys_ctx *ctx; + int saved_errno, orig_errno; + + orig_errno = errno; + + ret = chmod(filename, mode); + if (ret != -1 || + !allow_override || + errno != EACCES) { + return ret; + } + + saved_errno = errno; + + ctx = pvfs_sys_pushdir(pvfs, &filename); + if (ctx == NULL) { + errno = saved_errno; + return -1; + } + + ret = chmod(filename, mode); + if (ret == -1) { + talloc_free(ctx); + errno = saved_errno; + return -1; + } + + talloc_free(ctx); + errno = orig_errno; + return ret; +} diff --git a/source4/ntvfs/posix/pvfs_unlink.c b/source4/ntvfs/posix/pvfs_unlink.c new file mode 100644 index 0000000..605ec10 --- /dev/null +++ b/source4/ntvfs/posix/pvfs_unlink.c @@ -0,0 +1,276 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - unlink + + Copyright (C) Andrew Tridgell 2004 + + 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 "vfs_posix.h" +#include "system/dir.h" + +/* + retry an open after a sharing violation +*/ +static void pvfs_retry_unlink(struct pvfs_odb_retry *r, + struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + void *_io, + void *private_data, + enum pvfs_wait_notice reason) +{ + union smb_unlink *io = talloc_get_type(_io, union smb_unlink); + NTSTATUS status = NT_STATUS_INTERNAL_ERROR; + + talloc_free(r); + + switch (reason) { + case PVFS_WAIT_CANCEL: +/*TODO*/ + status = NT_STATUS_CANCELLED; + break; + case PVFS_WAIT_TIMEOUT: + /* if it timed out, then give the failure + immediately */ +/*TODO*/ + status = NT_STATUS_SHARING_VIOLATION; + break; + case PVFS_WAIT_EVENT: + + /* try the open again, which could trigger another retry setup + if it wants to, so we have to unmark the async flag so we + will know if it does a second async reply */ + req->async_states->state &= ~NTVFS_ASYNC_STATE_ASYNC; + + status = pvfs_unlink(ntvfs, req, io); + if (req->async_states->state & NTVFS_ASYNC_STATE_ASYNC) { + /* the 2nd try also replied async, so we don't send + the reply yet */ + return; + } + + /* re-mark it async, just in case someone up the chain does + paranoid checking */ + req->async_states->state |= NTVFS_ASYNC_STATE_ASYNC; + break; + } + + /* send the reply up the chain */ + req->async_states->status = status; + req->async_states->send_fn(req); +} + +/* + setup for a unlink retry after a sharing violation + or a non granted oplock +*/ +static NTSTATUS pvfs_unlink_setup_retry(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_unlink *io, + struct odb_lock *lck, + NTSTATUS status) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + struct timeval end_time; + + if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) { + end_time = timeval_add(&req->statistics.request_time, + 0, pvfs->sharing_violation_delay); + } else if (NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) { + end_time = timeval_add(&req->statistics.request_time, + pvfs->oplock_break_timeout, 0); + } else { + return NT_STATUS_INTERNAL_ERROR; + } + + return pvfs_odb_retry_setup(ntvfs, req, lck, end_time, io, NULL, + pvfs_retry_unlink); +} + + +/* + unlink a file +*/ +static NTSTATUS pvfs_unlink_file(struct pvfs_state *pvfs, + struct pvfs_filename *name) +{ + NTSTATUS status = NT_STATUS_OK; + + if (name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) { + return NT_STATUS_FILE_IS_A_DIRECTORY; + } + + if (name->st.st_nlink == 1) { + status = pvfs_xattr_unlink_hook(pvfs, name->full_name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + /* finally try the actual unlink */ + if (pvfs_sys_unlink(pvfs, name->full_name, name->allow_override) == -1) { + status = pvfs_map_errno(pvfs, errno); + } + + if (NT_STATUS_IS_OK(status)) { + notify_trigger(pvfs->notify_context, + NOTIFY_ACTION_REMOVED, + FILE_NOTIFY_CHANGE_FILE_NAME, + name->full_name); + } + + return status; +} + +/* + unlink one file +*/ +static NTSTATUS pvfs_unlink_one(struct pvfs_state *pvfs, + struct ntvfs_request *req, + union smb_unlink *unl, + struct pvfs_filename *name) +{ + NTSTATUS status; + struct odb_lock *lck = NULL; + + /* make sure its matches the given attributes */ + status = pvfs_match_attrib(pvfs, name, + unl->unlink.in.attrib, 0); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = pvfs_can_delete(pvfs, req, name, &lck); + + /* + * on a sharing violation we need to retry when the file is closed by + * the other user, or after 1 second + * on a non granted oplock we need to retry when the file is closed by + * the other user, or after 30 seconds + */ + if ((NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) || + NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) && + (req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return pvfs_unlink_setup_retry(pvfs->ntvfs, req, unl, lck, status); + } + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (name->stream_name) { + if (!name->stream_exists) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + return pvfs_stream_delete(pvfs, name, -1); + } + + return pvfs_unlink_file(pvfs, name); +} + +/* + delete a file - the dirtype specifies the file types to include in the search. + The name can contain CIFS wildcards, but rarely does (except with OS/2 clients) +*/ +NTSTATUS pvfs_unlink(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_unlink *unl) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + struct pvfs_dir *dir; + NTSTATUS status; + uint32_t total_deleted=0; + struct pvfs_filename *name; + const char *fname; + off_t ofs; + + /* resolve the cifs name to a posix name */ + status = pvfs_resolve_name(pvfs, req, unl->unlink.in.pattern, + PVFS_RESOLVE_WILDCARD | + PVFS_RESOLVE_STREAMS | + PVFS_RESOLVE_NO_OPENDB, + &name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (!name->exists && !name->has_wildcard) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + if (name->exists && + (name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY)) { + return NT_STATUS_FILE_IS_A_DIRECTORY; + } + + if (!name->has_wildcard) { + return pvfs_unlink_one(pvfs, req, unl, name); + } + + /* + * disable async requests in the wildcard case + * until we have proper tests for this + */ + req->async_states->state &= ~NTVFS_ASYNC_STATE_MAY_ASYNC; + + /* get list of matching files */ + status = pvfs_list_start(pvfs, name, req, &dir); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = NT_STATUS_NO_SUCH_FILE; + talloc_free(name); + + ofs = 0; + + while ((fname = pvfs_list_next(dir, &ofs))) { + /* this seems to be a special case */ + if ((unl->unlink.in.attrib & FILE_ATTRIBUTE_DIRECTORY) && + (ISDOT(fname) || ISDOTDOT(fname))) { + return NT_STATUS_OBJECT_NAME_INVALID; + } + + /* get a pvfs_filename object */ + status = pvfs_resolve_partial(pvfs, req, + pvfs_list_unix_path(dir), + fname, + PVFS_RESOLVE_NO_OPENDB, + &name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = pvfs_unlink_one(pvfs, req, unl, name); + if (NT_STATUS_IS_OK(status)) { + total_deleted++; + } + + talloc_free(name); + } + + if (total_deleted > 0) { + status = NT_STATUS_OK; + } + + return status; +} + + diff --git a/source4/ntvfs/posix/pvfs_util.c b/source4/ntvfs/posix/pvfs_util.c new file mode 100644 index 0000000..2975e04 --- /dev/null +++ b/source4/ntvfs/posix/pvfs_util.c @@ -0,0 +1,206 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Andrew Tridgell 2004 + + 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/>. +*/ +/* + utility functions for posix backend +*/ + +#include "includes.h" +#include "vfs_posix.h" + +/* + return true if a string contains one of the CIFS wildcard characters +*/ +bool pvfs_has_wildcard(const char *str) +{ + if (strpbrk(str, "*?<>\"")) { + return true; + } + return false; +} + +/* + map a unix errno to a NTSTATUS +*/ +NTSTATUS pvfs_map_errno(struct pvfs_state *pvfs, int unix_errno) +{ + NTSTATUS status; + status = map_nt_error_from_unix_common(unix_errno); + DEBUG(10,(__location__ " mapped unix errno %d -> %s\n", unix_errno, nt_errstr(status))); + return status; +} + + +/* + check if a filename has an attribute matching the given attribute search value + this is used by calls like unlink and search which take an attribute + and only include special files if they match the given attribute +*/ +NTSTATUS pvfs_match_attrib(struct pvfs_state *pvfs, struct pvfs_filename *name, + uint32_t attrib, uint32_t must_attrib) +{ + if ((name->dos.attrib & ~attrib) & FILE_ATTRIBUTE_DIRECTORY) { + return NT_STATUS_FILE_IS_A_DIRECTORY; + } + if ((name->dos.attrib & ~attrib) & (FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM)) { + return NT_STATUS_NO_SUCH_FILE; + } + if (must_attrib & ~name->dos.attrib) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + return NT_STATUS_OK; +} + + +/* + normalise a file attribute +*/ +uint32_t pvfs_attrib_normalise(uint32_t attrib, mode_t mode) +{ + if (attrib != FILE_ATTRIBUTE_NORMAL) { + attrib &= ~FILE_ATTRIBUTE_NORMAL; + } + if (S_ISDIR(mode)) { + attrib |= FILE_ATTRIBUTE_DIRECTORY; + } else { + attrib &= ~FILE_ATTRIBUTE_DIRECTORY; + } + return attrib; +} + + +/* + copy a file. Caller is supposed to have already ensured that the + operation is allowed. The destination file must not exist. +*/ +NTSTATUS pvfs_copy_file(struct pvfs_state *pvfs, + struct pvfs_filename *name1, + struct pvfs_filename *name2, + bool allow_override) +{ + int fd1, fd2; + mode_t mode; + NTSTATUS status; + size_t buf_size = 0x10000; + uint8_t *buf = talloc_array(name2, uint8_t, buf_size); + + if (buf == NULL) { + return NT_STATUS_NO_MEMORY; + } + + fd1 = pvfs_sys_open(pvfs, name1->full_name, O_RDONLY, 0, allow_override); + if (fd1 == -1) { + talloc_free(buf); + return pvfs_map_errno(pvfs, errno); + } + + fd2 = pvfs_sys_open(pvfs, name2->full_name, O_CREAT|O_EXCL|O_WRONLY, 0, allow_override); + if (fd2 == -1) { + close(fd1); + talloc_free(buf); + return pvfs_map_errno(pvfs, errno); + } + + while (1) { + ssize_t ret2, ret = read(fd1, buf, buf_size); + if (ret == -1 && + (errno == EINTR || errno == EAGAIN)) { + continue; + } + if (ret <= 0) break; + + ret2 = write(fd2, buf, ret); + if (ret2 == -1 && + (errno == EINTR || errno == EAGAIN)) { + continue; + } + + if (ret2 != ret) { + close(fd1); + close(fd2); + talloc_free(buf); + pvfs_sys_unlink(pvfs, name2->full_name, allow_override); + if (ret2 == -1) { + return pvfs_map_errno(pvfs, errno); + } + return NT_STATUS_DISK_FULL; + } + } + + talloc_free(buf); + close(fd1); + + mode = pvfs_fileperms(pvfs, name1->dos.attrib); + if (pvfs_sys_fchmod(pvfs, fd2, mode, allow_override) == -1) { + status = pvfs_map_errno(pvfs, errno); + close(fd2); + pvfs_sys_unlink(pvfs, name2->full_name, allow_override); + return status; + } + + name2->st.st_mode = mode; + name2->dos = name1->dos; + + status = pvfs_dosattrib_save(pvfs, name2, fd2); + if (!NT_STATUS_IS_OK(status)) { + close(fd2); + pvfs_sys_unlink(pvfs, name2->full_name, allow_override); + return status; + } + + close(fd2); + + return NT_STATUS_OK; +} + + +/* + hash a string of the specified length. The string does not need to be + null terminated + + hash algorithm changed to FNV1 by idra@samba.org (Simo Sorce). + see http://www.isthe.com/chongo/tech/comp/fnv/index.html for a + discussion on Fowler / Noll / Vo (FNV) Hash by one of it's authors +*/ +uint32_t pvfs_name_hash(const char *key, size_t length) +{ + const uint32_t fnv1_prime = 0x01000193; + const uint32_t fnv1_init = 0xa6b93095; + uint32_t value = fnv1_init; + + while (*key && length--) { + size_t c_size; + codepoint_t c = next_codepoint(key, &c_size); + c = toupper_m(c); + value *= fnv1_prime; + value ^= (uint32_t)c; + key += c_size; + } + + return value; +} + + +/* + file allocation size rounding. This is required to pass ifstest +*/ +uint64_t pvfs_round_alloc_size(struct pvfs_state *pvfs, uint64_t size) +{ + const uint32_t round_value = pvfs->alloc_size_rounding; + return round_value * ((size + round_value - 1)/round_value); +} diff --git a/source4/ntvfs/posix/pvfs_wait.c b/source4/ntvfs/posix/pvfs_wait.c new file mode 100644 index 0000000..21ff33e --- /dev/null +++ b/source4/ntvfs/posix/pvfs_wait.c @@ -0,0 +1,212 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - async request wait routines + + Copyright (C) Andrew Tridgell 2004 + + 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 "lib/events/events.h" +#include "../lib/util/dlinklist.h" +#include "vfs_posix.h" +#include "samba/service_stream.h" +#include "lib/messaging/irpc.h" + +/* the context for a single wait instance */ +struct pvfs_wait { + struct pvfs_wait *next, *prev; + struct pvfs_state *pvfs; + void (*handler)(void *, enum pvfs_wait_notice); + void *private_data; + int msg_type; + struct imessaging_context *msg_ctx; + struct tevent_context *ev; + struct ntvfs_request *req; + enum pvfs_wait_notice reason; +}; + +/* + called from the ntvfs layer when we have requested setup of an async + call. this ensures that async calls runs with the right state of + previous ntvfs handlers in the chain (such as security context) +*/ +NTSTATUS pvfs_async_setup(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, void *private_data) +{ + struct pvfs_wait *pwait = talloc_get_type(private_data, + struct pvfs_wait); + pwait->handler(pwait->private_data, pwait->reason); + return NT_STATUS_OK; +} + +/* + receive a completion message for a wait +*/ +static void pvfs_wait_dispatch(struct imessaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id src, + size_t num_fds, + int *fds, + DATA_BLOB *data) +{ + struct pvfs_wait *pwait = talloc_get_type(private_data, + struct pvfs_wait); + struct ntvfs_request *req; + void *p = NULL; + + if (num_fds != 0) { + DBG_WARNING("Received %zu fds, ignoring message\n", num_fds); + return; + } + + /* we need to check that this one is for us. See + imessaging_send_ptr() for the other side of this. + */ + if (data->length == sizeof(void *)) { + void **pp; + pp = (void **)data->data; + p = *pp; + } + if (p == NULL || p != pwait->private_data) { + return; + } + + pwait->reason = PVFS_WAIT_EVENT; + + /* the extra reference here is to ensure that the req + structure is not destroyed when the async request reply is + sent, which would cause problems with the other ntvfs + modules above us */ + req = talloc_reference(msg, pwait->req); + ntvfs_async_setup(pwait->req, pwait); + talloc_unlink(msg, req); +} + + +/* + receive a timeout on a message wait +*/ +static void pvfs_wait_timeout(struct tevent_context *ev, + struct tevent_timer *te, struct timeval t, + void *private_data) +{ + struct pvfs_wait *pwait = talloc_get_type(private_data, + struct pvfs_wait); + struct ntvfs_request *req = pwait->req; + + pwait->reason = PVFS_WAIT_TIMEOUT; + + req = talloc_reference(ev, req); + if (req != NULL) { + ntvfs_async_setup(req, pwait); + talloc_unlink(ev, req); + } +} + + +/* + destroy a pending wait + */ +static int pvfs_wait_destructor(struct pvfs_wait *pwait) +{ + if (pwait->msg_type != -1) { + imessaging_deregister(pwait->msg_ctx, pwait->msg_type, pwait); + } + DLIST_REMOVE(pwait->pvfs->wait_list, pwait); + return 0; +} + +/* + setup a request to wait on a message of type msg_type, with a + timeout (given as an expiry time) + + the return value is a handle. To stop waiting talloc_free this + handle. + + if msg_type == -1 then no message is registered, and it is assumed + that the caller handles any messaging setup needed +*/ +struct pvfs_wait *pvfs_wait_message(struct pvfs_state *pvfs, + struct ntvfs_request *req, + int msg_type, + struct timeval end_time, + void (*fn)(void *, enum pvfs_wait_notice), + void *private_data) +{ + struct pvfs_wait *pwait; + + pwait = talloc(pvfs, struct pvfs_wait); + if (pwait == NULL) { + return NULL; + } + + pwait->private_data = private_data; + pwait->handler = fn; + pwait->msg_ctx = pvfs->ntvfs->ctx->msg_ctx; + pwait->ev = pvfs->ntvfs->ctx->event_ctx; + pwait->msg_type = msg_type; + pwait->req = talloc_reference(pwait, req); + pwait->pvfs = pvfs; + + if (!timeval_is_zero(&end_time)) { + /* setup a timer */ + tevent_add_timer(pwait->ev, pwait, end_time, pvfs_wait_timeout, pwait); + } + + /* register with the messaging subsystem for this message + type */ + if (msg_type != -1) { + imessaging_register(pwait->msg_ctx, + pwait, + msg_type, + pvfs_wait_dispatch); + } + + /* tell the main smb server layer that we will be replying + asynchronously */ + req->async_states->state |= NTVFS_ASYNC_STATE_ASYNC; + + DLIST_ADD(pvfs->wait_list, pwait); + + /* make sure we cleanup the timer and message handler */ + talloc_set_destructor(pwait, pvfs_wait_destructor); + + return pwait; +} + + +/* + cancel an outstanding async request +*/ +NTSTATUS pvfs_cancel(struct ntvfs_module_context *ntvfs, struct ntvfs_request *req) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + struct pvfs_wait *pwait; + + for (pwait=pvfs->wait_list;pwait;pwait=pwait->next) { + if (pwait->req == req) { + /* trigger a cancel on the request */ + pwait->reason = PVFS_WAIT_CANCEL; + ntvfs_async_setup(pwait->req, pwait); + return NT_STATUS_OK; + } + } + + return NT_STATUS_DOS(ERRDOS, ERRcancelviolation); +} diff --git a/source4/ntvfs/posix/pvfs_write.c b/source4/ntvfs/posix/pvfs_write.c new file mode 100644 index 0000000..9773325 --- /dev/null +++ b/source4/ntvfs/posix/pvfs_write.c @@ -0,0 +1,145 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - write + + Copyright (C) Andrew Tridgell 2004 + + 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 "vfs_posix.h" +#include "librpc/gen_ndr/security.h" +#include "lib/events/events.h" + +static void pvfs_write_time_update_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data) +{ + struct pvfs_file_handle *h = talloc_get_type(private_data, + struct pvfs_file_handle); + struct odb_lock *lck; + NTSTATUS status; + NTTIME write_time; + + lck = odb_lock(h, h->pvfs->odb_context, &h->odb_locking_key); + if (lck == NULL) { + DEBUG(0,("Unable to lock opendb for write time update\n")); + return; + } + + write_time = timeval_to_nttime(&tv); + + status = odb_set_write_time(lck, write_time, false); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Unable to update write time: %s\n", + nt_errstr(status))); + return; + } + + talloc_free(lck); + + h->write_time.update_event = NULL; +} + +static void pvfs_trigger_write_time_update(struct pvfs_file_handle *h) +{ + struct pvfs_state *pvfs = h->pvfs; + struct timeval tv; + + if (h->write_time.update_triggered) { + return; + } + + tv = timeval_current_ofs_usec(pvfs->writetime_delay); + + h->write_time.update_triggered = true; + h->write_time.update_on_close = true; + h->write_time.update_event = tevent_add_timer(pvfs->ntvfs->ctx->event_ctx, + h, tv, + pvfs_write_time_update_handler, + h); + if (!h->write_time.update_event) { + DEBUG(0,("Failed tevent_add_timer\n")); + } +} + +/* + write to a file +*/ +NTSTATUS pvfs_write(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_write *wr) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + ssize_t ret; + struct pvfs_file *f; + NTSTATUS status; + + if (wr->generic.level != RAW_WRITE_WRITEX) { + return ntvfs_map_write(ntvfs, req, wr); + } + + f = pvfs_find_fd(pvfs, req, wr->writex.in.file.ntvfs); + if (!f) { + return NT_STATUS_INVALID_HANDLE; + } + + if (f->handle->fd == -1) { + return NT_STATUS_INVALID_DEVICE_REQUEST; + } + + if (!(f->access_mask & (SEC_FILE_WRITE_DATA | SEC_FILE_APPEND_DATA))) { + return NT_STATUS_ACCESS_DENIED; + } + + status = pvfs_check_lock(pvfs, f, req->smbpid, + wr->writex.in.offset, + wr->writex.in.count, + WRITE_LOCK); + NT_STATUS_NOT_OK_RETURN(status); + + status = pvfs_break_level2_oplocks(f); + NT_STATUS_NOT_OK_RETURN(status); + + pvfs_trigger_write_time_update(f->handle); + + if (f->handle->name->stream_name) { + ret = pvfs_stream_write(pvfs, + f->handle, + wr->writex.in.data, + wr->writex.in.count, + wr->writex.in.offset); + } else { + ret = pwrite(f->handle->fd, + wr->writex.in.data, + wr->writex.in.count, + wr->writex.in.offset); + } + if (ret == -1) { + if (errno == EFBIG) { + return NT_STATUS_INVALID_PARAMETER; + } + return pvfs_map_errno(pvfs, errno); + } + + f->handle->seek_offset = wr->writex.in.offset + ret; + + wr->writex.out.nwritten = ret; + wr->writex.out.remaining = 0; /* should fill this in? */ + + return NT_STATUS_OK; +} diff --git a/source4/ntvfs/posix/pvfs_xattr.c b/source4/ntvfs/posix/pvfs_xattr.c new file mode 100644 index 0000000..ab88d89 --- /dev/null +++ b/source4/ntvfs/posix/pvfs_xattr.c @@ -0,0 +1,488 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - xattr support + + Copyright (C) Andrew Tridgell 2004 + + 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 "vfs_posix.h" +#include "../lib/util/unix_privs.h" +#include "librpc/gen_ndr/ndr_xattr.h" +#include "param/param.h" +#include "ntvfs/posix/posix_eadb_proto.h" + +/* + pull a xattr as a blob +*/ +static NTSTATUS pull_xattr_blob(struct pvfs_state *pvfs, + TALLOC_CTX *mem_ctx, + const char *attr_name, + const char *fname, + int fd, + size_t estimated_size, + DATA_BLOB *blob) +{ + NTSTATUS status; + + if (pvfs->ea_db) { + return pull_xattr_blob_tdb(pvfs, mem_ctx, attr_name, fname, + fd, estimated_size, blob); + } + + status = pull_xattr_blob_system(pvfs, mem_ctx, attr_name, fname, + fd, estimated_size, blob); + + /* if the filesystem doesn't support them, then tell pvfs not to try again */ + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)|| + NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)|| + NT_STATUS_EQUAL(status, NT_STATUS_INVALID_SYSTEM_SERVICE)) { + DEBUG(2,("pvfs_xattr: xattr not supported in filesystem: %s\n", nt_errstr(status))); + pvfs->flags &= ~PVFS_FLAG_XATTR_ENABLE; + status = NT_STATUS_NOT_FOUND; + } + + return status; +} + +/* + push a xattr as a blob +*/ +static NTSTATUS push_xattr_blob(struct pvfs_state *pvfs, + const char *attr_name, + const char *fname, + int fd, + const DATA_BLOB *blob) +{ + if (pvfs->ea_db) { + return push_xattr_blob_tdb(pvfs, attr_name, fname, fd, blob); + } + return push_xattr_blob_system(pvfs, attr_name, fname, fd, blob); +} + + +/* + delete a xattr +*/ +static NTSTATUS delete_xattr(struct pvfs_state *pvfs, const char *attr_name, + const char *fname, int fd) +{ + if (pvfs->ea_db) { + return delete_posix_eadb(pvfs, attr_name, fname, fd); + } + return delete_xattr_system(pvfs, attr_name, fname, fd); +} + +/* + a hook called on unlink - allows the tdb xattr backend to cleanup +*/ +NTSTATUS pvfs_xattr_unlink_hook(struct pvfs_state *pvfs, const char *fname) +{ + if (pvfs->ea_db) { + return unlink_posix_eadb(pvfs, fname); + } + return unlink_xattr_system(pvfs, fname); +} + + +/* + load a NDR structure from a xattr +*/ +NTSTATUS pvfs_xattr_ndr_load(struct pvfs_state *pvfs, + TALLOC_CTX *mem_ctx, + const char *fname, int fd, const char *attr_name, + void *p, void *pull_fn) +{ + NTSTATUS status; + DATA_BLOB blob; + enum ndr_err_code ndr_err; + + status = pull_xattr_blob(pvfs, mem_ctx, attr_name, fname, + fd, XATTR_DOSATTRIB_ESTIMATED_SIZE, &blob); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* pull the blob */ + ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, p, + (ndr_pull_flags_fn_t)pull_fn); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return ndr_map_error2ntstatus(ndr_err); + } + + data_blob_free(&blob); + + return NT_STATUS_OK; +} + +/* + save a NDR structure into a xattr +*/ +NTSTATUS pvfs_xattr_ndr_save(struct pvfs_state *pvfs, + const char *fname, int fd, const char *attr_name, + void *p, void *push_fn) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + DATA_BLOB blob; + NTSTATUS status; + enum ndr_err_code ndr_err; + + ndr_err = ndr_push_struct_blob(&blob, mem_ctx, p, (ndr_push_flags_fn_t)push_fn); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + talloc_free(mem_ctx); + return ndr_map_error2ntstatus(ndr_err); + } + + status = push_xattr_blob(pvfs, attr_name, fname, fd, &blob); + talloc_free(mem_ctx); + + return status; +} + + +/* + fill in file attributes from extended attributes +*/ +NTSTATUS pvfs_dosattrib_load(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd) +{ + NTSTATUS status; + struct xattr_DosAttrib attrib; + TALLOC_CTX *mem_ctx = talloc_new(name); + struct xattr_DosInfo1 *info1; + struct xattr_DosInfo2Old *info2; + + if (name->stream_name != NULL) { + name->stream_exists = false; + } else { + name->stream_exists = true; + } + + if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE)) { + return NT_STATUS_OK; + } + + status = pvfs_xattr_ndr_load(pvfs, mem_ctx, name->full_name, + fd, XATTR_DOSATTRIB_NAME, + &attrib, + (void *) ndr_pull_xattr_DosAttrib); + + /* not having a DosAttrib is not an error */ + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + talloc_free(mem_ctx); + return pvfs_stream_info(pvfs, name, fd); + } + + if (!NT_STATUS_IS_OK(status)) { + talloc_free(mem_ctx); + return status; + } + + switch (attrib.version) { + case 1: + info1 = &attrib.info.info1; + name->dos.attrib = pvfs_attrib_normalise(info1->attrib, + name->st.st_mode); + name->dos.ea_size = info1->ea_size; + if (name->st.st_size == info1->size) { + name->dos.alloc_size = + pvfs_round_alloc_size(pvfs, info1->alloc_size); + } + if (!null_nttime(info1->create_time)) { + name->dos.create_time = info1->create_time; + } + if (!null_nttime(info1->change_time)) { + name->dos.change_time = info1->change_time; + } + name->dos.flags = 0; + break; + + case 2: + /* + * Note: This is only used to parse existing values from disk + * We use xattr_DosInfo1 again for storing new values + */ + info2 = &attrib.info.oldinfo2; + name->dos.attrib = pvfs_attrib_normalise(info2->attrib, + name->st.st_mode); + name->dos.ea_size = info2->ea_size; + if (name->st.st_size == info2->size) { + name->dos.alloc_size = + pvfs_round_alloc_size(pvfs, info2->alloc_size); + } + if (!null_nttime(info2->create_time)) { + name->dos.create_time = info2->create_time; + } + if (!null_nttime(info2->change_time)) { + name->dos.change_time = info2->change_time; + } + name->dos.flags = info2->flags; + break; + + default: + DEBUG(0,("ERROR: Unsupported xattr DosAttrib version %d on '%s'\n", + attrib.version, name->full_name)); + talloc_free(mem_ctx); + return NT_STATUS_INVALID_LEVEL; + } + talloc_free(mem_ctx); + + status = pvfs_stream_info(pvfs, name, fd); + + return status; +} + + +/* + save the file attribute into the xattr +*/ +NTSTATUS pvfs_dosattrib_save(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd) +{ + struct xattr_DosAttrib attrib; + struct xattr_DosInfo1 *info1; + + if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE)) { + return NT_STATUS_OK; + } + + attrib.version = 1; + info1 = &attrib.info.info1; + + name->dos.attrib = pvfs_attrib_normalise(name->dos.attrib, name->st.st_mode); + + info1->attrib = name->dos.attrib; + info1->ea_size = name->dos.ea_size; + info1->size = name->st.st_size; + info1->alloc_size = name->dos.alloc_size; + info1->create_time = name->dos.create_time; + info1->change_time = name->dos.change_time; + + return pvfs_xattr_ndr_save(pvfs, name->full_name, fd, + XATTR_DOSATTRIB_NAME, &attrib, + (void *) ndr_push_xattr_DosAttrib); +} + + +/* + load the set of DOS EAs +*/ +NTSTATUS pvfs_doseas_load(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd, + struct xattr_DosEAs *eas) +{ + NTSTATUS status; + ZERO_STRUCTP(eas); + + if (name->stream_name) { + /* We don't support EAs on streams */ + return NT_STATUS_INVALID_PARAMETER; + } + + if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE)) { + return NT_STATUS_OK; + } + status = pvfs_xattr_ndr_load(pvfs, eas, name->full_name, fd, XATTR_DOSEAS_NAME, + eas, (void *) ndr_pull_xattr_DosEAs); + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + return NT_STATUS_OK; + } + return status; +} + +/* + save the set of DOS EAs +*/ +NTSTATUS pvfs_doseas_save(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd, + struct xattr_DosEAs *eas) +{ + if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE)) { + return NT_STATUS_OK; + } + return pvfs_xattr_ndr_save(pvfs, name->full_name, fd, XATTR_DOSEAS_NAME, eas, + (void *) ndr_push_xattr_DosEAs); +} + + +/* + load the set of streams from extended attributes +*/ +NTSTATUS pvfs_streams_load(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd, + struct xattr_DosStreams *streams) +{ + NTSTATUS status; + ZERO_STRUCTP(streams); + if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE)) { + return NT_STATUS_OK; + } + status = pvfs_xattr_ndr_load(pvfs, streams, name->full_name, fd, + XATTR_DOSSTREAMS_NAME, + streams, + (void *) ndr_pull_xattr_DosStreams); + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + return NT_STATUS_OK; + } + return status; +} + +/* + save the set of streams into filesystem xattr +*/ +NTSTATUS pvfs_streams_save(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd, + struct xattr_DosStreams *streams) +{ + if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE)) { + return NT_STATUS_OK; + } + return pvfs_xattr_ndr_save(pvfs, name->full_name, fd, + XATTR_DOSSTREAMS_NAME, + streams, + (void *) ndr_push_xattr_DosStreams); +} + + +/* + load the current ACL from extended attributes +*/ +NTSTATUS pvfs_acl_load(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd, + struct xattr_NTACL *acl) +{ + NTSTATUS status; + ZERO_STRUCTP(acl); + if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE)) { + return NT_STATUS_NOT_FOUND; + } + status = pvfs_xattr_ndr_load(pvfs, acl, name->full_name, fd, + XATTR_NTACL_NAME, + acl, + (void *) ndr_pull_xattr_NTACL); + return status; +} + +/* + save the acl for a file into filesystem xattr +*/ +NTSTATUS pvfs_acl_save(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd, + struct xattr_NTACL *acl) +{ + NTSTATUS status; + void *privs; + + if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE)) { + return NT_STATUS_OK; + } + + /* this xattr is in the "system" namespace, so we need + admin privileges to set it */ + privs = root_privileges(); + status = pvfs_xattr_ndr_save(pvfs, name->full_name, fd, + XATTR_NTACL_NAME, + acl, + (void *) ndr_push_xattr_NTACL); + talloc_free(privs); + return status; +} + +/* + create a zero length xattr with the given name +*/ +NTSTATUS pvfs_xattr_create(struct pvfs_state *pvfs, + const char *fname, int fd, + const char *attr_prefix, + const char *attr_name) +{ + NTSTATUS status; + DATA_BLOB blob = data_blob(NULL, 0); + char *aname = talloc_asprintf(NULL, "%s%s", attr_prefix, attr_name); + if (aname == NULL) { + return NT_STATUS_NO_MEMORY; + } + status = push_xattr_blob(pvfs, aname, fname, fd, &blob); + talloc_free(aname); + return status; +} + + +/* + delete a xattr with the given name +*/ +NTSTATUS pvfs_xattr_delete(struct pvfs_state *pvfs, + const char *fname, int fd, + const char *attr_prefix, + const char *attr_name) +{ + NTSTATUS status; + char *aname = talloc_asprintf(NULL, "%s%s", attr_prefix, attr_name); + if (aname == NULL) { + return NT_STATUS_NO_MEMORY; + } + status = delete_xattr(pvfs, aname, fname, fd); + talloc_free(aname); + return status; +} + +/* + load a xattr with the given name +*/ +NTSTATUS pvfs_xattr_load(struct pvfs_state *pvfs, + TALLOC_CTX *mem_ctx, + const char *fname, int fd, + const char *attr_prefix, + const char *attr_name, + size_t estimated_size, + DATA_BLOB *blob) +{ + NTSTATUS status; + char *aname = talloc_asprintf(mem_ctx, "%s%s", attr_prefix, attr_name); + if (aname == NULL) { + return NT_STATUS_NO_MEMORY; + } + status = pull_xattr_blob(pvfs, mem_ctx, aname, fname, fd, estimated_size, blob); + talloc_free(aname); + return status; +} + +/* + save a xattr with the given name +*/ +NTSTATUS pvfs_xattr_save(struct pvfs_state *pvfs, + const char *fname, int fd, + const char *attr_prefix, + const char *attr_name, + const DATA_BLOB *blob) +{ + NTSTATUS status; + char *aname = talloc_asprintf(NULL, "%s%s", attr_prefix, attr_name); + if (aname == NULL) { + return NT_STATUS_NO_MEMORY; + } + status = push_xattr_blob(pvfs, aname, fname, fd, blob); + talloc_free(aname); + return status; +} + + +/* + probe for system support for xattrs +*/ +void pvfs_xattr_probe(struct pvfs_state *pvfs) +{ + TALLOC_CTX *tmp_ctx = talloc_new(pvfs); + DATA_BLOB blob; + pull_xattr_blob(pvfs, tmp_ctx, "user.XattrProbe", pvfs->base_directory, + -1, 1, &blob); + pull_xattr_blob(pvfs, tmp_ctx, "security.XattrProbe", pvfs->base_directory, + -1, 1, &blob); + talloc_free(tmp_ctx); +} diff --git a/source4/ntvfs/posix/python/pyposix_eadb.c b/source4/ntvfs/posix/python/pyposix_eadb.c new file mode 100644 index 0000000..aca92e7 --- /dev/null +++ b/source4/ntvfs/posix/python/pyposix_eadb.c @@ -0,0 +1,140 @@ +/* + Unix SMB/CIFS implementation. Xattr manipulation bindings. + Copyright (C) Matthieu Patou <mat@matws.net> 2009-2010 + Base on work of pyglue.c by Jelmer Vernooij <jelmer@samba.org> 2007 and + Matthias Dieter Wallnöfer 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 <http://www.gnu.org/licenses/>. +*/ + +#include "lib/replace/system/python.h" +#include "python/py3compat.h" +#include "includes.h" +#include "system/filesys.h" +#include <tdb.h> +#include "lib/tdb_wrap/tdb_wrap.h" +#include "librpc/ndr/libndr.h" +#include "ntvfs/posix/posix_eadb.h" +#include "libcli/util/pyerrors.h" +#include "param/pyparam.h" + +static PyObject *py_is_xattr_supported(PyObject *self, + PyObject *Py_UNUSED(ignored)) +{ + Py_RETURN_TRUE; +} + +static PyObject *py_wrap_setxattr(PyObject *self, PyObject *args) +{ + char *filename, *attribute, *tdbname; + DATA_BLOB blob; + Py_ssize_t blobsize; + NTSTATUS status; + TALLOC_CTX *mem_ctx; + struct tdb_wrap *eadb; + + if (!PyArg_ParseTuple(args, "sss"PYARG_BYTES_LEN, &tdbname, &filename, &attribute, + &blob.data, &blobsize)) + return NULL; + + blob.length = blobsize; + mem_ctx = talloc_new(NULL); + eadb = tdb_wrap_open( + mem_ctx, tdbname, 50000, + lpcfg_tdb_flags(py_default_loadparm_context(mem_ctx), + TDB_DEFAULT), + O_RDWR|O_CREAT, 0600); + + if (eadb == NULL) { + PyErr_SetFromErrno(PyExc_IOError); + talloc_free(mem_ctx); + return NULL; + } + status = push_xattr_blob_tdb_raw(eadb, attribute, filename, -1, + &blob); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + talloc_free(mem_ctx); + return NULL; + } + talloc_free(mem_ctx); + Py_RETURN_NONE; +} + +static PyObject *py_wrap_getxattr(PyObject *self, PyObject *args) +{ + char *filename, *attribute, *tdbname; + TALLOC_CTX *mem_ctx; + DATA_BLOB blob; + PyObject *ret; + NTSTATUS status; + struct tdb_wrap *eadb = NULL; + + if (!PyArg_ParseTuple(args, "sss", &tdbname, &filename, &attribute)) + return NULL; + + mem_ctx = talloc_new(NULL); + eadb = tdb_wrap_open( + mem_ctx, tdbname, 50000, + lpcfg_tdb_flags(py_default_loadparm_context(mem_ctx), + TDB_DEFAULT), + O_RDWR|O_CREAT, 0600); + if (eadb == NULL) { + PyErr_SetFromErrno(PyExc_IOError); + talloc_free(mem_ctx); + return NULL; + } + status = pull_xattr_blob_tdb_raw(eadb, mem_ctx, attribute, filename, + -1, 100, &blob); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + talloc_free(mem_ctx); + return NULL; + } + ret = Py_BuildValue(PYARG_BYTES_LEN, blob.data, blob.length); + talloc_free(mem_ctx); + return ret; +} + +static PyMethodDef py_posix_eadb_methods[] = { + { "wrap_getxattr", (PyCFunction)py_wrap_getxattr, METH_VARARGS, + "wrap_getxattr(filename,attribute) -> blob\n" + "Retrieve given attribute on the given file." }, + { "wrap_setxattr", (PyCFunction)py_wrap_setxattr, METH_VARARGS, + "wrap_setxattr(filename,attribute,value)\n" + "Set the given attribute to the given value on the given file." }, + { "is_xattr_supported", (PyCFunction)py_is_xattr_supported, METH_NOARGS, + "Return true if xattr are supported on this system\n"}, + {0} +}; + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + .m_name = "posix_eadb", + .m_doc = "Python bindings for xattr manipulation.", + .m_size = -1, + .m_methods = py_posix_eadb_methods, +}; + +MODULE_INIT_FUNC(posix_eadb) +{ + PyObject *m; + + m = PyModule_Create(&moduledef); + + if (m == NULL) + return NULL; + + return m; +} diff --git a/source4/ntvfs/posix/python/pyxattr_native.c b/source4/ntvfs/posix/python/pyxattr_native.c new file mode 100644 index 0000000..6b237b0 --- /dev/null +++ b/source4/ntvfs/posix/python/pyxattr_native.c @@ -0,0 +1,129 @@ +/* + Unix SMB/CIFS implementation. Xattr manipulation bindings. + Copyright (C) Matthieu Patou <mat@matws.net> 2009 + Base on work of pyglue.c by Jelmer Vernooij <jelmer@samba.org> 2007 and + Matthias Dieter Wallnöfer 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 <http://www.gnu.org/licenses/>. +*/ + +#include "lib/replace/system/python.h" +#include "python/py3compat.h" +#include "includes.h" +#include "librpc/ndr/libndr.h" +#include "system/filesys.h" +#include "lib/util/base64.h" + +static PyObject *py_is_xattr_supported(PyObject *self, + PyObject *Py_UNUSED(ignored)) +{ +#if !defined(HAVE_XATTR_SUPPORT) + Py_RETURN_FALSE; +#else + Py_RETURN_TRUE; +#endif +} + +static PyObject *py_wrap_setxattr(PyObject *self, PyObject *args) +{ + char *filename, *attribute; + int ret = 0; + Py_ssize_t blobsize; + DATA_BLOB blob; + + if (!PyArg_ParseTuple(args, "ss"PYARG_BYTES_LEN, &filename, &attribute, &blob.data, + &blobsize)) + return NULL; + + blob.length = blobsize; + ret = setxattr(filename, attribute, blob.data, blob.length, 0); + if( ret < 0 ) { + if (errno == ENOTSUP) { + PyErr_SetFromErrno(PyExc_IOError); + } else { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, filename); + } + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject *py_wrap_getxattr(PyObject *self, PyObject *args) +{ + char *filename, *attribute; + int len; + TALLOC_CTX *mem_ctx; + char *buf; + PyObject *ret; + if (!PyArg_ParseTuple(args, "ss", &filename, &attribute)) + return NULL; + mem_ctx = talloc_new(NULL); + len = getxattr(filename,attribute,NULL,0); + if( len < 0 ) { + if (errno == ENOTSUP) { + PyErr_SetFromErrno(PyExc_IOError); + } else { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, filename); + } + talloc_free(mem_ctx); + return NULL; + } + /* check length ... */ + buf = talloc_zero_array(mem_ctx, char, len); + len = getxattr(filename, attribute, buf, len); + if( len < 0 ) { + if (errno == ENOTSUP) { + PyErr_SetFromErrno(PyExc_IOError); + } else { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, filename); + } + talloc_free(mem_ctx); + return NULL; + } + ret = Py_BuildValue(PYARG_BYTES_LEN, buf, len); + talloc_free(mem_ctx); + return ret; +} + +static PyMethodDef py_xattr_methods[] = { + { "wrap_getxattr", (PyCFunction)py_wrap_getxattr, METH_VARARGS, + "wrap_getxattr(filename,attribute) -> blob\n" + "Retrieve given attribute on the given file." }, + { "wrap_setxattr", (PyCFunction)py_wrap_setxattr, METH_VARARGS, + "wrap_setxattr(filename,attribute,value)\n" + "Set the given attribute to the given value on the given file." }, + { "is_xattr_supported", (PyCFunction)py_is_xattr_supported, METH_NOARGS, + "Return true if xattr are supported on this system\n"}, + {0} +}; + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + .m_name = "xattr_native", + .m_doc = "Python bindings for xattr manipulation.", + .m_size = -1, + .m_methods = py_xattr_methods, +}; + +MODULE_INIT_FUNC(xattr_native) +{ + PyObject *m; + + m = PyModule_Create(&moduledef); + + if (m == NULL) + return NULL; + + return m; +} diff --git a/source4/ntvfs/posix/python/pyxattr_tdb.c b/source4/ntvfs/posix/python/pyxattr_tdb.c new file mode 100644 index 0000000..5f07809 --- /dev/null +++ b/source4/ntvfs/posix/python/pyxattr_tdb.c @@ -0,0 +1,177 @@ +/* + Unix SMB/CIFS implementation. Xattr manipulation bindings. + Copyright (C) Matthieu Patou <mat@matws.net> 2009-2010 + Base on work of pyglue.c by Jelmer Vernooij <jelmer@samba.org> 2007 and + Matthias Dieter Wallnöfer 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 <http://www.gnu.org/licenses/>. +*/ + +#include "lib/replace/system/python.h" +#include "python/py3compat.h" +#include "includes.h" +#include "system/filesys.h" +#include <tdb.h> +#include "lib/tdb_wrap/tdb_wrap.h" +#include "librpc/ndr/libndr.h" +#include "ntvfs/posix/posix_eadb.h" +#include "libcli/util/pyerrors.h" +#include "param/pyparam.h" +#include "lib/dbwrap/dbwrap.h" +#include "lib/dbwrap/dbwrap_open.h" +#include "lib/dbwrap/dbwrap_tdb.h" +#include "source3/lib/xattr_tdb.h" + +static PyObject *py_is_xattr_supported(PyObject *self, + PyObject *Py_UNUSED(ignored)) +{ + Py_RETURN_TRUE; +} + +static PyObject *py_wrap_setxattr(PyObject *self, PyObject *args) +{ + char *filename, *attribute, *tdbname; + DATA_BLOB blob; + Py_ssize_t blobsize; + int ret; + TALLOC_CTX *mem_ctx; + struct loadparm_context *lp_ctx; + struct db_context *eadb = NULL; + struct file_id id; + struct stat sbuf; + + if (!PyArg_ParseTuple(args, "sss"PYARG_BYTES_LEN, &tdbname, &filename, &attribute, + &blob.data, &blobsize)) + return NULL; + + blob.length = blobsize; + mem_ctx = talloc_new(NULL); + + lp_ctx = py_default_loadparm_context(mem_ctx); + eadb = db_open_tdb(mem_ctx, tdbname, 50000, + lpcfg_tdb_flags(lp_ctx, TDB_DEFAULT), + O_RDWR|O_CREAT, 0600, DBWRAP_LOCK_ORDER_2, + DBWRAP_FLAG_NONE); + + if (eadb == NULL) { + PyErr_SetFromErrno(PyExc_IOError); + talloc_free(mem_ctx); + return NULL; + } + + ret = stat(filename, &sbuf); + if (ret < 0) { + PyErr_SetFromErrno(PyExc_IOError); + talloc_free(mem_ctx); + return NULL; + } + + ZERO_STRUCT(id); + id.devid = sbuf.st_dev; + id.inode = sbuf.st_ino; + + ret = xattr_tdb_setattr(eadb, &id, attribute, blob.data, blob.length, 0); + if (ret < 0) { + PyErr_SetFromErrno(PyExc_TypeError); + talloc_free(mem_ctx); + return NULL; + } + talloc_free(mem_ctx); + Py_RETURN_NONE; +} + +static PyObject *py_wrap_getxattr(PyObject *self, PyObject *args) +{ + char *filename, *attribute, *tdbname; + TALLOC_CTX *mem_ctx; + struct loadparm_context *lp_ctx; + DATA_BLOB blob; + PyObject *ret_obj; + int ret; + ssize_t xattr_size; + struct db_context *eadb = NULL; + struct file_id id; + struct stat sbuf; + + if (!PyArg_ParseTuple(args, "sss", &tdbname, &filename, &attribute)) + return NULL; + + mem_ctx = talloc_new(NULL); + + lp_ctx = py_default_loadparm_context(mem_ctx); + eadb = db_open_tdb(mem_ctx, tdbname, 50000, + lpcfg_tdb_flags(lp_ctx, TDB_DEFAULT), + O_RDWR|O_CREAT, 0600, DBWRAP_LOCK_ORDER_2, + DBWRAP_FLAG_NONE); + + if (eadb == NULL) { + PyErr_SetFromErrno(PyExc_IOError); + talloc_free(mem_ctx); + return NULL; + } + + ret = stat(filename, &sbuf); + if (ret < 0) { + PyErr_SetFromErrno(PyExc_IOError); + talloc_free(mem_ctx); + return NULL; + } + + ZERO_STRUCT(id); + id.devid = sbuf.st_dev; + id.inode = sbuf.st_ino; + + xattr_size = xattr_tdb_getattr(eadb, mem_ctx, &id, attribute, &blob); + if (xattr_size < 0) { + PyErr_SetFromErrno(PyExc_TypeError); + talloc_free(mem_ctx); + return NULL; + } + ret_obj = Py_BuildValue(PYARG_BYTES_LEN, blob.data, xattr_size); + talloc_free(mem_ctx); + return ret_obj; +} + +static PyMethodDef py_xattr_methods[] = { + { "wrap_getxattr", (PyCFunction)py_wrap_getxattr, METH_VARARGS, + "wrap_getxattr(filename,attribute) -> blob\n" + "Retrieve given attribute on the given file." }, + { "wrap_setxattr", (PyCFunction)py_wrap_setxattr, METH_VARARGS, + "wrap_setxattr(filename,attribute,value)\n" + "Set the given attribute to the given value on the given file." }, + { "is_xattr_supported", (PyCFunction)py_is_xattr_supported, METH_NOARGS, + "Return true if xattr are supported on this system\n"}, + {0} +}; + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + .m_name = "xattr_tdb", + .m_doc = "Python bindings for xattr manipulation.", + .m_size = -1, + .m_methods = py_xattr_methods, +}; + +MODULE_INIT_FUNC(xattr_tdb) +{ + PyObject *m; + + m = PyModule_Create(&moduledef); + + if (m == NULL) + return NULL; + + return m; +} + diff --git a/source4/ntvfs/posix/vfs_posix.c b/source4/ntvfs/posix/vfs_posix.c new file mode 100644 index 0000000..8375def --- /dev/null +++ b/source4/ntvfs/posix/vfs_posix.c @@ -0,0 +1,426 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend + + Copyright (C) Andrew Tridgell 2004 + + 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/>. +*/ +/* + this implements most of the POSIX NTVFS backend + This is the default backend +*/ + +#include "includes.h" +#include "vfs_posix.h" +#include "librpc/gen_ndr/security.h" +#include <tdb.h> +#include "lib/tdb_wrap/tdb_wrap.h" +#include "libcli/security/security.h" +#include "lib/events/events.h" +#include "param/param.h" +#include "lib/util/idtree.h" + +/* + setup config options for a posix share +*/ +static void pvfs_setup_options(struct pvfs_state *pvfs) +{ + struct share_config *scfg = pvfs->ntvfs->ctx->config; + char *eadb; + char *xattr_backend; + bool def_perm_override = false; + + if (share_bool_option(scfg, SHARE_MAP_HIDDEN, SHARE_MAP_HIDDEN_DEFAULT)) + pvfs->flags |= PVFS_FLAG_MAP_HIDDEN; + if (share_bool_option(scfg, SHARE_MAP_ARCHIVE, SHARE_MAP_ARCHIVE_DEFAULT)) + pvfs->flags |= PVFS_FLAG_MAP_ARCHIVE; + if (share_bool_option(scfg, SHARE_MAP_SYSTEM, SHARE_MAP_SYSTEM_DEFAULT)) + pvfs->flags |= PVFS_FLAG_MAP_SYSTEM; + if (share_bool_option(scfg, SHARE_READONLY, SHARE_READONLY_DEFAULT)) + pvfs->flags |= PVFS_FLAG_READONLY; + if (share_bool_option(scfg, SHARE_STRICT_SYNC, SHARE_STRICT_SYNC_DEFAULT)) + pvfs->flags |= PVFS_FLAG_STRICT_SYNC; + if (share_bool_option(scfg, SHARE_STRICT_LOCKING, SHARE_STRICT_LOCKING_DEFAULT)) + pvfs->flags |= PVFS_FLAG_STRICT_LOCKING; + if (share_bool_option(scfg, SHARE_CI_FILESYSTEM, SHARE_CI_FILESYSTEM_DEFAULT)) + pvfs->flags |= PVFS_FLAG_CI_FILESYSTEM; + if (share_bool_option(scfg, PVFS_FAKE_OPLOCKS, PVFS_FAKE_OPLOCKS_DEFAULT)) + pvfs->flags |= PVFS_FLAG_FAKE_OPLOCKS; + +#if defined(O_DIRECTORY) && defined(O_NOFOLLOW) + /* set PVFS_PERM_OVERRIDE by default only if the system + * supports the necessary capabilities to make it secure + */ + def_perm_override = true; +#endif + if (share_bool_option(scfg, PVFS_PERM_OVERRIDE, def_perm_override)) + pvfs->flags |= PVFS_FLAG_PERM_OVERRIDE; + + /* file perm options */ + pvfs->options.create_mask = share_int_option(scfg, + SHARE_CREATE_MASK, + SHARE_CREATE_MASK_DEFAULT); + pvfs->options.dir_mask = share_int_option(scfg, + SHARE_DIR_MASK, + SHARE_DIR_MASK_DEFAULT); + pvfs->options.force_dir_mode = share_int_option(scfg, + SHARE_FORCE_DIR_MODE, + SHARE_FORCE_DIR_MODE_DEFAULT); + pvfs->options.force_create_mode = share_int_option(scfg, + SHARE_FORCE_CREATE_MODE, + SHARE_FORCE_CREATE_MODE_DEFAULT); + /* this must be a power of 2 */ + pvfs->alloc_size_rounding = share_int_option(scfg, + PVFS_ALLOCATION_ROUNDING, + PVFS_ALLOCATION_ROUNDING_DEFAULT); + + pvfs->search.inactivity_time = share_int_option(scfg, + PVFS_SEARCH_INACTIVITY, + PVFS_SEARCH_INACTIVITY_DEFAULT); + +#ifdef HAVE_XATTR_SUPPORT + if (share_bool_option(scfg, PVFS_XATTR, PVFS_XATTR_DEFAULT)) + pvfs->flags |= PVFS_FLAG_XATTR_ENABLE; +#endif + + pvfs->sharing_violation_delay = share_int_option(scfg, + PVFS_SHARE_DELAY, + PVFS_SHARE_DELAY_DEFAULT); + + pvfs->oplock_break_timeout = share_int_option(scfg, + PVFS_OPLOCK_TIMEOUT, + PVFS_OPLOCK_TIMEOUT_DEFAULT); + + pvfs->writetime_delay = share_int_option(scfg, + PVFS_WRITETIME_DELAY, + PVFS_WRITETIME_DELAY_DEFAULT); + + pvfs->share_name = talloc_strdup(pvfs, scfg->name); + + pvfs->fs_attribs = + FS_ATTR_CASE_SENSITIVE_SEARCH | + FS_ATTR_CASE_PRESERVED_NAMES | + FS_ATTR_UNICODE_ON_DISK; + + /* allow xattrs to be stored in a external tdb */ + eadb = share_string_option(pvfs, scfg, PVFS_EADB, NULL); + if (eadb != NULL) { + pvfs->ea_db = tdb_wrap_open( + pvfs, eadb, 50000, + lpcfg_tdb_flags(pvfs->ntvfs->ctx->lp_ctx, TDB_DEFAULT), + O_RDWR|O_CREAT, 0600); + if (pvfs->ea_db != NULL) { + pvfs->flags |= PVFS_FLAG_XATTR_ENABLE; + } else { + DEBUG(0,("Failed to open eadb '%s' - %s\n", + eadb, strerror(errno))); + pvfs->flags &= ~PVFS_FLAG_XATTR_ENABLE; + } + TALLOC_FREE(eadb); + } + + if (pvfs->flags & PVFS_FLAG_XATTR_ENABLE) { + pvfs->fs_attribs |= FS_ATTR_NAMED_STREAMS; + } + if (pvfs->flags & PVFS_FLAG_XATTR_ENABLE) { + pvfs->fs_attribs |= FS_ATTR_PERSISTANT_ACLS; + } + + pvfs->sid_cache.creator_owner = dom_sid_parse_talloc(pvfs, SID_CREATOR_OWNER); + pvfs->sid_cache.creator_group = dom_sid_parse_talloc(pvfs, SID_CREATOR_GROUP); + + /* check if the system really supports xattrs */ + if (pvfs->flags & PVFS_FLAG_XATTR_ENABLE) { + pvfs_xattr_probe(pvfs); + } + + /* enable an ACL backend */ + xattr_backend = share_string_option(pvfs, scfg, PVFS_ACL, "xattr"); + pvfs->acl_ops = pvfs_acl_backend_byname(xattr_backend); + TALLOC_FREE(xattr_backend); +} + +static int pvfs_state_destructor(struct pvfs_state *pvfs) +{ + struct pvfs_file *f, *fn; + struct pvfs_search_state *s, *sn; + + /* + * make sure we cleanup files and searches before anything else + * because there destructors need to access the pvfs_state struct + */ + for (f=pvfs->files.list; f; f=fn) { + fn = f->next; + talloc_free(f); + } + + for (s=pvfs->search.list; s; s=sn) { + sn = s->next; + talloc_free(s); + } + + return 0; +} + +/* + connect to a share - used when a tree_connect operation comes + in. For a disk based backend we needs to ensure that the base + directory exists (tho it doesn't need to be accessible by the user, + that comes later) +*/ +static NTSTATUS pvfs_connect(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_tcon* tcon) +{ + struct pvfs_state *pvfs; + struct stat st; + char *base_directory; + NTSTATUS status; + 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: + sharename = tcon->smb2.in.path; + break; + default: + return NT_STATUS_INVALID_LEVEL; + } + + if (strncmp(sharename, "\\\\", 2) == 0) { + char *p = strchr(sharename+2, '\\'); + if (p) { + sharename = p + 1; + } + } + + /* + * TODO: call this from ntvfs_posix_init() + * but currently we don't have a lp_ctx there + */ + status = pvfs_acl_init(); + NT_STATUS_NOT_OK_RETURN(status); + + pvfs = talloc_zero(ntvfs, struct pvfs_state); + NT_STATUS_HAVE_NO_MEMORY(pvfs); + + /* for simplicity of path construction, remove any trailing slash now */ + base_directory = share_string_option(pvfs, ntvfs->ctx->config, SHARE_PATH, ""); + NT_STATUS_HAVE_NO_MEMORY(base_directory); + if (strcmp(base_directory, "/") != 0) { + trim_string(base_directory, NULL, "/"); + } + + pvfs->ntvfs = ntvfs; + pvfs->base_directory = base_directory; + + /* the directory must exist. Note that we deliberately don't + check that it is readable */ + if (stat(pvfs->base_directory, &st) != 0 || !S_ISDIR(st.st_mode)) { + DEBUG(0,("pvfs_connect: '%s' is not a directory, when connecting to [%s]\n", + pvfs->base_directory, sharename)); + return NT_STATUS_BAD_NETWORK_NAME; + } + + ntvfs->ctx->fs_type = talloc_strdup(ntvfs->ctx, "NTFS"); + NT_STATUS_HAVE_NO_MEMORY(ntvfs->ctx->fs_type); + + ntvfs->ctx->dev_type = talloc_strdup(ntvfs->ctx, "A:"); + NT_STATUS_HAVE_NO_MEMORY(ntvfs->ctx->dev_type); + + 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; + } + + ntvfs->private_data = pvfs; + + pvfs->brl_context = brlock_init(pvfs, + pvfs->ntvfs->ctx->server_id, + pvfs->ntvfs->ctx->lp_ctx, + pvfs->ntvfs->ctx->msg_ctx); + if (pvfs->brl_context == NULL) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + pvfs->odb_context = odb_init(pvfs, pvfs->ntvfs->ctx); + if (pvfs->odb_context == NULL) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + /* allow this to be NULL - we just disable change notify */ + pvfs->notify_context = notify_init(pvfs, + pvfs->ntvfs->ctx->server_id, + pvfs->ntvfs->ctx->msg_ctx, + pvfs->ntvfs->ctx->lp_ctx, + pvfs->ntvfs->ctx->event_ctx, + pvfs->ntvfs->ctx->config); + + /* allocate the search handle -> ptr tree */ + pvfs->search.idtree = idr_init(pvfs); + NT_STATUS_HAVE_NO_MEMORY(pvfs->search.idtree); + + status = pvfs_mangle_init(pvfs); + NT_STATUS_NOT_OK_RETURN(status); + + pvfs_setup_options(pvfs); + + talloc_set_destructor(pvfs, pvfs_state_destructor); + +#ifdef SIGXFSZ + /* who had the stupid idea to generate a signal on a large + file write instead of just failing it!? */ + BlockSignals(true, SIGXFSZ); +#endif + + return NT_STATUS_OK; +} + +/* + disconnect from a share +*/ +static NTSTATUS pvfs_disconnect(struct ntvfs_module_context *ntvfs) +{ + return NT_STATUS_OK; +} + +/* + check if a directory exists +*/ +static NTSTATUS pvfs_chkpath(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_chkpath *cp) +{ + struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, + struct pvfs_state); + struct pvfs_filename *name; + NTSTATUS status; + + /* resolve the cifs name to a posix name */ + status = pvfs_resolve_name(pvfs, req, cp->chkpath.in.path, 0, &name); + NT_STATUS_NOT_OK_RETURN(status); + + if (!name->exists) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + if (!S_ISDIR(name->st.st_mode)) { + return NT_STATUS_NOT_A_DIRECTORY; + } + + return NT_STATUS_OK; +} + +/* + copy a set of files +*/ +static NTSTATUS pvfs_copy(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, struct smb_copy *cp) +{ + DEBUG(0,("pvfs_copy not implemented\n")); + return NT_STATUS_NOT_SUPPORTED; +} + +/* + return print queue info +*/ +static NTSTATUS pvfs_lpq(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_lpq *lpq) +{ + return NT_STATUS_NOT_SUPPORTED; +} + +/* SMBtrans - not used on file shares */ +static NTSTATUS pvfs_trans(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, struct smb_trans2 *trans2) +{ + return NT_STATUS_ACCESS_DENIED; +} + +/* + initialise the POSIX disk backend, registering ourselves with the ntvfs subsystem + */ +NTSTATUS ntvfs_posix_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret; + struct ntvfs_ops ops; + NTVFS_CURRENT_CRITICAL_SIZES(vers); + + ZERO_STRUCT(ops); + + ops.type = NTVFS_DISK; + + /* fill in all the operations */ + ops.connect_fn = pvfs_connect; + ops.disconnect_fn = pvfs_disconnect; + ops.unlink_fn = pvfs_unlink; + ops.chkpath_fn = pvfs_chkpath; + ops.qpathinfo_fn = pvfs_qpathinfo; + ops.setpathinfo_fn = pvfs_setpathinfo; + ops.open_fn = pvfs_open; + ops.mkdir_fn = pvfs_mkdir; + ops.rmdir_fn = pvfs_rmdir; + ops.rename_fn = pvfs_rename; + ops.copy_fn = pvfs_copy; + ops.ioctl_fn = pvfs_ioctl; + ops.read_fn = pvfs_read; + ops.write_fn = pvfs_write; + ops.seek_fn = pvfs_seek; + ops.flush_fn = pvfs_flush; + ops.close_fn = pvfs_close; + ops.exit_fn = pvfs_exit; + ops.lock_fn = pvfs_lock; + ops.setfileinfo_fn = pvfs_setfileinfo; + ops.qfileinfo_fn = pvfs_qfileinfo; + ops.fsinfo_fn = pvfs_fsinfo; + ops.lpq_fn = pvfs_lpq; + ops.search_first_fn = pvfs_search_first; + ops.search_next_fn = pvfs_search_next; + ops.search_close_fn = pvfs_search_close; + ops.trans_fn = pvfs_trans; + ops.logoff_fn = pvfs_logoff; + ops.async_setup_fn = pvfs_async_setup; + ops.cancel_fn = pvfs_cancel; + ops.notify_fn = pvfs_notify; + + /* register ourselves with the NTVFS subsystem. We register + under the name 'default' as we wish to be the default + backend, and also register as 'posix' */ + ops.name = "default"; + ret = ntvfs_register(&ops, &vers); + + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register POSIX backend as '%s'!\n", ops.name)); + } + + ops.name = "posix"; + ret = ntvfs_register(&ops, &vers); + + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register POSIX backend as '%s'!\n", ops.name)); + } + + if (NT_STATUS_IS_OK(ret)) { + ret = ntvfs_common_init(); + } + + return ret; +} diff --git a/source4/ntvfs/posix/vfs_posix.h b/source4/ntvfs/posix/vfs_posix.h new file mode 100644 index 0000000..3dbd785 --- /dev/null +++ b/source4/ntvfs/posix/vfs_posix.h @@ -0,0 +1,290 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - structure definitions + + Copyright (C) Andrew Tridgell 2004 + + 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/>. +*/ + +#ifndef _VFS_POSIX_H_ +#define _VFS_POSIX_H_ + +#include "librpc/gen_ndr/xattr.h" +#include "system/filesys.h" +#include "ntvfs/ntvfs.h" +#include "ntvfs/common/ntvfs_common.h" +#include "libcli/wbclient/wbclient.h" +#include "lib/events/events.h" + +struct pvfs_wait; +struct pvfs_oplock; + +/* this is the private structure for the posix vfs backend. It is used + to hold per-connection (per tree connect) state information */ +struct pvfs_state { + struct ntvfs_module_context *ntvfs; + const char *base_directory; + struct GUID *base_fs_uuid; + + const char *share_name; + unsigned int flags; + + struct pvfs_mangle_context *mangle_ctx; + + struct brl_context *brl_context; + struct odb_context *odb_context; + struct notify_context *notify_context; + + /* a list of pending async requests. Needed to support + ntcancel */ + struct pvfs_wait *wait_list; + + /* the sharing violation timeout (nsecs) */ + unsigned int sharing_violation_delay; + + /* the oplock break timeout (secs) */ + unsigned int oplock_break_timeout; + + /* the write time update delay (nsecs) */ + unsigned int writetime_delay; + + /* filesystem attributes (see FS_ATTR_*) */ + uint32_t fs_attribs; + + /* if posix:eadb is set, then this gets setup */ + struct tdb_wrap *ea_db; + + /* the allocation size rounding */ + uint32_t alloc_size_rounding; + + struct { + /* the open files as DLINKLIST */ + struct pvfs_file *list; + } files; + + struct { + /* an id tree mapping open search ID to a pvfs_search_state structure */ + struct idr_context *idtree; + + /* the open searches as DLINKLIST */ + struct pvfs_search_state *list; + + /* how long to keep inactive searches around for */ + unsigned int inactivity_time; + } search; + + /* used to accelerate acl mapping */ + struct { + const struct dom_sid *creator_owner; + const struct dom_sid *creator_group; + } sid_cache; + + /* the acl backend */ + const struct pvfs_acl_ops *acl_ops; + + /* non-flag share options */ + struct { + mode_t dir_mask; + mode_t force_dir_mode; + mode_t create_mask; + mode_t force_create_mode; + } options; +}; + +/* this is the basic information needed about a file from the filesystem */ +struct pvfs_dos_fileinfo { + NTTIME create_time; + NTTIME access_time; + NTTIME write_time; + NTTIME change_time; + uint32_t attrib; + uint64_t alloc_size; + uint32_t nlink; + uint32_t ea_size; + uint64_t file_id; + uint32_t flags; +}; + +/* + this is the structure returned by pvfs_resolve_name(). It holds the posix details of + a filename passed by the client to any function +*/ +struct pvfs_filename { + char *original_name; + char *full_name; + char *stream_name; /* does not include :$DATA suffix */ + uint32_t stream_id; /* this uses a hash, so is probabilistic */ + bool has_wildcard; + bool exists; /* true if the base filename exists */ + bool stream_exists; /* true if the stream exists */ + bool allow_override; + struct stat st; + struct pvfs_dos_fileinfo dos; +}; + + +/* open file handle state - encapsulates the posix fd + + Note that this is separated from the pvfs_file structure in order + to cope with the openx DENY_DOS semantics where a 2nd DENY_DOS open + on the same connection gets the same low level filesystem handle, + rather than a new handle +*/ +struct pvfs_file_handle { + int fd; + + struct pvfs_filename *name; + + /* a unique file key to be used for open file locking */ + DATA_BLOB odb_locking_key; + + uint32_t create_options; + + /* this is set by the mode_information level. What does it do? */ + uint32_t mode; + + /* yes, we need 2 independent positions ... */ + uint64_t seek_offset; + uint64_t position; + + bool have_opendb_entry; + + /* + * we need to wait for oplock break requests from other processes, + * and we need to remember the pvfs_file so we can correctly + * forward the oplock break to the client + */ + struct pvfs_oplock *oplock; + + /* we need this hook back to our parent for lock destruction */ + struct pvfs_state *pvfs; + + struct { + bool update_triggered; + struct tevent_timer *update_event; + bool update_on_close; + NTTIME close_time; + bool update_forced; + } write_time; + + /* the open went through to completion */ + bool open_completed; + + uint8_t private_flags; +}; + +/* open file state */ +struct pvfs_file { + struct pvfs_file *next, *prev; + struct pvfs_file_handle *handle; + struct ntvfs_handle *ntvfs; + + struct pvfs_state *pvfs; + + uint32_t impersonation; + uint32_t share_access; + uint32_t access_mask; + + /* a list of pending locks - used for locking cancel operations */ + struct pvfs_pending_lock *pending_list; + + /* a file handle to be used for byte range locking */ + struct brl_handle *brl_handle; + + /* a count of active locks - used to avoid calling brlock_close on + file close */ + uint64_t lock_count; + + /* for directories, a buffer of pending notify events */ + struct pvfs_notify_buffer *notify_buffer; + + /* for directories, the state of an incomplete SMB2 Find */ + struct pvfs_search_state *search; +}; + +/* the state of a search started with pvfs_search_first() */ +struct pvfs_search_state { + struct pvfs_search_state *prev, *next; + struct pvfs_state *pvfs; + uint16_t handle; + off_t current_index; + uint16_t search_attrib; + uint16_t must_attrib; + struct pvfs_dir *dir; + time_t last_used; /* monotonic clock time */ + unsigned int num_ea_names; + struct ea_name *ea_names; + struct tevent_timer *te; +}; + +/* flags to pvfs_resolve_name() */ +#define PVFS_RESOLVE_WILDCARD (1<<0) +#define PVFS_RESOLVE_STREAMS (1<<1) +#define PVFS_RESOLVE_NO_OPENDB (1<<2) + +/* flags in pvfs->flags */ +#define PVFS_FLAG_CI_FILESYSTEM (1<<0) /* the filesystem is case insensitive */ +#define PVFS_FLAG_MAP_ARCHIVE (1<<1) +#define PVFS_FLAG_MAP_SYSTEM (1<<2) +#define PVFS_FLAG_MAP_HIDDEN (1<<3) +#define PVFS_FLAG_READONLY (1<<4) +#define PVFS_FLAG_STRICT_SYNC (1<<5) +#define PVFS_FLAG_STRICT_LOCKING (1<<6) +#define PVFS_FLAG_XATTR_ENABLE (1<<7) +#define PVFS_FLAG_FAKE_OPLOCKS (1<<8) +#define PVFS_FLAG_PERM_OVERRIDE (1<<10) + +/* forward declare some anonymous structures */ +struct pvfs_dir; + +/* types of notification for pvfs wait events */ +enum pvfs_wait_notice {PVFS_WAIT_EVENT, PVFS_WAIT_TIMEOUT, PVFS_WAIT_CANCEL}; + +/* + state of a pending retry +*/ +struct pvfs_odb_retry; + +#define PVFS_EADB "posix:eadb" +#define PVFS_XATTR "posix:xattr" +#define PVFS_FAKE_OPLOCKS "posix:fakeoplocks" +#define PVFS_SHARE_DELAY "posix:sharedelay" +#define PVFS_OPLOCK_TIMEOUT "posix:oplocktimeout" +#define PVFS_WRITETIME_DELAY "posix:writetimeupdatedelay" +#define PVFS_ALLOCATION_ROUNDING "posix:allocationrounding" +#define PVFS_SEARCH_INACTIVITY "posix:searchinactivity" +#define PVFS_ACL "posix:acl" +#define PVFS_PERM_OVERRIDE "posix:permission override" + +#define PVFS_XATTR_DEFAULT true +#define PVFS_FAKE_OPLOCKS_DEFAULT false +#define PVFS_SHARE_DELAY_DEFAULT 1000000 /* nsecs */ +#define PVFS_OPLOCK_TIMEOUT_DEFAULT 30 /* secs */ +#define PVFS_WRITETIME_DELAY_DEFAULT 2000000 /* nsecs */ +#define PVFS_ALLOCATION_ROUNDING_DEFAULT 512 +#define PVFS_SEARCH_INACTIVITY_DEFAULT 300 + +struct pvfs_acl_ops { + const char *name; + NTSTATUS (*acl_load)(struct pvfs_state *, struct pvfs_filename *, int , TALLOC_CTX *, + struct security_descriptor **); + NTSTATUS (*acl_save)(struct pvfs_state *, struct pvfs_filename *, int , struct security_descriptor *); +}; + +#include "ntvfs/posix/vfs_posix_proto.h" +#include "ntvfs/posix/vfs_acl_proto.h" + +#endif /* _VFS_POSIX_H_ */ diff --git a/source4/ntvfs/posix/wscript_build b/source4/ntvfs/posix/wscript_build new file mode 100644 index 0000000..649dea6 --- /dev/null +++ b/source4/ntvfs/posix/wscript_build @@ -0,0 +1,61 @@ +#!/usr/bin/env python + +if bld.CONFIG_SET('WITH_NTVFS_FILESERVER'): + bld.SAMBA_SUBSYSTEM('pvfs_acl', + source='pvfs_acl.c', + autoproto='vfs_acl_proto.h', + deps='events samba-modules', + ) + + + bld.SAMBA_MODULE('pvfs_acl_xattr', + source='pvfs_acl_xattr.c', + subsystem='pvfs_acl', + init_function='pvfs_acl_xattr_init', + deps='NDR_XATTR events' + ) + + + bld.SAMBA_MODULE('pvfs_acl_nfs4', + source='pvfs_acl_nfs4.c', + subsystem='pvfs_acl', + init_function='pvfs_acl_nfs4_init', + deps='NDR_NFS4ACL samdb events' + ) + + + bld.SAMBA_MODULE('ntvfs_posix', + source='vfs_posix.c pvfs_util.c pvfs_search.c pvfs_dirlist.c pvfs_fileinfo.c pvfs_unlink.c pvfs_mkdir.c pvfs_open.c pvfs_read.c pvfs_flush.c pvfs_write.c pvfs_fsinfo.c pvfs_qfileinfo.c pvfs_setfileinfo.c pvfs_rename.c pvfs_resolve.c pvfs_shortname.c pvfs_lock.c pvfs_oplock.c pvfs_wait.c pvfs_seek.c pvfs_ioctl.c pvfs_xattr.c pvfs_streams.c pvfs_notify.c pvfs_sys.c xattr_system.c', + autoproto='vfs_posix_proto.h', + subsystem='ntvfs', + init_function='ntvfs_posix_init', + deps='NDR_XATTR attr ntvfs_common MESSAGING LIBWBCLIENT_OLD pvfs_acl posix_eadb', + internal_module=True + ) + + +bld.SAMBA_LIBRARY('posix_eadb', + source='posix_eadb.c', + deps='tdb tdb-wrap samba-util', + autoproto='posix_eadb_proto.h', + private_library=True) + +pyparam_util = bld.pyembed_libname('pyparam_util') + +bld.SAMBA_PYTHON('python_xattr_native', + source='python/pyxattr_native.c', + deps='ndr ldb samdb samba-credentials %s attr' % pyparam_util, + realname='samba/xattr_native.so' + ) + +bld.SAMBA_PYTHON('python_posix_eadb', + source='python/pyposix_eadb.c', + deps='%s posix_eadb tdb' % pyparam_util, + realname='samba/posix_eadb.so' + ) + +bld.SAMBA_PYTHON('python_xattr_tdb', + source='python/pyxattr_tdb.c', + deps='%s xattr_tdb' % pyparam_util, + realname='samba/xattr_tdb.so' + ) diff --git a/source4/ntvfs/posix/xattr_system.c b/source4/ntvfs/posix/xattr_system.c new file mode 100644 index 0000000..ebb2010 --- /dev/null +++ b/source4/ntvfs/posix/xattr_system.c @@ -0,0 +1,145 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - xattr support using filesystem xattrs + + Copyright (C) Andrew Tridgell 2004 + + 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 "vfs_posix.h" + +/* + pull a xattr as a blob, from either a file or a file descriptor +*/ +NTSTATUS pull_xattr_blob_system(struct pvfs_state *pvfs, + TALLOC_CTX *mem_ctx, + const char *attr_name, + const char *fname, + int fd, + size_t estimated_size, + DATA_BLOB *blob) +{ + int ret; + + *blob = data_blob_talloc(mem_ctx, NULL, estimated_size+16); + if (blob->data == NULL) { + return NT_STATUS_NO_MEMORY; + } + +again: + if (fd != -1) { + ret = fgetxattr(fd, attr_name, blob->data, estimated_size); + } else { + ret = getxattr(fname, attr_name, blob->data, estimated_size); + } + if (ret == -1 && errno == ERANGE) { + estimated_size *= 2; + blob->data = talloc_realloc(mem_ctx, blob->data, + uint8_t, estimated_size); + if (blob->data == NULL) { + return NT_STATUS_NO_MEMORY; + } + blob->length = estimated_size; + goto again; + } + if (ret == -1 && errno == EPERM) { + struct stat statbuf; + + if (fd != -1) { + ret = fstat(fd, &statbuf); + } else { + ret = stat(fname, &statbuf); + } + if (ret == 0) { + /* check if this is a directory and the sticky bit is set */ + if (S_ISDIR(statbuf.st_mode) && (statbuf.st_mode & S_ISVTX)) { + /* pretend we could not find the xattr */ + + data_blob_free(blob); + return NT_STATUS_NOT_FOUND; + + } else { + /* if not this was probably a legitimate error + * reset ret and errno to the correct values */ + errno = EPERM; + ret = -1; + } + } + } + + if (ret == -1) { + data_blob_free(blob); + return pvfs_map_errno(pvfs, errno); + } + + blob->length = ret; + + return NT_STATUS_OK; +} + +/* + push a xattr as a blob, from either a file or a file descriptor +*/ +NTSTATUS push_xattr_blob_system(struct pvfs_state *pvfs, + const char *attr_name, + const char *fname, + int fd, + const DATA_BLOB *blob) +{ + int ret; + + if (fd != -1) { + ret = fsetxattr(fd, attr_name, blob->data, blob->length, 0); + } else { + ret = setxattr(fname, attr_name, blob->data, blob->length, 0); + } + if (ret == -1) { + return pvfs_map_errno(pvfs, errno); + } + + return NT_STATUS_OK; +} + + +/* + delete a xattr +*/ +NTSTATUS delete_xattr_system(struct pvfs_state *pvfs, const char *attr_name, + const char *fname, int fd) +{ + int ret; + + if (fd != -1) { + ret = fremovexattr(fd, attr_name); + } else { + ret = removexattr(fname, attr_name); + } + if (ret == -1) { + return pvfs_map_errno(pvfs, errno); + } + + return NT_STATUS_OK; +} + +/* + unlink a file - cleanup any xattrs +*/ +NTSTATUS unlink_xattr_system(struct pvfs_state *pvfs, const char *fname) +{ + /* nothing needs to be done for filesystem based xattrs */ + return NT_STATUS_OK; +} diff --git a/source4/ntvfs/simple/README b/source4/ntvfs/simple/README new file mode 100644 index 0000000..f1de5d9 --- /dev/null +++ b/source4/ntvfs/simple/README @@ -0,0 +1,10 @@ +This module (ntvfs 'simple') provides a very, very simple posix backend. + +WARNING: All file access is done as user 'root'!!! + Only use this module for testing, with only test data!!! + +For activating this module use: + +[testshare] + path = /tmp/testshare + nfvfs handler = simple diff --git a/source4/ntvfs/simple/svfs.h b/source4/ntvfs/simple/svfs.h new file mode 100644 index 0000000..e5ad3b9 --- /dev/null +++ b/source4/ntvfs/simple/svfs.h @@ -0,0 +1,38 @@ + +struct svfs_private { + struct ntvfs_module_context *ntvfs; + + /* the base directory */ + char *connectpath; + + /* a linked list of open searches */ + struct search_state *search; + + /* next available search handle */ + uint16_t next_search_handle; + + struct svfs_file *open_files; +}; + +struct svfs_dir { + unsigned int count; + char *unix_dir; + struct svfs_dirfile { + char *name; + struct stat st; + } *files; +}; + +struct svfs_file { + struct svfs_file *next, *prev; + int fd; + struct ntvfs_handle *handle; + char *name; +}; + +struct search_state { + struct search_state *next, *prev; + uint16_t handle; + unsigned int current_index; + struct svfs_dir *dir; +}; diff --git a/source4/ntvfs/simple/svfs_util.c b/source4/ntvfs/simple/svfs_util.c new file mode 100644 index 0000000..fd32311 --- /dev/null +++ b/source4/ntvfs/simple/svfs_util.c @@ -0,0 +1,189 @@ +/* + Unix SMB/CIFS implementation. + + simple NTVFS filesystem backend + + Copyright (C) Andrew Tridgell 2003 + + 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/>. +*/ +/* + utility functions for simple backend +*/ + +#include "includes.h" +#include "system/filesys.h" +#include "svfs.h" +#include "system/time.h" +#include "system/dir.h" +#include "ntvfs/ntvfs.h" +#include "ntvfs/simple/proto.h" + +/* + convert a windows path to a unix path - don't do any mangling or case sensitive handling +*/ +char *svfs_unix_path(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, const char *name) +{ + struct svfs_private *p = ntvfs->private_data; + char *ret; + char *name_lower = strlower_talloc(p, name); + + if (*name != '\\') { + ret = talloc_asprintf(req, "%s/%s", p->connectpath, name_lower); + } else { + ret = talloc_asprintf(req, "%s%s", p->connectpath, name_lower); + } + all_string_sub(ret, "\\", "/", 0); + talloc_free(name_lower); + return ret; +} + + +/* + read a directory and find all matching file names and stat info + returned names are separate unix and DOS names. The returned names + are relative to the directory +*/ +struct svfs_dir *svfs_list_unix(TALLOC_CTX *mem_ctx, struct ntvfs_request *req, const char *unix_path) +{ + char *p, *mask; + struct svfs_dir *dir; + DIR *odir; + struct dirent *dent; + unsigned int allocated = 0; + char *low_mask; + + dir = talloc(mem_ctx, struct svfs_dir); + if (!dir) { return NULL; } + + dir->count = 0; + dir->files = 0; + + /* find the base directory */ + p = strrchr(unix_path, '/'); + if (!p) { return NULL; } + + dir->unix_dir = talloc_strndup(mem_ctx, unix_path, PTR_DIFF(p, unix_path)); + if (!dir->unix_dir) { return NULL; } + + /* the wildcard pattern is the last part */ + mask = p+1; + + low_mask = strlower_talloc(mem_ctx, mask); + if (!low_mask) { return NULL; } + + odir = opendir(dir->unix_dir); + if (!odir) { return NULL; } + + while ((dent = readdir(odir))) { + unsigned int i = dir->count; + char *full_name; + char *low_name; + + if (strchr(dent->d_name, ':') && !strchr(unix_path, ':')) { + /* don't show streams in dir listing */ + continue; + } + + low_name = strlower_talloc(mem_ctx, dent->d_name); + if (!low_name) { continue; } + + /* check it matches the wildcard pattern */ + if (ms_fnmatch_protocol(low_mask, low_name, PROTOCOL_NT1, + false) != 0) { + continue; + } + + if (dir->count >= allocated) { + allocated = (allocated + 100) * 1.2; + dir->files = talloc_realloc(dir, dir->files, struct svfs_dirfile, allocated); + if (!dir->files) { + closedir(odir); + return NULL; + } + } + + dir->files[i].name = low_name; + if (!dir->files[i].name) { continue; } + + full_name = talloc_asprintf(mem_ctx, "%s/%s", dir->unix_dir, + dir->files[i].name); + if (!full_name) { continue; } + + if (stat(full_name, &dir->files[i].st) == 0) { + dir->count++; + } + + talloc_free(full_name); + } + + closedir(odir); + + return dir; +} + +/* + read a directory and find all matching file names and stat info + returned names are separate unix and DOS names. The returned names + are relative to the directory +*/ +struct svfs_dir *svfs_list(struct ntvfs_module_context *ntvfs, struct ntvfs_request *req, const char *pattern) +{ + struct svfs_private *p = ntvfs->private_data; + char *unix_path; + + unix_path = svfs_unix_path(ntvfs, req, pattern); + if (!unix_path) { return NULL; } + + return svfs_list_unix(p, req, unix_path); +} + + +/******************************************************************* +set the time on a file via file descriptor +*******************************************************************/ +int svfs_file_utime(int fd, struct utimbuf *times) +{ + char *fd_path = NULL; + int ret; + + ret = asprintf(&fd_path, "/proc/self/%d", fd); + if (ret == -1) { + errno = ENOMEM; + return -1; + } + + if (!fd_path) { + errno = ENOMEM; + return -1; + } + + ret = utime(fd_path, times); + free(fd_path); + return ret; +} + + +/* + map a unix file attrib to a DOS attribute +*/ +uint16_t svfs_unix_to_dos_attrib(mode_t mode) +{ + uint16_t ret = 0; + if (S_ISDIR(mode)) ret |= FILE_ATTRIBUTE_DIRECTORY; + if (!(mode & S_IWUSR)) ret |= FILE_ATTRIBUTE_READONLY; + return ret; +} + diff --git a/source4/ntvfs/simple/vfs_simple.c b/source4/ntvfs/simple/vfs_simple.c new file mode 100644 index 0000000..000da41 --- /dev/null +++ b/source4/ntvfs/simple/vfs_simple.c @@ -0,0 +1,1112 @@ +/* + Unix SMB/CIFS implementation. + + simple NTVFS filesystem backend + + Copyright (C) Andrew Tridgell 2003 + + 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/>. +*/ +/* + this implements a very simple NTVFS filesystem backend. + + this backend largely ignores the POSIX -> CIFS mappings, just doing absolutely + minimal work to give a working backend. +*/ + +#include "includes.h" +#include "system/dir.h" +#include "system/filesys.h" +#include "svfs.h" +#include "system/time.h" +#include "../lib/util/dlinklist.h" +#include "ntvfs/ntvfs.h" +#include "ntvfs/simple/proto.h" + +#ifndef O_DIRECTORY +#define O_DIRECTORY 0 +#endif + +#define CHECK_READ_ONLY(req) do { if (share_bool_option(ntvfs->ctx->config, SHARE_READONLY, true)) return NT_STATUS_ACCESS_DENIED; } while (0) + +/* + connect to a share - used when a tree_connect operation comes + in. For a disk based backend we needs to ensure that the base + directory exists (tho it doesn't need to be accessible by the user, + that comes later) +*/ +static NTSTATUS svfs_connect(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_tcon* tcon) +{ + struct stat st; + struct svfs_private *p; + struct share_config *scfg = ntvfs->ctx->config; + 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: + sharename = tcon->smb2.in.path; + break; + default: + return NT_STATUS_INVALID_LEVEL; + } + + if (strncmp(sharename, "\\\\", 2) == 0) { + char *p2 = strchr(sharename+2, '\\'); + if (p2) { + sharename = p2 + 1; + } + } + + p = talloc(ntvfs, struct svfs_private); + NT_STATUS_HAVE_NO_MEMORY(p); + p->ntvfs = ntvfs; + p->next_search_handle = 0; + p->connectpath = share_string_option(p, scfg, SHARE_PATH, ""); + p->open_files = NULL; + p->search = NULL; + + /* the directory must exist */ + if (stat(p->connectpath, &st) != 0 || !S_ISDIR(st.st_mode)) { + DEBUG(0,("'%s' is not a directory, when connecting to [%s]\n", + p->connectpath, sharename)); + return NT_STATUS_BAD_NETWORK_NAME; + } + + ntvfs->ctx->fs_type = talloc_strdup(ntvfs->ctx, "NTFS"); + NT_STATUS_HAVE_NO_MEMORY(ntvfs->ctx->fs_type); + ntvfs->ctx->dev_type = talloc_strdup(ntvfs->ctx, "A:"); + NT_STATUS_HAVE_NO_MEMORY(ntvfs->ctx->dev_type); + + 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; + } + + ntvfs->private_data = p; + + return NT_STATUS_OK; +} + +/* + disconnect from a share +*/ +static NTSTATUS svfs_disconnect(struct ntvfs_module_context *ntvfs) +{ + return NT_STATUS_OK; +} + +/* + find open file handle given fd +*/ +static struct svfs_file *find_fd(struct svfs_private *sp, struct ntvfs_handle *handle) +{ + struct svfs_file *f; + void *p; + + p = ntvfs_handle_get_backend_data(handle, sp->ntvfs); + if (!p) return NULL; + + f = talloc_get_type(p, struct svfs_file); + if (!f) return NULL; + + return f; +} + +/* + delete a file - the dirtype specifies the file types to include in the search. + The name can contain CIFS wildcards, but rarely does (except with OS/2 clients) +*/ +static NTSTATUS svfs_unlink(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_unlink *unl) +{ + char *unix_path; + + CHECK_READ_ONLY(req); + + unix_path = svfs_unix_path(ntvfs, req, unl->unlink.in.pattern); + + /* ignoring wildcards ... */ + if (unlink(unix_path) == -1) { + return map_nt_error_from_unix_common(errno); + } + + return NT_STATUS_OK; +} + + +/* + ioctl interface - we don't do any +*/ +static NTSTATUS svfs_ioctl(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_ioctl *io) +{ + return NT_STATUS_INVALID_PARAMETER; +} + +/* + check if a directory exists +*/ +static NTSTATUS svfs_chkpath(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_chkpath *cp) +{ + char *unix_path; + struct stat st; + + unix_path = svfs_unix_path(ntvfs, req, cp->chkpath.in.path); + + if (stat(unix_path, &st) == -1) { + return map_nt_error_from_unix_common(errno); + } + + if (!S_ISDIR(st.st_mode)) { + return NT_STATUS_NOT_A_DIRECTORY; + } + + return NT_STATUS_OK; +} + +/* + build a file_id from a stat struct +*/ +static uint64_t svfs_file_id(struct stat *st) +{ + uint64_t ret = st->st_ino; + ret <<= 32; + ret |= st->st_dev; + return ret; +} + +/* + approximately map a struct stat to a generic fileinfo struct +*/ +static NTSTATUS svfs_map_fileinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_fileinfo *info, + struct stat *st, const char *unix_path) +{ + struct svfs_dir *dir = NULL; + char *pattern = NULL; + int i, ret; + const char *s, *short_name; + + s = strrchr(unix_path, '/'); + if (s) { + short_name = s+1; + } else { + short_name = ""; + } + + ret = asprintf(&pattern, "%s:*", unix_path); + if (ret == -1) { + return NT_STATUS_NO_MEMORY; + } + + if (pattern) { + dir = svfs_list_unix(req, req, pattern); + } + + unix_to_nt_time(&info->generic.out.create_time, st->st_ctime); + unix_to_nt_time(&info->generic.out.access_time, st->st_atime); + unix_to_nt_time(&info->generic.out.write_time, st->st_mtime); + unix_to_nt_time(&info->generic.out.change_time, st->st_mtime); + info->generic.out.alloc_size = st->st_size; + info->generic.out.size = st->st_size; + info->generic.out.attrib = svfs_unix_to_dos_attrib(st->st_mode); + info->generic.out.alloc_size = st->st_blksize * st->st_blocks; + info->generic.out.nlink = st->st_nlink; + info->generic.out.directory = S_ISDIR(st->st_mode) ? 1 : 0; + info->generic.out.file_id = svfs_file_id(st); + /* REWRITE: TODO stuff in here */ + info->generic.out.delete_pending = 0; + info->generic.out.ea_size = 0; + info->generic.out.num_eas = 0; + info->generic.out.fname.s = talloc_strdup(req, short_name); + info->generic.out.alt_fname.s = talloc_strdup(req, short_name); + info->generic.out.compressed_size = 0; + info->generic.out.format = 0; + info->generic.out.unit_shift = 0; + info->generic.out.chunk_shift = 0; + info->generic.out.cluster_shift = 0; + + info->generic.out.access_flags = 0; + info->generic.out.position = 0; + info->generic.out.mode = 0; + info->generic.out.alignment_requirement = 0; + info->generic.out.reparse_tag = 0; + info->generic.out.num_streams = 0; + /* setup a single data stream */ + info->generic.out.num_streams = 1 + (dir?dir->count:0); + info->generic.out.streams = talloc_array(req, + struct stream_struct, + info->generic.out.num_streams); + if (!info->generic.out.streams) { + return NT_STATUS_NO_MEMORY; + } + info->generic.out.streams[0].size = st->st_size; + info->generic.out.streams[0].alloc_size = st->st_size; + info->generic.out.streams[0].stream_name.s = talloc_strdup(req,"::$DATA"); + + for (i=0;dir && i<dir->count;i++) { + s = strchr(dir->files[i].name, ':'); + info->generic.out.streams[1+i].size = dir->files[i].st.st_size; + info->generic.out.streams[1+i].alloc_size = dir->files[i].st.st_size; + info->generic.out.streams[1+i].stream_name.s = s?s:dir->files[i].name; + } + + return NT_STATUS_OK; +} + +/* + return info on a pathname +*/ +static NTSTATUS svfs_qpathinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_fileinfo *info) +{ + char *unix_path; + struct stat st; + + DEBUG(19,("svfs_qpathinfo: file %s level 0x%x\n", info->generic.in.file.path, info->generic.level)); + if (info->generic.level != RAW_FILEINFO_GENERIC) { + return ntvfs_map_qpathinfo(ntvfs, req, info); + } + + unix_path = svfs_unix_path(ntvfs, req, info->generic.in.file.path); + DEBUG(19,("svfs_qpathinfo: file %s\n", unix_path)); + if (stat(unix_path, &st) == -1) { + DEBUG(19,("svfs_qpathinfo: file %s errno=%d\n", unix_path, errno)); + return map_nt_error_from_unix_common(errno); + } + DEBUG(19,("svfs_qpathinfo: file %s, stat done\n", unix_path)); + return svfs_map_fileinfo(ntvfs, req, info, &st, unix_path); +} + +/* + query info on a open file +*/ +static NTSTATUS svfs_qfileinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_fileinfo *info) +{ + struct svfs_private *p = ntvfs->private_data; + struct svfs_file *f; + struct stat st; + + if (info->generic.level != RAW_FILEINFO_GENERIC) { + return ntvfs_map_qfileinfo(ntvfs, req, info); + } + + f = find_fd(p, info->generic.in.file.ntvfs); + if (!f) { + return NT_STATUS_INVALID_HANDLE; + } + + if (fstat(f->fd, &st) == -1) { + return map_nt_error_from_unix_common(errno); + } + + return svfs_map_fileinfo(ntvfs, req,info, &st, f->name); +} + + +/* + open a file +*/ +static NTSTATUS svfs_open(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_open *io) +{ + struct svfs_private *p = ntvfs->private_data; + char *unix_path; + struct stat st; + int fd, flags; + struct svfs_file *f; + int create_flags, rdwr_flags; + bool readonly; + NTSTATUS status; + struct ntvfs_handle *handle; + + if (io->generic.level != RAW_OPEN_GENERIC) { + return ntvfs_map_open(ntvfs, req, io); + } + + readonly = share_bool_option(ntvfs->ctx->config, SHARE_READONLY, SHARE_READONLY_DEFAULT); + if (readonly) { + create_flags = 0; + rdwr_flags = O_RDONLY; + } else { + create_flags = O_CREAT; + rdwr_flags = O_RDWR; + } + + unix_path = svfs_unix_path(ntvfs, req, io->ntcreatex.in.fname); + + switch (io->generic.in.open_disposition) { + case NTCREATEX_DISP_SUPERSEDE: + case NTCREATEX_DISP_OVERWRITE_IF: + flags = create_flags | O_TRUNC; + break; + case NTCREATEX_DISP_OPEN: + case NTCREATEX_DISP_OVERWRITE: + flags = 0; + break; + case NTCREATEX_DISP_CREATE: + flags = create_flags | O_EXCL; + break; + case NTCREATEX_DISP_OPEN_IF: + flags = create_flags; + break; + default: + flags = 0; + break; + } + + flags |= rdwr_flags; + + if (io->generic.in.create_options & NTCREATEX_OPTIONS_DIRECTORY) { + flags = O_RDONLY | O_DIRECTORY; + if (readonly) { + goto do_open; + } + switch (io->generic.in.open_disposition) { + case NTCREATEX_DISP_CREATE: + if (mkdir(unix_path, 0755) == -1) { + DEBUG(9,("svfs_open: mkdir %s errno=%d\n", unix_path, errno)); + return map_nt_error_from_unix_common(errno); + } + break; + case NTCREATEX_DISP_OPEN_IF: + if (mkdir(unix_path, 0755) == -1 && errno != EEXIST) { + DEBUG(9,("svfs_open: mkdir %s errno=%d\n", unix_path, errno)); + return map_nt_error_from_unix_common(errno); + } + break; + } + } + +do_open: + fd = open(unix_path, flags, 0644); + if (fd == -1) { + return map_nt_error_from_unix_common(errno); + } + + if (fstat(fd, &st) == -1) { + DEBUG(9,("svfs_open: fstat errno=%d\n", errno)); + close(fd); + return map_nt_error_from_unix_common(errno); + } + + status = ntvfs_handle_new(ntvfs, req, &handle); + NT_STATUS_NOT_OK_RETURN(status); + + f = talloc(handle, struct svfs_file); + if (f == NULL) { + close(fd); + return NT_STATUS_NO_MEMORY; + } + f->fd = fd; + f->name = talloc_strdup(f, unix_path); + NT_STATUS_HAVE_NO_MEMORY(f->name); + + DLIST_ADD(p->open_files, f); + + status = ntvfs_handle_set_backend_data(handle, ntvfs, f); + NT_STATUS_NOT_OK_RETURN(status); + + ZERO_STRUCT(io->generic.out); + + unix_to_nt_time(&io->generic.out.create_time, st.st_ctime); + unix_to_nt_time(&io->generic.out.access_time, st.st_atime); + unix_to_nt_time(&io->generic.out.write_time, st.st_mtime); + unix_to_nt_time(&io->generic.out.change_time, st.st_mtime); + io->generic.out.file.ntvfs = handle; + io->generic.out.alloc_size = st.st_size; + io->generic.out.size = st.st_size; + io->generic.out.attrib = svfs_unix_to_dos_attrib(st.st_mode); + io->generic.out.is_directory = S_ISDIR(st.st_mode) ? 1 : 0; + + return NT_STATUS_OK; +} + +/* + create a directory +*/ +static NTSTATUS svfs_mkdir(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_mkdir *md) +{ + char *unix_path; + + CHECK_READ_ONLY(req); + + if (md->generic.level != RAW_MKDIR_MKDIR) { + return NT_STATUS_INVALID_LEVEL; + } + + unix_path = svfs_unix_path(ntvfs, req, md->mkdir.in.path); + + if (mkdir(unix_path, 0777) == -1) { + return map_nt_error_from_unix_common(errno); + } + + return NT_STATUS_OK; +} + +/* + remove a directory +*/ +static NTSTATUS svfs_rmdir(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, struct smb_rmdir *rd) +{ + char *unix_path; + + CHECK_READ_ONLY(req); + + unix_path = svfs_unix_path(ntvfs, req, rd->in.path); + + if (rmdir(unix_path) == -1) { + return map_nt_error_from_unix_common(errno); + } + + return NT_STATUS_OK; +} + +/* + rename a set of files +*/ +static NTSTATUS svfs_rename(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_rename *ren) +{ + char *unix_path1, *unix_path2; + + CHECK_READ_ONLY(req); + + if (ren->generic.level != RAW_RENAME_RENAME) { + return NT_STATUS_INVALID_LEVEL; + } + + unix_path1 = svfs_unix_path(ntvfs, req, ren->rename.in.pattern1); + unix_path2 = svfs_unix_path(ntvfs, req, ren->rename.in.pattern2); + + if (rename(unix_path1, unix_path2) == -1) { + return map_nt_error_from_unix_common(errno); + } + + return NT_STATUS_OK; +} + +/* + copy a set of files +*/ +static NTSTATUS svfs_copy(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, struct smb_copy *cp) +{ + return NT_STATUS_NOT_SUPPORTED; +} + +/* + read from a file +*/ +static NTSTATUS svfs_read(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_read *rd) +{ + struct svfs_private *p = ntvfs->private_data; + struct svfs_file *f; + ssize_t ret; + + if (rd->generic.level != RAW_READ_READX) { + return NT_STATUS_NOT_SUPPORTED; + } + + f = find_fd(p, rd->readx.in.file.ntvfs); + if (!f) { + return NT_STATUS_INVALID_HANDLE; + } + + ret = pread(f->fd, + rd->readx.out.data, + rd->readx.in.maxcnt, + rd->readx.in.offset); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + + rd->readx.out.nread = ret; + rd->readx.out.remaining = 0; /* should fill this in? */ + rd->readx.out.compaction_mode = 0; + + return NT_STATUS_OK; +} + +/* + write to a file +*/ +static NTSTATUS svfs_write(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_write *wr) +{ + struct svfs_private *p = ntvfs->private_data; + struct svfs_file *f; + ssize_t ret; + + if (wr->generic.level != RAW_WRITE_WRITEX) { + return ntvfs_map_write(ntvfs, req, wr); + } + + CHECK_READ_ONLY(req); + + f = find_fd(p, wr->writex.in.file.ntvfs); + if (!f) { + return NT_STATUS_INVALID_HANDLE; + } + + ret = pwrite(f->fd, + wr->writex.in.data, + wr->writex.in.count, + wr->writex.in.offset); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + + wr->writex.out.nwritten = ret; + wr->writex.out.remaining = 0; /* should fill this in? */ + + return NT_STATUS_OK; +} + +/* + seek in a file +*/ +static NTSTATUS svfs_seek(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_seek *io) +{ + return NT_STATUS_NOT_SUPPORTED; +} + +/* + flush a file +*/ +static NTSTATUS svfs_flush(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_flush *io) +{ + struct svfs_private *p = ntvfs->private_data; + struct svfs_file *f; + + switch (io->generic.level) { + case RAW_FLUSH_FLUSH: + case RAW_FLUSH_SMB2: + /* ignore the additional unknown option in SMB2 */ + f = find_fd(p, io->generic.in.file.ntvfs); + if (!f) { + return NT_STATUS_INVALID_HANDLE; + } + fsync(f->fd); + return NT_STATUS_OK; + + case RAW_FLUSH_ALL: + for (f=p->open_files;f;f=f->next) { + fsync(f->fd); + } + return NT_STATUS_OK; + } + + return NT_STATUS_INVALID_LEVEL; +} + +/* + close a file +*/ +static NTSTATUS svfs_close(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_close *io) +{ + struct svfs_private *p = ntvfs->private_data; + struct svfs_file *f; + + if (io->generic.level != RAW_CLOSE_CLOSE) { + /* we need a mapping function */ + return NT_STATUS_INVALID_LEVEL; + } + + f = find_fd(p, io->close.in.file.ntvfs); + if (!f) { + return NT_STATUS_INVALID_HANDLE; + } + + if (close(f->fd) == -1) { + return map_nt_error_from_unix_common(errno); + } + + DLIST_REMOVE(p->open_files, f); + talloc_free(f->name); + talloc_free(f); + + return NT_STATUS_OK; +} + +/* + exit - closing files +*/ +static NTSTATUS svfs_exit(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req) +{ + return NT_STATUS_NOT_SUPPORTED; +} + +/* + logoff - closing files +*/ +static NTSTATUS svfs_logoff(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req) +{ + return NT_STATUS_NOT_SUPPORTED; +} + +/* + setup for an async call +*/ +static NTSTATUS svfs_async_setup(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + void *private_data) +{ + return NT_STATUS_OK; +} + +/* + cancel an async call +*/ +static NTSTATUS svfs_cancel(struct ntvfs_module_context *ntvfs, struct ntvfs_request *req) +{ + return NT_STATUS_UNSUCCESSFUL; +} + +/* + lock a byte range +*/ +static NTSTATUS svfs_lock(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_lock *lck) +{ + DEBUG(0,("REWRITE: not doing byte range locking!\n")); + return NT_STATUS_OK; +} + +/* + set info on a pathname +*/ +static NTSTATUS svfs_setpathinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_setfileinfo *st) +{ + CHECK_READ_ONLY(req); + + return NT_STATUS_NOT_SUPPORTED; +} + +/* + set info on a open file +*/ +static NTSTATUS svfs_setfileinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_setfileinfo *info) +{ + struct svfs_private *p = ntvfs->private_data; + struct svfs_file *f; + struct utimbuf unix_times; + + CHECK_READ_ONLY(req); + + f = find_fd(p, info->generic.in.file.ntvfs); + if (!f) { + return NT_STATUS_INVALID_HANDLE; + } + + switch (info->generic.level) { + case RAW_SFILEINFO_END_OF_FILE_INFO: + case RAW_SFILEINFO_END_OF_FILE_INFORMATION: + if (ftruncate(f->fd, + info->end_of_file_info.in.size) == -1) { + return map_nt_error_from_unix_common(errno); + } + break; + case RAW_SFILEINFO_SETATTRE: + unix_times.actime = info->setattre.in.access_time; + unix_times.modtime = info->setattre.in.write_time; + + if (unix_times.actime == 0 && unix_times.modtime == 0) { + break; + } + + /* set modify time = to access time if modify time was 0 */ + if (unix_times.actime != 0 && unix_times.modtime == 0) { + unix_times.modtime = unix_times.actime; + } + + /* Set the date on this file */ + if (svfs_file_utime(f->fd, &unix_times) != 0) { + return NT_STATUS_ACCESS_DENIED; + } + break; + default: + DEBUG(2,("svfs_setfileinfo: level %d not implemented\n", + info->generic.level)); + return NT_STATUS_NOT_IMPLEMENTED; + } + return NT_STATUS_OK; +} + + +/* + return filesystem space info +*/ +static NTSTATUS svfs_fsinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_fsinfo *fs) +{ + struct svfs_private *p = ntvfs->private_data; + struct stat st; + + if (fs->generic.level != RAW_QFS_GENERIC) { + return ntvfs_map_fsinfo(ntvfs, req, fs); + } + + if (sys_fsusage(p->connectpath, + &fs->generic.out.blocks_free, + &fs->generic.out.blocks_total) == -1) { + return map_nt_error_from_unix_common(errno); + } + + fs->generic.out.block_size = 512; + + if (stat(p->connectpath, &st) != 0) { + return NT_STATUS_DISK_CORRUPT_ERROR; + } + + fs->generic.out.fs_id = st.st_ino; + unix_to_nt_time(&fs->generic.out.create_time, st.st_ctime); + fs->generic.out.serial_number = st.st_ino; + fs->generic.out.fs_attr = 0; + fs->generic.out.max_file_component_length = 255; + fs->generic.out.device_type = 0; + fs->generic.out.device_characteristics = 0; + fs->generic.out.quota_soft = 0; + fs->generic.out.quota_hard = 0; + fs->generic.out.quota_flags = 0; + fs->generic.out.volume_name = talloc_strdup(req, ntvfs->ctx->config->name); + fs->generic.out.fs_type = ntvfs->ctx->fs_type; + + return NT_STATUS_OK; +} + +#if 0 +/* + return filesystem attribute info +*/ +static NTSTATUS svfs_fsattr(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_fsattr *fs) +{ + struct stat st; + struct svfs_private *p = ntvfs->private_data; + + if (fs->generic.level != RAW_FSATTR_GENERIC) { + return ntvfs_map_fsattr(ntvfs, req, fs); + } + + if (stat(p->connectpath, &st) == -1) { + return map_nt_error_from_unix_common(errno); + } + + unix_to_nt_time(&fs->generic.out.create_time, st.st_ctime); + fs->generic.out.fs_attr = + FILE_CASE_PRESERVED_NAMES | + FILE_CASE_SENSITIVE_SEARCH | + FILE_PERSISTENT_ACLS; + fs->generic.out.max_file_component_length = 255; + fs->generic.out.serial_number = 1; + fs->generic.out.fs_type = talloc_strdup(req, "NTFS"); + fs->generic.out.volume_name = talloc_strdup(req, + lpcfg_servicename(req->tcon->service)); + + return NT_STATUS_OK; +} +#endif + +/* + return print queue info +*/ +static NTSTATUS svfs_lpq(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_lpq *lpq) +{ + return NT_STATUS_NOT_SUPPORTED; +} + +/* + list files in a directory matching a wildcard pattern +*/ +static NTSTATUS svfs_search_first(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_search_first *io, + void *search_private, + bool (*callback)(void *, const union smb_search_data *)) +{ + struct svfs_dir *dir; + int i; + struct svfs_private *p = ntvfs->private_data; + struct search_state *search; + union smb_search_data file; + unsigned int max_count; + + if (io->generic.level != RAW_SEARCH_TRANS2) { + return NT_STATUS_NOT_SUPPORTED; + } + + if (io->generic.data_level != RAW_SEARCH_DATA_BOTH_DIRECTORY_INFO) { + return NT_STATUS_NOT_SUPPORTED; + } + + search = talloc_zero(p, struct search_state); + if (!search) { + return NT_STATUS_NO_MEMORY; + } + + max_count = io->t2ffirst.in.max_count; + + dir = svfs_list(ntvfs, req, io->t2ffirst.in.pattern); + if (!dir) { + return NT_STATUS_FOOBAR; + } + + search->handle = p->next_search_handle; + search->dir = dir; + + if (dir->count < max_count) { + max_count = dir->count; + } + + for (i=0; i < max_count;i++) { + ZERO_STRUCT(file); + unix_to_nt_time(&file.both_directory_info.create_time, dir->files[i].st.st_ctime); + unix_to_nt_time(&file.both_directory_info.access_time, dir->files[i].st.st_atime); + unix_to_nt_time(&file.both_directory_info.write_time, dir->files[i].st.st_mtime); + unix_to_nt_time(&file.both_directory_info.change_time, dir->files[i].st.st_mtime); + file.both_directory_info.name.s = dir->files[i].name; + file.both_directory_info.short_name.s = dir->files[i].name; + file.both_directory_info.size = dir->files[i].st.st_size; + file.both_directory_info.attrib = svfs_unix_to_dos_attrib(dir->files[i].st.st_mode); + + if (!callback(search_private, &file)) { + break; + } + } + + search->current_index = i; + + io->t2ffirst.out.count = i; + io->t2ffirst.out.handle = search->handle; + io->t2ffirst.out.end_of_search = (i == dir->count) ? 1 : 0; + + /* work out if we are going to keep the search state */ + if ((io->t2ffirst.in.flags & FLAG_TRANS2_FIND_CLOSE) || + ((io->t2ffirst.in.flags & FLAG_TRANS2_FIND_CLOSE_IF_END) && (i == dir->count))) { + talloc_free(search); + } else { + p->next_search_handle++; + DLIST_ADD(p->search, search); + } + + return NT_STATUS_OK; +} + +/* continue a search */ +static NTSTATUS svfs_search_next(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_search_next *io, + void *search_private, + bool (*callback)(void *, const union smb_search_data *)) +{ + struct svfs_dir *dir; + int i; + struct svfs_private *p = ntvfs->private_data; + struct search_state *search; + union smb_search_data file; + unsigned int max_count; + + if (io->generic.level != RAW_SEARCH_TRANS2) { + return NT_STATUS_NOT_SUPPORTED; + } + + if (io->generic.data_level != RAW_SEARCH_DATA_BOTH_DIRECTORY_INFO) { + return NT_STATUS_NOT_SUPPORTED; + } + + for (search=p->search; search; search = search->next) { + if (search->handle == io->t2fnext.in.handle) break; + } + + if (!search) { + /* we didn't find the search handle */ + return NT_STATUS_FOOBAR; + } + + dir = search->dir; + + /* the client might be asking for something other than just continuing + with the search */ + if (!(io->t2fnext.in.flags & FLAG_TRANS2_FIND_CONTINUE) && + (io->t2fnext.in.flags & FLAG_TRANS2_FIND_REQUIRE_RESUME) && + io->t2fnext.in.last_name && *io->t2fnext.in.last_name) { + /* look backwards first */ + for (i=search->current_index; i > 0; i--) { + if (strcmp(io->t2fnext.in.last_name, dir->files[i-1].name) == 0) { + search->current_index = i; + goto found; + } + } + + /* then look forwards */ + for (i=search->current_index+1; i <= dir->count; i++) { + if (strcmp(io->t2fnext.in.last_name, dir->files[i-1].name) == 0) { + search->current_index = i; + goto found; + } + } + } + +found: + max_count = search->current_index + io->t2fnext.in.max_count; + + if (max_count > dir->count) { + max_count = dir->count; + } + + for (i = search->current_index; i < max_count;i++) { + ZERO_STRUCT(file); + unix_to_nt_time(&file.both_directory_info.create_time, dir->files[i].st.st_ctime); + unix_to_nt_time(&file.both_directory_info.access_time, dir->files[i].st.st_atime); + unix_to_nt_time(&file.both_directory_info.write_time, dir->files[i].st.st_mtime); + unix_to_nt_time(&file.both_directory_info.change_time, dir->files[i].st.st_mtime); + file.both_directory_info.name.s = dir->files[i].name; + file.both_directory_info.short_name.s = dir->files[i].name; + file.both_directory_info.size = dir->files[i].st.st_size; + file.both_directory_info.attrib = svfs_unix_to_dos_attrib(dir->files[i].st.st_mode); + + if (!callback(search_private, &file)) { + break; + } + } + + io->t2fnext.out.count = i - search->current_index; + io->t2fnext.out.end_of_search = (i == dir->count) ? 1 : 0; + + search->current_index = i; + + /* work out if we are going to keep the search state */ + if ((io->t2fnext.in.flags & FLAG_TRANS2_FIND_CLOSE) || + ((io->t2fnext.in.flags & FLAG_TRANS2_FIND_CLOSE_IF_END) && (i == dir->count))) { + DLIST_REMOVE(p->search, search); + talloc_free(search); + } + + return NT_STATUS_OK; +} + +/* close a search */ +static NTSTATUS svfs_search_close(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_search_close *io) +{ + struct svfs_private *p = ntvfs->private_data; + struct search_state *search; + + for (search=p->search; search; search = search->next) { + if (search->handle == io->findclose.in.handle) break; + } + + if (!search) { + /* we didn't find the search handle */ + return NT_STATUS_FOOBAR; + } + + DLIST_REMOVE(p->search, search); + talloc_free(search); + + return NT_STATUS_OK; +} + +/* SMBtrans - not used on file shares */ +static NTSTATUS svfs_trans(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, struct smb_trans2 *trans2) +{ + return NT_STATUS_ACCESS_DENIED; +} + + +/* + initialise the POSIX disk backend, registering ourselves with the ntvfs subsystem + */ +NTSTATUS ntvfs_simple_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret; + struct ntvfs_ops ops; + NTVFS_CURRENT_CRITICAL_SIZES(vers); + + ZERO_STRUCT(ops); + + /* fill in all the operations */ + ops.connect_fn = svfs_connect; + ops.disconnect_fn = svfs_disconnect; + ops.unlink_fn = svfs_unlink; + ops.chkpath_fn = svfs_chkpath; + ops.qpathinfo_fn = svfs_qpathinfo; + ops.setpathinfo_fn = svfs_setpathinfo; + ops.open_fn = svfs_open; + ops.mkdir_fn = svfs_mkdir; + ops.rmdir_fn = svfs_rmdir; + ops.rename_fn = svfs_rename; + ops.copy_fn = svfs_copy; + ops.ioctl_fn = svfs_ioctl; + ops.read_fn = svfs_read; + ops.write_fn = svfs_write; + ops.seek_fn = svfs_seek; + ops.flush_fn = svfs_flush; + ops.close_fn = svfs_close; + ops.exit_fn = svfs_exit; + ops.lock_fn = svfs_lock; + ops.setfileinfo_fn = svfs_setfileinfo; + ops.qfileinfo_fn = svfs_qfileinfo; + ops.fsinfo_fn = svfs_fsinfo; + ops.lpq_fn = svfs_lpq; + ops.search_first_fn = svfs_search_first; + ops.search_next_fn = svfs_search_next; + ops.search_close_fn = svfs_search_close; + ops.trans_fn = svfs_trans; + ops.logoff_fn = svfs_logoff; + ops.async_setup_fn = svfs_async_setup; + ops.cancel_fn = svfs_cancel; + + /* register ourselves with the NTVFS subsystem. We register + under names 'simple' + */ + + ops.type = NTVFS_DISK; + ops.name = "simple"; + ret = ntvfs_register(&ops, &vers); + + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register simple backend with name: %s!\n", + ops.name)); + } + + return ret; +} diff --git a/source4/ntvfs/sysdep/README b/source4/ntvfs/sysdep/README new file mode 100644 index 0000000..86b473e --- /dev/null +++ b/source4/ntvfs/sysdep/README @@ -0,0 +1,5 @@ +This directory contains OS dependent interfaces to facilities that +are only available on a few of our target systems, and require +substantial code to abstract. + + diff --git a/source4/ntvfs/sysdep/inotify.c b/source4/ntvfs/sysdep/inotify.c new file mode 100644 index 0000000..94e0b17 --- /dev/null +++ b/source4/ntvfs/sysdep/inotify.c @@ -0,0 +1,398 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Andrew Tridgell 2006 + + 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/>. +*/ + +/* + notify implementation using inotify +*/ + +#include "includes.h" +#include "system/filesys.h" +#include <tevent.h> +#include "ntvfs/sysdep/sys_notify.h" +#include "../lib/util/dlinklist.h" +#include "libcli/raw/smb.h" +#include "param/param.h" + +#include <sys/inotify.h> + +/* glibc < 2.5 headers don't have these defines */ +#ifndef IN_ONLYDIR +#define IN_ONLYDIR 0x01000000 +#endif +#ifndef IN_MASK_ADD +#define IN_MASK_ADD 0x20000000 +#endif + +struct inotify_private { + struct sys_notify_context *ctx; + int fd; + struct inotify_watch_context *watches; +}; + +struct inotify_watch_context { + struct inotify_watch_context *next, *prev; + struct inotify_private *in; + int wd; + sys_notify_callback_t callback; + void *private_data; + uint32_t mask; /* the inotify mask */ + uint32_t filter; /* the windows completion filter */ + const char *path; +}; + + +/* + see if a particular event from inotify really does match a requested + notify event in SMB +*/ +static bool filter_match(struct inotify_watch_context *w, + struct inotify_event *e) +{ + if ((e->mask & w->mask) == 0) { + /* this happens because inotify_add_watch() coalesces watches on the same + path, oring their masks together */ + return false; + } + + /* SMB separates the filters for files and directories */ + if (e->mask & IN_ISDIR) { + if ((w->filter & FILE_NOTIFY_CHANGE_DIR_NAME) == 0) { + return false; + } + } else { + if ((e->mask & IN_ATTRIB) && + (w->filter & (FILE_NOTIFY_CHANGE_ATTRIBUTES| + FILE_NOTIFY_CHANGE_LAST_WRITE| + FILE_NOTIFY_CHANGE_LAST_ACCESS| + FILE_NOTIFY_CHANGE_EA| + FILE_NOTIFY_CHANGE_SECURITY))) { + return true; + } + if ((e->mask & IN_MODIFY) && + (w->filter & FILE_NOTIFY_CHANGE_ATTRIBUTES)) { + return true; + } + if ((w->filter & FILE_NOTIFY_CHANGE_FILE_NAME) == 0) { + return false; + } + } + + return true; +} + + + +/* + dispatch one inotify event + + the cookies are used to correctly handle renames +*/ +static void inotify_dispatch(struct inotify_private *in, + struct inotify_event *e, + uint32_t prev_cookie, + struct inotify_event *e2) +{ + struct inotify_watch_context *w, *next; + struct notify_event ne; + + /* ignore extraneous events, such as unmount and IN_IGNORED events */ + if ((e->mask & (IN_ATTRIB|IN_MODIFY|IN_CREATE|IN_DELETE| + IN_MOVED_FROM|IN_MOVED_TO)) == 0) { + return; + } + + /* map the inotify mask to a action. This gets complicated for + renames */ + if (e->mask & IN_CREATE) { + ne.action = NOTIFY_ACTION_ADDED; + } else if (e->mask & IN_DELETE) { + ne.action = NOTIFY_ACTION_REMOVED; + } else if (e->mask & IN_MOVED_FROM) { + if (e2 != NULL && e2->cookie == e->cookie) { + ne.action = NOTIFY_ACTION_OLD_NAME; + } else { + ne.action = NOTIFY_ACTION_REMOVED; + } + } else if (e->mask & IN_MOVED_TO) { + if (e->cookie == prev_cookie) { + ne.action = NOTIFY_ACTION_NEW_NAME; + } else { + ne.action = NOTIFY_ACTION_ADDED; + } + } else { + ne.action = NOTIFY_ACTION_MODIFIED; + } + ne.path = e->name; + + /* find any watches that have this watch descriptor */ + for (w=in->watches;w;w=next) { + next = w->next; + if (w->wd == e->wd && filter_match(w, e)) { + ne.dir = w->path; + w->callback(in->ctx, w->private_data, &ne); + } + } + + /* SMB expects a file rename to generate three events, two for + the rename and the other for a modify of the + destination. Strange! */ + if (ne.action != NOTIFY_ACTION_NEW_NAME || + (e->mask & IN_ISDIR) != 0) { + return; + } + + ne.action = NOTIFY_ACTION_MODIFIED; + e->mask = IN_ATTRIB; + + for (w=in->watches;w;w=next) { + next = w->next; + if (w->wd == e->wd && filter_match(w, e) && + !(w->filter & FILE_NOTIFY_CHANGE_CREATION)) { + ne.dir = w->path; + w->callback(in->ctx, w->private_data, &ne); + } + } +} + +/* + called when the kernel has some events for us +*/ +static void inotify_handler(struct tevent_context *ev, struct tevent_fd *fde, + uint16_t flags, void *private_data) +{ + struct inotify_private *in = talloc_get_type(private_data, + struct inotify_private); + int bufsize = 0; + struct inotify_event *e0, *e; + uint32_t prev_cookie=0; + + /* + we must use FIONREAD as we cannot predict the length of the + filenames, and thus can't know how much to allocate + otherwise + */ + if (ioctl(in->fd, FIONREAD, &bufsize) != 0 || + bufsize == 0) { + DEBUG(0,("No data on inotify fd?!\n")); + return; + } + + e0 = e = talloc_size(in, bufsize); + if (e == NULL) return; + + if (read(in->fd, e0, bufsize) != bufsize) { + DEBUG(0,("Failed to read all inotify data\n")); + talloc_free(e0); + return; + } + + /* we can get more than one event in the buffer */ + while (bufsize >= sizeof(*e)) { + struct inotify_event *e2 = NULL; + bufsize -= e->len + sizeof(*e); + if (bufsize >= sizeof(*e)) { + e2 = (struct inotify_event *)(e->len + sizeof(*e) + (char *)e); + } + inotify_dispatch(in, e, prev_cookie, e2); + prev_cookie = e->cookie; + e = e2; + } + + talloc_free(e0); +} + +/* + setup the inotify handle - called the first time a watch is added on + this context +*/ +static NTSTATUS inotify_setup(struct sys_notify_context *ctx) +{ + struct inotify_private *in; + struct tevent_fd *fde; + + in = talloc(ctx, struct inotify_private); + NT_STATUS_HAVE_NO_MEMORY(in); + + in->fd = inotify_init(); + if (in->fd == -1) { + DEBUG(0,("Failed to init inotify - %s\n", strerror(errno))); + talloc_free(in); + return map_nt_error_from_unix_common(errno); + } + in->ctx = ctx; + in->watches = NULL; + + ctx->private_data = in; + + /* add a event waiting for the inotify fd to be readable */ + fde = tevent_add_fd(ctx->ev, in, in->fd, + TEVENT_FD_READ, inotify_handler, in); + if (!fde) { + if (errno == 0) { + errno = ENOMEM; + } + DEBUG(0,("Failed to tevent_add_fd() - %s\n", strerror(errno))); + talloc_free(in); + return map_nt_error_from_unix_common(errno); + } + + tevent_fd_set_auto_close(fde); + + return NT_STATUS_OK; +} + + +/* + map from a change notify mask to a inotify mask. Remove any bits + which we can handle +*/ +static const struct { + uint32_t notify_mask; + uint32_t inotify_mask; +} inotify_mapping[] = { + {FILE_NOTIFY_CHANGE_FILE_NAME, IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO}, + {FILE_NOTIFY_CHANGE_DIR_NAME, IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO}, + {FILE_NOTIFY_CHANGE_ATTRIBUTES, IN_ATTRIB|IN_MOVED_TO|IN_MOVED_FROM|IN_MODIFY}, + {FILE_NOTIFY_CHANGE_LAST_WRITE, IN_ATTRIB}, + {FILE_NOTIFY_CHANGE_LAST_ACCESS, IN_ATTRIB}, + {FILE_NOTIFY_CHANGE_EA, IN_ATTRIB}, + {FILE_NOTIFY_CHANGE_SECURITY, IN_ATTRIB} +}; + +static uint32_t inotify_map(struct notify_entry *e) +{ + int i; + uint32_t out=0; + for (i=0;i<ARRAY_SIZE(inotify_mapping);i++) { + if (inotify_mapping[i].notify_mask & e->filter) { + out |= inotify_mapping[i].inotify_mask; + e->filter &= ~inotify_mapping[i].notify_mask; + } + } + return out; +} + +/* + destroy a watch +*/ +static int watch_destructor(struct inotify_watch_context *w) +{ + struct inotify_private *in = w->in; + int wd = w->wd; + DLIST_REMOVE(w->in->watches, w); + + /* only rm the watch if its the last one with this wd */ + for (w=in->watches;w;w=w->next) { + if (w->wd == wd) break; + } + if (w == NULL) { + inotify_rm_watch(in->fd, wd); + } + return 0; +} + + +/* + add a watch. The watch is removed when the caller calls + talloc_free() on *handle +*/ +static NTSTATUS inotify_watch(struct sys_notify_context *ctx, + struct notify_entry *e, + sys_notify_callback_t callback, + void *private_data, + void *handle_p) +{ + struct inotify_private *in; + int wd; + uint32_t mask; + struct inotify_watch_context *w; + uint32_t filter = e->filter; + void **handle = (void **)handle_p; + + /* maybe setup the inotify fd */ + if (ctx->private_data == NULL) { + NTSTATUS status; + status = inotify_setup(ctx); + NT_STATUS_NOT_OK_RETURN(status); + } + + in = talloc_get_type(ctx->private_data, struct inotify_private); + + mask = inotify_map(e); + if (mask == 0) { + /* this filter can't be handled by inotify */ + return NT_STATUS_INVALID_PARAMETER; + } + + /* using IN_MASK_ADD allows us to cope with inotify() returning the same + watch descriptor for multiple watches on the same path */ + mask |= (IN_MASK_ADD | IN_ONLYDIR); + + /* get a new watch descriptor for this path */ + wd = inotify_add_watch(in->fd, e->path, mask); + if (wd == -1) { + e->filter = filter; + return map_nt_error_from_unix_common(errno); + } + + w = talloc(in, struct inotify_watch_context); + if (w == NULL) { + inotify_rm_watch(in->fd, wd); + e->filter = filter; + return NT_STATUS_NO_MEMORY; + } + + w->in = in; + w->wd = wd; + w->callback = callback; + w->private_data = private_data; + w->mask = mask; + w->filter = filter; + w->path = talloc_strdup(w, e->path); + if (w->path == NULL) { + inotify_rm_watch(in->fd, wd); + e->filter = filter; + return NT_STATUS_NO_MEMORY; + } + + (*handle) = w; + + DLIST_ADD(in->watches, w); + + /* the caller frees the handle to stop watching */ + talloc_set_destructor(w, watch_destructor); + + return NT_STATUS_OK; +} + + +static struct sys_notify_backend inotify = { + .name = "inotify", + .notify_watch = inotify_watch +}; + +/* + initialise the inotify module + */ +NTSTATUS sys_notify_inotify_init(TALLOC_CTX *); +NTSTATUS sys_notify_inotify_init(TALLOC_CTX *ctx) +{ + /* register ourselves as a system inotify module */ + return sys_notify_register(ctx, &inotify); +} diff --git a/source4/ntvfs/sysdep/sys_lease.c b/source4/ntvfs/sysdep/sys_lease.c new file mode 100644 index 0000000..1ef72f1 --- /dev/null +++ b/source4/ntvfs/sysdep/sys_lease.c @@ -0,0 +1,152 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher 2008 + + 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/>. +*/ + +/* + abstract the various kernel interfaces to leases (oplocks) into a + single Samba friendly interface +*/ + +#include "includes.h" +#include "system/filesys.h" +#include "ntvfs/sysdep/sys_lease.h" +#include "../lib/util/dlinklist.h" +#include "param/param.h" +#include "lib/util/samba_modules.h" + +#undef strcasecmp + +/* list of registered backends */ +static struct sys_lease_ops *backends; +static uint32_t num_backends; + +#define LEASE_BACKEND "lease:backend" + +/* + initialise a system change notify backend +*/ +_PUBLIC_ struct sys_lease_context *sys_lease_context_create(struct share_config *scfg, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct imessaging_context *msg, + sys_lease_send_break_fn break_send) +{ + struct sys_lease_context *ctx; + const char *bname; + int i; + NTSTATUS status; + TALLOC_CTX * tmp_ctx; + + if (num_backends == 0) { + return NULL; + } + + if (ev == NULL) { + return NULL; + } + + ctx = talloc_zero(mem_ctx, struct sys_lease_context); + if (ctx == NULL) { + return NULL; + } + + tmp_ctx = talloc_new(ctx); + if (tmp_ctx == NULL) { + return NULL; + } + + ctx->event_ctx = ev; + ctx->msg_ctx = msg; + ctx->break_send = break_send; + + bname = share_string_option(tmp_ctx, scfg, LEASE_BACKEND, NULL); + if (!bname) { + talloc_free(ctx); + return NULL; + } + + for (i=0;i<num_backends;i++) { + if (strcasecmp(backends[i].name, bname) == 0) { + ctx->ops = &backends[i]; + break; + } + } + + if (!ctx->ops) { + talloc_free(ctx); + return NULL; + } + + status = ctx->ops->init(ctx); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(ctx); + return NULL; + } + + TALLOC_FREE(tmp_ctx); + return ctx; +} + +/* + register a lease backend +*/ +_PUBLIC_ NTSTATUS sys_lease_register(TALLOC_CTX *ctx, + const struct sys_lease_ops *backend) +{ + struct sys_lease_ops *b; + b = talloc_realloc(ctx, backends, + struct sys_lease_ops, num_backends+1); + NT_STATUS_HAVE_NO_MEMORY(b); + backends = b; + backends[num_backends] = *backend; + num_backends++; + return NT_STATUS_OK; +} + +_PUBLIC_ NTSTATUS sys_lease_init(void) +{ + static bool initialized = false; +#define _MODULE_PROTO(init) extern NTSTATUS init(TALLOC_CTX *); + STATIC_sys_lease_MODULES_PROTO; + init_module_fn static_init[] = { STATIC_sys_lease_MODULES }; + + if (initialized) return NT_STATUS_OK; + initialized = true; + + run_init_functions(NULL, static_init); + + return NT_STATUS_OK; +} + +NTSTATUS sys_lease_setup(struct sys_lease_context *ctx, + struct opendb_entry *e) +{ + return ctx->ops->setup(ctx, e); +} + +NTSTATUS sys_lease_update(struct sys_lease_context *ctx, + struct opendb_entry *e) +{ + return ctx->ops->update(ctx, e); +} + +NTSTATUS sys_lease_remove(struct sys_lease_context *ctx, + struct opendb_entry *e) +{ + return ctx->ops->remove(ctx, e); +} diff --git a/source4/ntvfs/sysdep/sys_lease.h b/source4/ntvfs/sysdep/sys_lease.h new file mode 100644 index 0000000..8b8d4bd --- /dev/null +++ b/source4/ntvfs/sysdep/sys_lease.h @@ -0,0 +1,66 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher 2008 + + 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 "param/share.h" + +struct sys_lease_context; +struct opendb_entry; +struct imessaging_context; +struct tevent_context; + +typedef NTSTATUS (*sys_lease_send_break_fn)(struct imessaging_context *, + struct opendb_entry *, + uint8_t level); + +struct sys_lease_ops { + const char *name; + NTSTATUS (*init)(struct sys_lease_context *ctx); + NTSTATUS (*setup)(struct sys_lease_context *ctx, + struct opendb_entry *e); + NTSTATUS (*update)(struct sys_lease_context *ctx, + struct opendb_entry *e); + NTSTATUS (*remove)(struct sys_lease_context *ctx, + struct opendb_entry *e); +}; + +struct sys_lease_context { + struct tevent_context *event_ctx; + struct imessaging_context *msg_ctx; + sys_lease_send_break_fn break_send; + void *private_data; /* for use of backend */ + const struct sys_lease_ops *ops; +}; + +NTSTATUS sys_lease_register(TALLOC_CTX *ctx, const struct sys_lease_ops *ops); +NTSTATUS sys_lease_init(void); + +struct sys_lease_context *sys_lease_context_create(struct share_config *scfg, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct imessaging_context *msg_ctx, + sys_lease_send_break_fn break_send); + +NTSTATUS sys_lease_setup(struct sys_lease_context *ctx, + struct opendb_entry *e); + +NTSTATUS sys_lease_update(struct sys_lease_context *ctx, + struct opendb_entry *e); + +NTSTATUS sys_lease_remove(struct sys_lease_context *ctx, + struct opendb_entry *e); diff --git a/source4/ntvfs/sysdep/sys_lease_linux.c b/source4/ntvfs/sysdep/sys_lease_linux.c new file mode 100644 index 0000000..54c0007 --- /dev/null +++ b/source4/ntvfs/sysdep/sys_lease_linux.c @@ -0,0 +1,215 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Stefan Metzmacher 2008 + + 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/>. +*/ + +/* + lease (oplock) implementation using fcntl F_SETLEASE on linux +*/ + +#include "includes.h" +#include <tevent.h> +#include "system/filesys.h" +#include "ntvfs/sysdep/sys_lease.h" +#include "ntvfs/ntvfs.h" +#include "librpc/gen_ndr/ndr_opendb.h" +#include "../lib/util/dlinklist.h" +#include "cluster/cluster.h" + +NTSTATUS sys_lease_linux_init(TALLOC_CTX *); + +#define LINUX_LEASE_RT_SIGNAL (SIGRTMIN+1) + +struct linux_lease_pending { + struct linux_lease_pending *prev, *next; + struct sys_lease_context *ctx; + struct opendb_entry e; +}; + +/* the global linked list of pending leases */ +static struct linux_lease_pending *leases; + +static void linux_lease_signal_handler(struct tevent_context *ev_ctx, + struct tevent_signal *se, + int signum, int count, + void *_info, void *private_data) +{ + struct sys_lease_context *ctx = talloc_get_type(private_data, + struct sys_lease_context); + siginfo_t *info = (siginfo_t *)_info; + struct linux_lease_pending *c; + int got_fd = info->si_fd; + + for (c = leases; c; c = c->next) { + int *fd = (int *)c->e.fd; + + if (got_fd == *fd) { + break; + } + } + + if (!c) { + return; + } + + ctx->break_send(ctx->msg_ctx, &c->e, OPLOCK_BREAK_TO_NONE); +} + +static int linux_lease_pending_destructor(struct linux_lease_pending *p) +{ + int ret; + int *fd = (int *)p->e.fd; + + DLIST_REMOVE(leases, p); + + if (*fd == -1) { + return 0; + } + + ret = fcntl(*fd, F_SETLEASE, F_UNLCK); + if (ret == -1) { + DEBUG(0,("%s: failed to remove oplock: %s\n", + __FUNCTION__, strerror(errno))); + } + + return 0; +} + +static NTSTATUS linux_lease_init(struct sys_lease_context *ctx) +{ + struct tevent_signal *se; + + se = tevent_add_signal(ctx->event_ctx, ctx, + LINUX_LEASE_RT_SIGNAL, SA_SIGINFO, + linux_lease_signal_handler, ctx); + NT_STATUS_HAVE_NO_MEMORY(se); + + return NT_STATUS_OK; +} + +static NTSTATUS linux_lease_setup(struct sys_lease_context *ctx, + struct opendb_entry *e) +{ + int ret; + int *fd = (int *)e->fd; + struct linux_lease_pending *p; + + if (e->oplock_level == OPLOCK_NONE) { + e->fd = NULL; + return NT_STATUS_OK; + } else if (e->oplock_level == OPLOCK_LEVEL_II) { + /* + * the linux kernel doesn't support level2 oplocks + * so fix up the granted oplock level + */ + e->oplock_level = OPLOCK_NONE; + e->allow_level_II_oplock = false; + e->fd = NULL; + return NT_STATUS_OK; + } + + p = talloc(ctx, struct linux_lease_pending); + NT_STATUS_HAVE_NO_MEMORY(p); + + p->ctx = ctx; + p->e = *e; + + ret = fcntl(*fd, F_SETSIG, LINUX_LEASE_RT_SIGNAL); + if (ret == -1) { + talloc_free(p); + return map_nt_error_from_unix_common(errno); + } + + ret = fcntl(*fd, F_SETLEASE, F_WRLCK); + if (ret == -1) { + talloc_free(p); + return map_nt_error_from_unix_common(errno); + } + + DLIST_ADD(leases, p); + + talloc_set_destructor(p, linux_lease_pending_destructor); + + return NT_STATUS_OK; +} + +static NTSTATUS linux_lease_remove(struct sys_lease_context *ctx, + struct opendb_entry *e); + +static NTSTATUS linux_lease_update(struct sys_lease_context *ctx, + struct opendb_entry *e) +{ + struct linux_lease_pending *c; + + for (c = leases; c; c = c->next) { + if (c->e.fd == e->fd) { + break; + } + } + + if (!c) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + /* + * set the fd pointer to NULL so that the caller + * will not call the remove function as the oplock + * is already removed + */ + e->fd = NULL; + + talloc_free(c); + + return NT_STATUS_OK; +} + +static NTSTATUS linux_lease_remove(struct sys_lease_context *ctx, + struct opendb_entry *e) +{ + struct linux_lease_pending *c; + + for (c = leases; c; c = c->next) { + if (c->e.fd == e->fd) { + break; + } + } + + if (!c) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + talloc_free(c); + + return NT_STATUS_OK; +} + +static struct sys_lease_ops linux_lease_ops = { + .name = "linux", + .init = linux_lease_init, + .setup = linux_lease_setup, + .update = linux_lease_update, + .remove = linux_lease_remove +}; + +/* + initialise the linux lease module + */ +NTSTATUS sys_lease_linux_init(TALLOC_CTX *ctx) +{ + /* register ourselves as a system lease module */ + return sys_lease_register(ctx, &linux_lease_ops); +} diff --git a/source4/ntvfs/sysdep/sys_notify.c b/source4/ntvfs/sysdep/sys_notify.c new file mode 100644 index 0000000..6991461 --- /dev/null +++ b/source4/ntvfs/sysdep/sys_notify.c @@ -0,0 +1,151 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Andrew Tridgell 2006 + + 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/>. +*/ + +/* + abstract the various kernel interfaces to change notify into a + single Samba friendly interface +*/ + +#include "includes.h" +#include "system/filesys.h" +#include "ntvfs/sysdep/sys_notify.h" +#include <tevent.h> +#include "../lib/util/dlinklist.h" +#include "param/param.h" +#include "lib/util/samba_modules.h" + +#undef strcasecmp + +/* list of registered backends */ +static struct sys_notify_backend *backends; +static uint32_t num_backends; + +#define NOTIFY_BACKEND "notify:backend" + +/* + initialise a system change notify backend +*/ +_PUBLIC_ struct sys_notify_context *sys_notify_context_create(struct share_config *scfg, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev) +{ + struct sys_notify_context *ctx; + const char *bname; + int i; + + if (num_backends == 0) { + return NULL; + } + + if (ev == NULL) { + return NULL; + } + + ctx = talloc_zero(mem_ctx, struct sys_notify_context); + if (ctx == NULL) { + return NULL; + } + + ctx->ev = ev; + + bname = share_string_option(ctx, scfg, NOTIFY_BACKEND, NULL); + if (!bname) { + if (num_backends) { + bname = backends[0].name; + } else { + bname = "__unknown__"; + } + } + + for (i=0;i<num_backends;i++) { + char *enable_opt_name; + bool enabled; + + enable_opt_name = talloc_asprintf(mem_ctx, "notify:%s", + backends[i].name); + enabled = share_bool_option(scfg, enable_opt_name, true); + talloc_free(enable_opt_name); + + if (!enabled) + continue; + + if (strcasecmp(backends[i].name, bname) == 0) { + bname = backends[i].name; + break; + } + } + + ctx->name = bname; + ctx->notify_watch = NULL; + + if (i < num_backends) { + ctx->notify_watch = backends[i].notify_watch; + } + + return ctx; +} + +/* + add a watch + + note that this call must modify the e->filter and e->subdir_filter + bits to remove ones handled by this backend. Any remaining bits will + be handled by the generic notify layer +*/ +_PUBLIC_ NTSTATUS sys_notify_watch(struct sys_notify_context *ctx, + struct notify_entry *e, + sys_notify_callback_t callback, + void *private_data, void *handle) +{ + if (!ctx->notify_watch) { + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + return ctx->notify_watch(ctx, e, callback, private_data, handle); +} + +/* + register a notify backend +*/ +_PUBLIC_ NTSTATUS sys_notify_register(TALLOC_CTX *ctx, + struct sys_notify_backend *backend) +{ + struct sys_notify_backend *b; + b = talloc_realloc(ctx, backends, + struct sys_notify_backend, num_backends+1); + NT_STATUS_HAVE_NO_MEMORY(b); + backends = b; + backends[num_backends] = *backend; + num_backends++; + return NT_STATUS_OK; +} + +_PUBLIC_ NTSTATUS sys_notify_init(void) +{ + static bool initialized = false; +#define _MODULE_PROTO(init) extern NTSTATUS init(TALLOC_CTX *); + STATIC_sys_notify_MODULES_PROTO; + init_module_fn static_init[] = { STATIC_sys_notify_MODULES }; + + if (initialized) return NT_STATUS_OK; + initialized = true; + + run_init_functions(NULL, static_init); + + return NT_STATUS_OK; +} diff --git a/source4/ntvfs/sysdep/sys_notify.h b/source4/ntvfs/sysdep/sys_notify.h new file mode 100644 index 0000000..9e10f14 --- /dev/null +++ b/source4/ntvfs/sysdep/sys_notify.h @@ -0,0 +1,54 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Andrew Tridgell 2006 + + 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 "librpc/gen_ndr/notify.h" +#include "param/share.h" + +struct sys_notify_context; + +typedef void (*sys_notify_callback_t)(struct sys_notify_context *, + void *, struct notify_event *ev); + +typedef NTSTATUS (*notify_watch_t)(struct sys_notify_context *ctx, + struct notify_entry *e, + sys_notify_callback_t callback, + void *private_data, + void *handle_p); + +struct sys_notify_context { + struct tevent_context *ev; + void *private_data; /* for use of backend */ + const char *name; + notify_watch_t notify_watch; +}; + +struct sys_notify_backend { + const char *name; + notify_watch_t notify_watch; +}; + +NTSTATUS sys_notify_register(TALLOC_CTX *ctx, + struct sys_notify_backend *backend); +struct sys_notify_context *sys_notify_context_create(struct share_config *scfg, + TALLOC_CTX *mem_ctx, + struct tevent_context *ev); +NTSTATUS sys_notify_watch(struct sys_notify_context *ctx, struct notify_entry *e, + sys_notify_callback_t callback, void *private_data, + void *handle); +NTSTATUS sys_notify_init(void); diff --git a/source4/ntvfs/sysdep/wscript_build b/source4/ntvfs/sysdep/wscript_build new file mode 100644 index 0000000..bfb4e55 --- /dev/null +++ b/source4/ntvfs/sysdep/wscript_build @@ -0,0 +1,31 @@ +#!/usr/bin/env python + +bld.SAMBA_MODULE('sys_notify_inotify', + source='inotify.c', + subsystem='sys_notify', + init_function='sys_notify_inotify_init', + deps='events inotify', + enabled = bld.CONFIG_SET('HAVE_LINUX_INOTIFY') + ) + + +bld.SAMBA_SUBSYSTEM('sys_notify', + source='sys_notify.c', + deps='talloc tevent' + ) + + +bld.SAMBA_MODULE('sys_lease_linux', + source='sys_lease_linux.c', + deps='tevent', + subsystem='sys_lease', + init_function='sys_lease_linux_init', + enabled = bld.CONFIG_SET('HAVE_F_SETLEASE_DECL') + ) + + +bld.SAMBA_SUBSYSTEM('sys_lease', + source='sys_lease.c', + deps='talloc' + ) + diff --git a/source4/ntvfs/sysdep/wscript_configure b/source4/ntvfs/sysdep/wscript_configure new file mode 100644 index 0000000..2035884 --- /dev/null +++ b/source4/ntvfs/sysdep/wscript_configure @@ -0,0 +1,13 @@ +#!/usr/bin/env python + +import sys + +# Check for inotify support (Skip if we are SunOS) +#NOTE: illumos provides sys/inotify.h but is not an exact match for linux +host_os = sys.platform +if host_os.rfind('sunos') == -1: + conf.CHECK_HEADERS('sys/inotify.h', add_headers=False) + if (conf.CONFIG_SET('HAVE_SYS_INOTIFY_H')): + conf.DEFINE('HAVE_LINUX_INOTIFY', 1) + +conf.CHECK_DECLS('SA_SIGINFO', headers='signal.h', reverse=True) diff --git a/source4/ntvfs/unixuid/vfs_unixuid.c b/source4/ntvfs/unixuid/vfs_unixuid.c new file mode 100644 index 0000000..a7729d2 --- /dev/null +++ b/source4/ntvfs/unixuid/vfs_unixuid.c @@ -0,0 +1,724 @@ +/* + Unix SMB/CIFS implementation. + + A pass-through NTVFS module to setup a security context using unix + uid/gid + + Copyright (C) Andrew Tridgell 2004 + + 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/filesys.h" +#include "system/passwd.h" +#include "auth/auth.h" +#include "ntvfs/ntvfs.h" +#include "libcli/wbclient/wbclient.h" +#define TEVENT_DEPRECATED +#include <tevent.h> +#include "../lib/util/setid.h" + +NTSTATUS ntvfs_unixuid_init(TALLOC_CTX *); + +struct unixuid_private { + struct security_unix_token *last_sec_ctx; + struct security_token *last_token; +}; + + +/* + pull the current security context into a security_unix_token +*/ +static struct security_unix_token *save_unix_security(TALLOC_CTX *mem_ctx) +{ + struct security_unix_token *sec = talloc(mem_ctx, struct security_unix_token); + if (sec == NULL) { + return NULL; + } + sec->uid = geteuid(); + sec->gid = getegid(); + sec->ngroups = getgroups(0, NULL); + if (sec->ngroups == -1) { + talloc_free(sec); + return NULL; + } + sec->groups = talloc_array(sec, gid_t, sec->ngroups); + if (sec->groups == NULL) { + talloc_free(sec); + return NULL; + } + + if (getgroups(sec->ngroups, sec->groups) != sec->ngroups) { + talloc_free(sec); + return NULL; + } + + return sec; +} + +/* + set the current security context from a security_unix_token +*/ +static NTSTATUS set_unix_security(struct security_unix_token *sec) +{ + samba_seteuid(0); + + if (samba_setgroups(sec->ngroups, sec->groups) != 0) { + DBG_ERR("*** samba_setgroups failed\n"); + return NT_STATUS_ACCESS_DENIED; + } + if (samba_setegid(sec->gid) != 0) { + DBG_ERR("*** samba_setegid(%u) failed\n", sec->gid); + return NT_STATUS_ACCESS_DENIED; + } + if (samba_seteuid(sec->uid) != 0) { + DBG_ERR("*** samba_seteuid(%u) failed\n", sec->uid); + return NT_STATUS_ACCESS_DENIED; + } + return NT_STATUS_OK; +} + +static int unixuid_nesting_level; + +/* + called at the start and end of a tevent nesting loop. Needs to save/restore + unix security context + */ +static int unixuid_event_nesting_hook(struct tevent_context *ev, + void *private_data, + uint32_t level, + bool begin, + void *stack_ptr, + const char *location) +{ + struct security_unix_token *sec_ctx; + + if (unixuid_nesting_level == 0) { + /* we don't need to do anything unless we are nested + inside of a call in this module */ + return 0; + } + + if (begin) { + sec_ctx = save_unix_security(ev); + if (sec_ctx == NULL) { + DEBUG(0,("%s: Failed to save security context\n", location)); + return -1; + } + *(struct security_unix_token **)stack_ptr = sec_ctx; + if (samba_seteuid(0) != 0 || samba_setegid(0) != 0) { + DEBUG(0,("%s: Failed to change to root\n", location)); + return -1; + } + } else { + /* called when we come out of a nesting level */ + NTSTATUS status; + + sec_ctx = *(struct security_unix_token **)stack_ptr; + if (sec_ctx == NULL) { + /* this happens the first time this function + is called, as we install the hook while + inside an event in unixuid_connect() */ + return 0; + } + + sec_ctx = talloc_get_type_abort(sec_ctx, struct security_unix_token); + status = set_unix_security(sec_ctx); + talloc_free(sec_ctx); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("%s: Failed to revert security context (%s)\n", + location, nt_errstr(status))); + return -1; + } + } + + return 0; +} + + +/* + form a security_unix_token from the current security_token +*/ +static NTSTATUS nt_token_to_unix_security(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + struct security_token *token, + struct security_unix_token **sec) +{ + return security_token_to_unix_token(req, token, sec); +} + +/* + setup our unix security context according to the session authentication info +*/ +static NTSTATUS unixuid_setup_security(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, struct security_unix_token **sec) +{ + struct unixuid_private *priv = ntvfs->private_data; + struct security_token *token; + struct security_unix_token *newsec; + NTSTATUS status; + + /* If we are asked to set up, but have not had a successful + * session setup or tree connect, then these may not be filled + * in. ACCESS_DENIED is the right error code here */ + if (req->session_info == NULL || priv == NULL) { + return NT_STATUS_ACCESS_DENIED; + } + + token = req->session_info->security_token; + + *sec = save_unix_security(ntvfs); + if (*sec == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (token == priv->last_token) { + newsec = priv->last_sec_ctx; + } else { + status = nt_token_to_unix_security(ntvfs, req, token, &newsec); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(*sec); + return status; + } + if (priv->last_sec_ctx) { + talloc_free(priv->last_sec_ctx); + } + priv->last_sec_ctx = newsec; + priv->last_token = token; + talloc_steal(priv, newsec); + } + + status = set_unix_security(newsec); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(*sec); + return status; + } + + return NT_STATUS_OK; +} + +/* + this pass through macro operates on request contexts +*/ +#define PASS_THRU_REQ(ntvfs, req, op, args) do { \ + NTSTATUS status2; \ + struct security_unix_token *sec; \ + status = unixuid_setup_security(ntvfs, req, &sec); \ + NT_STATUS_NOT_OK_RETURN(status); \ + unixuid_nesting_level++; \ + status = ntvfs_next_##op args; \ + unixuid_nesting_level--; \ + status2 = set_unix_security(sec); \ + talloc_free(sec); \ + if (!NT_STATUS_IS_OK(status2)) smb_panic("Unable to reset security context"); \ +} while (0) + + + +/* + connect to a share - used when a tree_connect operation comes in. +*/ +static NTSTATUS unixuid_connect(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_tcon *tcon) +{ + struct unixuid_private *priv; + NTSTATUS status; + + priv = talloc(ntvfs, struct unixuid_private); + if (!priv) { + return NT_STATUS_NO_MEMORY; + } + + priv->last_sec_ctx = NULL; + priv->last_token = NULL; + ntvfs->private_data = priv; + + tevent_loop_set_nesting_hook(ntvfs->ctx->event_ctx, + unixuid_event_nesting_hook, + &unixuid_nesting_level); + + /* we don't use PASS_THRU_REQ here, as the connect operation runs with + root privileges. This allows the backends to setup any database + links they might need during the connect. */ + status = ntvfs_next_connect(ntvfs, req, tcon); + + return status; +} + +/* + disconnect from a share +*/ +static NTSTATUS unixuid_disconnect(struct ntvfs_module_context *ntvfs) +{ + struct unixuid_private *priv = ntvfs->private_data; + NTSTATUS status; + + talloc_free(priv); + ntvfs->private_data = NULL; + + status = ntvfs_next_disconnect(ntvfs); + + return status; +} + + +/* + delete a file +*/ +static NTSTATUS unixuid_unlink(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_unlink *unl) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, unlink, (ntvfs, req, unl)); + + return status; +} + +/* + ioctl interface +*/ +static NTSTATUS unixuid_ioctl(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_ioctl *io) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, ioctl, (ntvfs, req, io)); + + return status; +} + +/* + check if a directory exists +*/ +static NTSTATUS unixuid_chkpath(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_chkpath *cp) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, chkpath, (ntvfs, req, cp)); + + return status; +} + +/* + return info on a pathname +*/ +static NTSTATUS unixuid_qpathinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_fileinfo *info) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, qpathinfo, (ntvfs, req, info)); + + return status; +} + +/* + query info on a open file +*/ +static NTSTATUS unixuid_qfileinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_fileinfo *info) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, qfileinfo, (ntvfs, req, info)); + + return status; +} + + +/* + set info on a pathname +*/ +static NTSTATUS unixuid_setpathinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_setfileinfo *st) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, setpathinfo, (ntvfs, req, st)); + + return status; +} + +/* + open a file +*/ +static NTSTATUS unixuid_open(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_open *io) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, open, (ntvfs, req, io)); + + return status; +} + +/* + create a directory +*/ +static NTSTATUS unixuid_mkdir(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_mkdir *md) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, mkdir, (ntvfs, req, md)); + + return status; +} + +/* + remove a directory +*/ +static NTSTATUS unixuid_rmdir(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, struct smb_rmdir *rd) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, rmdir, (ntvfs, req, rd)); + + return status; +} + +/* + rename a set of files +*/ +static NTSTATUS unixuid_rename(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_rename *ren) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, rename, (ntvfs, req, ren)); + + return status; +} + +/* + copy a set of files +*/ +static NTSTATUS unixuid_copy(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, struct smb_copy *cp) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, copy, (ntvfs, req, cp)); + + return status; +} + +/* + read from a file +*/ +static NTSTATUS unixuid_read(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_read *rd) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, read, (ntvfs, req, rd)); + + return status; +} + +/* + write to a file +*/ +static NTSTATUS unixuid_write(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_write *wr) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, write, (ntvfs, req, wr)); + + return status; +} + +/* + seek in a file +*/ +static NTSTATUS unixuid_seek(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_seek *io) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, seek, (ntvfs, req, io)); + + return status; +} + +/* + flush a file +*/ +static NTSTATUS unixuid_flush(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_flush *io) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, flush, (ntvfs, req, io)); + + return status; +} + +/* + close a file +*/ +static NTSTATUS unixuid_close(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_close *io) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, close, (ntvfs, req, io)); + + return status; +} + +/* + exit - closing files +*/ +static NTSTATUS unixuid_exit(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, exit, (ntvfs, req)); + + return status; +} + +/* + logoff - closing files +*/ +static NTSTATUS unixuid_logoff(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req) +{ + struct unixuid_private *priv = ntvfs->private_data; + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, logoff, (ntvfs, req)); + + priv->last_token = NULL; + + return status; +} + +/* + async setup +*/ +static NTSTATUS unixuid_async_setup(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + void *private_data) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, async_setup, (ntvfs, req, private_data)); + + return status; +} + +/* + cancel an async request +*/ +static NTSTATUS unixuid_cancel(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, cancel, (ntvfs, req)); + + return status; +} + +/* + change notify +*/ +static NTSTATUS unixuid_notify(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_notify *info) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, notify, (ntvfs, req, info)); + + return status; +} + +/* + lock a byte range +*/ +static NTSTATUS unixuid_lock(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_lock *lck) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, lock, (ntvfs, req, lck)); + + return status; +} + +/* + set info on a open file +*/ +static NTSTATUS unixuid_setfileinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_setfileinfo *info) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, setfileinfo, (ntvfs, req, info)); + + return status; +} + + +/* + return filesystem space info +*/ +static NTSTATUS unixuid_fsinfo(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_fsinfo *fs) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, fsinfo, (ntvfs, req, fs)); + + return status; +} + +/* + return print queue info +*/ +static NTSTATUS unixuid_lpq(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_lpq *lpq) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, lpq, (ntvfs, req, lpq)); + + return status; +} + +/* + list files in a directory matching a wildcard pattern +*/ +static NTSTATUS unixuid_search_first(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_search_first *io, + void *search_private, + bool (*callback)(void *, const union smb_search_data *)) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, search_first, (ntvfs, req, io, search_private, callback)); + + return status; +} + +/* continue a search */ +static NTSTATUS unixuid_search_next(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_search_next *io, + void *search_private, + bool (*callback)(void *, const union smb_search_data *)) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, search_next, (ntvfs, req, io, search_private, callback)); + + return status; +} + +/* close a search */ +static NTSTATUS unixuid_search_close(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_search_close *io) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, search_close, (ntvfs, req, io)); + + return status; +} + +/* SMBtrans - not used on file shares */ +static NTSTATUS unixuid_trans(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, struct smb_trans2 *trans2) +{ + NTSTATUS status; + + PASS_THRU_REQ(ntvfs, req, trans, (ntvfs, req, trans2)); + + return status; +} + +/* + initialise the unixuid backend, registering ourselves with the ntvfs subsystem + */ +NTSTATUS ntvfs_unixuid_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret; + struct ntvfs_ops ops; + NTVFS_CURRENT_CRITICAL_SIZES(vers); + + ZERO_STRUCT(ops); + + /* fill in all the operations */ + ops.connect_fn = unixuid_connect; + ops.disconnect_fn = unixuid_disconnect; + ops.unlink_fn = unixuid_unlink; + ops.chkpath_fn = unixuid_chkpath; + ops.qpathinfo_fn = unixuid_qpathinfo; + ops.setpathinfo_fn = unixuid_setpathinfo; + ops.open_fn = unixuid_open; + ops.mkdir_fn = unixuid_mkdir; + ops.rmdir_fn = unixuid_rmdir; + ops.rename_fn = unixuid_rename; + ops.copy_fn = unixuid_copy; + ops.ioctl_fn = unixuid_ioctl; + ops.read_fn = unixuid_read; + ops.write_fn = unixuid_write; + ops.seek_fn = unixuid_seek; + ops.flush_fn = unixuid_flush; + ops.close_fn = unixuid_close; + ops.exit_fn = unixuid_exit; + ops.lock_fn = unixuid_lock; + ops.setfileinfo_fn = unixuid_setfileinfo; + ops.qfileinfo_fn = unixuid_qfileinfo; + ops.fsinfo_fn = unixuid_fsinfo; + ops.lpq_fn = unixuid_lpq; + ops.search_first_fn = unixuid_search_first; + ops.search_next_fn = unixuid_search_next; + ops.search_close_fn = unixuid_search_close; + ops.trans_fn = unixuid_trans; + ops.logoff_fn = unixuid_logoff; + ops.async_setup_fn = unixuid_async_setup; + ops.cancel_fn = unixuid_cancel; + ops.notify_fn = unixuid_notify; + + ops.name = "unixuid"; + + /* we register under all 3 backend types, as we are not type specific */ + ops.type = NTVFS_DISK; + ret = ntvfs_register(&ops, &vers); + if (!NT_STATUS_IS_OK(ret)) goto failed; + + ops.type = NTVFS_PRINT; + ret = ntvfs_register(&ops, &vers); + if (!NT_STATUS_IS_OK(ret)) goto failed; + + ops.type = NTVFS_IPC; + ret = ntvfs_register(&ops, &vers); + if (!NT_STATUS_IS_OK(ret)) goto failed; + +failed: + return ret; +} diff --git a/source4/ntvfs/unixuid/wscript_build b/source4/ntvfs/unixuid/wscript_build new file mode 100644 index 0000000..56fd42d --- /dev/null +++ b/source4/ntvfs/unixuid/wscript_build @@ -0,0 +1,9 @@ +#!/usr/bin/env python + +bld.SAMBA_MODULE('ntvfs_unixuid', + source='vfs_unixuid.c', + subsystem='ntvfs', + init_function='ntvfs_unixuid_init', + deps='auth_unix_token talloc' + ) + diff --git a/source4/ntvfs/wscript_build b/source4/ntvfs/wscript_build new file mode 100644 index 0000000..3b81216 --- /dev/null +++ b/source4/ntvfs/wscript_build @@ -0,0 +1,40 @@ +#!/usr/bin/env python + +bld.SAMBA_LIBRARY('ntvfs', + source='ntvfs_base.c ntvfs_generic.c ntvfs_interface.c ntvfs_util.c', + autoproto='ntvfs_proto.h', + deps='tevent samba-modules', + private_library=True, + enabled=bld.CONFIG_SET('WITH_NTVFS_FILESERVER') + ) + +bld.RECURSE('posix') +if bld.CONFIG_SET('WITH_NTVFS_FILESERVER'): + bld.RECURSE('common') + bld.RECURSE('unixuid') + bld.RECURSE('sysdep') + + bld.SAMBA_MODULE('ntvfs_cifs', + source='cifs/vfs_cifs.c', + subsystem='ntvfs', + init_function='ntvfs_cifs_init', + deps='LIBCLI_SMB smbclient-raw param_options' + ) + + + bld.SAMBA_MODULE('ntvfs_simple', + source='simple/vfs_simple.c simple/svfs_util.c', + autoproto='simple/proto.h', + subsystem='ntvfs', + init_function='ntvfs_simple_init', + deps='talloc' + ) + + + bld.SAMBA_MODULE('ntvfs_ipc', + source='ipc/vfs_ipc.c ipc/ipc_rap.c ipc/rap_server.c', + autoproto='ipc/proto.h', + subsystem='ntvfs', + init_function='ntvfs_ipc_init', + deps='NDR_NAMED_PIPE_AUTH npa_tstream gssapi samba-credentials DCERPC_SHARE' + ) |