summaryrefslogtreecommitdiffstats
path: root/source3/libsmb/libsmb_dir.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
commit8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch)
tree4099e8021376c7d8c05bdf8503093d80e9c7bad0 /source3/libsmb/libsmb_dir.c
parentInitial commit. (diff)
downloadsamba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.tar.xz
samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.zip
Adding upstream version 2:4.20.0+dfsg.upstream/2%4.20.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source3/libsmb/libsmb_dir.c')
-rw-r--r--source3/libsmb/libsmb_dir.c2726
1 files changed, 2726 insertions, 0 deletions
diff --git a/source3/libsmb/libsmb_dir.c b/source3/libsmb/libsmb_dir.c
new file mode 100644
index 0000000..34d6731
--- /dev/null
+++ b/source3/libsmb/libsmb_dir.c
@@ -0,0 +1,2726 @@
+/*
+ Unix SMB/Netbios implementation.
+ SMB client library implementation
+ Copyright (C) Andrew Tridgell 1998
+ Copyright (C) Richard Sharpe 2000, 2002
+ Copyright (C) John Terpstra 2000
+ Copyright (C) Tom Jansen (Ninja ISD) 2002
+ Copyright (C) Derrell Lipman 2003-2008
+ Copyright (C) Jeremy Allison 2007, 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 "libsmb/namequery.h"
+#include "libsmb/libsmb.h"
+#include "libsmbclient.h"
+#include "libsmb_internal.h"
+#include "rpc_client/cli_pipe.h"
+#include "../librpc/gen_ndr/ndr_srvsvc_c.h"
+#include "libsmb/nmblib.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "../libcli/security/security.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/util/time_basic.h"
+#include "lib/util/string_wrappers.h"
+
+/*
+ * Routine to open a directory
+ * We accept the URL syntax explained in SMBC_parse_path(), above.
+ */
+
+static void remove_dirplus(SMBCFILE *dir)
+{
+ struct smbc_dirplus_list *d = NULL;
+
+ d = dir->dirplus_list;
+ while (d != NULL) {
+ struct smbc_dirplus_list *f = d;
+ d = d->next;
+
+ SAFE_FREE(f->smb_finfo->short_name);
+ SAFE_FREE(f->smb_finfo->name);
+ SAFE_FREE(f->smb_finfo);
+ SAFE_FREE(f);
+ }
+
+ dir->dirplus_list = NULL;
+ dir->dirplus_end = NULL;
+ dir->dirplus_next = NULL;
+}
+
+static void
+remove_dir(SMBCFILE *dir)
+{
+ struct smbc_dir_list *d,*f;
+
+ d = dir->dir_list;
+ while (d) {
+
+ f = d; d = d->next;
+
+ SAFE_FREE(f->dirent);
+ SAFE_FREE(f);
+
+ }
+
+ dir->dir_list = dir->dir_end = dir->dir_next = NULL;
+
+}
+
+static int
+add_dirent(SMBCFILE *dir,
+ const char *name,
+ const char *comment,
+ uint32_t type)
+{
+ struct smbc_dirent *dirent;
+ int size;
+ int name_length = (name == NULL ? 0 : strlen(name));
+ int comment_len = (comment == NULL ? 0 : strlen(comment));
+
+ /*
+ * Allocate space for the dirent, which must be increased by the
+ * size of the name and the comment and 1 each for the null terminator.
+ */
+
+ size = sizeof(struct smbc_dirent) + name_length + comment_len + 2;
+
+ dirent = (struct smbc_dirent *)SMB_MALLOC(size);
+
+ if (!dirent) {
+
+ dir->dir_error = ENOMEM;
+ return -1;
+
+ }
+
+ ZERO_STRUCTP(dirent);
+
+ if (dir->dir_list == NULL) {
+
+ dir->dir_list = SMB_MALLOC_P(struct smbc_dir_list);
+ if (!dir->dir_list) {
+
+ SAFE_FREE(dirent);
+ dir->dir_error = ENOMEM;
+ return -1;
+
+ }
+ ZERO_STRUCTP(dir->dir_list);
+
+ dir->dir_end = dir->dir_next = dir->dir_list;
+ }
+ else {
+
+ dir->dir_end->next = SMB_MALLOC_P(struct smbc_dir_list);
+
+ if (!dir->dir_end->next) {
+
+ SAFE_FREE(dirent);
+ dir->dir_error = ENOMEM;
+ return -1;
+
+ }
+ ZERO_STRUCTP(dir->dir_end->next);
+
+ dir->dir_end = dir->dir_end->next;
+ }
+
+ dir->dir_end->next = NULL;
+ dir->dir_end->dirent = dirent;
+
+ dirent->smbc_type = type;
+ dirent->namelen = name_length;
+ dirent->commentlen = comment_len;
+ dirent->dirlen = size;
+
+ /*
+ * dirent->namelen + 1 includes the null (no null termination needed)
+ * Ditto for dirent->commentlen.
+ * The space for the two null bytes was allocated.
+ */
+ strncpy(dirent->name, (name?name:""), dirent->namelen + 1);
+ dirent->comment = (char *)(&dirent->name + dirent->namelen + 1);
+ strncpy(dirent->comment, (comment?comment:""), dirent->commentlen + 1);
+
+ return 0;
+
+}
+
+static int add_dirplus(SMBCFILE *dir, struct file_info *finfo)
+{
+ struct smbc_dirplus_list *new_entry = NULL;
+ struct libsmb_file_info *info = NULL;
+
+ new_entry = SMB_MALLOC_P(struct smbc_dirplus_list);
+ if (new_entry == NULL) {
+ dir->dir_error = ENOMEM;
+ return -1;
+ }
+ ZERO_STRUCTP(new_entry);
+ new_entry->ino = finfo->ino;
+
+ info = SMB_MALLOC_P(struct libsmb_file_info);
+ if (info == NULL) {
+ SAFE_FREE(new_entry);
+ dir->dir_error = ENOMEM;
+ return -1;
+ }
+
+ ZERO_STRUCTP(info);
+
+ info->btime_ts = finfo->btime_ts;
+ info->atime_ts = finfo->atime_ts;
+ info->ctime_ts = finfo->ctime_ts;
+ info->mtime_ts = finfo->mtime_ts;
+ info->gid = finfo->gid;
+ info->attrs = finfo->attr;
+ info->size = finfo->size;
+ info->uid = finfo->uid;
+ info->name = SMB_STRDUP(finfo->name);
+ if (info->name == NULL) {
+ SAFE_FREE(info);
+ SAFE_FREE(new_entry);
+ dir->dir_error = ENOMEM;
+ return -1;
+ }
+
+ if (finfo->short_name) {
+ info->short_name = SMB_STRDUP(finfo->short_name);
+ } else {
+ info->short_name = SMB_STRDUP("");
+ }
+
+ if (info->short_name == NULL) {
+ SAFE_FREE(info->name);
+ SAFE_FREE(info);
+ SAFE_FREE(new_entry);
+ dir->dir_error = ENOMEM;
+ return -1;
+ }
+ new_entry->smb_finfo = info;
+
+ /* Now add to the list. */
+ if (dir->dirplus_list == NULL) {
+ /* Empty list - point everything at new_entry. */
+ dir->dirplus_list = new_entry;
+ dir->dirplus_end = new_entry;
+ dir->dirplus_next = new_entry;
+ } else {
+ /* Append to list but leave the ->next cursor alone. */
+ dir->dirplus_end->next = new_entry;
+ dir->dirplus_end = new_entry;
+ }
+
+ return 0;
+}
+
+static void
+list_unique_wg_fn(const char *name,
+ uint32_t type,
+ const char *comment,
+ void *state)
+{
+ SMBCFILE *dir = (SMBCFILE *)state;
+ struct smbc_dir_list *dir_list;
+ struct smbc_dirent *dirent;
+ int dirent_type;
+ int do_remove = 0;
+
+ dirent_type = dir->dir_type;
+
+ if (add_dirent(dir, name, comment, dirent_type) < 0) {
+ /* An error occurred, what do we do? */
+ /* FIXME: Add some code here */
+ /* Change cli_NetServerEnum to take a fn
+ returning NTSTATUS... JRA. */
+ }
+
+ /* Point to the one just added */
+ dirent = dir->dir_end->dirent;
+
+ /* See if this was a duplicate */
+ for (dir_list = dir->dir_list;
+ dir_list != dir->dir_end;
+ dir_list = dir_list->next) {
+ if (! do_remove &&
+ strcmp(dir_list->dirent->name, dirent->name) == 0) {
+ /* Duplicate. End end of list need to be removed. */
+ do_remove = 1;
+ }
+
+ if (do_remove && dir_list->next == dir->dir_end) {
+ /* Found the end of the list. Remove it. */
+ dir->dir_end = dir_list;
+ free(dir_list->next);
+ free(dirent);
+ dir_list->next = NULL;
+ break;
+ }
+ }
+}
+
+static void
+list_fn(const char *name,
+ uint32_t type,
+ const char *comment,
+ void *state)
+{
+ SMBCFILE *dir = (SMBCFILE *)state;
+ int dirent_type;
+
+ /*
+ * We need to process the type a little ...
+ *
+ * Disk share = 0x00000000
+ * Print share = 0x00000001
+ * Comms share = 0x00000002 (obsolete?)
+ * IPC$ share = 0x00000003
+ *
+ * administrative shares:
+ * ADMIN$, IPC$, C$, D$, E$ ... are type |= 0x80000000
+ */
+
+ if (dir->dir_type == SMBC_FILE_SHARE) {
+ switch (type) {
+ case 0 | 0x80000000:
+ case 0:
+ dirent_type = SMBC_FILE_SHARE;
+ break;
+
+ case 1:
+ dirent_type = SMBC_PRINTER_SHARE;
+ break;
+
+ case 2:
+ dirent_type = SMBC_COMMS_SHARE;
+ break;
+
+ case 3 | 0x80000000:
+ case 3:
+ dirent_type = SMBC_IPC_SHARE;
+ break;
+
+ default:
+ dirent_type = SMBC_FILE_SHARE; /* FIXME, error? */
+ break;
+ }
+ }
+ else {
+ dirent_type = dir->dir_type;
+ }
+
+ if (add_dirent(dir, name, comment, dirent_type) < 0) {
+ /* An error occurred, what do we do? */
+ /* FIXME: Add some code here */
+ /* Change cli_NetServerEnum to take a fn
+ returning NTSTATUS... JRA. */
+ }
+}
+
+static NTSTATUS
+dir_list_fn(struct file_info *finfo,
+ const char *mask,
+ void *state)
+{
+ SMBCFILE *dirp = (SMBCFILE *)state;
+ int ret;
+
+ if (add_dirent((SMBCFILE *)state, finfo->name, "",
+ (finfo->attr&FILE_ATTRIBUTE_DIRECTORY?SMBC_DIR:SMBC_FILE)) < 0) {
+ SMBCFILE *dir = (SMBCFILE *)state;
+ return map_nt_error_from_unix(dir->dir_error);
+ }
+ ret = add_dirplus(dirp, finfo);
+ if (ret < 0) {
+ return map_nt_error_from_unix(dirp->dir_error);
+ }
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS
+net_share_enum_rpc(struct cli_state *cli,
+ void (*fn)(const char *name,
+ uint32_t type,
+ const char *comment,
+ void *state),
+ void *state)
+{
+ uint32_t i;
+ WERROR result;
+ uint32_t preferred_len = 0xffffffff;
+ uint32_t type;
+ struct srvsvc_NetShareInfoCtr info_ctr;
+ struct srvsvc_NetShareCtr1 ctr1;
+ fstring name = "";
+ fstring comment = "";
+ struct rpc_pipe_client *pipe_hnd = NULL;
+ NTSTATUS nt_status;
+ uint32_t resume_handle = 0;
+ uint32_t total_entries = 0;
+ struct dcerpc_binding_handle *b;
+
+ /* Open the server service pipe */
+ nt_status = cli_rpc_pipe_open_noauth(cli, &ndr_table_srvsvc,
+ &pipe_hnd);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(1, ("net_share_enum_rpc pipe open fail!\n"));
+ goto done;
+ }
+
+ ZERO_STRUCT(info_ctr);
+ ZERO_STRUCT(ctr1);
+
+ info_ctr.level = 1;
+ info_ctr.ctr.ctr1 = &ctr1;
+
+ b = pipe_hnd->binding_handle;
+
+ /* Issue the NetShareEnum RPC call and retrieve the response */
+ nt_status = dcerpc_srvsvc_NetShareEnumAll(b, talloc_tos(),
+ pipe_hnd->desthost,
+ &info_ctr,
+ preferred_len,
+ &total_entries,
+ &resume_handle,
+ &result);
+
+ /* Was it successful? */
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ /* Nope. Go clean up. */
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ /* Nope. Go clean up. */
+ nt_status = werror_to_ntstatus(result);
+ goto done;
+ }
+
+ if (total_entries == 0) {
+ /* Nope. Go clean up. */
+ nt_status = NT_STATUS_NOT_FOUND;
+ goto done;
+ }
+
+ /* For each returned entry... */
+ for (i = 0; i < info_ctr.ctr.ctr1->count; i++) {
+
+ /* pull out the share name */
+ fstrcpy(name, info_ctr.ctr.ctr1->array[i].name);
+
+ /* pull out the share's comment */
+ fstrcpy(comment, info_ctr.ctr.ctr1->array[i].comment);
+
+ /* Get the type value */
+ type = info_ctr.ctr.ctr1->array[i].type;
+
+ /* Add this share to the list */
+ (*fn)(name, type, comment, state);
+ }
+
+done:
+ /* Close the server service pipe */
+ TALLOC_FREE(pipe_hnd);
+
+ /* Tell 'em if it worked */
+ return nt_status;
+}
+
+
+/*
+ * Verify that the options specified in a URL are valid
+ */
+int
+SMBC_check_options(char *server,
+ char *share,
+ char *path,
+ char *options)
+{
+ DEBUG(4, ("SMBC_check_options(): server='%s' share='%s' "
+ "path='%s' options='%s'\n",
+ server, share, path, options));
+
+ /* No options at all is always ok */
+ if (! *options) return 0;
+
+ /* Currently, we don't support any options. */
+ return -1;
+}
+
+
+SMBCFILE *
+SMBC_opendir_ctx(SMBCCTX *context,
+ const char *fname)
+{
+ char *server = NULL;
+ char *share = NULL;
+ char *user = NULL;
+ char *password = NULL;
+ char *options = NULL;
+ char *workgroup = NULL;
+ char *path = NULL;
+ size_t path_len = 0;
+ uint16_t port = 0;
+ SMBCSRV *srv = NULL;
+ SMBCFILE *dir = NULL;
+ struct sockaddr_storage rem_ss;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (!context || !context->internal->initialized) {
+ DEBUG(4, ("no valid context\n"));
+ TALLOC_FREE(frame);
+ errno = EINVAL + 8192;
+ return NULL;
+
+ }
+
+ if (!fname) {
+ DEBUG(4, ("no valid fname\n"));
+ TALLOC_FREE(frame);
+ errno = EINVAL + 8193;
+ return NULL;
+ }
+
+ if (SMBC_parse_path(frame,
+ context,
+ fname,
+ &workgroup,
+ &server,
+ &port,
+ &share,
+ &path,
+ &user,
+ &password,
+ &options)) {
+ DEBUG(4, ("no valid path\n"));
+ TALLOC_FREE(frame);
+ errno = EINVAL + 8194;
+ return NULL;
+ }
+
+ DEBUG(4, ("parsed path: fname='%s' server='%s' share='%s' "
+ "path='%s' options='%s'\n",
+ fname, server, share, path, options));
+
+ /* Ensure the options are valid */
+ if (SMBC_check_options(server, share, path, options)) {
+ DEBUG(4, ("unacceptable options (%s)\n", options));
+ TALLOC_FREE(frame);
+ errno = EINVAL + 8195;
+ return NULL;
+ }
+
+ if (!user || user[0] == (char)0) {
+ user = talloc_strdup(frame, smbc_getUser(context));
+ if (!user) {
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return NULL;
+ }
+ }
+
+ dir = SMB_MALLOC_P(SMBCFILE);
+
+ if (!dir) {
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ ZERO_STRUCTP(dir);
+
+ dir->cli_fd = 0;
+ dir->fname = SMB_STRDUP(fname);
+ if (dir->fname == NULL) {
+ SAFE_FREE(dir);
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return NULL;
+ }
+ dir->srv = NULL;
+ dir->offset = 0;
+ dir->file = False;
+ dir->dir_list = dir->dir_next = dir->dir_end = NULL;
+
+ if (server[0] == (char)0) {
+
+ size_t i;
+ size_t count = 0;
+ size_t max_lmb_count;
+ struct sockaddr_storage *ip_list;
+ struct sockaddr_storage server_addr;
+ struct cli_credentials *creds = NULL;
+ NTSTATUS status;
+
+ if (share[0] != (char)0 || path[0] != (char)0) {
+
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ errno = EINVAL + 8196;
+ return NULL;
+ }
+
+ /* Determine how many local master browsers to query */
+ max_lmb_count = (smbc_getOptionBrowseMaxLmbCount(context) == 0
+ ? INT_MAX
+ : smbc_getOptionBrowseMaxLmbCount(context));
+
+ creds = cli_credentials_init(frame);
+ if (creds == NULL) {
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ (void)cli_credentials_set_username(creds, user, CRED_SPECIFIED);
+ (void)cli_credentials_set_password(creds, password, CRED_SPECIFIED);
+
+ /*
+ * We have server and share and path empty but options
+ * requesting that we scan all master browsers for their list
+ * of workgroups/domains. This implies that we must first try
+ * broadcast queries to find all master browsers, and if that
+ * doesn't work, then try our other methods which return only
+ * a single master browser.
+ */
+
+ ip_list = NULL;
+ status = name_resolve_bcast(talloc_tos(),
+ MSBROWSE,
+ 1,
+ &ip_list,
+ &count);
+ if (!NT_STATUS_IS_OK(status))
+ {
+
+ TALLOC_FREE(ip_list);
+
+ if (!find_master_ip(workgroup, &server_addr)) {
+
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ errno = ENOENT;
+ return NULL;
+ }
+
+ ip_list = (struct sockaddr_storage *)talloc_memdup(
+ talloc_tos(), &server_addr,
+ sizeof(server_addr));
+ if (ip_list == NULL) {
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return NULL;
+ }
+ count = 1;
+ }
+
+ for (i = 0; i < count && i < max_lmb_count; i++) {
+ char addr[INET6_ADDRSTRLEN];
+ char *wg_ptr = NULL;
+ struct cli_state *cli = NULL;
+
+ print_sockaddr(addr, sizeof(addr), &ip_list[i]);
+ DEBUG(99, ("Found master browser %zu of %zu: %s\n",
+ i+1, MAX(count, max_lmb_count),
+ addr));
+
+ cli = get_ipc_connect_master_ip(talloc_tos(),
+ &ip_list[i],
+ creds,
+ &wg_ptr);
+ /* cli == NULL is the master browser refused to talk or
+ could not be found */
+ if (!cli) {
+ continue;
+ }
+
+ workgroup = talloc_strdup(frame, wg_ptr);
+ server = talloc_strdup(frame, smbXcli_conn_remote_name(cli->conn));
+
+ cli_shutdown(cli);
+
+ if (!workgroup || !server) {
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ DEBUG(4, ("using workgroup %s %s\n",
+ workgroup, server));
+
+ /*
+ * For each returned master browser IP address, get a
+ * connection to IPC$ on the server if we do not
+ * already have one, and determine the
+ * workgroups/domains that it knows about.
+ */
+
+ srv = SMBC_server(frame, context, True, server, port, "IPC$",
+ &workgroup, &user, &password);
+ if (!srv) {
+ continue;
+ }
+
+ if (smbXcli_conn_protocol(srv->cli->conn) > PROTOCOL_NT1) {
+ continue;
+ }
+
+ dir->srv = srv;
+ dir->dir_type = SMBC_WORKGROUP;
+
+ /* Now, list the stuff ... */
+
+ if (!cli_NetServerEnum(srv->cli,
+ workgroup,
+ SV_TYPE_DOMAIN_ENUM,
+ list_unique_wg_fn,
+ (void *)dir)) {
+ continue;
+ }
+ }
+
+ TALLOC_FREE(ip_list);
+ } else {
+ /*
+ * Server not an empty string ... Check the rest and see what
+ * gives
+ */
+ if (*share == '\0') {
+ if (*path != '\0') {
+
+ /* Should not have empty share with path */
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ errno = EINVAL + 8197;
+ return NULL;
+
+ }
+
+ /*
+ * We don't know if <server> is really a server name
+ * or is a workgroup/domain name. If we already have
+ * a server structure for it, we'll use it.
+ * Otherwise, check to see if <server><1D>,
+ * <server><1B>, or <server><20> translates. We check
+ * to see if <server> is an IP address first.
+ */
+
+ /*
+ * See if we have an existing server. Do not
+ * establish a connection if one does not already
+ * exist.
+ */
+ srv = SMBC_server(frame, context, False,
+ server, port, "IPC$",
+ &workgroup, &user, &password);
+
+ /*
+ * If no existing server and not an IP addr, look for
+ * LMB or DMB
+ */
+ if (!srv &&
+ !is_ipaddress(server) &&
+ (resolve_name(server, &rem_ss, 0x1d, false) || /* LMB */
+ resolve_name(server, &rem_ss, 0x1b, false) )) { /* DMB */
+ /*
+ * "server" is actually a workgroup name,
+ * not a server. Make this clear.
+ */
+ char *wgroup = server;
+ fstring buserver;
+
+ dir->dir_type = SMBC_SERVER;
+
+ /*
+ * Get the backup list ...
+ */
+ if (!name_status_find(wgroup, 0, 0,
+ &rem_ss, buserver)) {
+ char addr[INET6_ADDRSTRLEN];
+
+ print_sockaddr(addr, sizeof(addr), &rem_ss);
+ DEBUG(0,("Could not get name of "
+ "local/domain master browser "
+ "for workgroup %s from "
+ "address %s\n",
+ wgroup,
+ addr));
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ errno = EPERM;
+ return NULL;
+
+ }
+
+ /*
+ * Get a connection to IPC$ on the server if
+ * we do not already have one
+ */
+ srv = SMBC_server(frame, context, True,
+ buserver, port, "IPC$",
+ &workgroup,
+ &user, &password);
+ if (!srv) {
+ DEBUG(0, ("got no contact to IPC$\n"));
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ return NULL;
+
+ }
+
+ dir->srv = srv;
+
+ if (smbXcli_conn_protocol(srv->cli->conn) > PROTOCOL_NT1) {
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ /* Now, list the servers ... */
+ if (!cli_NetServerEnum(srv->cli, wgroup,
+ 0x0000FFFE, list_fn,
+ (void *)dir)) {
+
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+ } else if (srv ||
+ (resolve_name(server, &rem_ss, 0x20, false))) {
+ NTSTATUS status;
+
+ /*
+ * If we hadn't found the server, get one now
+ */
+ if (!srv) {
+ srv = SMBC_server(frame, context, True,
+ server, port, "IPC$",
+ &workgroup,
+ &user, &password);
+ }
+
+ if (!srv) {
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ return NULL;
+
+ }
+
+ dir->dir_type = SMBC_FILE_SHARE;
+ dir->srv = srv;
+
+ /* List the shares ... */
+
+ status = net_share_enum_rpc(srv->cli,
+ list_fn,
+ (void *)dir);
+ if (!NT_STATUS_IS_OK(status) &&
+ smbXcli_conn_protocol(srv->cli->conn) <=
+ PROTOCOL_NT1) {
+ /*
+ * Only call cli_RNetShareEnum()
+ * on SMB1 connections, not SMB2+.
+ */
+ int rc = cli_RNetShareEnum(srv->cli,
+ list_fn,
+ (void *)dir);
+ if (rc != 0) {
+ status = cli_nt_error(srv->cli);
+ } else {
+ status = NT_STATUS_OK;
+ }
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * Set cli->raw_status so SMBC_errno()
+ * will correctly return the error.
+ */
+ srv->cli->raw_status = status;
+ if (dir != NULL) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ errno = map_errno_from_nt_status(
+ status);
+ return NULL;
+ }
+ } else {
+ /* Neither the workgroup nor server exists */
+ errno = ECONNREFUSED;
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ }
+ else {
+ /*
+ * The server and share are specified ... work from
+ * there ...
+ */
+ char *targetpath;
+ struct cli_state *targetcli;
+ struct cli_credentials *creds = NULL;
+ NTSTATUS status;
+
+ /* We connect to the server and list the directory */
+ dir->dir_type = SMBC_FILE_SHARE;
+
+ srv = SMBC_server(frame, context, True, server, port, share,
+ &workgroup, &user, &password);
+
+ if (!srv) {
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ dir->srv = srv;
+
+ /* Now, list the files ... */
+
+ path_len = strlen(path);
+ path = talloc_asprintf_append(path, "\\*");
+ if (!path) {
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ creds = context->internal->creds;
+
+ status = cli_resolve_path(
+ frame, "",
+ creds,
+ srv->cli, path, &targetcli, &targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Could not resolve %s\n", path);
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ status = cli_list(targetcli, targetpath,
+ FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN,
+ dir_list_fn, (void *)dir);
+ if (!NT_STATUS_IS_OK(status)) {
+ int saved_errno;
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ saved_errno = cli_status_to_errno(status);
+
+ if (saved_errno == EINVAL) {
+ struct stat sb = {0};
+ /*
+ * See if they asked to opendir
+ * something other than a directory.
+ * If so, the converted error value we
+ * got would have been EINVAL rather
+ * than ENOTDIR.
+ */
+ path[path_len] = '\0'; /* restore original path */
+
+ status = SMBC_getatr(
+ context,
+ srv,
+ path,
+ &sb);
+ if (NT_STATUS_IS_OK(status) &&
+ !S_ISDIR(sb.st_mode)) {
+
+ /* It is. Correct the error value */
+ saved_errno = ENOTDIR;
+ }
+ }
+
+ /*
+ * If there was an error and the server is no
+ * good any more...
+ */
+ if (cli_is_error(targetcli) &&
+ smbc_getFunctionCheckServer(context)(context, srv)) {
+
+ /* ... then remove it. */
+ if (smbc_getFunctionRemoveUnusedServer(context)(context,
+ srv)) {
+ /*
+ * We could not remove the
+ * server completely, remove
+ * it from the cache so we
+ * will not get it again. It
+ * will be removed when the
+ * last file/dir is closed.
+ */
+ smbc_getFunctionRemoveCachedServer(context)(context, srv);
+ }
+ }
+
+ TALLOC_FREE(frame);
+ errno = saved_errno;
+ return NULL;
+ }
+ }
+
+ }
+
+ DLIST_ADD(context->internal->files, dir);
+ TALLOC_FREE(frame);
+ return dir;
+
+}
+
+/*
+ * Routine to close a directory
+ */
+
+int
+SMBC_closedir_ctx(SMBCCTX *context,
+ SMBCFILE *dir)
+{
+ TALLOC_CTX *frame = NULL;
+
+ if (!context || !context->internal->initialized) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (dir == NULL) {
+ return 0;
+ }
+
+ frame = talloc_stackframe();
+
+ if (!SMBC_dlist_contains(context->internal->files, dir)) {
+ errno = EBADF;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ remove_dir(dir); /* Clean it up */
+ remove_dirplus(dir);
+
+ DLIST_REMOVE(context->internal->files, dir);
+
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir); /* Free the space too */
+
+ TALLOC_FREE(frame);
+ return 0;
+
+}
+
+static int
+smbc_readdir_internal(SMBCCTX * context,
+ struct smbc_dirent *dest,
+ struct smbc_dirent *src,
+ int max_namebuf_len)
+{
+ if (smbc_getOptionUrlEncodeReaddirEntries(context)) {
+ int remaining_len;
+
+ /* url-encode the name. get back remaining buffer space */
+ remaining_len =
+ smbc_urlencode(dest->name, src->name, max_namebuf_len);
+
+ /* -1 means no null termination. */
+ if (remaining_len < 0) {
+ return -1;
+ }
+
+ /* We now know the name length */
+ dest->namelen = strlen(dest->name);
+
+ if (dest->namelen + 1 < 1) {
+ /* Integer wrap. */
+ return -1;
+ }
+
+ if (dest->namelen + 1 >= max_namebuf_len) {
+ /* Out of space for comment. */
+ return -1;
+ }
+
+ /* Save the pointer to the beginning of the comment */
+ dest->comment = dest->name + dest->namelen + 1;
+
+ if (remaining_len < 1) {
+ /* No room for comment null termination. */
+ return -1;
+ }
+
+ /* Copy the comment */
+ strlcpy(dest->comment, src->comment, remaining_len);
+
+ /* Save other fields */
+ dest->smbc_type = src->smbc_type;
+ dest->commentlen = strlen(dest->comment);
+ dest->dirlen = ((dest->comment + dest->commentlen + 1) -
+ (char *) dest);
+ } else {
+
+ /* No encoding. Just copy the entry as is. */
+ if (src->dirlen > max_namebuf_len) {
+ return -1;
+ }
+ memcpy(dest, src, src->dirlen);
+ if (src->namelen + 1 < 1) {
+ /* Integer wrap */
+ return -1;
+ }
+ if (src->namelen + 1 >= max_namebuf_len) {
+ /* Comment off the end. */
+ return -1;
+ }
+ dest->comment = (char *)(&dest->name + src->namelen + 1);
+ }
+ return 0;
+}
+
+/*
+ * Routine to get a directory entry
+ */
+
+struct smbc_dirent *
+SMBC_readdir_ctx(SMBCCTX *context,
+ SMBCFILE *dir)
+{
+ int maxlen;
+ int ret;
+ struct smbc_dirent *dirp, *dirent;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ /* Check that all is ok first ... */
+
+ if (!context || !context->internal->initialized) {
+
+ errno = EINVAL;
+ DEBUG(0, ("Invalid context in SMBC_readdir_ctx()\n"));
+ TALLOC_FREE(frame);
+ return NULL;
+
+ }
+
+ if (!SMBC_dlist_contains(context->internal->files, dir)) {
+
+ errno = EBADF;
+ DEBUG(0, ("Invalid dir in SMBC_readdir_ctx()\n"));
+ TALLOC_FREE(frame);
+ return NULL;
+
+ }
+
+ if (dir->file != False) { /* FIXME, should be dir, perhaps */
+
+ errno = ENOTDIR;
+ DEBUG(0, ("Found file vs directory in SMBC_readdir_ctx()\n"));
+ TALLOC_FREE(frame);
+ return NULL;
+
+ }
+
+ if (!dir->dir_next) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ dirent = dir->dir_next->dirent;
+ if (!dirent) {
+
+ errno = ENOENT;
+ TALLOC_FREE(frame);
+ return NULL;
+
+ }
+
+ dirp = &context->internal->dirent;
+ maxlen = sizeof(context->internal->_dirent_name);
+
+ ret = smbc_readdir_internal(context, dirp, dirent, maxlen);
+ if (ret == -1) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ dir->dir_next = dir->dir_next->next;
+
+ /*
+ * If we are returning file entries, we
+ * have a duplicate list in dirplus.
+ *
+ * Update dirplus_next also so readdir and
+ * readdirplus are kept in sync.
+ */
+ if (dir->dirplus_list != NULL) {
+ dir->dirplus_next = dir->dirplus_next->next;
+ }
+
+ TALLOC_FREE(frame);
+ return dirp;
+}
+
+/*
+ * Routine to get a directory entry with all attributes
+ */
+
+const struct libsmb_file_info *
+SMBC_readdirplus_ctx(SMBCCTX *context,
+ SMBCFILE *dir)
+{
+ struct libsmb_file_info *smb_finfo = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ /* Check that all is ok first ... */
+
+ if (context == NULL || !context->internal->initialized) {
+ DBG_ERR("Invalid context in SMBC_readdirplus_ctx()\n");
+ TALLOC_FREE(frame);
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (!SMBC_dlist_contains(context->internal->files, dir)) {
+ DBG_ERR("Invalid dir in SMBC_readdirplus_ctx()\n");
+ TALLOC_FREE(frame);
+ errno = EBADF;
+ return NULL;
+ }
+
+ if (dir->dirplus_next == NULL) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ smb_finfo = dir->dirplus_next->smb_finfo;
+ if (smb_finfo == NULL) {
+ TALLOC_FREE(frame);
+ errno = ENOENT;
+ return NULL;
+ }
+ dir->dirplus_next = dir->dirplus_next->next;
+
+ /*
+ * If we are returning file entries, we
+ * have a duplicate list in dir_list
+ *
+ * Update dir_next also so readdir and
+ * readdirplus are kept in sync.
+ */
+ if (dir->dir_list) {
+ dir->dir_next = dir->dir_next->next;
+ }
+
+ TALLOC_FREE(frame);
+ return smb_finfo;
+}
+
+/*
+ * Routine to get a directory entry plus a filled in stat structure if
+ * requested.
+ */
+
+const struct libsmb_file_info *SMBC_readdirplus2_ctx(SMBCCTX *context,
+ SMBCFILE *dir,
+ struct stat *st)
+{
+ struct libsmb_file_info *smb_finfo = NULL;
+ struct smbc_dirplus_list *dp_list = NULL;
+ ino_t ino;
+ char *full_pathname = NULL;
+ char *workgroup = NULL;
+ char *server = NULL;
+ uint16_t port = 0;
+ char *share = NULL;
+ char *path = NULL;
+ char *user = NULL;
+ char *password = NULL;
+ char *options = NULL;
+ int rc;
+ TALLOC_CTX *frame = NULL;
+
+ /*
+ * Allow caller to pass in NULL for stat pointer if
+ * required. This makes this call identical to
+ * smbc_readdirplus().
+ */
+
+ if (st == NULL) {
+ return SMBC_readdirplus_ctx(context, dir);
+ }
+
+ frame = talloc_stackframe();
+
+ /* Check that all is ok first ... */
+ if (context == NULL || !context->internal->initialized) {
+ DBG_ERR("Invalid context in SMBC_readdirplus2_ctx()\n");
+ TALLOC_FREE(frame);
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (!SMBC_dlist_contains(context->internal->files, dir)) {
+ DBG_ERR("Invalid dir in SMBC_readdirplus2_ctx()\n");
+ TALLOC_FREE(frame);
+ errno = EBADF;
+ return NULL;
+ }
+
+ dp_list = dir->dirplus_next;
+ if (dp_list == NULL) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ ino = (ino_t)dp_list->ino;
+
+ smb_finfo = dp_list->smb_finfo;
+ if (smb_finfo == NULL) {
+ TALLOC_FREE(frame);
+ errno = ENOENT;
+ return NULL;
+ }
+
+ full_pathname = talloc_asprintf(frame,
+ "%s/%s",
+ dir->fname,
+ smb_finfo->name);
+ if (full_pathname == NULL) {
+ TALLOC_FREE(frame);
+ errno = ENOENT;
+ return NULL;
+ }
+
+ rc = SMBC_parse_path(frame,
+ context,
+ full_pathname,
+ &workgroup,
+ &server,
+ &port,
+ &share,
+ &path,
+ &user,
+ &password,
+ &options);
+ if (rc != 0) {
+ TALLOC_FREE(frame);
+ errno = ENOENT;
+ return NULL;
+ }
+
+ setup_stat(st,
+ path,
+ smb_finfo->size,
+ smb_finfo->attrs,
+ ino,
+ dir->srv->dev,
+ smb_finfo->atime_ts,
+ smb_finfo->ctime_ts,
+ smb_finfo->mtime_ts);
+
+ TALLOC_FREE(full_pathname);
+
+ dir->dirplus_next = dir->dirplus_next->next;
+
+ /*
+ * If we are returning file entries, we
+ * have a duplicate list in dir_list
+ *
+ * Update dir_next also so readdir and
+ * readdirplus are kept in sync.
+ */
+ if (dir->dir_list) {
+ dir->dir_next = dir->dir_next->next;
+ }
+
+ TALLOC_FREE(frame);
+ return smb_finfo;
+}
+
+/*
+ * Routine to get directory entries
+ */
+
+int
+SMBC_getdents_ctx(SMBCCTX *context,
+ SMBCFILE *dir,
+ struct smbc_dirent *dirp,
+ int count)
+{
+ int rem = count;
+ int reqd;
+ int maxlen;
+ char *ndir = (char *)dirp;
+ struct smbc_dir_list *dirlist;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ /* Check that all is ok first ... */
+
+ if (!context || !context->internal->initialized) {
+
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ if (!SMBC_dlist_contains(context->internal->files, dir)) {
+
+ errno = EBADF;
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ if (dir->file != False) { /* FIXME, should be dir, perhaps */
+
+ errno = ENOTDIR;
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ /*
+ * Now, retrieve the number of entries that will fit in what was passed
+ * We have to figure out if the info is in the list, or we need to
+ * send a request to the server to get the info.
+ */
+
+ while ((dirlist = dir->dir_next)) {
+ int ret;
+ struct smbc_dirent *dirent;
+ struct smbc_dirent *currentEntry = (struct smbc_dirent *)ndir;
+
+ if (!dirlist->dirent) {
+
+ errno = ENOENT; /* Bad error */
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ /* Do urlencoding of next entry, if so selected */
+ dirent = &context->internal->dirent;
+ maxlen = sizeof(context->internal->_dirent_name);
+ ret = smbc_readdir_internal(context, dirent,
+ dirlist->dirent, maxlen);
+ if (ret == -1) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ reqd = dirent->dirlen;
+
+ if (rem < reqd) {
+
+ if (rem < count) { /* We managed to copy something */
+
+ errno = 0;
+ TALLOC_FREE(frame);
+ return count - rem;
+
+ }
+ else { /* Nothing copied ... */
+
+ errno = EINVAL; /* Not enough space ... */
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ }
+
+ memcpy(currentEntry, dirent, reqd); /* Copy the data in ... */
+
+ currentEntry->comment = &currentEntry->name[0] +
+ dirent->namelen + 1;
+
+ ndir += reqd;
+ rem -= reqd;
+
+ /* Try and align the struct for the next entry
+ on a valid pointer boundary by appending zeros */
+ while((rem > 0) && ((uintptr_t)ndir & (sizeof(void*) - 1))) {
+ *ndir = '\0';
+ rem--;
+ ndir++;
+ currentEntry->dirlen++;
+ }
+
+ dir->dir_next = dirlist = dirlist -> next;
+
+ /*
+ * If we are returning file entries, we
+ * have a duplicate list in dirplus.
+ *
+ * Update dirplus_next also so readdir and
+ * readdirplus are kept in sync.
+ */
+ if (dir->dirplus_list != NULL) {
+ dir->dirplus_next = dir->dirplus_next->next;
+ }
+ }
+
+ TALLOC_FREE(frame);
+
+ if (rem == count)
+ return 0;
+ else
+ return count - rem;
+
+}
+
+/*
+ * Routine to create a directory ...
+ */
+
+int
+SMBC_mkdir_ctx(SMBCCTX *context,
+ const char *fname,
+ mode_t mode)
+{
+ SMBCSRV *srv = NULL;
+ char *server = NULL;
+ char *share = NULL;
+ char *user = NULL;
+ char *password = NULL;
+ char *workgroup = NULL;
+ char *path = NULL;
+ char *targetpath = NULL;
+ uint16_t port = 0;
+ struct cli_state *targetcli = NULL;
+ struct cli_credentials *creds = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ if (!context || !context->internal->initialized) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!fname) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ DEBUG(4, ("smbc_mkdir(%s)\n", fname));
+
+ if (SMBC_parse_path(frame,
+ context,
+ fname,
+ &workgroup,
+ &server,
+ &port,
+ &share,
+ &path,
+ &user,
+ &password,
+ NULL)) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!user || user[0] == (char)0) {
+ user = talloc_strdup(frame, smbc_getUser(context));
+ if (!user) {
+ errno = ENOMEM;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ }
+
+ srv = SMBC_server(frame, context, True,
+ server, port, share, &workgroup, &user, &password);
+
+ if (!srv) {
+
+ TALLOC_FREE(frame);
+ return -1; /* errno set by SMBC_server */
+
+ }
+
+ creds = context->internal->creds;
+
+ /*d_printf(">>>mkdir: resolving %s\n", path);*/
+ status = cli_resolve_path(frame, "",
+ creds,
+ srv->cli, path, &targetcli, &targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Could not resolve %s\n", path);
+ errno = ENOENT;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ /*d_printf(">>>mkdir: resolved path as %s\n", targetpath);*/
+
+ status = cli_mkdir(targetcli, targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ errno = cli_status_to_errno(status);
+ return -1;
+
+ }
+
+ TALLOC_FREE(frame);
+ return 0;
+
+}
+
+/*
+ * Our list function simply checks to see if a directory is not empty
+ */
+
+static NTSTATUS
+rmdir_list_fn(struct file_info *finfo,
+ const char *mask,
+ void *state)
+{
+ if (strncmp(finfo->name, ".", 1) != 0 &&
+ strncmp(finfo->name, "..", 2) != 0) {
+ bool *smbc_rmdir_dirempty = (bool *)state;
+ *smbc_rmdir_dirempty = false;
+ }
+ return NT_STATUS_OK;
+}
+
+/*
+ * Routine to remove a directory
+ */
+
+int
+SMBC_rmdir_ctx(SMBCCTX *context,
+ const char *fname)
+{
+ SMBCSRV *srv = NULL;
+ char *server = NULL;
+ char *share = NULL;
+ char *user = NULL;
+ char *password = NULL;
+ char *workgroup = NULL;
+ char *path = NULL;
+ char *targetpath = NULL;
+ uint16_t port = 0;
+ struct cli_state *targetcli = NULL;
+ struct cli_credentials *creds = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ if (!context || !context->internal->initialized) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!fname) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ DEBUG(4, ("smbc_rmdir(%s)\n", fname));
+
+ if (SMBC_parse_path(frame,
+ context,
+ fname,
+ &workgroup,
+ &server,
+ &port,
+ &share,
+ &path,
+ &user,
+ &password,
+ NULL)) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!user || user[0] == (char)0) {
+ user = talloc_strdup(frame, smbc_getUser(context));
+ if (!user) {
+ errno = ENOMEM;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ }
+
+ srv = SMBC_server(frame, context, True,
+ server, port, share, &workgroup, &user, &password);
+
+ if (!srv) {
+
+ TALLOC_FREE(frame);
+ return -1; /* errno set by SMBC_server */
+
+ }
+
+ creds = context->internal->creds;
+
+ /*d_printf(">>>rmdir: resolving %s\n", path);*/
+ status = cli_resolve_path(frame, "",
+ creds,
+ srv->cli, path, &targetcli, &targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Could not resolve %s\n", path);
+ errno = ENOENT;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ /*d_printf(">>>rmdir: resolved path as %s\n", targetpath);*/
+
+ status = cli_rmdir(targetcli, targetpath);
+
+ if (!NT_STATUS_IS_OK(status)) {
+
+ errno = cli_status_to_errno(status);
+
+ if (errno == EACCES) { /* Check if the dir empty or not */
+
+ /* Local storage to avoid buffer overflows */
+ char *lpath;
+ bool smbc_rmdir_dirempty = true;
+
+ lpath = talloc_asprintf(frame, "%s\\*",
+ targetpath);
+ if (!lpath) {
+ errno = ENOMEM;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ status = cli_list(targetcli, lpath,
+ FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN,
+ rmdir_list_fn,
+ &smbc_rmdir_dirempty);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Fix errno to ignore latest error ... */
+ DBG_INFO("cli_list returned an error: %s\n",
+ nt_errstr(status));
+ errno = EACCES;
+
+ }
+
+ if (smbc_rmdir_dirempty)
+ errno = EACCES;
+ else
+ errno = ENOTEMPTY;
+
+ }
+
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ TALLOC_FREE(frame);
+ return 0;
+
+}
+
+/*
+ * Routine to return the current directory position
+ */
+
+off_t
+SMBC_telldir_ctx(SMBCCTX *context,
+ SMBCFILE *dir)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (!context || !context->internal->initialized) {
+
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ if (!SMBC_dlist_contains(context->internal->files, dir)) {
+
+ errno = EBADF;
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ if (dir->file != False) { /* FIXME, should be dir, perhaps */
+
+ errno = ENOTDIR;
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ /* See if we're already at the end. */
+ if (dir->dir_next == NULL) {
+ /* We are. */
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ /*
+ * We return the pointer here as the offset
+ */
+ TALLOC_FREE(frame);
+ return (off_t)(long)dir->dir_next->dirent;
+}
+
+/*
+ * A routine to run down the list and see if the entry is OK
+ * Modifies the dir list and the dirplus list (if it exists)
+ * to point at the correct next entry on success.
+ */
+
+static bool update_dir_ents(SMBCFILE *dir, struct smbc_dirent *dirent)
+{
+ struct smbc_dir_list *tmp_dir = dir->dir_list;
+ struct smbc_dirplus_list *tmp_dirplus = dir->dirplus_list;
+
+ /*
+ * Run down the list looking for what we want.
+ * If we're enumerating files both dir_list
+ * and dirplus_list contain the same entry
+ * list, as they were seeded from the same
+ * cli_list callback.
+ *
+ * If we're enumerating servers then
+ * dirplus_list will be NULL, so don't
+ * update in that case.
+ */
+
+ while (tmp_dir != NULL) {
+ if (tmp_dir->dirent == dirent) {
+ dir->dir_next = tmp_dir;
+ if (tmp_dirplus != NULL) {
+ dir->dirplus_next = tmp_dirplus;
+ }
+ return true;
+ }
+ tmp_dir = tmp_dir->next;
+ if (tmp_dirplus != NULL) {
+ tmp_dirplus = tmp_dirplus->next;
+ }
+ }
+ return false;
+}
+
+/*
+ * Routine to seek on a directory
+ */
+
+int
+SMBC_lseekdir_ctx(SMBCCTX *context,
+ SMBCFILE *dir,
+ off_t offset)
+{
+ long int l_offset = offset; /* Handle problems of size */
+ struct smbc_dirent *dirent = (struct smbc_dirent *)l_offset;
+ TALLOC_CTX *frame = talloc_stackframe();
+ bool ok;
+
+ if (!context || !context->internal->initialized) {
+
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ if (dir->file != False) { /* FIXME, should be dir, perhaps */
+
+ errno = ENOTDIR;
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ /* Now, check what we were passed and see if it is OK ... */
+
+ if (dirent == NULL) { /* Seek to the beginning of the list */
+
+ dir->dir_next = dir->dir_list;
+
+ /* Do the same for dirplus. */
+ dir->dirplus_next = dir->dirplus_list;
+
+ TALLOC_FREE(frame);
+ return 0;
+
+ }
+
+ if (offset == -1) { /* Seek to the end of the list */
+ dir->dir_next = NULL;
+
+ /* Do the same for dirplus. */
+ dir->dirplus_next = NULL;
+
+ TALLOC_FREE(frame);
+ return 0;
+ }
+
+ /*
+ * Run down the list and make sure that the entry is OK.
+ * Update the position of both dir and dirplus lists.
+ */
+
+ ok = update_dir_ents(dir, dirent);
+ if (!ok) {
+ errno = EINVAL; /* Bad entry */
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ TALLOC_FREE(frame);
+ return 0;
+}
+
+/*
+ * Routine to fstat a dir
+ */
+
+int
+SMBC_fstatdir_ctx(SMBCCTX *context,
+ SMBCFILE *dir,
+ struct stat *st)
+{
+
+ if (!context || !context->internal->initialized) {
+
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* No code yet ... */
+ return 0;
+}
+
+int
+SMBC_chmod_ctx(SMBCCTX *context,
+ const char *fname,
+ mode_t newmode)
+{
+ SMBCSRV *srv = NULL;
+ char *server = NULL;
+ char *share = NULL;
+ char *user = NULL;
+ char *password = NULL;
+ char *workgroup = NULL;
+ char *targetpath = NULL;
+ struct cli_state *targetcli = NULL;
+ char *path = NULL;
+ uint32_t attr;
+ uint16_t port = 0;
+ struct cli_credentials *creds = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ if (!context || !context->internal->initialized) {
+
+ errno = EINVAL; /* Best I can think of ... */
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!fname) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ DEBUG(4, ("smbc_chmod(%s, 0%3o)\n", fname, (unsigned int)newmode));
+
+ if (SMBC_parse_path(frame,
+ context,
+ fname,
+ &workgroup,
+ &server,
+ &port,
+ &share,
+ &path,
+ &user,
+ &password,
+ NULL)) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!user || user[0] == (char)0) {
+ user = talloc_strdup(frame, smbc_getUser(context));
+ if (!user) {
+ errno = ENOMEM;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ }
+
+ srv = SMBC_server(frame, context, True,
+ server, port, share, &workgroup, &user, &password);
+
+ if (!srv) {
+ TALLOC_FREE(frame);
+ return -1; /* errno set by SMBC_server */
+ }
+
+ creds = context->internal->creds;
+
+ /*d_printf(">>>unlink: resolving %s\n", path);*/
+ status = cli_resolve_path(frame, "",
+ creds,
+ srv->cli, path, &targetcli, &targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Could not resolve %s\n", path);
+ errno = ENOENT;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ attr = 0;
+
+ if (!(newmode & (S_IWUSR | S_IWGRP | S_IWOTH))) attr |= FILE_ATTRIBUTE_READONLY;
+ if ((newmode & S_IXUSR) && lp_map_archive(-1)) attr |= FILE_ATTRIBUTE_ARCHIVE;
+ if ((newmode & S_IXGRP) && lp_map_system(-1)) attr |= FILE_ATTRIBUTE_SYSTEM;
+ if ((newmode & S_IXOTH) && lp_map_hidden(-1)) attr |= FILE_ATTRIBUTE_HIDDEN;
+
+ status = cli_setatr(targetcli, targetpath, attr, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ errno = cli_status_to_errno(status);
+ return -1;
+ }
+
+ TALLOC_FREE(frame);
+ return 0;
+}
+
+int
+SMBC_utimes_ctx(SMBCCTX *context,
+ const char *fname,
+ struct timeval *tbuf)
+{
+ SMBCSRV *srv = NULL;
+ char *server = NULL;
+ char *share = NULL;
+ char *user = NULL;
+ char *password = NULL;
+ char *workgroup = NULL;
+ char *path = NULL;
+ struct timespec access_time, write_time;
+ uint16_t port = 0;
+ TALLOC_CTX *frame = talloc_stackframe();
+ bool ok;
+
+ if (!context || !context->internal->initialized) {
+
+ errno = EINVAL; /* Best I can think of ... */
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!fname) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (tbuf == NULL) {
+ access_time = write_time = timespec_current();
+ } else {
+ access_time = convert_timeval_to_timespec(tbuf[0]);
+ write_time = convert_timeval_to_timespec(tbuf[1]);
+ }
+
+ if (DEBUGLVL(4)) {
+ struct timeval_buf abuf, wbuf;
+
+ dbgtext("smbc_utimes(%s, atime = %s mtime = %s)\n",
+ fname,
+ timespec_string_buf(&access_time, false, &abuf),
+ timespec_string_buf(&write_time, false, &wbuf));
+ }
+
+ if (SMBC_parse_path(frame,
+ context,
+ fname,
+ &workgroup,
+ &server,
+ &port,
+ &share,
+ &path,
+ &user,
+ &password,
+ NULL)) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!user || user[0] == (char)0) {
+ user = talloc_strdup(frame, smbc_getUser(context));
+ if (!user) {
+ errno = ENOMEM;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ }
+
+ srv = SMBC_server(frame, context, True,
+ server, port, share, &workgroup, &user, &password);
+
+ if (!srv) {
+ TALLOC_FREE(frame);
+ return -1; /* errno set by SMBC_server */
+ }
+
+ ok = SMBC_setatr(
+ context,
+ srv,
+ path,
+ (struct timespec) { .tv_nsec = SAMBA_UTIME_OMIT },
+ access_time,
+ write_time,
+ (struct timespec) { .tv_nsec = SAMBA_UTIME_OMIT },
+ 0);
+ if (!ok) {
+ TALLOC_FREE(frame);
+ return -1; /* errno set by SMBC_setatr */
+ }
+
+ TALLOC_FREE(frame);
+ return 0;
+}
+
+/*
+ * Routine to unlink() a file
+ */
+
+int
+SMBC_unlink_ctx(SMBCCTX *context,
+ const char *fname)
+{
+ char *server = NULL;
+ char *share = NULL;
+ char *user = NULL;
+ char *password = NULL;
+ char *workgroup = NULL;
+ char *path = NULL;
+ char *targetpath = NULL;
+ uint16_t port = 0;
+ struct cli_state *targetcli = NULL;
+ SMBCSRV *srv = NULL;
+ struct cli_credentials *creds = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ if (!context || !context->internal->initialized) {
+
+ errno = EINVAL; /* Best I can think of ... */
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ if (!fname) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ if (SMBC_parse_path(frame,
+ context,
+ fname,
+ &workgroup,
+ &server,
+ &port,
+ &share,
+ &path,
+ &user,
+ &password,
+ NULL)) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!user || user[0] == (char)0) {
+ user = talloc_strdup(frame, smbc_getUser(context));
+ if (!user) {
+ errno = ENOMEM;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ }
+
+ srv = SMBC_server(frame, context, True,
+ server, port, share, &workgroup, &user, &password);
+
+ if (!srv) {
+ TALLOC_FREE(frame);
+ return -1; /* SMBC_server sets errno */
+
+ }
+
+ creds = context->internal->creds;
+
+ /*d_printf(">>>unlink: resolving %s\n", path);*/
+ status = cli_resolve_path(frame, "",
+ creds,
+ srv->cli, path, &targetcli, &targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Could not resolve %s\n", path);
+ errno = ENOENT;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ /*d_printf(">>>unlink: resolved path as %s\n", targetpath);*/
+
+ status = cli_unlink(
+ targetcli,
+ targetpath,
+ FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ if (!NT_STATUS_IS_OK(status)) {
+
+ errno = cli_status_to_errno(status);
+
+ if (errno == EACCES) { /* Check if the file is a directory */
+
+ int saverr = errno;
+ struct stat sb = {0};
+
+ status = SMBC_getatr(context, srv, path, &sb);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Hmmm, bad error ... What? */
+
+ TALLOC_FREE(frame);
+ errno = cli_status_to_errno(status);
+ return -1;
+
+ }
+ else {
+
+ if (S_ISDIR(sb.st_mode))
+ errno = EISDIR;
+ else
+ errno = saverr; /* Restore this */
+
+ }
+ }
+
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ TALLOC_FREE(frame);
+ return 0; /* Success ... */
+
+}
+
+/*
+ * Routine to rename() a file
+ */
+
+int
+SMBC_rename_ctx(SMBCCTX *ocontext,
+ const char *oname,
+ SMBCCTX *ncontext,
+ const char *nname)
+{
+ char *server1 = NULL;
+ char *share1 = NULL;
+ char *server2 = NULL;
+ char *share2 = NULL;
+ char *user1 = NULL;
+ char *user2 = NULL;
+ char *password1 = NULL;
+ char *password2 = NULL;
+ char *workgroup = NULL;
+ char *path1 = NULL;
+ char *path2 = NULL;
+ char *targetpath1 = NULL;
+ char *targetpath2 = NULL;
+ struct cli_state *targetcli1 = NULL;
+ struct cli_state *targetcli2 = NULL;
+ SMBCSRV *srv = NULL;
+ uint16_t port1 = 0;
+ uint16_t port2 = 0;
+ struct cli_credentials *ocreds = NULL;
+ struct cli_credentials *ncreds = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ if (!ocontext || !ncontext ||
+ !ocontext->internal->initialized ||
+ !ncontext->internal->initialized) {
+
+ errno = EINVAL; /* Best I can think of ... */
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!oname || !nname) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ DEBUG(4, ("smbc_rename(%s,%s)\n", oname, nname));
+
+ if (SMBC_parse_path(frame,
+ ocontext,
+ oname,
+ &workgroup,
+ &server1,
+ &port1,
+ &share1,
+ &path1,
+ &user1,
+ &password1,
+ NULL)) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!user1 || user1[0] == (char)0) {
+ user1 = talloc_strdup(frame, smbc_getUser(ocontext));
+ if (!user1) {
+ errno = ENOMEM;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ }
+
+ if (SMBC_parse_path(frame,
+ ncontext,
+ nname,
+ NULL,
+ &server2,
+ &port2,
+ &share2,
+ &path2,
+ &user2,
+ &password2,
+ NULL)) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!user2 || user2[0] == (char)0) {
+ user2 = talloc_strdup(frame, smbc_getUser(ncontext));
+ if (!user2) {
+ errno = ENOMEM;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ }
+
+ if (strcmp(server1, server2) || strcmp(share1, share2) ||
+ strcmp(user1, user2)) {
+ /* Can't rename across file systems, or users?? */
+ errno = EXDEV;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ srv = SMBC_server(frame, ocontext, True,
+ server1, port1, share1, &workgroup, &user1, &password1);
+ if (!srv) {
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ /* set the credentials to make DFS work */
+ smbc_set_credentials_with_fallback(ocontext,
+ workgroup,
+ user1,
+ password1);
+
+ /*d_printf(">>>rename: resolving %s\n", path1);*/
+ ocreds = ocontext->internal->creds;
+
+ status = cli_resolve_path(frame, "",
+ ocreds,
+ srv->cli, path1, &targetcli1, &targetpath1);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Could not resolve %s\n", path1);
+ errno = ENOENT;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ /* set the credentials to make DFS work */
+ smbc_set_credentials_with_fallback(ncontext,
+ workgroup,
+ user2,
+ password2);
+
+ /*d_printf(">>>rename: resolved path as %s\n", targetpath1);*/
+ /*d_printf(">>>rename: resolving %s\n", path2);*/
+ ncreds = ncontext->internal->creds;
+
+ status = cli_resolve_path(frame, "",
+ ncreds,
+ srv->cli, path2, &targetcli2, &targetpath2);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Could not resolve %s\n", path2);
+ errno = ENOENT;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ /*d_printf(">>>rename: resolved path as %s\n", targetpath2);*/
+
+ if (strcmp(smbXcli_conn_remote_name(targetcli1->conn), smbXcli_conn_remote_name(targetcli2->conn)) ||
+ strcmp(targetcli1->share, targetcli2->share))
+ {
+ /* can't rename across file systems */
+ errno = EXDEV;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ status = cli_rename(targetcli1, targetpath1, targetpath2, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ int eno = cli_status_to_errno(status);
+
+ if (eno != EEXIST ||
+ !NT_STATUS_IS_OK(cli_unlink(targetcli1, targetpath2,
+ FILE_ATTRIBUTE_SYSTEM |
+ FILE_ATTRIBUTE_HIDDEN)) ||
+ !NT_STATUS_IS_OK(cli_rename(targetcli1, targetpath1,
+ targetpath2, false))) {
+
+ errno = eno;
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+ }
+
+ TALLOC_FREE(frame);
+ return 0; /* Success */
+}
+
+struct smbc_notify_cb_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ uint16_t fnum;
+ bool recursive;
+ uint32_t completion_filter;
+ unsigned callback_timeout_ms;
+ smbc_notify_callback_fn cb;
+ void *private_data;
+};
+
+static void smbc_notify_cb_got_changes(struct tevent_req *subreq);
+static void smbc_notify_cb_timedout(struct tevent_req *subreq);
+
+static struct tevent_req *smbc_notify_cb_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli,
+ uint16_t fnum, bool recursive, uint32_t completion_filter,
+ unsigned callback_timeout_ms,
+ smbc_notify_callback_fn cb, void *private_data)
+{
+ struct tevent_req *req, *subreq;
+ struct smbc_notify_cb_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct smbc_notify_cb_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ state->fnum = fnum;
+ state->recursive = recursive;
+ state->completion_filter = completion_filter;
+ state->callback_timeout_ms = callback_timeout_ms;
+ state->cb = cb;
+ state->private_data = private_data;
+
+ subreq = cli_notify_send(
+ state, state->ev, state->cli, state->fnum, 1000,
+ state->completion_filter, state->recursive);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smbc_notify_cb_got_changes, req);
+
+ if (state->callback_timeout_ms == 0) {
+ return req;
+ }
+
+ subreq = tevent_wakeup_send(
+ state, state->ev,
+ tevent_timeval_current_ofs(state->callback_timeout_ms/1000,
+ state->callback_timeout_ms*1000));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smbc_notify_cb_timedout, req);
+
+ return req;
+}
+
+static void smbc_notify_cb_got_changes(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbc_notify_cb_state *state = tevent_req_data(
+ req, struct smbc_notify_cb_state);
+ uint32_t num_changes;
+ struct notify_change *changes;
+ NTSTATUS status;
+ int cb_ret;
+
+ status = cli_notify_recv(subreq, state, &num_changes, &changes);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ {
+ struct smbc_notify_callback_action actions[num_changes];
+ uint32_t i;
+
+ for (i=0; i<num_changes; i++) {
+ actions[i].action = changes[i].action;
+ actions[i].filename = changes[i].name;
+ }
+
+ cb_ret = state->cb(actions, num_changes, state->private_data);
+ }
+
+ TALLOC_FREE(changes);
+
+ if (cb_ret != 0) {
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = cli_notify_send(
+ state, state->ev, state->cli, state->fnum, 1000,
+ state->completion_filter, state->recursive);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, smbc_notify_cb_got_changes, req);
+}
+
+static void smbc_notify_cb_timedout(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbc_notify_cb_state *state = tevent_req_data(
+ req, struct smbc_notify_cb_state);
+ int cb_ret;
+ bool ok;
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_oom(req);
+ return;
+ }
+
+ cb_ret = state->cb(NULL, 0, state->private_data);
+ if (cb_ret != 0) {
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = tevent_wakeup_send(
+ state, state->ev,
+ tevent_timeval_current_ofs(state->callback_timeout_ms/1000,
+ state->callback_timeout_ms*1000));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, smbc_notify_cb_timedout, req);
+}
+
+static NTSTATUS smbc_notify_cb_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static NTSTATUS smbc_notify_cb(struct cli_state *cli, uint16_t fnum,
+ bool recursive, uint32_t completion_filter,
+ unsigned callback_timeout_ms,
+ smbc_notify_callback_fn cb, void *private_data)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = smbc_notify_cb_send(frame, ev, cli, fnum, recursive,
+ completion_filter,
+ callback_timeout_ms, cb, private_data);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = smbc_notify_cb_recv(req);
+ TALLOC_FREE(req);
+fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+int
+SMBC_notify_ctx(SMBCCTX *context, SMBCFILE *dir, smbc_bool recursive,
+ uint32_t completion_filter, unsigned callback_timeout_ms,
+ smbc_notify_callback_fn cb, void *private_data)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct cli_state *cli;
+ char *server = NULL;
+ char *share = NULL;
+ char *user = NULL;
+ char *password = NULL;
+ char *options = NULL;
+ char *workgroup = NULL;
+ char *path = NULL;
+ uint16_t port;
+ NTSTATUS status;
+ uint16_t fnum;
+
+ if ((context == NULL) || !context->internal->initialized) {
+ TALLOC_FREE(frame);
+ errno = EINVAL;
+ return -1;
+ }
+ if (!SMBC_dlist_contains(context->internal->files, dir)) {
+ TALLOC_FREE(frame);
+ errno = EBADF;
+ return -1;
+ }
+
+ if (SMBC_parse_path(frame,
+ context,
+ dir->fname,
+ &workgroup,
+ &server,
+ &port,
+ &share,
+ &path,
+ &user,
+ &password,
+ &options)) {
+ DEBUG(4, ("no valid path\n"));
+ TALLOC_FREE(frame);
+ errno = EINVAL + 8194;
+ return -1;
+ }
+
+ DEBUG(4, ("parsed path: fname='%s' server='%s' share='%s' "
+ "path='%s' options='%s'\n",
+ dir->fname, server, share, path, options));
+
+ DEBUG(4, ("%s(%p, %d, %"PRIu32")\n", __func__, dir,
+ (int)recursive, completion_filter));
+
+ cli = dir->srv->cli;
+ status = cli_ntcreate(
+ cli, path, 0, FILE_READ_DATA, 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN, 0, 0, &fnum, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ errno = cli_status_to_errno(status);
+ return -1;
+ }
+
+ status = smbc_notify_cb(cli, fnum, recursive != 0, completion_filter,
+ callback_timeout_ms, cb, private_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ cli_close(cli, fnum);
+ TALLOC_FREE(frame);
+ errno = cli_status_to_errno(status);
+ return -1;
+ }
+
+ cli_close(cli, fnum);
+
+ TALLOC_FREE(frame);
+ return 0;
+}