summaryrefslogtreecommitdiffstats
path: root/source4/client
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-05 17:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-05 17:47:29 +0000
commit4f5791ebd03eaec1c7da0865a383175b05102712 (patch)
tree8ce7b00f7a76baa386372422adebbe64510812d4 /source4/client
parentInitial commit. (diff)
downloadsamba-upstream.tar.xz
samba-upstream.zip
Adding upstream version 2:4.17.12+dfsg.upstream/2%4.17.12+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source4/client')
-rw-r--r--source4/client/cifsdd.c733
-rw-r--r--source4/client/cifsdd.h109
-rw-r--r--source4/client/cifsddio.c524
-rw-r--r--source4/client/client.c3552
-rwxr-xr-xsource4/client/tests/test_cifsdd.sh77
-rwxr-xr-xsource4/client/tests/test_smbclient.sh154
6 files changed, 5149 insertions, 0 deletions
diff --git a/source4/client/cifsdd.c b/source4/client/cifsdd.c
new file mode 100644
index 0000000..812698e
--- /dev/null
+++ b/source4/client/cifsdd.c
@@ -0,0 +1,733 @@
+/*
+ CIFSDD - dd for SMB.
+ Main program, argument handling and block copying.
+
+ Copyright (C) James Peach 2005-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 "system/filesys.h"
+#include "auth/gensec/gensec.h"
+#include "lib/cmdline/cmdline.h"
+#include "libcli/resolve/resolve.h"
+#include "libcli/raw/libcliraw.h"
+#include "lib/events/events.h"
+
+#include "cifsdd.h"
+#include "param/param.h"
+
+const char * const PROGNAME = "cifsdd";
+
+#define SYNTAX_EXIT_CODE 1 /* Invokation syntax or logic error. */
+#define EOM_EXIT_CODE 9 /* Out of memory error. */
+#define FILESYS_EXIT_CODE 10 /* File manipulation error. */
+#define IOERROR_EXIT_CODE 11 /* Error during IO phase. */
+
+struct dd_stats_record dd_stats;
+
+static int dd_sigint;
+static int dd_sigusr1;
+
+static void dd_handle_signal(int sig)
+{
+ switch (sig)
+ {
+ case SIGINT:
+ ++dd_sigint;
+ break;
+ case SIGUSR1:
+ ++dd_sigusr1;
+ break;
+ default:
+ break;
+ }
+}
+
+/* ------------------------------------------------------------------------- */
+/* Argument handling. */
+/* ------------------------------------------------------------------------- */
+
+static const struct {
+ enum argtype arg_type;
+ const char * arg_name;
+} names [] = {
+ { ARG_NUMERIC, "COUNT" },
+ { ARG_SIZE, "SIZE" },
+ { ARG_PATHNAME, "FILE" },
+ { ARG_BOOL, "BOOLEAN" },
+};
+
+static const char * argtype_str(enum argtype arg_type)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(names); ++i) {
+ if (arg_type == names[i].arg_type) {
+ return(names[i].arg_name);
+ }
+ }
+
+ return("<unknown>");
+}
+
+static struct argdef args[] =
+{
+ {
+ .arg_name = "bs",
+ .arg_type = ARG_SIZE,
+ .arg_help = "force ibs and obs to SIZE bytes",
+ },
+ {
+ .arg_name = "ibs",
+ .arg_type = ARG_SIZE,
+ .arg_help = "read SIZE bytes at a time",
+ },
+ {
+ .arg_name = "obs",
+ .arg_type = ARG_SIZE,
+ .arg_help = "write SIZE bytes at a time",
+ },
+
+ {
+ .arg_name = "count",
+ .arg_type = ARG_NUMERIC,
+ .arg_help = "copy COUNT input blocks",
+ },
+ {
+ .arg_name = "seek",
+ .arg_type = ARG_NUMERIC,
+ .arg_help = "skip COUNT blocks at start of output",
+ },
+ {
+ .arg_name = "skip",
+ .arg_type = ARG_NUMERIC,
+ .arg_help = "skip COUNT blocks at start of input",
+ },
+
+ {
+ .arg_name = "if",
+ .arg_type = ARG_PATHNAME,
+ .arg_help = "read input from FILE",
+ },
+ {
+ .arg_name = "of",
+ .arg_type = ARG_PATHNAME,
+ .arg_help = "write output to FILE",
+ },
+
+ {
+ .arg_name = "direct",
+ .arg_type = ARG_BOOL,
+ .arg_help = "use direct I/O if non-zero",
+ },
+ {
+ .arg_name = "sync",
+ .arg_type = ARG_BOOL,
+ .arg_help = "use synchronous writes if non-zero",
+ },
+ {
+ .arg_name = "oplock",
+ .arg_type = ARG_BOOL,
+ .arg_help = "take oplocks on the input and output files",
+ },
+
+/* FIXME: We should support using iflags and oflags for setting oplock and I/O
+ * options. This would make us compatible with GNU dd.
+ */
+};
+
+static struct argdef * find_named_arg(const char * arg)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(args); ++i) {
+ if (strwicmp(arg, args[i].arg_name) == 0) {
+ return(&args[i]);
+ }
+ }
+
+ return(NULL);
+}
+
+int set_arg_argv(const char * argv)
+{
+ struct argdef * arg;
+
+ char * name;
+ char * val;
+
+ if ((name = strdup(argv)) == NULL) {
+ return(0);
+ }
+
+ if ((val = strchr(name, '=')) == NULL) {
+ fprintf(stderr, "%s: malformed argument \"%s\"\n",
+ PROGNAME, argv);
+ goto fail;
+ }
+
+ *val = '\0';
+ val++;
+
+ if ((arg = find_named_arg(name)) == NULL) {
+ fprintf(stderr, "%s: ignoring unknown argument \"%s\"\n",
+ PROGNAME, name);
+ goto fail;
+ }
+
+ /* Found a matching name; convert the variable argument. */
+ switch (arg->arg_type) {
+ case ARG_NUMERIC:
+ if (!conv_str_u64(val, &arg->arg_val.nval)) {
+ goto fail;
+ }
+ break;
+ case ARG_SIZE:
+ if (!conv_str_size_error(val, &arg->arg_val.nval)) {
+ goto fail;
+ }
+ break;
+ case ARG_BOOL:
+ if (!conv_str_bool(val, &arg->arg_val.bval)) {
+ goto fail;
+ }
+ break;
+ case ARG_PATHNAME:
+ if (!(arg->arg_val.pval = strdup(val))) {
+ goto fail;
+ }
+ break;
+ default:
+ fprintf(stderr, "%s: argument \"%s\" is of "
+ "unknown type\n", PROGNAME, name);
+ goto fail;
+ }
+
+ free(name);
+ return(1);
+
+fail:
+ free(name);
+ return(0);
+}
+
+void set_arg_val(const char * name, ...)
+{
+ va_list ap;
+ struct argdef * arg;
+
+ va_start(ap, name);
+ if ((arg = find_named_arg(name)) == NULL) {
+ goto fail;
+ }
+
+ /* Found a matching name; convert the variable argument. */
+ switch (arg->arg_type) {
+ case ARG_NUMERIC:
+ case ARG_SIZE:
+ arg->arg_val.nval = va_arg(ap, uint64_t);
+ break;
+ case ARG_BOOL:
+ arg->arg_val.bval = va_arg(ap, int);
+ break;
+ case ARG_PATHNAME:
+ arg->arg_val.pval = va_arg(ap, char *);
+ if (arg->arg_val.pval) {
+ arg->arg_val.pval = strdup(arg->arg_val.pval);
+ }
+ break;
+ default:
+ fprintf(stderr, "%s: argument \"%s\" is of "
+ "unknown type\n", PROGNAME, name);
+ goto fail;
+ }
+
+ va_end(ap);
+ return;
+
+fail:
+ fprintf(stderr, "%s: ignoring unknown argument \"%s\"\n",
+ PROGNAME, name);
+ va_end(ap);
+ return;
+}
+
+bool check_arg_bool(const char * name)
+{
+ struct argdef * arg;
+
+ if ((arg = find_named_arg(name)) &&
+ (arg->arg_type == ARG_BOOL)) {
+ return(arg->arg_val.bval);
+ }
+
+ DEBUG(0, ("invalid argument name: %s", name));
+ SMB_ASSERT(0);
+ return(false);
+}
+
+uint64_t check_arg_numeric(const char * name)
+{
+ struct argdef * arg;
+
+ if ((arg = find_named_arg(name)) &&
+ (arg->arg_type == ARG_NUMERIC || arg->arg_type == ARG_SIZE)) {
+ return(arg->arg_val.nval);
+ }
+
+ DEBUG(0, ("invalid argument name: %s", name));
+ SMB_ASSERT(0);
+ return(-1);
+}
+
+const char * check_arg_pathname(const char * name)
+{
+ struct argdef * arg;
+
+ if ((arg = find_named_arg(name)) &&
+ (arg->arg_type == ARG_PATHNAME)) {
+ return(arg->arg_val.pval);
+ }
+
+ DEBUG(0, ("invalid argument name: %s", name));
+ SMB_ASSERT(0);
+ return(NULL);
+}
+
+static void dump_args(void)
+{
+ int i;
+
+ DEBUG(10, ("dumping argument values:\n"));
+ for (i = 0; i < ARRAY_SIZE(args); ++i) {
+ switch (args[i].arg_type) {
+ case ARG_NUMERIC:
+ case ARG_SIZE:
+ DEBUG(10, ("\t%s=%llu\n", args[i].arg_name,
+ (unsigned long long)args[i].arg_val.nval));
+ break;
+ case ARG_BOOL:
+ DEBUG(10, ("\t%s=%s\n", args[i].arg_name,
+ args[i].arg_val.bval ? "yes" : "no"));
+ break;
+ case ARG_PATHNAME:
+ DEBUG(10, ("\t%s=%s\n", args[i].arg_name,
+ args[i].arg_val.pval ?
+ args[i].arg_val.pval :
+ "(NULL)"));
+ break;
+ default:
+ SMB_ASSERT(0);
+ }
+ }
+}
+
+static void cifsdd_help_message(poptContext pctx,
+ enum poptCallbackReason preason,
+ struct poptOption * poption,
+ const char * parg,
+ void * pdata)
+{
+ static const char notes[] =
+"FILE can be a local filename or a UNC path of the form //server/share/path.\n";
+
+ char prefix[24];
+ int i;
+
+ if (poption->shortName != '?') {
+ poptPrintUsage(pctx, stdout, 0);
+ fprintf(stdout, " [dd options]\n");
+ exit(0);
+ }
+
+ poptPrintHelp(pctx, stdout, 0);
+ fprintf(stdout, "\nCIFS dd options:\n");
+
+ for (i = 0; i < ARRAY_SIZE(args); ++i) {
+ if (args[i].arg_name == NULL) {
+ break;
+ }
+
+ snprintf(prefix, sizeof(prefix), "%s=%-*s",
+ args[i].arg_name,
+ (int)(sizeof(prefix) - strlen(args[i].arg_name) - 2),
+ argtype_str(args[i].arg_type));
+ prefix[sizeof(prefix) - 1] = '\0';
+ fprintf(stdout, " %s%s\n", prefix, args[i].arg_help);
+ }
+
+ fprintf(stdout, "\n%s\n", notes);
+ exit(0);
+}
+
+/* ------------------------------------------------------------------------- */
+/* Main block copying routine. */
+/* ------------------------------------------------------------------------- */
+
+static void print_transfer_stats(void)
+{
+ if (DEBUGLEVEL > 0) {
+ printf("%llu+%llu records in (%llu bytes)\n"
+ "%llu+%llu records out (%llu bytes)\n",
+ (unsigned long long)dd_stats.in.fblocks,
+ (unsigned long long)dd_stats.in.pblocks,
+ (unsigned long long)dd_stats.in.bytes,
+ (unsigned long long)dd_stats.out.fblocks,
+ (unsigned long long)dd_stats.out.pblocks,
+ (unsigned long long)dd_stats.out.bytes);
+ } else {
+ printf("%llu+%llu records in\n%llu+%llu records out\n",
+ (unsigned long long)dd_stats.in.fblocks,
+ (unsigned long long)dd_stats.in.pblocks,
+ (unsigned long long)dd_stats.out.fblocks,
+ (unsigned long long)dd_stats.out.pblocks);
+ }
+}
+
+static struct dd_iohandle * open_file(struct resolve_context *resolve_ctx,
+ struct tevent_context *ev,
+ const char * which, const char **ports,
+ struct smbcli_options *smb_options,
+ const char *socket_options,
+ struct smbcli_session_options *smb_session_options,
+ struct gensec_settings *gensec_settings)
+{
+ int options = 0;
+ const char * path = NULL;
+ struct dd_iohandle * handle = NULL;
+
+ if (check_arg_bool("direct")) {
+ options |= DD_DIRECT_IO;
+ }
+
+ if (check_arg_bool("sync")) {
+ options |= DD_SYNC_IO;
+ }
+
+ if (check_arg_bool("oplock")) {
+ options |= DD_OPLOCK;
+ }
+
+ if (strcmp(which, "if") == 0) {
+ path = check_arg_pathname("if");
+ handle = dd_open_path(resolve_ctx, ev, path, ports,
+ check_arg_numeric("ibs"), options,
+ socket_options,
+ smb_options, smb_session_options,
+ gensec_settings);
+ } else if (strcmp(which, "of") == 0) {
+ options |= DD_WRITE;
+ path = check_arg_pathname("of");
+ handle = dd_open_path(resolve_ctx, ev, path, ports,
+ check_arg_numeric("obs"), options,
+ socket_options,
+ smb_options, smb_session_options,
+ gensec_settings);
+ } else {
+ SMB_ASSERT(0);
+ return(NULL);
+ }
+
+ if (!handle) {
+ fprintf(stderr, "%s: failed to open %s\n", PROGNAME, path);
+ }
+
+ return(handle);
+}
+
+static int copy_files(struct tevent_context *ev, struct loadparm_context *lp_ctx)
+{
+ uint8_t * iobuf; /* IO buffer. */
+ uint64_t iomax; /* Size of the IO buffer. */
+ uint64_t data_size; /* Amount of data in the IO buffer. */
+
+ uint64_t ibs;
+ uint64_t obs;
+ uint64_t count;
+
+ struct dd_iohandle * ifile;
+ struct dd_iohandle * ofile;
+
+ struct smbcli_options options;
+ struct smbcli_session_options session_options;
+
+ ibs = check_arg_numeric("ibs");
+ obs = check_arg_numeric("obs");
+ count = check_arg_numeric("count");
+
+ lpcfg_smbcli_options(lp_ctx, &options);
+ lpcfg_smbcli_session_options(lp_ctx, &session_options);
+
+ /* Allocate IO buffer. We need more than the max IO size because we
+ * could accumulate a remainder if ibs and obs don't match.
+ */
+ iomax = 2 * MAX(ibs, obs);
+ if ((iobuf = malloc_array_p(uint8_t, iomax)) == NULL) {
+ fprintf(stderr,
+ "%s: failed to allocate IO buffer of %llu bytes\n",
+ PROGNAME, (unsigned long long)iomax);
+ return(EOM_EXIT_CODE);
+ }
+
+ options.max_xmit = MAX(ibs, obs);
+
+ DEBUG(4, ("IO buffer size is %llu, max xmit is %d\n",
+ (unsigned long long)iomax, options.max_xmit));
+
+ if (!(ifile = open_file(lpcfg_resolve_context(lp_ctx), ev, "if",
+ lpcfg_smb_ports(lp_ctx), &options,
+ lpcfg_socket_options(lp_ctx),
+ &session_options,
+ lpcfg_gensec_settings(lp_ctx, lp_ctx)))) {
+ SAFE_FREE(iobuf);
+ return(FILESYS_EXIT_CODE);
+ }
+
+ if (!(ofile = open_file(lpcfg_resolve_context(lp_ctx), ev, "of",
+ lpcfg_smb_ports(lp_ctx), &options,
+ lpcfg_socket_options(lp_ctx),
+ &session_options,
+ lpcfg_gensec_settings(lp_ctx, lp_ctx)))) {
+ SAFE_FREE(iobuf);
+ return(FILESYS_EXIT_CODE);
+ }
+
+ /* Seek the files to their respective starting points. */
+ ifile->io_seek(ifile, check_arg_numeric("skip") * ibs);
+ ofile->io_seek(ofile, check_arg_numeric("seek") * obs);
+
+ DEBUG(4, ("max xmit was negotiated to be %d\n", options.max_xmit));
+
+ for (data_size = 0;;) {
+
+ /* Handle signals. We are somewhat compatible with GNU dd.
+ * SIGINT makes us stop, but still print transfer statistics.
+ * SIGUSR1 makes us print transfer statistics but we continue
+ * copying.
+ */
+ if (dd_sigint) {
+ break;
+ }
+
+ if (dd_sigusr1) {
+ print_transfer_stats();
+ dd_sigusr1 = 0;
+ }
+
+ if (ifile->io_flags & DD_END_OF_FILE) {
+ DEBUG(4, ("flushing %llu bytes at EOF\n",
+ (unsigned long long)data_size));
+ while (data_size > 0) {
+ if (!dd_flush_block(ofile, iobuf,
+ &data_size, obs)) {
+ SAFE_FREE(iobuf);
+ return(IOERROR_EXIT_CODE);
+ }
+ }
+ goto done;
+ }
+
+ /* Try and read enough blocks of ibs bytes to be able write
+ * out one of obs bytes.
+ */
+ if (!dd_fill_block(ifile, iobuf, &data_size, obs, ibs)) {
+ SAFE_FREE(iobuf);
+ return(IOERROR_EXIT_CODE);
+ }
+
+ if (data_size == 0) {
+ /* Done. */
+ SMB_ASSERT(ifile->io_flags & DD_END_OF_FILE);
+ }
+
+ /* Stop reading when we hit the block count. */
+ if (dd_stats.in.bytes >= (ibs * count)) {
+ ifile->io_flags |= DD_END_OF_FILE;
+ }
+
+ /* If we wanted to be a legitimate dd, we would do character
+ * conversions and other shenanigans here.
+ */
+
+ /* Flush what we read in units of obs bytes. We want to have
+ * at least obs bytes in the IO buffer but might not if the
+ * file is too small.
+ */
+ if (data_size &&
+ !dd_flush_block(ofile, iobuf, &data_size, obs)) {
+ SAFE_FREE(iobuf);
+ return(IOERROR_EXIT_CODE);
+ }
+ }
+
+done:
+ SAFE_FREE(iobuf);
+ print_transfer_stats();
+ return(0);
+}
+
+/* ------------------------------------------------------------------------- */
+/* Main. */
+/* ------------------------------------------------------------------------- */
+
+struct poptOption cifsddHelpOptions[] = {
+ {
+ .longName = NULL,
+ .shortName = '\0',
+ .argInfo = POPT_ARG_CALLBACK,
+ .arg = (void *)&cifsdd_help_message,
+ .val = '\0',
+ },
+ {
+ .longName = "help",
+ .shortName = '?',
+ .val = '?',
+ .descrip = "Show this help message",
+ },
+ {
+ .longName = "usage",
+ .shortName = '\0',
+ .val = 'u',
+ .descrip = "Display brief usage message",
+ },
+ POPT_TABLEEND
+};
+
+int main(int argc, char *argv[])
+{
+ const char **const_argv = discard_const_p(const char *, argv);
+ int i;
+ const char ** dd_args;
+ TALLOC_CTX *mem_ctx = NULL;
+ struct loadparm_context *lp_ctx = NULL;
+ struct tevent_context *ev;
+ bool ok;
+ int rc;
+
+ poptContext pctx;
+ struct poptOption poptions[] = {
+ /* POPT_AUTOHELP */
+ { NULL, '\0', POPT_ARG_INCLUDE_TABLE, cifsddHelpOptions,
+ 0, "Help options:", NULL },
+ POPT_COMMON_SAMBA
+ POPT_COMMON_CONNECTION
+ POPT_COMMON_CREDENTIALS
+ POPT_LEGACY_S4
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+
+ /* Block sizes. */
+ set_arg_val("bs", (uint64_t)4096);
+ set_arg_val("ibs", (uint64_t)4096);
+ set_arg_val("obs", (uint64_t)4096);
+ /* Block counts. */
+ set_arg_val("count", (uint64_t)-1);
+ set_arg_val("seek", (uint64_t)0);
+ set_arg_val("skip", (uint64_t)0);
+ /* Files. */
+ set_arg_val("if", NULL);
+ set_arg_val("of", NULL);
+ /* Options. */
+ set_arg_val("direct", false);
+ set_arg_val("sync", false);
+ set_arg_val("oplock", false);
+
+ mem_ctx = talloc_init("cifsdd.c/main");
+ if (mem_ctx == NULL) {
+ d_printf("Not enough memory\n");
+ exit(1);
+ }
+
+ ok = samba_cmdline_init(mem_ctx,
+ SAMBA_CMDLINE_CONFIG_CLIENT,
+ false /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ TALLOC_FREE(mem_ctx);
+ exit(1);
+ }
+
+ pctx = samba_popt_get_context(getprogname(), argc, const_argv, poptions, 0);
+ if (pctx == NULL) {
+ DBG_ERR("Failed to setup popt context!\n");
+ TALLOC_FREE(mem_ctx);
+ exit(1);
+ }
+
+ while ((i = poptGetNextOpt(pctx)) != -1) {
+ switch (i) {
+ case POPT_ERROR_BADOPT:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pctx, 0), poptStrerror(i));
+ poptPrintUsage(pctx, stderr, 0);
+ exit(1);
+ }
+ }
+
+ for (dd_args = poptGetArgs(pctx); dd_args && *dd_args; ++dd_args) {
+
+ if (!set_arg_argv(*dd_args)) {
+ fprintf(stderr, "%s: invalid option: %s\n",
+ PROGNAME, *dd_args);
+ exit(SYNTAX_EXIT_CODE);
+ }
+
+ /* "bs" has the side-effect of setting "ibs" and "obs". */
+ if (strncmp(*dd_args, "bs=", 3) == 0) {
+ uint64_t bs = check_arg_numeric("bs");
+ set_arg_val("ibs", bs);
+ set_arg_val("obs", bs);
+ }
+ }
+
+ poptFreeContext(pctx);
+ samba_cmdline_burn(argc, argv);
+
+ ev = s4_event_context_init(mem_ctx);
+ lp_ctx = samba_cmdline_get_lp_ctx();
+
+ gensec_init();
+ dump_args();
+
+ if (check_arg_numeric("ibs") == 0 || check_arg_numeric("obs") == 0) {
+ fprintf(stderr, "%s: block sizes must be greater that zero\n",
+ PROGNAME);
+ talloc_free(mem_ctx);
+ exit(SYNTAX_EXIT_CODE);
+ }
+
+ if (check_arg_pathname("if") == NULL) {
+ fprintf(stderr, "%s: missing input filename\n", PROGNAME);
+ talloc_free(mem_ctx);
+ exit(SYNTAX_EXIT_CODE);
+ }
+
+ if (check_arg_pathname("of") == NULL) {
+ fprintf(stderr, "%s: missing output filename\n", PROGNAME);
+ talloc_free(mem_ctx);
+ exit(SYNTAX_EXIT_CODE);
+ }
+
+ CatchSignal(SIGINT, dd_handle_signal);
+ CatchSignal(SIGUSR1, dd_handle_signal);
+ rc = copy_files(ev, lp_ctx);
+
+ talloc_free(mem_ctx);
+ return rc;
+}
+
+/* vim: set sw=8 sts=8 ts=8 tw=79 : */
diff --git a/source4/client/cifsdd.h b/source4/client/cifsdd.h
new file mode 100644
index 0000000..4e9cb9d
--- /dev/null
+++ b/source4/client/cifsdd.h
@@ -0,0 +1,109 @@
+/*
+ CIFSDD - dd for SMB.
+ Declarations and administrivia.
+
+ Copyright (C) James Peach 2005-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/>.
+*/
+
+extern const char * const PROGNAME;
+
+enum argtype
+{
+ ARG_NUMERIC,
+ ARG_SIZE,
+ ARG_PATHNAME,
+ ARG_BOOL,
+};
+
+struct argdef
+{
+ const char * arg_name;
+ enum argtype arg_type;
+ const char * arg_help;
+
+ union
+ {
+ bool bval;
+ uint64_t nval;
+ const char * pval;
+ } arg_val;
+};
+
+int set_arg_argv(const char * argv);
+void set_arg_val(const char * name, ...);
+
+bool check_arg_bool(const char * name);
+uint64_t check_arg_numeric(const char * name);
+const char * check_arg_pathname(const char * name);
+
+typedef bool (*dd_seek_func)(void * handle, uint64_t offset);
+typedef bool (*dd_read_func)(void * handle, uint8_t * buf,
+ uint64_t wanted, uint64_t * actual);
+typedef bool (*dd_write_func)(void * handle, uint8_t * buf,
+ uint64_t wanted, uint64_t * actual);
+
+struct dd_stats_record
+{
+ struct
+ {
+ uint64_t fblocks; /* Full blocks. */
+ uint64_t pblocks; /* Partial blocks. */
+ uint64_t bytes; /* Total bytes read. */
+ } in;
+ struct
+ {
+ uint64_t fblocks; /* Full blocks. */
+ uint64_t pblocks; /* Partial blocks. */
+ uint64_t bytes; /* Total bytes written. */
+ } out;
+};
+
+extern struct dd_stats_record dd_stats;
+
+struct dd_iohandle
+{
+ dd_seek_func io_seek;
+ dd_read_func io_read;
+ dd_write_func io_write;
+ int io_flags;
+};
+
+#define DD_END_OF_FILE 0x10000000
+
+#define DD_DIRECT_IO 0x00000001
+#define DD_SYNC_IO 0x00000002
+#define DD_WRITE 0x00000004
+#define DD_OPLOCK 0x00000008
+
+struct smbcli_options;
+struct smbcli_session_options;
+struct tevent_context;
+
+struct dd_iohandle * dd_open_path(struct resolve_context *resolve_ctx,
+ struct tevent_context *ev,
+ const char * path,
+ const char **ports,
+ uint64_t io_size, int options,
+ const char *socket_options,
+ struct smbcli_options *smb_options,
+ struct smbcli_session_options *smb_session_options,
+ struct gensec_settings *gensec_settings);
+bool dd_fill_block(struct dd_iohandle * h, uint8_t * buf,
+ uint64_t * buf_size, uint64_t need_size, uint64_t block_size);
+bool dd_flush_block(struct dd_iohandle * h, uint8_t * buf,
+ uint64_t * buf_size, uint64_t block_size);
+
+/* vim: set sw=8 sts=8 ts=8 tw=79 : */
diff --git a/source4/client/cifsddio.c b/source4/client/cifsddio.c
new file mode 100644
index 0000000..0163daa
--- /dev/null
+++ b/source4/client/cifsddio.c
@@ -0,0 +1,524 @@
+/*
+ CIFSDD - dd for SMB.
+ IO routines, generic and specific.
+
+ Copyright (C) James Peach 2005-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 "system/filesys.h"
+#include "libcli/libcli.h"
+#include "lib/cmdline/cmdline.h"
+
+#include "cifsdd.h"
+
+/* ------------------------------------------------------------------------- */
+/* UNIX file descriptor IO. */
+/* ------------------------------------------------------------------------- */
+
+struct fd_handle
+{
+ struct dd_iohandle h;
+ int fd;
+};
+
+#define IO_HANDLE_TO_FD(h) (((struct fd_handle *)(h))->fd)
+
+static bool fd_seek_func(void * handle, uint64_t offset)
+{
+ ssize_t ret;
+
+ ret = lseek(IO_HANDLE_TO_FD(handle), offset, SEEK_SET);
+ if (ret < 0) {
+ fprintf(stderr, "%s: seek failed: %s\n",
+ PROGNAME, strerror(errno));
+ return(false);
+ }
+
+ return(true);
+}
+
+static bool fd_read_func(void * handle,
+ uint8_t * buf,
+ uint64_t wanted,
+ uint64_t * actual)
+{
+ ssize_t ret;
+
+ ret = read(IO_HANDLE_TO_FD(handle), buf, wanted);
+ if (ret < 0) {
+ fprintf(stderr, "%s: %llu byte read failed: %s\n",
+ PROGNAME, (unsigned long long)wanted,
+ strerror(errno));
+ return(false);
+ }
+
+ *actual = (uint64_t)ret;
+ return(true);
+}
+
+static bool fd_write_func(void * handle,
+ uint8_t * buf,
+ uint64_t wanted,
+ uint64_t * actual)
+{
+ ssize_t ret;
+
+ ret = write(IO_HANDLE_TO_FD(handle), buf, wanted);
+ if (ret < 0) {
+ fprintf(stderr, "%s: %llu byte write failed: %s\n",
+ PROGNAME, (unsigned long long)wanted,
+ strerror(errno));
+ return(false);
+ }
+
+ *actual = (uint64_t)ret;
+ return(true);
+}
+
+static struct dd_iohandle * open_fd_handle(const char * path,
+ uint64_t io_size,
+ int options)
+{
+ struct fd_handle * fdh;
+ int oflags = 0;
+
+ DEBUG(4, ("opening fd stream for %s\n", path));
+ if ((fdh = talloc_zero(NULL, struct fd_handle)) == NULL) {
+ return(NULL);
+ }
+
+ fdh->h.io_read = fd_read_func;
+ fdh->h.io_write = fd_write_func;
+ fdh->h.io_seek = fd_seek_func;
+
+ if (options & DD_DIRECT_IO) {
+#ifdef HAVE_OPEN_O_DIRECT
+ oflags |= O_DIRECT;
+#else
+ DEBUG(1, ("no support for direct IO on this platform\n"));
+#endif
+ }
+
+ if (options & DD_SYNC_IO)
+ oflags |= O_SYNC;
+
+ oflags |= (options & DD_WRITE) ? (O_WRONLY | O_CREAT) : (O_RDONLY);
+
+ fdh->fd = open(path, oflags, 0644);
+ if (fdh->fd < 0) {
+ fprintf(stderr, "%s: %s: %s\n",
+ PROGNAME, path, strerror(errno));
+ talloc_free(fdh);
+ return(NULL);
+ }
+
+ if (options & DD_OPLOCK) {
+ DEBUG(2, ("FIXME: take local oplock on %s\n", path));
+ }
+
+ SMB_ASSERT((void *)fdh == (void *)&fdh->h);
+ return(&fdh->h);
+}
+
+/* ------------------------------------------------------------------------- */
+/* CIFS client IO. */
+/* ------------------------------------------------------------------------- */
+
+struct cifs_handle
+{
+ struct dd_iohandle h;
+ struct smbcli_state * cli;
+ int fnum;
+ uint64_t offset;
+};
+
+#define IO_HANDLE_TO_SMB(h) ((struct cifs_handle *)(h))
+
+static bool smb_seek_func(void * handle, uint64_t offset)
+{
+ IO_HANDLE_TO_SMB(handle)->offset = offset;
+ return(true);
+}
+
+static bool smb_read_func(void * handle, uint8_t * buf, uint64_t wanted,
+ uint64_t * actual)
+{
+ NTSTATUS ret;
+ union smb_read r;
+ struct cifs_handle * smbh;
+
+ ZERO_STRUCT(r);
+ smbh = IO_HANDLE_TO_SMB(handle);
+
+ r.generic.level = RAW_READ_READX;
+ r.readx.in.file.fnum = smbh->fnum;
+ r.readx.in.offset = smbh->offset;
+ r.readx.in.mincnt = wanted;
+ r.readx.in.maxcnt = wanted;
+ r.readx.out.data = buf;
+
+ /* FIXME: Should I really set readx.in.remaining? That just seems
+ * redundant.
+ */
+ ret = smb_raw_read(smbh->cli->tree, &r);
+ if (!NT_STATUS_IS_OK(ret)) {
+ fprintf(stderr, "%s: %llu byte read failed: %s\n",
+ PROGNAME, (unsigned long long)wanted,
+ nt_errstr(ret));
+ return(false);
+ }
+
+ /* Trap integer wrap. */
+ SMB_ASSERT((smbh->offset + r.readx.out.nread) >= smbh->offset);
+
+ *actual = r.readx.out.nread;
+ smbh->offset += r.readx.out.nread;
+ return(true);
+}
+
+static bool smb_write_func(void * handle, uint8_t * buf, uint64_t wanted,
+ uint64_t * actual)
+{
+ NTSTATUS ret;
+ union smb_write w;
+ struct cifs_handle * smbh;
+
+ ZERO_STRUCT(w);
+ smbh = IO_HANDLE_TO_SMB(handle);
+
+ w.generic.level = RAW_WRITE_WRITEX;
+ w.writex.in.file.fnum = smbh->fnum;
+ w.writex.in.offset = smbh->offset;
+ w.writex.in.count = wanted;
+ w.writex.in.data = buf;
+
+ ret = smb_raw_write(smbh->cli->tree, &w);
+ if (!NT_STATUS_IS_OK(ret)) {
+ fprintf(stderr, "%s: %llu byte write failed: %s\n",
+ PROGNAME, (unsigned long long)wanted,
+ nt_errstr(ret));
+ return(false);
+ }
+
+ *actual = w.writex.out.nwritten;
+ smbh->offset += w.writex.out.nwritten;
+ return(true);
+}
+
+static struct smbcli_state * init_smb_session(struct resolve_context *resolve_ctx,
+ struct tevent_context *ev,
+ const char * host,
+ const char **ports,
+ const char * share,
+ const char *socket_options,
+ struct smbcli_options *options,
+ struct smbcli_session_options *session_options,
+ struct gensec_settings *gensec_settings)
+{
+ NTSTATUS ret;
+ struct smbcli_state * cli = NULL;
+
+ /* When we support SMB URLs, we can get different user credentials for
+ * each connection, but for now, we just use the same one for both.
+ */
+ ret = smbcli_full_connection(NULL, &cli, host, ports, share,
+ NULL /* devtype */,
+ socket_options,
+ samba_cmdline_get_creds(),
+ resolve_ctx,
+ ev, options,
+ session_options,
+ gensec_settings);
+
+ if (!NT_STATUS_IS_OK(ret)) {
+ fprintf(stderr, "%s: connecting to //%s/%s: %s\n",
+ PROGNAME, host, share, nt_errstr(ret));
+ return(NULL);
+ }
+
+ return(cli);
+}
+
+static int open_smb_file(struct smbcli_state * cli,
+ const char * path,
+ int options)
+{
+ NTSTATUS ret;
+ union smb_open o;
+
+ ZERO_STRUCT(o);
+
+ o.ntcreatex.level = RAW_OPEN_NTCREATEX;
+ o.ntcreatex.in.fname = path;
+
+ /* TODO: It's not clear whether to use these flags or to use the
+ * similarly named NTCREATEX flags in the create_options field.
+ */
+ if (options & DD_DIRECT_IO)
+ o.ntcreatex.in.flags |= FILE_FLAG_NO_BUFFERING;
+
+ if (options & DD_SYNC_IO)
+ o.ntcreatex.in.flags |= FILE_FLAG_WRITE_THROUGH;
+
+ o.ntcreatex.in.access_mask |=
+ (options & DD_WRITE) ? SEC_FILE_WRITE_DATA
+ : SEC_FILE_READ_DATA;
+
+ /* Try to create the file only if we will be writing to it. */
+ o.ntcreatex.in.open_disposition =
+ (options & DD_WRITE) ? NTCREATEX_DISP_OPEN_IF
+ : NTCREATEX_DISP_OPEN;
+
+ o.ntcreatex.in.share_access =
+ NTCREATEX_SHARE_ACCESS_READ | NTCREATEX_SHARE_ACCESS_WRITE;
+
+ if (options & DD_OPLOCK) {
+ o.ntcreatex.in.flags |= NTCREATEX_FLAGS_REQUEST_OPLOCK;
+ }
+
+ ret = smb_raw_open(cli->tree, NULL, &o);
+ if (!NT_STATUS_IS_OK(ret)) {
+ fprintf(stderr, "%s: opening %s: %s\n",
+ PROGNAME, path, nt_errstr(ret));
+ return(-1);
+ }
+
+ return(o.ntcreatex.out.file.fnum);
+}
+
+static struct dd_iohandle * open_cifs_handle(struct resolve_context *resolve_ctx,
+ struct tevent_context *ev,
+ const char * host,
+ const char **ports,
+ const char * share,
+ const char * path,
+ uint64_t io_size,
+ int options,
+ const char *socket_options,
+ struct smbcli_options *smb_options,
+ struct smbcli_session_options *smb_session_options,
+ struct gensec_settings *gensec_settings)
+{
+ struct cifs_handle * smbh;
+
+ if (path == NULL || *path == '\0') {
+ fprintf(stderr, "%s: missing path name within share //%s/%s\n",
+ PROGNAME, host, share);
+ }
+
+ DEBUG(4, ("opening SMB stream to //%s/%s for %s\n",
+ host, share, path));
+
+ if ((smbh = talloc_zero(NULL, struct cifs_handle)) == NULL) {
+ return(NULL);
+ }
+
+ smbh->h.io_read = smb_read_func;
+ smbh->h.io_write = smb_write_func;
+ smbh->h.io_seek = smb_seek_func;
+
+ if ((smbh->cli = init_smb_session(resolve_ctx, ev, host, ports, share,
+ socket_options,
+ smb_options, smb_session_options,
+ gensec_settings)) == NULL) {
+ return(NULL);
+ }
+
+ DEBUG(4, ("connected to //%s/%s with xmit size of %u bytes\n",
+ host, share, smbh->cli->transport->negotiate.max_xmit));
+
+ smbh->fnum = open_smb_file(smbh->cli, path, options);
+ return(&smbh->h);
+}
+
+/* ------------------------------------------------------------------------- */
+/* Abstract IO interface. */
+/* ------------------------------------------------------------------------- */
+
+struct dd_iohandle * dd_open_path(struct resolve_context *resolve_ctx,
+ struct tevent_context *ev,
+ const char * path,
+ const char **ports,
+ uint64_t io_size,
+ int options,
+ const char *socket_options,
+ struct smbcli_options *smb_options,
+ struct smbcli_session_options *smb_session_options,
+ struct gensec_settings *gensec_settings)
+{
+ if (file_exist(path)) {
+ return(open_fd_handle(path, io_size, options));
+ } else {
+ char * host;
+ char * share;
+
+ if (smbcli_parse_unc(path, NULL, &host, &share)) {
+ const char * remain;
+ remain = strstr(path, share) + strlen(share);
+
+ /* Skip over leading directory separators. */
+ while (*remain == '/' || *remain == '\\') { remain++; }
+
+ return(open_cifs_handle(resolve_ctx, ev, host, ports,
+ share, remain,
+ io_size, options,
+ socket_options, smb_options,
+ smb_session_options,
+ gensec_settings));
+ }
+
+ return(open_fd_handle(path, io_size, options));
+ }
+}
+
+/* Fill the buffer till it has at least need_size bytes. Use read operations of
+ * block_size bytes. Return the number of bytes read and fill buf_size with
+ * the new buffer size.
+ *
+ * NOTE: The IO buffer is guaranteed to be big enough to fit
+ * need_size + block_size bytes into it.
+ */
+bool dd_fill_block(struct dd_iohandle * h,
+ uint8_t * buf,
+ uint64_t * buf_size,
+ uint64_t need_size,
+ uint64_t block_size)
+{
+ uint64_t read_size;
+
+ SMB_ASSERT(block_size > 0);
+ SMB_ASSERT(need_size > 0);
+
+ while (*buf_size < need_size) {
+
+ if (!h->io_read(h, buf + (*buf_size), block_size, &read_size)) {
+ return(false);
+ }
+
+ if (read_size == 0) {
+ h->io_flags |= DD_END_OF_FILE;
+ break;
+ }
+
+ DEBUG(6, ("added %llu bytes to IO buffer (need %llu bytes)\n",
+ (unsigned long long)read_size,
+ (unsigned long long)need_size));
+
+ *buf_size += read_size;
+ dd_stats.in.bytes += read_size;
+
+ if (read_size == block_size) {
+ dd_stats.in.fblocks++;
+ } else {
+ DEBUG(3, ("partial read of %llu bytes (expected %llu)\n",
+ (unsigned long long)read_size,
+ (unsigned long long)block_size));
+ dd_stats.in.pblocks++;
+ }
+ }
+
+ return(true);
+}
+
+/* Flush a buffer that contains buf_size bytes. Use writes of block_size to do it,
+ * and shift any remaining bytes back to the head of the buffer when there are
+ * no more block_size sized IOs left.
+ */
+bool dd_flush_block(struct dd_iohandle * h,
+ uint8_t * buf,
+ uint64_t * buf_size,
+ uint64_t block_size)
+{
+ uint64_t write_size;
+ uint64_t total_size = 0;
+
+ SMB_ASSERT(block_size > 0);
+
+ /* We have explicitly been asked to write a partial block. */
+ if ((*buf_size) < block_size) {
+
+ if (!h->io_write(h, buf, *buf_size, &write_size)) {
+ return(false);
+ }
+
+ if (write_size == 0) {
+ fprintf(stderr, "%s: unexpectedly wrote 0 bytes\n",
+ PROGNAME);
+ return(false);
+ }
+
+ total_size += write_size;
+ dd_stats.out.bytes += write_size;
+ dd_stats.out.pblocks++;
+ }
+
+ /* Write as many full blocks as there are in the buffer. */
+ while (((*buf_size) - total_size) >= block_size) {
+
+ if (!h->io_write(h, buf + total_size, block_size, &write_size)) {
+ return(false);
+ }
+
+ if (write_size == 0) {
+ fprintf(stderr, "%s: unexpectedly wrote 0 bytes\n",
+ PROGNAME);
+ return(false);
+ }
+
+ if (write_size == block_size) {
+ dd_stats.out.fblocks++;
+ } else {
+ dd_stats.out.pblocks++;
+ }
+
+ total_size += write_size;
+ dd_stats.out.bytes += write_size;
+
+ DEBUG(6, ("flushed %llu bytes from IO buffer of %llu bytes (%llu remain)\n",
+ (unsigned long long)block_size,
+ (unsigned long long)block_size,
+ (unsigned long long)(block_size - total_size)));
+ }
+
+ SMB_ASSERT(total_size > 0);
+
+ /* We have flushed as much of the IO buffer as we can while
+ * still doing block_size'd operations. Shift any remaining data
+ * to the front of the IO buffer.
+ */
+ if ((*buf_size) > total_size) {
+ uint64_t remain = (*buf_size) - total_size;
+
+ DEBUG(3, ("shifting %llu remainder bytes to IO buffer head\n",
+ (unsigned long long)remain));
+
+ memmove(buf, buf + total_size, remain);
+ (*buf_size) = remain;
+ } else if ((*buf_size) == total_size) {
+ (*buf_size) = 0;
+ } else {
+ /* Else buffer contains buf_size bytes that we will append
+ * to next time round.
+ */
+ DEBUG(3, ("%llu unflushed bytes left in IO buffer\n",
+ (unsigned long long)(*buf_size)));
+ }
+
+ return(true);
+}
+
+/* vim: set sw=8 sts=8 ts=8 tw=79 : */
diff --git a/source4/client/client.c b/source4/client/client.c
new file mode 100644
index 0000000..7190682
--- /dev/null
+++ b/source4/client/client.c
@@ -0,0 +1,3552 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB client
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Simo Sorce 2001-2002
+ Copyright (C) Jelmer Vernooij 2003-2004
+ 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/>.
+*/
+
+/*
+ * TODO: remove this ... and don't use talloc_append_string()
+ *
+ * NOTE: I'm not changing the code yet, because I assume there're
+ * some bugs in the existing code and I'm not sure how to fix
+ * them correctly.
+ */
+#define TALLOC_DEPRECATED 1
+
+#include "includes.h"
+#include "version.h"
+#include "libcli/libcli.h"
+#include "lib/events/events.h"
+#include "lib/cmdline/cmdline.h"
+#include "librpc/gen_ndr/ndr_srvsvc_c.h"
+#include "librpc/gen_ndr/ndr_lsa.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "libcli/util/clilsa.h"
+#include "system/dir.h"
+#include "system/filesys.h"
+#include "../lib/util/dlinklist.h"
+#include "system/readline.h"
+#include "auth/credentials/credentials.h"
+#include "auth/gensec/gensec.h"
+#include "system/time.h" /* needed by some systems for asctime() */
+#include "libcli/resolve/resolve.h"
+#include "libcli/security/security.h"
+#include "../libcli/smbreadline/smbreadline.h"
+#include "librpc/gen_ndr/ndr_nbt.h"
+#include "param/param.h"
+#include "libcli/raw/raw_proto.h"
+
+/* the default pager to use for the client "more" command. Users can
+ * override this with the PAGER environment variable */
+#ifndef DEFAULT_PAGER
+#define DEFAULT_PAGER "more"
+#endif
+
+#undef strncasecmp
+
+struct smbclient_context {
+ char *remote_cur_dir;
+ struct smbcli_state *cli;
+ char *fileselection;
+ time_t newer_than;
+ bool prompt;
+ bool recurse;
+ int archive_level;
+ bool lowercase;
+ int printmode;
+ bool translation;
+ int io_bufsize;
+};
+
+/* timing globals */
+static uint64_t get_total_size = 0;
+static unsigned int get_total_time_ms = 0;
+static uint64_t put_total_size = 0;
+static unsigned int put_total_time_ms = 0;
+
+/* Unfortunately, there is no way to pass the a context to the completion function as an argument */
+static struct smbclient_context *rl_ctx;
+
+/* totals globals */
+static double dir_total;
+
+/*******************************************************************
+ Reduce a file name, removing .. elements.
+********************************************************************/
+static void dos_clean_name(char *s)
+{
+ char *p=NULL,*r;
+
+ DEBUG(3,("dos_clean_name [%s]\n",s));
+
+ /* remove any double slashes */
+ all_string_sub(s, "\\\\", "\\", 0);
+
+ while ((p = strstr(s,"\\..\\")) != NULL) {
+ *p = '\0';
+ if ((r = strrchr(s,'\\')) != NULL)
+ memmove(r,p+3,strlen(p+3)+1);
+ }
+
+ trim_string(s,NULL,"\\..");
+
+ all_string_sub(s, "\\.\\", "\\", 0);
+}
+
+/****************************************************************************
+write to a local file with CR/LF->LF translation if appropriate. return the
+number taken from the buffer. This may not equal the number written.
+****************************************************************************/
+static int writefile(int f, const void *_b, int n, bool translation)
+{
+ const uint8_t *b = (const uint8_t *)_b;
+ int i;
+
+ if (!translation) {
+ return write(f,b,n);
+ }
+
+ i = 0;
+ while (i < n) {
+ if (*b == '\r' && (i<(n-1)) && *(b+1) == '\n') {
+ b++;i++;
+ }
+ if (write(f, b, 1) != 1) {
+ break;
+ }
+ b++;
+ i++;
+ }
+
+ return(i);
+}
+
+/****************************************************************************
+ read from a file with LF->CR/LF translation if appropriate. return the
+ number read. read approx n bytes.
+****************************************************************************/
+static int readfile(void *_b, int n, FILE *f, bool translation)
+{
+ uint8_t *b = (uint8_t *)_b;
+ int i;
+ int c;
+
+ if (!translation)
+ return fread(b,1,n,f);
+
+ i = 0;
+ while (i < (n - 1)) {
+ if ((c = getc(f)) == EOF) {
+ break;
+ }
+
+ if (c == '\n') { /* change all LFs to CR/LF */
+ b[i++] = '\r';
+ }
+
+ b[i++] = c;
+ }
+
+ return(i);
+}
+
+
+/****************************************************************************
+send a message
+****************************************************************************/
+static void send_message(struct smbcli_state *cli, const char *desthost)
+{
+ char msg[1600];
+ int total_len = 0;
+ int grp_id;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+
+ if (!smbcli_message_start(cli->tree,
+ desthost,
+ cli_credentials_get_username(creds),
+ &grp_id)) {
+ d_printf("message start: %s\n", smbcli_errstr(cli->tree));
+ return;
+ }
+
+
+ d_printf("Connected. Type your message, ending it with a Control-D\n");
+
+ while (!feof(stdin) && total_len < 1600) {
+ int maxlen = MIN(1600 - total_len,127);
+ int l=0;
+ int c;
+
+ for (l=0;l<maxlen && (c=fgetc(stdin))!=EOF;l++) {
+ if (c == '\n')
+ msg[l++] = '\r';
+ msg[l] = c;
+ }
+
+ if (!smbcli_message_text(cli->tree, msg, l, grp_id)) {
+ d_printf("SMBsendtxt failed (%s)\n",smbcli_errstr(cli->tree));
+ return;
+ }
+
+ total_len += l;
+ }
+
+ if (total_len >= 1600)
+ d_printf("the message was truncated to 1600 bytes\n");
+ else
+ d_printf("sent %d bytes\n",total_len);
+
+ if (!smbcli_message_end(cli->tree, grp_id)) {
+ d_printf("SMBsendend failed (%s)\n",smbcli_errstr(cli->tree));
+ return;
+ }
+}
+
+
+
+/****************************************************************************
+check the space on a device
+****************************************************************************/
+static int do_dskattr(struct smbclient_context *ctx)
+{
+ uint32_t bsize;
+ uint64_t total, avail;
+
+ if (NT_STATUS_IS_ERR(smbcli_dskattr(ctx->cli->tree, &bsize, &total, &avail))) {
+ d_printf("Error in dskattr: %s\n",smbcli_errstr(ctx->cli->tree));
+ return 1;
+ }
+
+ d_printf("\n\t\t%llu blocks of size %u. %llu blocks available\n",
+ (unsigned long long)total,
+ (unsigned)bsize,
+ (unsigned long long)avail);
+
+ return 0;
+}
+
+/****************************************************************************
+show cd/pwd
+****************************************************************************/
+static int cmd_pwd(struct smbclient_context *ctx, const char **args)
+{
+ d_printf("Current directory is %s\n", ctx->remote_cur_dir);
+ return 0;
+}
+
+/*
+ convert a string to dos format
+*/
+static void dos_format(char *s)
+{
+ string_replace(s, '/', '\\');
+}
+
+/****************************************************************************
+change directory - inner section
+****************************************************************************/
+static int do_cd(struct smbclient_context *ctx, const char *newdir)
+{
+ char *dname;
+
+ /* Save the current directory in case the
+ new directory is invalid */
+ if (newdir[0] == '\\')
+ dname = talloc_strdup(ctx, newdir);
+ else
+ dname = talloc_asprintf(ctx, "%s\\%s", ctx->remote_cur_dir, newdir);
+
+ dos_format(dname);
+
+ if (*(dname+strlen(dname)-1) != '\\') {
+ dname = talloc_append_string(NULL, dname, "\\");
+ }
+ dos_clean_name(dname);
+
+ if (NT_STATUS_IS_ERR(smbcli_chkpath(ctx->cli->tree, dname))) {
+ d_printf("cd %s: %s\n", dname, smbcli_errstr(ctx->cli->tree));
+ talloc_free(dname);
+ } else {
+ ctx->remote_cur_dir = dname;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+change directory
+****************************************************************************/
+static int cmd_cd(struct smbclient_context *ctx, const char **args)
+{
+ int rc = 0;
+
+ if (args[1])
+ rc = do_cd(ctx, args[1]);
+ else
+ d_printf("Current directory is %s\n",ctx->remote_cur_dir);
+
+ return rc;
+}
+
+
+static bool mask_match(struct smbcli_state *c, const char *string,
+ const char *pattern, bool is_case_sensitive)
+{
+ int ret;
+
+ if (ISDOTDOT(string))
+ string = ".";
+ if (ISDOT(pattern))
+ return false;
+
+ ret = ms_fnmatch_protocol(pattern, string,
+ c->transport->negotiate.protocol,
+ is_case_sensitive);
+ return (ret == 0);
+}
+
+
+
+/*******************************************************************
+ decide if a file should be operated on
+ ********************************************************************/
+static bool do_this_one(struct smbclient_context *ctx, struct clilist_file_info *finfo)
+{
+ if (finfo->attrib & FILE_ATTRIBUTE_DIRECTORY) return(true);
+
+ if (ctx->fileselection &&
+ !mask_match(ctx->cli, finfo->name,ctx->fileselection,false)) {
+ DEBUG(3,("mask_match %s failed\n", finfo->name));
+ return false;
+ }
+
+ if (ctx->newer_than && finfo->mtime < ctx->newer_than) {
+ DEBUG(3,("newer_than %s failed\n", finfo->name));
+ return(false);
+ }
+
+ if ((ctx->archive_level==1 || ctx->archive_level==2) && !(finfo->attrib & FILE_ATTRIBUTE_ARCHIVE)) {
+ DEBUG(3,("archive %s failed\n", finfo->name));
+ return(false);
+ }
+
+ return(true);
+}
+
+/****************************************************************************
+ display info about a file
+ ****************************************************************************/
+static void display_finfo(struct smbclient_context *ctx, struct clilist_file_info *finfo)
+{
+ if (do_this_one(ctx, finfo)) {
+ time_t t = finfo->mtime; /* the time is assumed to be passed as GMT */
+ char *astr = attrib_string(NULL, finfo->attrib);
+ d_printf(" %-30s%7.7s %8.0f %s",
+ finfo->name,
+ astr,
+ (double)finfo->size,
+ asctime(localtime(&t)));
+ dir_total += finfo->size;
+ talloc_free(astr);
+ }
+}
+
+
+/****************************************************************************
+ accumulate size of a file
+ ****************************************************************************/
+static void do_du(struct smbclient_context *ctx, struct clilist_file_info *finfo)
+{
+ if (do_this_one(ctx, finfo)) {
+ dir_total += finfo->size;
+ }
+}
+
+static bool do_list_recurse;
+static bool do_list_dirs;
+static char *do_list_queue = 0;
+static long do_list_queue_size = 0;
+static long do_list_queue_start = 0;
+static long do_list_queue_end = 0;
+static void (*do_list_fn)(struct smbclient_context *, struct clilist_file_info *);
+
+/****************************************************************************
+functions for do_list_queue
+ ****************************************************************************/
+
+/*
+ * The do_list_queue is a NUL-separated list of strings stored in a
+ * char*. Since this is a FIFO, we keep track of the beginning and
+ * ending locations of the data in the queue. When we overflow, we
+ * double the size of the char*. When the start of the data passes
+ * the midpoint, we move everything back. This is logically more
+ * complex than a linked list, but easier from a memory management
+ * angle. In any memory error condition, do_list_queue is reset.
+ * Functions check to ensure that do_list_queue is non-NULL before
+ * accessing it.
+ */
+static void reset_do_list_queue(void)
+{
+ SAFE_FREE(do_list_queue);
+ do_list_queue_size = 0;
+ do_list_queue_start = 0;
+ do_list_queue_end = 0;
+}
+
+static void init_do_list_queue(void)
+{
+ reset_do_list_queue();
+ do_list_queue_size = 1024;
+ do_list_queue = malloc_array_p(char, do_list_queue_size);
+ if (do_list_queue == 0) {
+ d_printf("malloc fail for size %d\n",
+ (int)do_list_queue_size);
+ reset_do_list_queue();
+ } else {
+ memset(do_list_queue, 0, do_list_queue_size);
+ }
+}
+
+static void adjust_do_list_queue(void)
+{
+ if (do_list_queue == NULL) return;
+
+ /*
+ * If the starting point of the queue is more than half way through,
+ * move everything toward the beginning.
+ */
+ if (do_list_queue_start == do_list_queue_end)
+ {
+ DEBUG(4,("do_list_queue is empty\n"));
+ do_list_queue_start = do_list_queue_end = 0;
+ *do_list_queue = '\0';
+ }
+ else if (do_list_queue_start > (do_list_queue_size / 2))
+ {
+ DEBUG(4,("sliding do_list_queue backward\n"));
+ memmove(do_list_queue,
+ do_list_queue + do_list_queue_start,
+ do_list_queue_end - do_list_queue_start);
+ do_list_queue_end -= do_list_queue_start;
+ do_list_queue_start = 0;
+ }
+
+}
+
+static void add_to_do_list_queue(const char* entry)
+{
+ char *dlq;
+ long new_end;
+
+ if (entry == NULL) {
+ entry = "";
+ }
+
+ new_end = do_list_queue_end + ((long)strlen(entry)) + 1;
+ while (new_end > do_list_queue_size)
+ {
+ do_list_queue_size *= 2;
+ DEBUG(4,("enlarging do_list_queue to %d\n",
+ (int)do_list_queue_size));
+ dlq = realloc_p(do_list_queue, char, do_list_queue_size);
+ if (! dlq) {
+ d_printf("failure enlarging do_list_queue to %d bytes\n",
+ (int)do_list_queue_size);
+ reset_do_list_queue();
+ }
+ else
+ {
+ do_list_queue = dlq;
+ memset(do_list_queue + do_list_queue_size / 2,
+ 0, do_list_queue_size / 2);
+ }
+ }
+ if (do_list_queue)
+ {
+ strlcpy(do_list_queue + do_list_queue_end, entry,
+ do_list_queue_size - do_list_queue_end);
+ do_list_queue_end = new_end;
+ DEBUG(4,("added %s to do_list_queue (start=%d, end=%d)\n",
+ entry, (int)do_list_queue_start, (int)do_list_queue_end));
+ }
+}
+
+static char *do_list_queue_head(void)
+{
+ return do_list_queue + do_list_queue_start;
+}
+
+static void remove_do_list_queue_head(void)
+{
+ if (do_list_queue_end > do_list_queue_start)
+ {
+ do_list_queue_start += strlen(do_list_queue_head()) + 1;
+ adjust_do_list_queue();
+ DEBUG(4,("removed head of do_list_queue (start=%d, end=%d)\n",
+ (int)do_list_queue_start, (int)do_list_queue_end));
+ }
+}
+
+static int do_list_queue_empty(void)
+{
+ return (! (do_list_queue && *do_list_queue));
+}
+
+/****************************************************************************
+a helper for do_list
+ ****************************************************************************/
+static void do_list_helper(struct clilist_file_info *f, const char *mask, void *state)
+{
+ struct smbclient_context *ctx = (struct smbclient_context *)state;
+
+ if (f->attrib & FILE_ATTRIBUTE_DIRECTORY) {
+ if (do_list_dirs && do_this_one(ctx, f)) {
+ do_list_fn(ctx, f);
+ }
+ if (do_list_recurse &&
+ !ISDOT(f->name) &&
+ !ISDOTDOT(f->name)) {
+ char *mask2;
+ char *p;
+
+ mask2 = talloc_strdup(NULL, mask);
+ p = strrchr_m(mask2,'\\');
+ if (!p) return;
+ p[1] = 0;
+ mask2 = talloc_asprintf_append_buffer(mask2, "%s\\*", f->name);
+ add_to_do_list_queue(mask2);
+ }
+ return;
+ }
+
+ if (do_this_one(ctx, f)) {
+ do_list_fn(ctx, f);
+ }
+}
+
+
+/****************************************************************************
+a wrapper around smbcli_list that adds recursion
+ ****************************************************************************/
+static void do_list(struct smbclient_context *ctx, const char *mask,uint16_t attribute,
+ void (*fn)(struct smbclient_context *, struct clilist_file_info *),bool rec, bool dirs)
+{
+ static int in_do_list = 0;
+
+ if (in_do_list && rec)
+ {
+ fprintf(stderr, "INTERNAL ERROR: do_list called recursively when the recursive flag is true\n");
+ exit(1);
+ }
+
+ in_do_list = 1;
+
+ do_list_recurse = rec;
+ do_list_dirs = dirs;
+ do_list_fn = fn;
+
+ if (rec)
+ {
+ init_do_list_queue();
+ add_to_do_list_queue(mask);
+
+ while (! do_list_queue_empty())
+ {
+ /*
+ * Need to copy head so that it doesn't become
+ * invalid inside the call to smbcli_list. This
+ * would happen if the list were expanded
+ * during the call.
+ * Fix from E. Jay Berkenbilt (ejb@ql.org)
+ */
+ char *head;
+ head = do_list_queue_head();
+ smbcli_list(ctx->cli->tree, head, attribute, do_list_helper, ctx);
+ remove_do_list_queue_head();
+ if ((! do_list_queue_empty()) && (fn == display_finfo))
+ {
+ char* next_file = do_list_queue_head();
+ char* save_ch = 0;
+ if ((strlen(next_file) >= 2) &&
+ (next_file[strlen(next_file) - 1] == '*') &&
+ (next_file[strlen(next_file) - 2] == '\\'))
+ {
+ save_ch = next_file +
+ strlen(next_file) - 2;
+ *save_ch = '\0';
+ }
+ d_printf("\n%s\n",next_file);
+ if (save_ch)
+ {
+ *save_ch = '\\';
+ }
+ }
+ }
+ }
+ else
+ {
+ if (smbcli_list(ctx->cli->tree, mask, attribute, do_list_helper, ctx) == -1)
+ {
+ d_printf("%s listing %s\n", smbcli_errstr(ctx->cli->tree), mask);
+ }
+ }
+
+ in_do_list = 0;
+ reset_do_list_queue();
+}
+
+/****************************************************************************
+ get a directory listing
+ ****************************************************************************/
+static int cmd_dir(struct smbclient_context *ctx, const char **args)
+{
+ uint16_t attribute = FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN;
+ char *mask;
+ int rc;
+
+ dir_total = 0;
+
+ mask = talloc_strdup(ctx, ctx->remote_cur_dir);
+ if(mask[strlen(mask)-1]!='\\')
+ mask = talloc_append_string(ctx, mask,"\\");
+
+ if (args[1]) {
+ mask = talloc_strdup(ctx, args[1]);
+ if (mask[0] != '\\')
+ mask = talloc_append_string(ctx, mask, "\\");
+ dos_format(mask);
+ }
+ else {
+ if (ctx->cli->tree->session->transport->negotiate.protocol <=
+ PROTOCOL_LANMAN1) {
+ mask = talloc_append_string(ctx, mask, "*.*");
+ } else {
+ mask = talloc_append_string(ctx, mask, "*");
+ }
+ }
+
+ do_list(ctx, mask, attribute, display_finfo, ctx->recurse, true);
+
+ rc = do_dskattr(ctx);
+
+ DEBUG(3, ("Total bytes listed: %.0f\n", dir_total));
+
+ return rc;
+}
+
+
+/****************************************************************************
+ get a directory listing
+ ****************************************************************************/
+static int cmd_du(struct smbclient_context *ctx, const char **args)
+{
+ uint16_t attribute = FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN;
+ int rc;
+ char *mask;
+
+ dir_total = 0;
+
+ if (args[1]) {
+ if (args[1][0] == '\\')
+ mask = talloc_strdup(ctx, args[1]);
+ else
+ mask = talloc_asprintf(ctx, "%s\\%s", ctx->remote_cur_dir, args[1]);
+ dos_format(mask);
+ } else {
+ mask = talloc_asprintf(ctx, "%s\\*", ctx->remote_cur_dir);
+ }
+
+ do_list(ctx, mask, attribute, do_du, ctx->recurse, true);
+
+ talloc_free(mask);
+
+ rc = do_dskattr(ctx);
+
+ d_printf("Total number of bytes: %.0f\n", dir_total);
+
+ return rc;
+}
+
+
+/****************************************************************************
+ get a file from rname to lname
+ ****************************************************************************/
+static int do_get(struct smbclient_context *ctx, char *rname, const char *p_lname, bool reget)
+{
+ int handle = 0, fnum;
+ bool newhandle = false;
+ uint8_t *data;
+ struct timeval tp_start;
+ int read_size = ctx->io_bufsize;
+ uint16_t attr;
+ size_t size;
+ off_t start = 0;
+ off_t nread = 0;
+ int rc = 0;
+ char *lname;
+
+
+ GetTimeOfDay(&tp_start);
+
+ if (ctx->lowercase) {
+ lname = strlower_talloc(ctx, p_lname);
+ } else {
+ lname = talloc_strdup(ctx, p_lname);
+ }
+
+ fnum = smbcli_open(ctx->cli->tree, rname, O_RDONLY, DENY_NONE);
+
+ if (fnum == -1) {
+ d_printf("%s opening remote file %s\n",smbcli_errstr(ctx->cli->tree),rname);
+ return 1;
+ }
+
+ if(!strcmp(lname,"-")) {
+ handle = fileno(stdout);
+ } else {
+ if (reget) {
+ handle = open(lname, O_WRONLY|O_CREAT, 0644);
+ if (handle >= 0) {
+ start = lseek(handle, 0, SEEK_END);
+ if (start == -1) {
+ d_printf("Error seeking local file\n");
+ close(handle);
+ return 1;
+ }
+ }
+ } else {
+ handle = open(lname, O_WRONLY|O_CREAT|O_TRUNC, 0644);
+ }
+ newhandle = true;
+ }
+ if (handle < 0) {
+ d_printf("Error opening local file %s\n",lname);
+ return 1;
+ }
+
+
+ if (NT_STATUS_IS_ERR(smbcli_qfileinfo(ctx->cli->tree, fnum,
+ &attr, &size, NULL, NULL, NULL, NULL, NULL)) &&
+ NT_STATUS_IS_ERR(smbcli_getattrE(ctx->cli->tree, fnum,
+ &attr, &size, NULL, NULL, NULL))) {
+ d_printf("getattrib: %s\n",smbcli_errstr(ctx->cli->tree));
+ if (newhandle) {
+ close(handle);
+ }
+ return 1;
+ }
+
+ DEBUG(2,("getting file %s of size %.0f as %s ",
+ rname, (double)size, lname));
+
+ if(!(data = (uint8_t *)malloc(read_size))) {
+ d_printf("malloc fail for size %d\n", read_size);
+ smbcli_close(ctx->cli->tree, fnum);
+ if (newhandle) {
+ close(handle);
+ }
+ return 1;
+ }
+
+ while (1) {
+ int n = smbcli_read(ctx->cli->tree, fnum, data, nread + start, read_size);
+
+ if (n <= 0) break;
+
+ if (writefile(handle,data, n, ctx->translation) != n) {
+ d_printf("Error writing local file\n");
+ rc = 1;
+ break;
+ }
+
+ nread += n;
+ }
+
+ if (nread + start < size) {
+ DEBUG (0, ("Short read when getting file %s. Only got %ld bytes.\n",
+ rname, (long)nread));
+
+ rc = 1;
+ }
+
+ SAFE_FREE(data);
+
+ if (NT_STATUS_IS_ERR(smbcli_close(ctx->cli->tree, fnum))) {
+ d_printf("Error %s closing remote file\n",smbcli_errstr(ctx->cli->tree));
+ rc = 1;
+ }
+
+ if (newhandle) {
+ close(handle);
+ }
+
+ if (ctx->archive_level >= 2 && (attr & FILE_ATTRIBUTE_ARCHIVE)) {
+ smbcli_setatr(ctx->cli->tree, rname, attr & ~(uint16_t)FILE_ATTRIBUTE_ARCHIVE, 0);
+ }
+
+ {
+ struct timeval tp_end;
+ int this_time;
+
+ GetTimeOfDay(&tp_end);
+ this_time =
+ (tp_end.tv_sec - tp_start.tv_sec)*1000 +
+ (tp_end.tv_usec - tp_start.tv_usec)/1000;
+ get_total_time_ms += this_time;
+ get_total_size += nread;
+
+ DEBUG(2,("(%3.1f kb/s) (average %3.1f kb/s)\n",
+ nread / (1.024*this_time + 1.0e-4),
+ get_total_size / (1.024*get_total_time_ms)));
+ }
+
+ return rc;
+}
+
+
+/****************************************************************************
+ get a file
+ ****************************************************************************/
+static int cmd_get(struct smbclient_context *ctx, const char **args)
+{
+ const char *lname;
+ char *rname;
+
+ if (!args[1]) {
+ d_printf("get <filename>\n");
+ return 1;
+ }
+
+ rname = talloc_asprintf(ctx, "%s\\%s", ctx->remote_cur_dir, args[1]);
+
+ if (args[2])
+ lname = args[2];
+ else
+ lname = args[1];
+
+ dos_clean_name(rname);
+
+ return do_get(ctx, rname, lname, false);
+}
+
+/****************************************************************************
+ Put up a yes/no prompt.
+****************************************************************************/
+static bool yesno(char *p)
+{
+ char ans[4];
+ printf("%s",p);
+
+ if (!fgets(ans,sizeof(ans)-1,stdin))
+ return(false);
+
+ if (*ans == 'y' || *ans == 'Y')
+ return(true);
+
+ return(false);
+}
+
+/****************************************************************************
+ do a mget operation on one file
+ ****************************************************************************/
+static void do_mget(struct smbclient_context *ctx, struct clilist_file_info *finfo)
+{
+ char *rname;
+ char *quest;
+ char *mget_mask;
+ char *saved_curdir;
+ char *l_fname;
+ int ret;
+
+ if (ISDOT(finfo->name) || ISDOTDOT(finfo->name))
+ return;
+
+ if (finfo->attrib & FILE_ATTRIBUTE_DIRECTORY)
+ quest = talloc_asprintf(ctx, "Get directory %s? ",finfo->name);
+ else
+ quest = talloc_asprintf(ctx, "Get file %s? ",finfo->name);
+
+ if (ctx->prompt && !yesno(quest)) return;
+
+ talloc_free(quest);
+
+ if (!(finfo->attrib & FILE_ATTRIBUTE_DIRECTORY)) {
+ rname = talloc_asprintf(ctx, "%s%s",ctx->remote_cur_dir,
+ finfo->name);
+ do_get(ctx, rname, finfo->name, false);
+ talloc_free(rname);
+ return;
+ }
+
+ /* handle directories */
+ saved_curdir = talloc_strdup(ctx, ctx->remote_cur_dir);
+
+ ctx->remote_cur_dir = talloc_asprintf_append_buffer(NULL, "%s\\", finfo->name);
+
+ if (ctx->lowercase) {
+ l_fname = strlower_talloc(ctx, finfo->name);
+ } else {
+ l_fname = talloc_strdup(ctx, finfo->name);
+ }
+
+ string_replace(l_fname, '\\', '/');
+
+ if (!directory_exist(l_fname) &&
+ mkdir(l_fname, 0777) != 0) {
+ d_printf("failed to create directory %s\n", l_fname);
+ return;
+ }
+
+ if (chdir(l_fname) != 0) {
+ d_printf("failed to chdir to directory %s\n", l_fname);
+ return;
+ }
+
+ mget_mask = talloc_asprintf(ctx, "%s*", ctx->remote_cur_dir);
+
+ do_list(ctx, mget_mask, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_DIRECTORY,do_mget,false, true);
+ ret = chdir("..");
+ if (ret == -1) {
+ d_printf("failed to chdir to '..': %s\n", strerror(errno));
+ return;
+ }
+ talloc_free(ctx->remote_cur_dir);
+
+ ctx->remote_cur_dir = saved_curdir;
+}
+
+
+/****************************************************************************
+view the file using the pager
+****************************************************************************/
+static int cmd_more(struct smbclient_context *ctx, const char **args)
+{
+ char *rname;
+ char *pager_cmd;
+ char *lname;
+ char *pager;
+ int fd;
+ int rc = 0;
+ mode_t mask;
+
+ lname = talloc_asprintf(ctx, "%s/smbmore.XXXXXX",tmpdir());
+ mask = umask(S_IRWXO | S_IRWXG);
+ fd = mkstemp(lname);
+ umask(mask);
+ if (fd == -1) {
+ d_printf("failed to create temporary file for more\n");
+ return 1;
+ }
+ close(fd);
+
+ if (!args[1]) {
+ d_printf("more <filename>\n");
+ unlink(lname);
+ return 1;
+ }
+ rname = talloc_asprintf(ctx, "%s%s", ctx->remote_cur_dir, args[1]);
+ dos_clean_name(rname);
+
+ rc = do_get(ctx, rname, lname, false);
+
+ pager=getenv("PAGER");
+
+ pager_cmd = talloc_asprintf(ctx, "%s %s",(pager? pager:DEFAULT_PAGER), lname);
+ rc = system(pager_cmd);
+ if (rc == -1) {
+ d_printf("failed to call pager command\n");
+ return 1;
+ }
+ unlink(lname);
+
+ return rc;
+}
+
+
+
+/****************************************************************************
+do a mget command
+****************************************************************************/
+static int cmd_mget(struct smbclient_context *ctx, const char **args)
+{
+ uint16_t attribute = FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN;
+ char *mget_mask = NULL;
+ int i;
+
+ if (ctx->recurse)
+ attribute |= FILE_ATTRIBUTE_DIRECTORY;
+
+ for (i = 1; args[i]; i++) {
+ mget_mask = talloc_strdup(ctx, ctx->remote_cur_dir);
+ if(mget_mask[strlen(mget_mask)-1]!='\\')
+ mget_mask = talloc_append_string(ctx, mget_mask, "\\");
+
+ mget_mask = talloc_strdup(ctx, args[i]);
+ if (mget_mask[0] != '\\')
+ mget_mask = talloc_append_string(ctx, mget_mask, "\\");
+ do_list(ctx, mget_mask, attribute,do_mget,false,true);
+
+ talloc_free(mget_mask);
+ }
+
+ if (mget_mask == NULL) {
+ mget_mask = talloc_asprintf(ctx, "%s\\*", ctx->remote_cur_dir);
+ do_list(ctx, mget_mask, attribute,do_mget,false,true);
+ talloc_free(mget_mask);
+ }
+
+ return 0;
+}
+
+
+/****************************************************************************
+make a directory of name "name"
+****************************************************************************/
+static NTSTATUS do_mkdir(struct smbclient_context *ctx, char *name)
+{
+ NTSTATUS status;
+
+ if (NT_STATUS_IS_ERR(status = smbcli_mkdir(ctx->cli->tree, name))) {
+ d_printf("%s making remote directory %s\n",
+ smbcli_errstr(ctx->cli->tree),name);
+ return status;
+ }
+
+ return status;
+}
+
+
+/****************************************************************************
+ Exit client.
+****************************************************************************/
+static int cmd_quit(struct smbclient_context *ctx, const char **args)
+{
+ talloc_free(ctx);
+ exit(0);
+ /* NOTREACHED */
+ return 0;
+}
+
+
+/****************************************************************************
+ make a directory
+ ****************************************************************************/
+static int cmd_mkdir(struct smbclient_context *ctx, const char **args)
+{
+ char *mask, *p;
+
+ if (!args[1]) {
+ if (!ctx->recurse)
+ d_printf("mkdir <dirname>\n");
+ return 1;
+ }
+
+ mask = talloc_asprintf(ctx, "%s%s", ctx->remote_cur_dir,args[1]);
+
+ if (ctx->recurse) {
+ dos_clean_name(mask);
+
+ trim_string(mask,".",NULL);
+ for (p = strtok(mask,"/\\"); p; p = strtok(p, "/\\")) {
+ char *parent = talloc_strndup(ctx, mask, PTR_DIFF(p, mask));
+
+ if (NT_STATUS_IS_ERR(smbcli_chkpath(ctx->cli->tree, parent))) {
+ do_mkdir(ctx, parent);
+ }
+
+ talloc_free(parent);
+ }
+ } else {
+ do_mkdir(ctx, mask);
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+show 8.3 name of a file
+****************************************************************************/
+static int cmd_altname(struct smbclient_context *ctx, const char **args)
+{
+ const char *p;
+ char *altname;
+ char *name;
+
+ if (!args[1]) {
+ d_printf("altname <file>\n");
+ return 1;
+ }
+
+ name = talloc_asprintf(ctx, "%s%s", ctx->remote_cur_dir, args[1]);
+
+ if (!NT_STATUS_IS_OK(smbcli_qpathinfo_alt_name(ctx->cli->tree, name, &p))) {
+ d_printf("%s getting alt name for %s\n",
+ smbcli_errstr(ctx->cli->tree),name);
+ return(false);
+ }
+ altname = discard_const_p(char, p);
+ d_printf("%s\n", altname);
+
+ SAFE_FREE(altname);
+
+ return 0;
+}
+
+
+/****************************************************************************
+ put a single file
+ ****************************************************************************/
+static int do_put(struct smbclient_context *ctx, char *rname, char *lname, bool reput)
+{
+ int fnum;
+ FILE *f;
+ size_t start = 0;
+ off_t nread = 0;
+ uint8_t *buf = NULL;
+ int maxwrite = ctx->io_bufsize;
+ int rc = 0;
+
+ struct timeval tp_start;
+ GetTimeOfDay(&tp_start);
+
+ if (reput) {
+ fnum = smbcli_open(ctx->cli->tree, rname, O_RDWR|O_CREAT, DENY_NONE);
+ if (fnum >= 0) {
+ if (NT_STATUS_IS_ERR(smbcli_qfileinfo(ctx->cli->tree, fnum, NULL, &start, NULL, NULL, NULL, NULL, NULL)) &&
+ NT_STATUS_IS_ERR(smbcli_getattrE(ctx->cli->tree, fnum, NULL, &start, NULL, NULL, NULL))) {
+ d_printf("getattrib: %s\n",smbcli_errstr(ctx->cli->tree));
+ return 1;
+ }
+ }
+ } else {
+ fnum = smbcli_open(ctx->cli->tree, rname, O_RDWR|O_CREAT|O_TRUNC,
+ DENY_NONE);
+ }
+
+ if (fnum == -1) {
+ d_printf("%s opening remote file %s\n",smbcli_errstr(ctx->cli->tree),rname);
+ return 1;
+ }
+
+ /* allow files to be piped into smbclient
+ jdblair 24.jun.98
+
+ Note that in this case this function will exit(0) rather
+ than returning. */
+ if (!strcmp(lname, "-")) {
+ f = stdin;
+ /* size of file is not known */
+ } else {
+ f = fopen(lname, "r");
+ if (f && reput) {
+ if (fseek(f, start, SEEK_SET) == -1) {
+ d_printf("Error seeking local file\n");
+ fclose(f);
+ return 1;
+ }
+ }
+ }
+
+ if (!f) {
+ d_printf("Error opening local file %s\n",lname);
+ return 1;
+ }
+
+
+ DEBUG(1,("putting file %s as %s ",lname,
+ rname));
+
+ buf = (uint8_t *)malloc(maxwrite);
+ if (!buf) {
+ d_printf("ERROR: Not enough memory!\n");
+ fclose(f);
+ return 1;
+ }
+ while (!feof(f)) {
+ int n = maxwrite;
+ int ret;
+
+ if ((n = readfile(buf,n,f,ctx->translation)) < 1) {
+ if((n == 0) && feof(f))
+ break; /* Empty local file. */
+
+ d_printf("Error reading local file: %s\n", strerror(errno));
+ rc = 1;
+ break;
+ }
+
+ ret = smbcli_write(ctx->cli->tree, fnum, 0, buf, nread + start, n);
+
+ if (n != ret) {
+ d_printf("Error writing file: %s\n", smbcli_errstr(ctx->cli->tree));
+ rc = 1;
+ break;
+ }
+
+ nread += n;
+ }
+
+ if (NT_STATUS_IS_ERR(smbcli_close(ctx->cli->tree, fnum))) {
+ d_printf("%s closing remote file %s\n",smbcli_errstr(ctx->cli->tree),rname);
+ fclose(f);
+ SAFE_FREE(buf);
+ return 1;
+ }
+
+
+ if (f != stdin) {
+ fclose(f);
+ }
+
+ SAFE_FREE(buf);
+
+ {
+ struct timeval tp_end;
+ int this_time;
+
+ GetTimeOfDay(&tp_end);
+ this_time =
+ (tp_end.tv_sec - tp_start.tv_sec)*1000 +
+ (tp_end.tv_usec - tp_start.tv_usec)/1000;
+ put_total_time_ms += this_time;
+ put_total_size += nread;
+
+ DEBUG(1,("(%3.1f kb/s) (average %3.1f kb/s)\n",
+ nread / (1.024*this_time + 1.0e-4),
+ put_total_size / (1.024*put_total_time_ms)));
+ }
+
+ if (f == stdin) {
+ talloc_free(ctx);
+ exit(0);
+ }
+
+ return rc;
+}
+
+
+
+/****************************************************************************
+ put a file
+ ****************************************************************************/
+static int cmd_put(struct smbclient_context *ctx, const char **args)
+{
+ char *lname;
+ char *rname;
+
+ if (!args[1]) {
+ d_printf("put <filename> [<remotename>]\n");
+ return 1;
+ }
+
+ lname = talloc_strdup(ctx, args[1]);
+
+ if (args[2]) {
+ if (args[2][0]=='\\')
+ rname = talloc_strdup(ctx, args[2]);
+ else
+ rname = talloc_asprintf(ctx, "%s\\%s", ctx->remote_cur_dir, args[2]);
+ } else {
+ rname = talloc_asprintf(ctx, "%s\\%s", ctx->remote_cur_dir, lname);
+ }
+
+ dos_clean_name(rname);
+
+ /* allow '-' to represent stdin
+ jdblair, 24.jun.98 */
+ if (!file_exist(lname) && (strcmp(lname,"-"))) {
+ d_printf("%s does not exist\n",lname);
+ return 1;
+ }
+
+ return do_put(ctx, rname, lname, false);
+}
+
+/*************************************
+ File list structure
+*************************************/
+
+static struct file_list {
+ struct file_list *prev, *next;
+ char *file_path;
+ bool isdir;
+} *file_list;
+
+/****************************************************************************
+ Free a file_list structure
+****************************************************************************/
+
+static void free_file_list (struct file_list * list)
+{
+ struct file_list *tmp;
+
+ while (list)
+ {
+ tmp = list;
+ DLIST_REMOVE(list, list);
+ SAFE_FREE(tmp->file_path);
+ SAFE_FREE(tmp);
+ }
+}
+
+/****************************************************************************
+ seek in a directory/file list until you get something that doesn't start with
+ the specified name
+ ****************************************************************************/
+static bool seek_list(struct file_list *list, char *name)
+{
+ while (list) {
+ trim_string(list->file_path,"./","\n");
+ if (strncmp(list->file_path, name, strlen(name)) != 0) {
+ return(true);
+ }
+ list = list->next;
+ }
+
+ return(false);
+}
+
+/****************************************************************************
+ set the file selection mask
+ ****************************************************************************/
+static int cmd_select(struct smbclient_context *ctx, const char **args)
+{
+ talloc_free(ctx->fileselection);
+ ctx->fileselection = talloc_strdup(ctx, args[1]);
+
+ return 0;
+}
+
+/*******************************************************************
+ A readdir wrapper which just returns the file name.
+ ********************************************************************/
+static const char *readdirname(DIR *p)
+{
+ struct dirent *ptr;
+ char *dname;
+
+ if (!p)
+ return(NULL);
+
+ ptr = (struct dirent *)readdir(p);
+ if (!ptr)
+ return(NULL);
+
+ dname = ptr->d_name;
+
+#ifdef HAVE_BROKEN_READDIR
+ /* using /usr/ucb/cc is BAD */
+ dname = dname - 2;
+#endif
+
+ {
+ static char *buf;
+ int len = NAMLEN(ptr);
+ buf = talloc_strndup(NULL, dname, len);
+ dname = buf;
+ }
+
+ return(dname);
+}
+
+/****************************************************************************
+ Recursive file matching function act as find
+ match must be always set to true when calling this function
+****************************************************************************/
+static int file_find(struct smbclient_context *ctx, struct file_list **list, const char *directory,
+ const char *expression, bool match)
+{
+ DIR *dir;
+ struct file_list *entry;
+ struct stat statbuf;
+ int ret;
+ char *path;
+ bool isdir;
+ const char *dname;
+
+ dir = opendir(directory);
+ if (!dir) return -1;
+
+ while ((dname = readdirname(dir))) {
+ if (ISDOT(dname) || ISDOTDOT(dname)) {
+ continue;
+ }
+
+ if (asprintf(&path, "%s/%s", directory, dname) <= 0) {
+ continue;
+ }
+
+ isdir = false;
+ if (!match || !gen_fnmatch(expression, dname)) {
+ if (ctx->recurse) {
+ ret = stat(path, &statbuf);
+ if (ret == 0) {
+ if (S_ISDIR(statbuf.st_mode)) {
+ isdir = true;
+ ret = file_find(ctx, list, path, expression, false);
+ }
+ } else {
+ d_printf("file_find: cannot stat file %s\n", path);
+ }
+
+ if (ret == -1) {
+ SAFE_FREE(path);
+ closedir(dir);
+ return -1;
+ }
+ }
+ entry = malloc_p(struct file_list);
+ if (!entry) {
+ d_printf("Out of memory in file_find\n");
+ closedir(dir);
+ return -1;
+ }
+ entry->file_path = path;
+ entry->isdir = isdir;
+ DLIST_ADD(*list, entry);
+ } else {
+ SAFE_FREE(path);
+ }
+ }
+
+ closedir(dir);
+ return 0;
+}
+
+/****************************************************************************
+ mput some files
+ ****************************************************************************/
+static int cmd_mput(struct smbclient_context *ctx, const char **args)
+{
+ int i;
+
+ for (i = 1; args[i]; i++) {
+ int ret;
+ struct file_list *temp_list;
+ char *quest, *lname, *rname;
+
+ printf("%s\n", args[i]);
+
+ file_list = NULL;
+
+ ret = file_find(ctx, &file_list, ".", args[i], true);
+ if (ret) {
+ free_file_list(file_list);
+ continue;
+ }
+
+ quest = NULL;
+ lname = NULL;
+ rname = NULL;
+
+ for (temp_list = file_list; temp_list;
+ temp_list = temp_list->next) {
+
+ SAFE_FREE(lname);
+ if (asprintf(&lname, "%s/", temp_list->file_path) <= 0)
+ continue;
+ trim_string(lname, "./", "/");
+
+ /* check if it's a directory */
+ if (temp_list->isdir) {
+ /* if (!recurse) continue; */
+
+ SAFE_FREE(quest);
+ if (asprintf(&quest, "Put directory %s? ", lname) < 0) break;
+ if (ctx->prompt && !yesno(quest)) { /* No */
+ /* Skip the directory */
+ lname[strlen(lname)-1] = '/';
+ if (!seek_list(temp_list, lname))
+ break;
+ } else { /* Yes */
+ SAFE_FREE(rname);
+ if(asprintf(&rname, "%s%s", ctx->remote_cur_dir, lname) < 0) break;
+ dos_format(rname);
+ if (NT_STATUS_IS_ERR(smbcli_chkpath(ctx->cli->tree, rname)) &&
+ NT_STATUS_IS_ERR(do_mkdir(ctx, rname))) {
+ DEBUG (0, ("Unable to make dir, skipping..."));
+ /* Skip the directory */
+ lname[strlen(lname)-1] = '/';
+ if (!seek_list(temp_list, lname))
+ break;
+ }
+ }
+ continue;
+ } else {
+ SAFE_FREE(quest);
+ if (asprintf(&quest,"Put file %s? ", lname) < 0) break;
+ if (ctx->prompt && !yesno(quest)) /* No */
+ continue;
+
+ /* Yes */
+ SAFE_FREE(rname);
+ if (asprintf(&rname, "%s%s", ctx->remote_cur_dir, lname) < 0) break;
+ }
+
+ dos_format(rname);
+
+ do_put(ctx, rname, lname, false);
+ }
+ free_file_list(file_list);
+ SAFE_FREE(quest);
+ SAFE_FREE(lname);
+ SAFE_FREE(rname);
+ }
+
+ return 0;
+}
+
+
+/****************************************************************************
+ print a file
+ ****************************************************************************/
+static int cmd_print(struct smbclient_context *ctx, const char **args)
+{
+ char *lname, *rname;
+ char *p;
+
+ if (!args[1]) {
+ d_printf("print <filename>\n");
+ return 1;
+ }
+
+ lname = talloc_strdup(ctx, args[1]);
+ if (lname == NULL) {
+ d_printf("Out of memory in cmd_print\n");
+ return 1;
+ }
+
+ if (strequal(lname, "-")) {
+ rname = talloc_asprintf(ctx, "stdin-%d", (int)getpid());
+ } else {
+ p = strrchr_m(lname, '/');
+ if (p) {
+ rname = talloc_asprintf(ctx, "%s-%d", p + 1,
+ (int)getpid());
+ } else {
+ rname = talloc_strdup(ctx, lname);
+ }
+ }
+
+ if (rname == NULL) {
+ d_printf("Out of memory in cmd_print (stdin)\n");
+ return 1;
+ }
+
+ return do_put(ctx, rname, lname, false);
+}
+
+
+static int cmd_rewrite(struct smbclient_context *ctx, const char **args)
+{
+ d_printf("REWRITE: command not implemented (FIXME!)\n");
+
+ return 0;
+}
+
+/****************************************************************************
+delete some files
+****************************************************************************/
+static int cmd_del(struct smbclient_context *ctx, const char **args)
+{
+ char *mask;
+
+ if (!args[1]) {
+ d_printf("del <filename>\n");
+ return 1;
+ }
+ mask = talloc_asprintf(ctx, "%s%s", ctx->remote_cur_dir, args[1]);
+
+ if (NT_STATUS_IS_ERR(smbcli_unlink(ctx->cli->tree, mask))) {
+ d_printf("%s deleting remote file %s\n",smbcli_errstr(ctx->cli->tree),mask);
+ }
+
+ return 0;
+}
+
+
+/****************************************************************************
+delete a whole directory tree
+****************************************************************************/
+static int cmd_deltree(struct smbclient_context *ctx, const char **args)
+{
+ char *dname;
+ int ret;
+
+ if (!args[1]) {
+ d_printf("deltree <dirname>\n");
+ return 1;
+ }
+
+ dname = talloc_asprintf(ctx, "%s%s", ctx->remote_cur_dir, args[1]);
+
+ ret = smbcli_deltree(ctx->cli->tree, dname);
+
+ if (ret == -1) {
+ printf("Failed to delete tree %s - %s\n", dname, smbcli_errstr(ctx->cli->tree));
+ return -1;
+ }
+
+ printf("Deleted %d files in %s\n", ret, dname);
+
+ return 0;
+}
+
+typedef struct {
+ const char *level_name;
+ enum smb_fsinfo_level level;
+} fsinfo_level_t;
+
+fsinfo_level_t fsinfo_levels[] = {
+ {"dskattr", RAW_QFS_DSKATTR},
+ {"allocation", RAW_QFS_ALLOCATION},
+ {"volume", RAW_QFS_VOLUME},
+ {"volumeinfo", RAW_QFS_VOLUME_INFO},
+ {"sizeinfo", RAW_QFS_SIZE_INFO},
+ {"deviceinfo", RAW_QFS_DEVICE_INFO},
+ {"attributeinfo", RAW_QFS_ATTRIBUTE_INFO},
+ {"unixinfo", RAW_QFS_UNIX_INFO},
+ {"volume-information", RAW_QFS_VOLUME_INFORMATION},
+ {"size-information", RAW_QFS_SIZE_INFORMATION},
+ {"device-information", RAW_QFS_DEVICE_INFORMATION},
+ {"attribute-information", RAW_QFS_ATTRIBUTE_INFORMATION},
+ {"quota-information", RAW_QFS_QUOTA_INFORMATION},
+ {"fullsize-information", RAW_QFS_FULL_SIZE_INFORMATION},
+ {"objectid", RAW_QFS_OBJECTID_INFORMATION},
+ {"sector-size-info", RAW_QFS_SECTOR_SIZE_INFORMATION},
+ {NULL, RAW_QFS_GENERIC}
+};
+
+
+static int cmd_fsinfo(struct smbclient_context *ctx, const char **args)
+{
+ union smb_fsinfo fsinfo;
+ NTSTATUS status;
+ fsinfo_level_t *fsinfo_level;
+
+ if (!args[1]) {
+ d_printf("fsinfo <level>, where level is one of following:\n");
+ fsinfo_level = fsinfo_levels;
+ while(fsinfo_level->level_name) {
+ d_printf("%s\n", fsinfo_level->level_name);
+ fsinfo_level++;
+ }
+ return 1;
+ }
+
+ fsinfo_level = fsinfo_levels;
+ while(fsinfo_level->level_name && !strequal(args[1],fsinfo_level->level_name)) {
+ fsinfo_level++;
+ }
+
+ if (!fsinfo_level->level_name) {
+ d_printf("wrong level name!\n");
+ return 1;
+ }
+
+ fsinfo.generic.level = fsinfo_level->level;
+ status = smb_raw_fsinfo(ctx->cli->tree, ctx, &fsinfo);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("fsinfo-level-%s - %s\n", fsinfo_level->level_name, nt_errstr(status));
+ return 1;
+ }
+
+ d_printf("fsinfo-level-%s:\n", fsinfo_level->level_name);
+ switch(fsinfo.generic.level) {
+ case RAW_QFS_DSKATTR:
+ d_printf("\tunits_total: %hu\n",
+ (unsigned short) fsinfo.dskattr.out.units_total);
+ d_printf("\tblocks_per_unit: %hu\n",
+ (unsigned short) fsinfo.dskattr.out.blocks_per_unit);
+ d_printf("\tblocks_size: %hu\n",
+ (unsigned short) fsinfo.dskattr.out.block_size);
+ d_printf("\tunits_free: %hu\n",
+ (unsigned short) fsinfo.dskattr.out.units_free);
+ break;
+ case RAW_QFS_ALLOCATION:
+ d_printf("\tfs_id: %lu\n",
+ (unsigned long) fsinfo.allocation.out.fs_id);
+ d_printf("\tsectors_per_unit: %lu\n",
+ (unsigned long) fsinfo.allocation.out.sectors_per_unit);
+ d_printf("\ttotal_alloc_units: %lu\n",
+ (unsigned long) fsinfo.allocation.out.total_alloc_units);
+ d_printf("\tavail_alloc_units: %lu\n",
+ (unsigned long) fsinfo.allocation.out.avail_alloc_units);
+ d_printf("\tbytes_per_sector: %hu\n",
+ (unsigned short) fsinfo.allocation.out.bytes_per_sector);
+ break;
+ case RAW_QFS_VOLUME:
+ d_printf("\tserial_number: %lu\n",
+ (unsigned long) fsinfo.volume.out.serial_number);
+ d_printf("\tvolume_name: %s\n", fsinfo.volume.out.volume_name.s);
+ break;
+ case RAW_QFS_VOLUME_INFO:
+ case RAW_QFS_VOLUME_INFORMATION:
+ d_printf("\tcreate_time: %s\n",
+ nt_time_string(ctx,fsinfo.volume_info.out.create_time));
+ d_printf("\tserial_number: %lu\n",
+ (unsigned long) fsinfo.volume_info.out.serial_number);
+ d_printf("\tvolume_name: %s\n", fsinfo.volume_info.out.volume_name.s);
+ break;
+ case RAW_QFS_SIZE_INFO:
+ case RAW_QFS_SIZE_INFORMATION:
+ d_printf("\ttotal_alloc_units: %llu\n",
+ (unsigned long long) fsinfo.size_info.out.total_alloc_units);
+ d_printf("\tavail_alloc_units: %llu\n",
+ (unsigned long long) fsinfo.size_info.out.avail_alloc_units);
+ d_printf("\tsectors_per_unit: %lu\n",
+ (unsigned long) fsinfo.size_info.out.sectors_per_unit);
+ d_printf("\tbytes_per_sector: %lu\n",
+ (unsigned long) fsinfo.size_info.out.bytes_per_sector);
+ break;
+ case RAW_QFS_DEVICE_INFO:
+ case RAW_QFS_DEVICE_INFORMATION:
+ d_printf("\tdevice_type: %lu\n",
+ (unsigned long) fsinfo.device_info.out.device_type);
+ d_printf("\tcharacteristics: 0x%lx\n",
+ (unsigned long) fsinfo.device_info.out.characteristics);
+ break;
+ case RAW_QFS_ATTRIBUTE_INFORMATION:
+ case RAW_QFS_ATTRIBUTE_INFO:
+ d_printf("\tfs_attr: 0x%lx\n",
+ (unsigned long) fsinfo.attribute_info.out.fs_attr);
+ d_printf("\tmax_file_component_length: %lu\n",
+ (unsigned long) fsinfo.attribute_info.out.max_file_component_length);
+ d_printf("\tfs_type: %s\n", fsinfo.attribute_info.out.fs_type.s);
+ break;
+ case RAW_QFS_UNIX_INFO:
+ d_printf("\tmajor_version: %hu\n",
+ (unsigned short) fsinfo.unix_info.out.major_version);
+ d_printf("\tminor_version: %hu\n",
+ (unsigned short) fsinfo.unix_info.out.minor_version);
+ d_printf("\tcapability: 0x%llx\n",
+ (unsigned long long) fsinfo.unix_info.out.capability);
+ break;
+ case RAW_QFS_QUOTA_INFORMATION:
+ d_printf("\tunknown[3]: [%llu,%llu,%llu]\n",
+ (unsigned long long) fsinfo.quota_information.out.unknown[0],
+ (unsigned long long) fsinfo.quota_information.out.unknown[1],
+ (unsigned long long) fsinfo.quota_information.out.unknown[2]);
+ d_printf("\tquota_soft: %llu\n",
+ (unsigned long long) fsinfo.quota_information.out.quota_soft);
+ d_printf("\tquota_hard: %llu\n",
+ (unsigned long long) fsinfo.quota_information.out.quota_hard);
+ d_printf("\tquota_flags: 0x%llx\n",
+ (unsigned long long) fsinfo.quota_information.out.quota_flags);
+ break;
+ case RAW_QFS_FULL_SIZE_INFORMATION:
+ d_printf("\ttotal_alloc_units: %llu\n",
+ (unsigned long long) fsinfo.full_size_information.out.total_alloc_units);
+ d_printf("\tcall_avail_alloc_units: %llu\n",
+ (unsigned long long) fsinfo.full_size_information.out.call_avail_alloc_units);
+ d_printf("\tactual_avail_alloc_units: %llu\n",
+ (unsigned long long) fsinfo.full_size_information.out.actual_avail_alloc_units);
+ d_printf("\tsectors_per_unit: %lu\n",
+ (unsigned long) fsinfo.full_size_information.out.sectors_per_unit);
+ d_printf("\tbytes_per_sector: %lu\n",
+ (unsigned long) fsinfo.full_size_information.out.bytes_per_sector);
+ break;
+ case RAW_QFS_OBJECTID_INFORMATION:
+ d_printf("\tGUID: %s\n",
+ GUID_string(ctx,&fsinfo.objectid_information.out.guid));
+ d_printf("\tunknown[6]: [%llu,%llu,%llu,%llu,%llu,%llu]\n",
+ (unsigned long long) fsinfo.objectid_information.out.unknown[0],
+ (unsigned long long) fsinfo.objectid_information.out.unknown[1],
+ (unsigned long long) fsinfo.objectid_information.out.unknown[2],
+ (unsigned long long) fsinfo.objectid_information.out.unknown[3],
+ (unsigned long long) fsinfo.objectid_information.out.unknown[4],
+ (unsigned long long) fsinfo.objectid_information.out.unknown[5] );
+ break;
+ case RAW_QFS_SECTOR_SIZE_INFORMATION:
+ d_printf("\tlogical_bytes_per_sector: %u\n",
+ (unsigned)fsinfo.sector_size_info.out.logical_bytes_per_sector);
+ d_printf("\tphys_bytes_per_sector_atomic: %u\n",
+ (unsigned)fsinfo.sector_size_info.out.phys_bytes_per_sector_atomic);
+ d_printf("\tphys_bytes_per_sector_perf: %u\n",
+ (unsigned)fsinfo.sector_size_info.out.phys_bytes_per_sector_perf);
+ d_printf("\tfs_effective_phys_bytes_per_sector_atomic: %u\n",
+ (unsigned)fsinfo.sector_size_info.out.fs_effective_phys_bytes_per_sector_atomic);
+ d_printf("\tflags: 0x%x\n",
+ (unsigned)fsinfo.sector_size_info.out.flags);
+ d_printf("\tbyte_off_sector_align: %u\n",
+ (unsigned)fsinfo.sector_size_info.out.byte_off_sector_align);
+ d_printf("\tbyte_off_partition_align: %u\n",
+ (unsigned)fsinfo.sector_size_info.out.byte_off_partition_align);
+ break;
+ case RAW_QFS_GENERIC:
+ d_printf("\twrong level returned\n");
+ break;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+show as much information as possible about a file
+****************************************************************************/
+static int cmd_allinfo(struct smbclient_context *ctx, const char **args)
+{
+ char *fname;
+ union smb_fileinfo finfo;
+ NTSTATUS status;
+ int fnum;
+
+ if (!args[1]) {
+ d_printf("allinfo <filename>\n");
+ return 1;
+ }
+ fname = talloc_asprintf(ctx, "%s%s", ctx->remote_cur_dir, args[1]);
+
+ /* first a ALL_INFO QPATHINFO */
+ finfo.generic.level = RAW_FILEINFO_ALL_INFO;
+ finfo.generic.in.file.path = fname;
+ status = smb_raw_pathinfo(ctx->cli->tree, ctx, &finfo);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s - %s\n", fname, nt_errstr(status));
+ return 1;
+ }
+
+ d_printf("\tcreate_time: %s\n", nt_time_string(ctx, finfo.all_info.out.create_time));
+ d_printf("\taccess_time: %s\n", nt_time_string(ctx, finfo.all_info.out.access_time));
+ d_printf("\twrite_time: %s\n", nt_time_string(ctx, finfo.all_info.out.write_time));
+ d_printf("\tchange_time: %s\n", nt_time_string(ctx, finfo.all_info.out.change_time));
+ d_printf("\tattrib: 0x%x\n", finfo.all_info.out.attrib);
+ d_printf("\talloc_size: %lu\n", (unsigned long)finfo.all_info.out.alloc_size);
+ d_printf("\tsize: %lu\n", (unsigned long)finfo.all_info.out.size);
+ d_printf("\tnlink: %u\n", finfo.all_info.out.nlink);
+ d_printf("\tdelete_pending: %u\n", finfo.all_info.out.delete_pending);
+ d_printf("\tdirectory: %u\n", finfo.all_info.out.directory);
+ d_printf("\tea_size: %u\n", finfo.all_info.out.ea_size);
+ d_printf("\tfname: '%s'\n", finfo.all_info.out.fname.s);
+
+ /* 8.3 name if any */
+ finfo.generic.level = RAW_FILEINFO_ALT_NAME_INFO;
+ status = smb_raw_pathinfo(ctx->cli->tree, ctx, &finfo);
+ if (NT_STATUS_IS_OK(status)) {
+ d_printf("\talt_name: %s\n", finfo.alt_name_info.out.fname.s);
+ }
+
+ /* file_id if available */
+ finfo.generic.level = RAW_FILEINFO_INTERNAL_INFORMATION;
+ status = smb_raw_pathinfo(ctx->cli->tree, ctx, &finfo);
+ if (NT_STATUS_IS_OK(status)) {
+ d_printf("\tfile_id %.0f\n",
+ (double)finfo.internal_information.out.file_id);
+ }
+
+ /* the EAs, if any */
+ finfo.generic.level = RAW_FILEINFO_ALL_EAS;
+ status = smb_raw_pathinfo(ctx->cli->tree, ctx, &finfo);
+ if (NT_STATUS_IS_OK(status)) {
+ int i;
+ for (i=0;i<finfo.all_eas.out.num_eas;i++) {
+ d_printf("\tEA[%d] flags=%d len=%d '%s'\n", i,
+ finfo.all_eas.out.eas[i].flags,
+ (int)finfo.all_eas.out.eas[i].value.length,
+ finfo.all_eas.out.eas[i].name.s);
+ }
+ }
+
+ /* streams, if available */
+ finfo.generic.level = RAW_FILEINFO_STREAM_INFO;
+ status = smb_raw_pathinfo(ctx->cli->tree, ctx, &finfo);
+ if (NT_STATUS_IS_OK(status)) {
+ int i;
+ for (i=0;i<finfo.stream_info.out.num_streams;i++) {
+ d_printf("\tstream %d:\n", i);
+ d_printf("\t\tsize %ld\n",
+ (long)finfo.stream_info.out.streams[i].size);
+ d_printf("\t\talloc size %ld\n",
+ (long)finfo.stream_info.out.streams[i].alloc_size);
+ d_printf("\t\tname %s\n", finfo.stream_info.out.streams[i].stream_name.s);
+ }
+ }
+
+ /* dev/inode if available */
+ finfo.generic.level = RAW_FILEINFO_COMPRESSION_INFORMATION;
+ status = smb_raw_pathinfo(ctx->cli->tree, ctx, &finfo);
+ if (NT_STATUS_IS_OK(status)) {
+ d_printf("\tcompressed size %ld\n", (long)finfo.compression_info.out.compressed_size);
+ d_printf("\tformat %ld\n", (long)finfo.compression_info.out.format);
+ d_printf("\tunit_shift %ld\n", (long)finfo.compression_info.out.unit_shift);
+ d_printf("\tchunk_shift %ld\n", (long)finfo.compression_info.out.chunk_shift);
+ d_printf("\tcluster_shift %ld\n", (long)finfo.compression_info.out.cluster_shift);
+ }
+
+ /* shadow copies if available */
+ fnum = smbcli_open(ctx->cli->tree, fname, O_RDONLY, DENY_NONE);
+ if (fnum != -1) {
+ struct smb_shadow_copy info;
+ int i;
+ info.in.file.fnum = fnum;
+ info.in.max_data = ~0;
+ status = smb_raw_shadow_data(ctx->cli->tree, ctx, &info);
+ if (NT_STATUS_IS_OK(status)) {
+ d_printf("\tshadow_copy: %u volumes %u names\n",
+ info.out.num_volumes, info.out.num_names);
+ for (i=0;i<info.out.num_names;i++) {
+ d_printf("\t%s\n", info.out.names[i]);
+ finfo.generic.level = RAW_FILEINFO_ALL_INFO;
+ finfo.generic.in.file.path = talloc_asprintf(ctx, "%s%s",
+ info.out.names[i], fname);
+ status = smb_raw_pathinfo(ctx->cli->tree, ctx, &finfo);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_NOT_FOUND) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ continue;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s - %s\n", finfo.generic.in.file.path,
+ nt_errstr(status));
+ return 1;
+ }
+
+ d_printf("\t\tcreate_time: %s\n", nt_time_string(ctx, finfo.all_info.out.create_time));
+ d_printf("\t\twrite_time: %s\n", nt_time_string(ctx, finfo.all_info.out.write_time));
+ d_printf("\t\tchange_time: %s\n", nt_time_string(ctx, finfo.all_info.out.change_time));
+ d_printf("\t\tsize: %lu\n", (unsigned long)finfo.all_info.out.size);
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+/****************************************************************************
+shows EA contents
+****************************************************************************/
+static int cmd_eainfo(struct smbclient_context *ctx, const char **args)
+{
+ char *fname;
+ union smb_fileinfo finfo;
+ NTSTATUS status;
+ int i;
+
+ if (!args[1]) {
+ d_printf("eainfo <filename>\n");
+ return 1;
+ }
+ fname = talloc_strdup(ctx, args[1]);
+
+ finfo.generic.level = RAW_FILEINFO_ALL_EAS;
+ finfo.generic.in.file.path = fname;
+ status = smb_raw_pathinfo(ctx->cli->tree, ctx, &finfo);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("RAW_FILEINFO_ALL_EAS - %s\n", nt_errstr(status));
+ return 1;
+ }
+
+ d_printf("%s has %d EAs\n", fname, finfo.all_eas.out.num_eas);
+
+ for (i=0;i<finfo.all_eas.out.num_eas;i++) {
+ d_printf("\tEA[%d] flags=%d len=%d '%s'\n", i,
+ finfo.all_eas.out.eas[i].flags,
+ (int)finfo.all_eas.out.eas[i].value.length,
+ finfo.all_eas.out.eas[i].name.s);
+ fflush(stdout);
+ dump_data(0,
+ finfo.all_eas.out.eas[i].value.data,
+ finfo.all_eas.out.eas[i].value.length);
+ }
+
+ return 0;
+}
+
+
+/****************************************************************************
+show any ACL on a file
+****************************************************************************/
+static int cmd_acl(struct smbclient_context *ctx, const char **args)
+{
+ char *fname;
+ union smb_fileinfo query;
+ NTSTATUS status;
+ int fnum;
+
+ if (!args[1]) {
+ d_printf("acl <filename>\n");
+ return 1;
+ }
+ fname = talloc_asprintf(ctx, "%s%s", ctx->remote_cur_dir, args[1]);
+
+ fnum = smbcli_nt_create_full(ctx->cli->tree, fname, 0,
+ SEC_STD_READ_CONTROL,
+ 0,
+ NTCREATEX_SHARE_ACCESS_DELETE|
+ NTCREATEX_SHARE_ACCESS_READ|
+ NTCREATEX_SHARE_ACCESS_WRITE,
+ NTCREATEX_DISP_OPEN,
+ 0, 0);
+ if (fnum == -1) {
+ d_printf("%s - %s\n", fname, smbcli_errstr(ctx->cli->tree));
+ return -1;
+ }
+
+ query.query_secdesc.level = RAW_FILEINFO_SEC_DESC;
+ query.query_secdesc.in.file.fnum = fnum;
+ query.query_secdesc.in.secinfo_flags = 0x7;
+
+ status = smb_raw_fileinfo(ctx->cli->tree, ctx, &query);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s - %s\n", fname, nt_errstr(status));
+ return 1;
+ }
+
+ NDR_PRINT_DEBUG(security_descriptor, query.query_secdesc.out.sd);
+
+ return 0;
+}
+
+/****************************************************************************
+lookup a name or sid
+****************************************************************************/
+static int cmd_lookup(struct smbclient_context *ctx, const char **args)
+{
+ NTSTATUS status;
+ struct dom_sid *sid;
+
+ if (!args[1]) {
+ d_printf("lookup <sid|name>\n");
+ return 1;
+ }
+
+ sid = dom_sid_parse_talloc(ctx, args[1]);
+ if (sid == NULL) {
+ const char *sidstr;
+ status = smblsa_lookup_name(ctx->cli, args[1], ctx, &sidstr);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("lsa_LookupNames - %s\n", nt_errstr(status));
+ return 1;
+ }
+
+ d_printf("%s\n", sidstr);
+ } else {
+ const char *name;
+ status = smblsa_lookup_sid(ctx->cli, args[1], ctx, &name);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("lsa_LookupSids - %s\n", nt_errstr(status));
+ return 1;
+ }
+
+ d_printf("%s\n", name);
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+show privileges for a user
+****************************************************************************/
+static int cmd_privileges(struct smbclient_context *ctx, const char **args)
+{
+ NTSTATUS status;
+ struct dom_sid *sid;
+ struct lsa_RightSet rights;
+ unsigned i;
+
+ if (!args[1]) {
+ d_printf("privileges <sid|name>\n");
+ return 1;
+ }
+
+ sid = dom_sid_parse_talloc(ctx, args[1]);
+ if (sid == NULL) {
+ const char *sid_str;
+ status = smblsa_lookup_name(ctx->cli, args[1], ctx, &sid_str);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("lsa_LookupNames - %s\n", nt_errstr(status));
+ return 1;
+ }
+ sid = dom_sid_parse_talloc(ctx, sid_str);
+ }
+
+ status = smblsa_sid_privileges(ctx->cli, sid, ctx, &rights);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("lsa_EnumAccountRights - %s\n", nt_errstr(status));
+ return 1;
+ }
+
+ for (i=0;i<rights.count;i++) {
+ d_printf("\t%s\n", rights.names[i].string);
+ }
+
+ return 0;
+}
+
+
+/****************************************************************************
+add privileges for a user
+****************************************************************************/
+static int cmd_addprivileges(struct smbclient_context *ctx, const char **args)
+{
+ NTSTATUS status;
+ struct dom_sid *sid;
+ struct lsa_RightSet rights;
+ int i;
+
+ if (!args[1]) {
+ d_printf("addprivileges <sid|name> <privilege...>\n");
+ return 1;
+ }
+
+ sid = dom_sid_parse_talloc(ctx, args[1]);
+ if (sid == NULL) {
+ const char *sid_str;
+ status = smblsa_lookup_name(ctx->cli, args[1], ctx, &sid_str);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("lsa_LookupNames - %s\n", nt_errstr(status));
+ return 1;
+ }
+ sid = dom_sid_parse_talloc(ctx, sid_str);
+ }
+
+ ZERO_STRUCT(rights);
+ for (i = 2; args[i]; i++) {
+ rights.names = talloc_realloc(ctx, rights.names,
+ struct lsa_StringLarge, rights.count+1);
+ rights.names[rights.count].string = talloc_strdup(ctx, args[i]);
+ rights.count++;
+ }
+
+
+ status = smblsa_sid_add_privileges(ctx->cli, sid, ctx, &rights);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("lsa_AddAccountRights - %s\n", nt_errstr(status));
+ return 1;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+delete privileges for a user
+****************************************************************************/
+static int cmd_delprivileges(struct smbclient_context *ctx, const char **args)
+{
+ NTSTATUS status;
+ struct dom_sid *sid;
+ struct lsa_RightSet rights;
+ int i;
+
+ if (!args[1]) {
+ d_printf("delprivileges <sid|name> <privilege...>\n");
+ return 1;
+ }
+
+ sid = dom_sid_parse_talloc(ctx, args[1]);
+ if (sid == NULL) {
+ const char *sid_str;
+ status = smblsa_lookup_name(ctx->cli, args[1], ctx, &sid_str);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("lsa_LookupNames - %s\n", nt_errstr(status));
+ return 1;
+ }
+ sid = dom_sid_parse_talloc(ctx, sid_str);
+ }
+
+ ZERO_STRUCT(rights);
+ for (i = 2; args[i]; i++) {
+ rights.names = talloc_realloc(ctx, rights.names,
+ struct lsa_StringLarge, rights.count+1);
+ rights.names[rights.count].string = talloc_strdup(ctx, args[i]);
+ rights.count++;
+ }
+
+
+ status = smblsa_sid_del_privileges(ctx->cli, sid, ctx, &rights);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("lsa_RemoveAccountRights - %s\n", nt_errstr(status));
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/****************************************************************************
+open a file
+****************************************************************************/
+static int cmd_open(struct smbclient_context *ctx, const char **args)
+{
+ char *filename;
+ union smb_open io;
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx;
+
+ if (!args[1]) {
+ d_printf("open <filename>\n");
+ return 1;
+ }
+ tmp_ctx = talloc_new(ctx);
+
+ filename = talloc_asprintf(tmp_ctx, "%s%s", ctx->remote_cur_dir, args[1]);
+
+ io.generic.level = RAW_OPEN_NTCREATEX;
+ io.ntcreatex.in.root_fid.fnum = 0;
+ io.ntcreatex.in.flags = 0;
+ io.ntcreatex.in.access_mask = SEC_RIGHTS_FILE_ALL;
+ io.ntcreatex.in.create_options = 0;
+ io.ntcreatex.in.file_attr = FILE_ATTRIBUTE_NORMAL;
+ io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ;
+ io.ntcreatex.in.alloc_size = 0;
+ io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN_IF;
+ io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_ANONYMOUS;
+ io.ntcreatex.in.security_flags = 0;
+ io.ntcreatex.in.fname = filename;
+
+ status = smb_raw_open(ctx->cli->tree, tmp_ctx, &io);
+ talloc_free(tmp_ctx);
+
+ if (NT_STATUS_IS_OK(status)) {
+ d_printf("Opened file with fnum %u\n", (unsigned)io.ntcreatex.out.file.fnum);
+ } else {
+ d_printf("Opened failed: %s\n", nt_errstr(status));
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+close a file
+****************************************************************************/
+static int cmd_close(struct smbclient_context *ctx, const char **args)
+{
+ union smb_close io;
+ NTSTATUS status;
+ uint16_t fnum;
+
+ if (!args[1]) {
+ d_printf("close <fnum>\n");
+ return 1;
+ }
+
+ fnum = atoi(args[1]);
+
+ ZERO_STRUCT(io);
+ io.generic.level = RAW_CLOSE_CLOSE;
+ io.close.in.file.fnum = fnum;
+
+ status = smb_raw_close(ctx->cli->tree, &io);
+
+ if (NT_STATUS_IS_OK(status)) {
+ d_printf("Closed file OK\n");
+ } else {
+ d_printf("Close failed: %s\n", nt_errstr(status));
+ }
+
+ return 0;
+}
+
+
+/****************************************************************************
+remove a directory
+****************************************************************************/
+static int cmd_rmdir(struct smbclient_context *ctx, const char **args)
+{
+ char *mask;
+
+ if (!args[1]) {
+ d_printf("rmdir <dirname>\n");
+ return 1;
+ }
+ mask = talloc_asprintf(ctx, "%s%s", ctx->remote_cur_dir, args[1]);
+
+ if (NT_STATUS_IS_ERR(smbcli_rmdir(ctx->cli->tree, mask))) {
+ d_printf("%s removing remote directory file %s\n",
+ smbcli_errstr(ctx->cli->tree),mask);
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ UNIX hardlink.
+****************************************************************************/
+static int cmd_link(struct smbclient_context *ctx, const char **args)
+{
+ char *src,*dest;
+
+ if (!(ctx->cli->transport->negotiate.capabilities & CAP_UNIX)) {
+ d_printf("Server doesn't support UNIX CIFS calls.\n");
+ return 1;
+ }
+
+
+ if (!args[1] || !args[2]) {
+ d_printf("link <src> <dest>\n");
+ return 1;
+ }
+
+ src = talloc_asprintf(ctx, "%s%s", ctx->remote_cur_dir, args[1]);
+ dest = talloc_asprintf(ctx, "%s%s", ctx->remote_cur_dir, args[2]);
+
+ if (NT_STATUS_IS_ERR(smbcli_unix_hardlink(ctx->cli->tree, src, dest))) {
+ d_printf("%s linking files (%s -> %s)\n", smbcli_errstr(ctx->cli->tree), src, dest);
+ return 1;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ UNIX symlink.
+****************************************************************************/
+
+static int cmd_symlink(struct smbclient_context *ctx, const char **args)
+{
+ char *src,*dest;
+
+ if (!(ctx->cli->transport->negotiate.capabilities & CAP_UNIX)) {
+ d_printf("Server doesn't support UNIX CIFS calls.\n");
+ return 1;
+ }
+
+ if (!args[1] || !args[2]) {
+ d_printf("symlink <src> <dest>\n");
+ return 1;
+ }
+
+ src = talloc_asprintf(ctx, "%s%s", ctx->remote_cur_dir, args[1]);
+ dest = talloc_asprintf(ctx, "%s%s", ctx->remote_cur_dir, args[2]);
+
+ if (NT_STATUS_IS_ERR(smbcli_unix_symlink(ctx->cli->tree, src, dest))) {
+ d_printf("%s symlinking files (%s -> %s)\n",
+ smbcli_errstr(ctx->cli->tree), src, dest);
+ return 1;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ UNIX chmod.
+****************************************************************************/
+
+static int cmd_chmod(struct smbclient_context *ctx, const char **args)
+{
+ char *src;
+ mode_t mode;
+
+ if (!(ctx->cli->transport->negotiate.capabilities & CAP_UNIX)) {
+ d_printf("Server doesn't support UNIX CIFS calls.\n");
+ return 1;
+ }
+
+ if (!args[1] || !args[2]) {
+ d_printf("chmod mode file\n");
+ return 1;
+ }
+
+ src = talloc_asprintf(ctx, "%s%s", ctx->remote_cur_dir, args[2]);
+
+ mode = (mode_t)strtol(args[1], NULL, 8);
+
+ if (NT_STATUS_IS_ERR(smbcli_unix_chmod(ctx->cli->tree, src, mode))) {
+ d_printf("%s chmod file %s 0%o\n",
+ smbcli_errstr(ctx->cli->tree), src, (unsigned)mode);
+ return 1;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ UNIX chown.
+****************************************************************************/
+
+static int cmd_chown(struct smbclient_context *ctx, const char **args)
+{
+ char *src;
+ uid_t uid;
+ gid_t gid;
+
+ if (!(ctx->cli->transport->negotiate.capabilities & CAP_UNIX)) {
+ d_printf("Server doesn't support UNIX CIFS calls.\n");
+ return 1;
+ }
+
+ if (!args[1] || !args[2] || !args[3]) {
+ d_printf("chown uid gid file\n");
+ return 1;
+ }
+
+ uid = (uid_t)atoi(args[1]);
+ gid = (gid_t)atoi(args[2]);
+ src = talloc_asprintf(ctx, "%s%s", ctx->remote_cur_dir, args[3]);
+
+ if (NT_STATUS_IS_ERR(smbcli_unix_chown(ctx->cli->tree, src, uid, gid))) {
+ d_printf("%s chown file %s uid=%d, gid=%d\n",
+ smbcli_errstr(ctx->cli->tree), src, (int)uid, (int)gid);
+ return 1;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+rename some files
+****************************************************************************/
+static int cmd_rename(struct smbclient_context *ctx, const char **args)
+{
+ char *src,*dest;
+
+ if (!args[1] || !args[2]) {
+ d_printf("rename <src> <dest>\n");
+ return 1;
+ }
+
+ src = talloc_asprintf(ctx, "%s%s", ctx->remote_cur_dir, args[1]);
+ dest = talloc_asprintf(ctx, "%s%s", ctx->remote_cur_dir, args[2]);
+
+ if (NT_STATUS_IS_ERR(smbcli_rename(ctx->cli->tree, src, dest))) {
+ d_printf("%s renaming files\n",smbcli_errstr(ctx->cli->tree));
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/****************************************************************************
+toggle the prompt flag
+****************************************************************************/
+static int cmd_prompt(struct smbclient_context *ctx, const char **args)
+{
+ ctx->prompt = !ctx->prompt;
+ DEBUG(2,("prompting is now %s\n",ctx->prompt?"on":"off"));
+
+ return 1;
+}
+
+
+/****************************************************************************
+set the newer than time
+****************************************************************************/
+static int cmd_newer(struct smbclient_context *ctx, const char **args)
+{
+ struct stat sbuf;
+
+ if (args[1] && (stat(args[1],&sbuf) == 0)) {
+ ctx->newer_than = sbuf.st_mtime;
+ DEBUG(1,("Getting files newer than %s",
+ asctime(localtime(&ctx->newer_than))));
+ } else {
+ ctx->newer_than = 0;
+ }
+
+ if (args[1] && ctx->newer_than == 0) {
+ d_printf("Error setting newer-than time\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+set the archive level
+****************************************************************************/
+static int cmd_archive(struct smbclient_context *ctx, const char **args)
+{
+ if (args[1]) {
+ ctx->archive_level = atoi(args[1]);
+ } else
+ d_printf("Archive level is %d\n",ctx->archive_level);
+
+ return 0;
+}
+
+/****************************************************************************
+toggle the lowercaseflag
+****************************************************************************/
+static int cmd_lowercase(struct smbclient_context *ctx, const char **args)
+{
+ ctx->lowercase = !ctx->lowercase;
+ DEBUG(2,("filename lowercasing is now %s\n",ctx->lowercase?"on":"off"));
+
+ return 0;
+}
+
+
+
+
+/****************************************************************************
+toggle the recurse flag
+****************************************************************************/
+static int cmd_recurse(struct smbclient_context *ctx, const char **args)
+{
+ ctx->recurse = !ctx->recurse;
+ DEBUG(2,("directory recursion is now %s\n",ctx->recurse?"on":"off"));
+
+ return 0;
+}
+
+/****************************************************************************
+toggle the translate flag
+****************************************************************************/
+static int cmd_translate(struct smbclient_context *ctx, const char **args)
+{
+ ctx->translation = !ctx->translation;
+ DEBUG(2,("CR/LF<->LF and print text translation now %s\n",
+ ctx->translation?"on":"off"));
+
+ return 0;
+}
+
+
+/****************************************************************************
+do a printmode command
+****************************************************************************/
+static int cmd_printmode(struct smbclient_context *ctx, const char **args)
+{
+ if (args[1]) {
+ if (strequal(args[1],"text")) {
+ ctx->printmode = 0;
+ } else {
+ if (strequal(args[1],"graphics"))
+ ctx->printmode = 1;
+ else
+ ctx->printmode = atoi(args[1]);
+ }
+ }
+
+ switch(ctx->printmode)
+ {
+ case 0:
+ DEBUG(2,("the printmode is now text\n"));
+ break;
+ case 1:
+ DEBUG(2,("the printmode is now graphics\n"));
+ break;
+ default:
+ DEBUG(2,("the printmode is now %d\n", ctx->printmode));
+ break;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ do the lcd command
+ ****************************************************************************/
+static int cmd_lcd(struct smbclient_context *ctx, const char **args)
+{
+ char d[PATH_MAX];
+
+ if (args[1]) {
+ int ret;
+
+ ret = chdir(args[1]);
+ if (ret == -1) {
+ d_printf("failed to chdir to dir '%s': %s\n",
+ args[1], strerror(errno));
+ return 1;
+ }
+ }
+
+ DEBUG(2,("the local directory is now %s\n",getcwd(d, PATH_MAX)));
+
+ return 0;
+}
+
+/****************************************************************************
+history
+****************************************************************************/
+static int cmd_history(struct smbclient_context *ctx, const char **args)
+{
+#if defined(HAVE_LIBREADLINE) && defined(HAVE_HISTORY_LIST)
+ HIST_ENTRY **hlist;
+ int i;
+
+ hlist = history_list();
+
+ for (i = 0; hlist && hlist[i]; i++) {
+ DEBUG(0, ("%d: %s\n", i, hlist[i]->line));
+ }
+#else
+ DEBUG(0,("no history without readline support\n"));
+#endif
+
+ return 0;
+}
+
+/****************************************************************************
+ get a file restarting at end of local file
+ ****************************************************************************/
+static int cmd_reget(struct smbclient_context *ctx, const char **args)
+{
+ char *local_name;
+ char *remote_name;
+
+ if (!args[1]) {
+ d_printf("reget <filename>\n");
+ return 1;
+ }
+ remote_name = talloc_asprintf(ctx, "%s\\%s", ctx->remote_cur_dir, args[1]);
+ dos_clean_name(remote_name);
+
+ if (args[2])
+ local_name = talloc_strdup(ctx, args[2]);
+ else
+ local_name = talloc_strdup(ctx, args[1]);
+
+ return do_get(ctx, remote_name, local_name, true);
+}
+
+/****************************************************************************
+ put a file restarting at end of local file
+ ****************************************************************************/
+static int cmd_reput(struct smbclient_context *ctx, const char **args)
+{
+ char *local_name;
+ char *remote_name;
+
+ if (!args[1]) {
+ d_printf("reput <filename>\n");
+ return 1;
+ }
+ local_name = talloc_asprintf(ctx, "%s\\%s", ctx->remote_cur_dir, args[1]);
+
+ if (!file_exist(local_name)) {
+ d_printf("%s does not exist\n", local_name);
+ return 1;
+ }
+
+ if (args[2])
+ remote_name = talloc_strdup(ctx, args[2]);
+ else
+ remote_name = talloc_strdup(ctx, args[1]);
+
+ dos_clean_name(remote_name);
+
+ return do_put(ctx, remote_name, local_name, true);
+}
+
+
+/*
+ return a string representing a share type
+*/
+static const char *share_type_str(uint32_t type)
+{
+ switch (type & 0xF) {
+ case STYPE_DISKTREE:
+ return "Disk";
+ case STYPE_PRINTQ:
+ return "Printer";
+ case STYPE_DEVICE:
+ return "Device";
+ case STYPE_IPC:
+ return "IPC";
+ default:
+ return "Unknown";
+ }
+}
+
+
+/*
+ display a list of shares from a level 1 share enum
+*/
+static void display_share_result(struct srvsvc_NetShareCtr1 *ctr1)
+{
+ int i;
+
+ for (i=0;i<ctr1->count;i++) {
+ struct srvsvc_NetShareInfo1 *info = ctr1->array+i;
+
+ printf("\t%-15s %-10.10s %s\n",
+ info->name,
+ share_type_str(info->type),
+ info->comment);
+ }
+}
+
+
+
+/****************************************************************************
+try and browse available shares on a host
+****************************************************************************/
+static bool browse_host(struct loadparm_context *lp_ctx,
+ struct tevent_context *ev_ctx,
+ const char *query_host)
+{
+ struct dcerpc_pipe *p;
+ char *binding;
+ NTSTATUS status;
+ struct srvsvc_NetShareEnumAll r;
+ struct srvsvc_NetShareInfoCtr info_ctr;
+ uint32_t resume_handle = 0;
+ TALLOC_CTX *mem_ctx = talloc_init("browse_host");
+ struct srvsvc_NetShareCtr1 ctr1;
+ uint32_t totalentries = 0;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+
+ binding = talloc_asprintf(mem_ctx, "ncacn_np:%s", query_host);
+
+ status = dcerpc_pipe_connect(mem_ctx, &p, binding,
+ &ndr_table_srvsvc,
+ creds,
+ ev_ctx,
+ lp_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Failed to connect to %s - %s\n",
+ binding, nt_errstr(status));
+ talloc_free(mem_ctx);
+ return false;
+ }
+
+ info_ctr.level = 1;
+ info_ctr.ctr.ctr1 = &ctr1;
+
+ r.in.server_unc = talloc_asprintf(mem_ctx,"\\\\%s",dcerpc_server_name(p));
+ r.in.info_ctr = &info_ctr;
+ r.in.max_buffer = ~0;
+ r.in.resume_handle = &resume_handle;
+ r.out.resume_handle = &resume_handle;
+ r.out.totalentries = &totalentries;
+ r.out.info_ctr = &info_ctr;
+
+ d_printf("\n\tSharename Type Comment\n");
+ d_printf("\t--------- ---- -------\n");
+
+ do {
+ ZERO_STRUCT(ctr1);
+ status = dcerpc_srvsvc_NetShareEnumAll_r(p->binding_handle, mem_ctx, &r);
+
+ if (NT_STATUS_IS_OK(status) &&
+ (W_ERROR_EQUAL(r.out.result, WERR_MORE_DATA) ||
+ W_ERROR_IS_OK(r.out.result)) &&
+ r.out.info_ctr->ctr.ctr1) {
+ display_share_result(r.out.info_ctr->ctr.ctr1);
+ resume_handle += r.out.info_ctr->ctr.ctr1->count;
+ }
+ } while (NT_STATUS_IS_OK(status) && W_ERROR_EQUAL(r.out.result, WERR_MORE_DATA));
+
+ talloc_free(mem_ctx);
+
+ if (!NT_STATUS_IS_OK(status) || !W_ERROR_IS_OK(r.out.result)) {
+ d_printf("Failed NetShareEnumAll %s - %s/%s\n",
+ binding, nt_errstr(status), win_errstr(r.out.result));
+ return false;
+ }
+
+ return false;
+}
+
+/****************************************************************************
+try and browse available connections on a host
+****************************************************************************/
+static bool list_servers(const char *wk_grp)
+{
+ d_printf("REWRITE: list servers not implemented\n");
+ return false;
+}
+
+/* Some constants for completing filename arguments */
+
+#define COMPL_NONE 0 /* No completions */
+#define COMPL_REMOTE 1 /* Complete remote filename */
+#define COMPL_LOCAL 2 /* Complete local filename */
+
+static int cmd_help(struct smbclient_context *ctx, const char **args);
+
+/* This defines the commands supported by this client.
+ * NOTE: The "!" must be the last one in the list because it's fn pointer
+ * field is NULL, and NULL in that field is used in process_tok()
+ * (below) to indicate the end of the list. crh
+ */
+static struct
+{
+ const char *name;
+ int (*fn)(struct smbclient_context *ctx, const char **args);
+ const char *description;
+ char compl_args[2]; /* Completion argument info */
+} commands[] =
+{
+ {"?",cmd_help,"[command] give help on a command",{COMPL_NONE,COMPL_NONE}},
+ {"addprivileges",cmd_addprivileges,"<sid|name> <privilege...> add privileges for a user",{COMPL_NONE,COMPL_NONE}},
+ {"altname",cmd_altname,"<file> show alt name",{COMPL_NONE,COMPL_NONE}},
+ {"acl",cmd_acl,"<file> show file ACL",{COMPL_NONE,COMPL_NONE}},
+ {"allinfo",cmd_allinfo,"<file> show all possible info about a file",{COMPL_NONE,COMPL_NONE}},
+ {"archive",cmd_archive,"<level>\n0=ignore archive bit\n1=only get archive files\n2=only get archive files and reset archive bit\n3=get all files and reset archive bit",{COMPL_NONE,COMPL_NONE}},
+ {"cancel",cmd_rewrite,"<jobid> cancel a print queue entry",{COMPL_NONE,COMPL_NONE}},
+ {"cd",cmd_cd,"[directory] change/report the remote directory",{COMPL_REMOTE,COMPL_NONE}},
+ {"chmod",cmd_chmod,"<src> <mode> chmod a file using UNIX permission",{COMPL_REMOTE,COMPL_REMOTE}},
+ {"chown",cmd_chown,"<src> <uid> <gid> chown a file using UNIX uids and gids",{COMPL_REMOTE,COMPL_REMOTE}},
+ {"del",cmd_del,"<mask> delete all matching files",{COMPL_REMOTE,COMPL_NONE}},
+ {"delprivileges",cmd_delprivileges,"<sid|name> <privilege...> remove privileges for a user",{COMPL_NONE,COMPL_NONE}},
+ {"deltree",cmd_deltree,"<dir> delete a whole directory tree",{COMPL_REMOTE,COMPL_NONE}},
+ {"dir",cmd_dir,"<mask> list the contents of the current directory",{COMPL_REMOTE,COMPL_NONE}},
+ {"du",cmd_du,"<mask> computes the total size of the current directory",{COMPL_REMOTE,COMPL_NONE}},
+ {"eainfo",cmd_eainfo,"<file> show EA contents for a file",{COMPL_NONE,COMPL_NONE}},
+ {"exit",cmd_quit,"logoff the server",{COMPL_NONE,COMPL_NONE}},
+ {"fsinfo",cmd_fsinfo,"query file system info",{COMPL_NONE,COMPL_NONE}},
+ {"get",cmd_get,"<remote name> [local name] get a file",{COMPL_REMOTE,COMPL_LOCAL}},
+ {"help",cmd_help,"[command] give help on a command",{COMPL_NONE,COMPL_NONE}},
+ {"history",cmd_history,"displays the command history",{COMPL_NONE,COMPL_NONE}},
+ {"lcd",cmd_lcd,"[directory] change/report the local current working directory",{COMPL_LOCAL,COMPL_NONE}},
+ {"link",cmd_link,"<src> <dest> create a UNIX hard link",{COMPL_REMOTE,COMPL_REMOTE}},
+ {"lookup",cmd_lookup,"<sid|name> show SID for name or name for SID",{COMPL_NONE,COMPL_NONE}},
+ {"lowercase",cmd_lowercase,"toggle lowercasing of filenames for get",{COMPL_NONE,COMPL_NONE}},
+ {"ls",cmd_dir,"<mask> list the contents of the current directory",{COMPL_REMOTE,COMPL_NONE}},
+ {"mask",cmd_select,"<mask> mask all filenames against this",{COMPL_REMOTE,COMPL_NONE}},
+ {"md",cmd_mkdir,"<directory> make a directory",{COMPL_NONE,COMPL_NONE}},
+ {"mget",cmd_mget,"<mask> get all the matching files",{COMPL_REMOTE,COMPL_NONE}},
+ {"mkdir",cmd_mkdir,"<directory> make a directory",{COMPL_NONE,COMPL_NONE}},
+ {"more",cmd_more,"<remote name> view a remote file with your pager",{COMPL_REMOTE,COMPL_NONE}},
+ {"mput",cmd_mput,"<mask> put all matching files",{COMPL_REMOTE,COMPL_NONE}},
+ {"newer",cmd_newer,"<file> only mget files newer than the specified local file",{COMPL_LOCAL,COMPL_NONE}},
+ {"open",cmd_open,"<mask> open a file",{COMPL_REMOTE,COMPL_NONE}},
+ {"close",cmd_close,"<fnum> close a file",{COMPL_NONE,COMPL_NONE}},
+ {"privileges",cmd_privileges,"<user> show privileges for a user",{COMPL_NONE,COMPL_NONE}},
+ {"print",cmd_print,"<file name> print a file",{COMPL_NONE,COMPL_NONE}},
+ {"printmode",cmd_printmode,"<graphics or text> set the print mode",{COMPL_NONE,COMPL_NONE}},
+ {"prompt",cmd_prompt,"toggle prompting for filenames for mget and mput",{COMPL_NONE,COMPL_NONE}},
+ {"put",cmd_put,"<local name> [remote name] put a file",{COMPL_LOCAL,COMPL_REMOTE}},
+ {"pwd",cmd_pwd,"show current remote directory (same as 'cd' with no args)",{COMPL_NONE,COMPL_NONE}},
+ {"q",cmd_quit,"logoff the server",{COMPL_NONE,COMPL_NONE}},
+ {"queue",cmd_rewrite,"show the print queue",{COMPL_NONE,COMPL_NONE}},
+ {"quit",cmd_quit,"logoff the server",{COMPL_NONE,COMPL_NONE}},
+ {"rd",cmd_rmdir,"<directory> remove a directory",{COMPL_NONE,COMPL_NONE}},
+ {"recurse",cmd_recurse,"toggle directory recursion for mget and mput",{COMPL_NONE,COMPL_NONE}},
+ {"reget",cmd_reget,"<remote name> [local name] get a file restarting at end of local file",{COMPL_REMOTE,COMPL_LOCAL}},
+ {"rename",cmd_rename,"<src> <dest> rename some files",{COMPL_REMOTE,COMPL_REMOTE}},
+ {"reput",cmd_reput,"<local name> [remote name] put a file restarting at end of remote file",{COMPL_LOCAL,COMPL_REMOTE}},
+ {"rm",cmd_del,"<mask> delete all matching files",{COMPL_REMOTE,COMPL_NONE}},
+ {"rmdir",cmd_rmdir,"<directory> remove a directory",{COMPL_NONE,COMPL_NONE}},
+ {"symlink",cmd_symlink,"<src> <dest> create a UNIX symlink",{COMPL_REMOTE,COMPL_REMOTE}},
+ {"translate",cmd_translate,"toggle text translation for printing",{COMPL_NONE,COMPL_NONE}},
+
+ /* Yes, this must be here, see crh's comment above. */
+ {"!",NULL,"run a shell command on the local system",{COMPL_NONE,COMPL_NONE}},
+ {NULL,NULL,NULL,{COMPL_NONE,COMPL_NONE}}
+};
+
+
+/*******************************************************************
+ lookup a command string in the list of commands, including
+ abbreviations
+ ******************************************************************/
+static int process_tok(const char *tok)
+{
+ size_t i = 0, matches = 0;
+ size_t cmd=0;
+ size_t tok_len = strlen(tok);
+
+ while (commands[i].fn != NULL) {
+ if (strequal(commands[i].name,tok)) {
+ matches = 1;
+ cmd = i;
+ break;
+ } else if (strncasecmp(commands[i].name, tok, tok_len) == 0) {
+ matches++;
+ cmd = i;
+ }
+ i++;
+ }
+
+ if (matches == 0)
+ return(-1);
+ else if (matches == 1)
+ return(cmd);
+ else
+ return(-2);
+}
+
+/****************************************************************************
+help
+****************************************************************************/
+static int cmd_help(struct smbclient_context *ctx, const char **args)
+{
+ int i=0,j;
+
+ if (args[1]) {
+ if ((i = process_tok(args[1])) >= 0)
+ d_printf("HELP %s:\n\t%s\n\n",commands[i].name,commands[i].description);
+ } else {
+ while (commands[i].description) {
+ for (j=0; commands[i].description && (j<5); j++) {
+ d_printf("%-15s",commands[i].name);
+ i++;
+ }
+ d_printf("\n");
+ }
+ }
+ return 0;
+}
+
+static int process_line(struct smbclient_context *ctx, const char *cline);
+/****************************************************************************
+process a -c command string
+****************************************************************************/
+static int process_command_string(struct smbclient_context *ctx, const char *cmd)
+{
+ char **lines;
+ int i, rc = 0;
+
+ lines = str_list_make(NULL, cmd, ";");
+ for (i = 0; lines[i]; i++) {
+ rc |= process_line(ctx, lines[i]);
+ }
+ talloc_free(lines);
+
+ return rc;
+}
+
+#define MAX_COMPLETIONS 100
+
+typedef struct {
+ char *dirmask;
+ char **matches;
+ int count, samelen;
+ const char *text;
+ int len;
+} completion_remote_t;
+
+static void completion_remote_filter(struct clilist_file_info *f, const char *mask, void *state)
+{
+ completion_remote_t *info = (completion_remote_t *)state;
+
+ if ((info->count < MAX_COMPLETIONS - 1) && (strncmp(info->text, f->name, info->len) == 0) && (!ISDOT(f->name)) && (!ISDOTDOT(f->name))) {
+ if ((info->dirmask[0] == 0) && !(f->attrib & FILE_ATTRIBUTE_DIRECTORY))
+ info->matches[info->count] = strdup(f->name);
+ else {
+ char *tmp;
+
+ if (info->dirmask[0] != 0)
+ tmp = talloc_asprintf(NULL, "%s/%s", info->dirmask, f->name);
+ else
+ tmp = talloc_strdup(NULL, f->name);
+
+ if (f->attrib & FILE_ATTRIBUTE_DIRECTORY)
+ tmp = talloc_append_string(NULL, tmp, "/");
+ info->matches[info->count] = tmp;
+ }
+ if (info->matches[info->count] == NULL)
+ return;
+ if (f->attrib & FILE_ATTRIBUTE_DIRECTORY)
+ smb_readline_ca_char(0);
+
+ if (info->count == 1)
+ info->samelen = strlen(info->matches[info->count]);
+ else
+ while (strncmp(info->matches[info->count], info->matches[info->count-1], info->samelen) != 0)
+ info->samelen--;
+ info->count++;
+ }
+}
+
+static char **remote_completion(const char *text, int len)
+{
+ char *dirmask;
+ int i, ret;
+ completion_remote_t info;
+
+ info.samelen = len;
+ info.text = text;
+ info.len = len;
+ info.count = 0;
+
+ if (len >= PATH_MAX)
+ return(NULL);
+
+ info.matches = malloc_array_p(char *, MAX_COMPLETIONS);
+ if (!info.matches) return NULL;
+ info.matches[0] = NULL;
+
+ for (i = len-1; i >= 0; i--)
+ if ((text[i] == '/') || (text[i] == '\\'))
+ break;
+ info.text = text+i+1;
+ info.samelen = info.len = len-i-1;
+
+ if (i > 0) {
+ info.dirmask = talloc_strndup(NULL, text, i+1);
+ info.dirmask[i+1] = 0;
+ ret = asprintf(&dirmask, "%s%*s*", rl_ctx->remote_cur_dir, i-1,
+ text);
+ } else {
+ ret = asprintf(&dirmask, "%s*", rl_ctx->remote_cur_dir);
+ }
+ if (ret < 0) {
+ goto cleanup;
+ }
+
+ if (smbcli_list(rl_ctx->cli->tree, dirmask,
+ FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN,
+ completion_remote_filter, &info) < 0)
+ goto cleanup;
+
+ if (info.count == 2)
+ info.matches[0] = strdup(info.matches[1]);
+ else {
+ info.matches[0] = malloc_array_p(char, info.samelen+1);
+ if (!info.matches[0])
+ goto cleanup;
+ strncpy(info.matches[0], info.matches[1], info.samelen);
+ info.matches[0][info.samelen] = 0;
+ }
+ info.matches[info.count] = NULL;
+ return info.matches;
+
+cleanup:
+ for (i = 0; i < info.count; i++)
+ free(info.matches[i]);
+ free(info.matches);
+ return NULL;
+}
+
+static char **completion_fn(const char *text, int start, int end)
+{
+ smb_readline_ca_char(' ');
+
+ if (start) {
+ const char *buf, *sp;
+ int i;
+ char compl_type;
+
+ buf = smb_readline_get_line_buffer();
+ if (buf == NULL)
+ return NULL;
+
+ sp = strchr(buf, ' ');
+ if (sp == NULL)
+ return NULL;
+
+ for (i = 0; commands[i].name; i++)
+ if ((strncmp(commands[i].name, text, sp - buf) == 0) && (commands[i].name[sp - buf] == 0))
+ break;
+ if (commands[i].name == NULL)
+ return NULL;
+
+ while (*sp == ' ')
+ sp++;
+
+ if (sp == (buf + start))
+ compl_type = commands[i].compl_args[0];
+ else
+ compl_type = commands[i].compl_args[1];
+
+ if (compl_type == COMPL_REMOTE)
+ return remote_completion(text, end - start);
+ else /* fall back to local filename completion */
+ return NULL;
+ } else {
+ char **matches;
+ size_t i, len, samelen = 0, count=1;
+
+ matches = malloc_array_p(char *, MAX_COMPLETIONS);
+ if (!matches) return NULL;
+ matches[0] = NULL;
+
+ len = strlen(text);
+ for (i=0;commands[i].fn && count < MAX_COMPLETIONS-1;i++) {
+ if (strncmp(text, commands[i].name, len) == 0) {
+ matches[count] = strdup(commands[i].name);
+ if (!matches[count])
+ goto cleanup;
+ if (count == 1)
+ samelen = strlen(matches[count]);
+ else
+ while (strncmp(matches[count], matches[count-1], samelen) != 0)
+ samelen--;
+ count++;
+ }
+ }
+
+ switch (count) {
+ case 0: /* should never happen */
+ case 1:
+ goto cleanup;
+ case 2:
+ matches[0] = strdup(matches[1]);
+ break;
+ default:
+ matches[0] = malloc_array_p(char, samelen+1);
+ if (!matches[0])
+ goto cleanup;
+ strncpy(matches[0], matches[1], samelen);
+ matches[0][samelen] = 0;
+ }
+ matches[count] = NULL;
+ return matches;
+
+cleanup:
+ for (i = 0; i < count; i++) {
+ free(matches[i]);
+ }
+ free(matches);
+ return NULL;
+ }
+}
+
+/****************************************************************************
+make sure we swallow keepalives during idle time
+****************************************************************************/
+static void readline_callback(void)
+{
+ static time_t last_t;
+ time_t t;
+
+ t = time(NULL);
+
+ if (t - last_t < 5) return;
+
+ last_t = t;
+
+ smbcli_transport_process(rl_ctx->cli->transport);
+
+ if (rl_ctx->cli->tree) {
+ smbcli_chkpath(rl_ctx->cli->tree, "\\");
+ }
+}
+
+static int process_line(struct smbclient_context *ctx, const char *cline)
+{
+ char **args;
+ int i;
+
+ /* and get the first part of the command */
+ args = str_list_make_shell(ctx, cline, NULL);
+ if (!args || !args[0])
+ return 0;
+
+ if ((i = process_tok(args[0])) >= 0) {
+ const char **a = discard_const_p(const char *, args);
+ i = commands[i].fn(ctx, a);
+ } else if (i == -2) {
+ d_printf("%s: command abbreviation ambiguous\n",args[0]);
+ } else {
+ d_printf("%s: command not found\n",args[0]);
+ }
+
+ talloc_free(args);
+
+ return i;
+}
+
+/****************************************************************************
+process commands on stdin
+****************************************************************************/
+static int process_stdin(struct smbclient_context *ctx)
+{
+ int rc = 0;
+ while (1) {
+ /* display a prompt */
+ char *the_prompt = talloc_asprintf(ctx, "smb: %s> ", ctx->remote_cur_dir);
+ char *cline = smb_readline(the_prompt, readline_callback, completion_fn);
+ talloc_free(the_prompt);
+
+ if (!cline) break;
+
+ /* special case - first char is ! */
+ if (*cline == '!') {
+ int ret;
+ ret = system(cline + 1);
+ free(cline);
+ if (ret == -1) {
+ rc |= ret;
+ }
+ continue;
+ }
+
+ rc |= process_command_string(ctx, cline);
+ free(cline);
+
+ }
+
+ return rc;
+}
+
+
+/*****************************************************
+return a connection to a server
+*******************************************************/
+static bool do_connect(struct smbclient_context *ctx,
+ struct tevent_context *ev_ctx,
+ struct resolve_context *resolve_ctx,
+ const char *specified_server, const char **ports,
+ const char *specified_share,
+ const char *socket_options,
+ struct cli_credentials *cred,
+ struct smbcli_options *options,
+ struct smbcli_session_options *session_options,
+ struct gensec_settings *gensec_settings)
+{
+ NTSTATUS status;
+ char *server, *share;
+
+ rl_ctx = ctx; /* Ugly hack */
+
+ if (strncmp(specified_share, "\\\\", 2) == 0 ||
+ strncmp(specified_share, "//", 2) == 0) {
+ bool ok;
+
+ ok = smbcli_parse_unc(specified_share, ctx, &server, &share);
+ if (!ok) {
+ d_printf("Failed to parse UNC\n");
+ talloc_free(ctx);
+ return false;
+ }
+ } else {
+ share = talloc_strdup(ctx, specified_share);
+ server = talloc_strdup(ctx, specified_server);
+ if (share == NULL || server == NULL) {
+ d_printf("Failed to allocate memory for server and share\n");
+ talloc_free(ctx);
+ return false;
+ }
+ }
+
+ ctx->remote_cur_dir = talloc_strdup(ctx, "\\");
+ if (ctx->remote_cur_dir == NULL) {
+ talloc_free(ctx);
+ return false;
+ }
+
+ status = smbcli_full_connection(ctx, &ctx->cli, server, ports,
+ share, NULL,
+ socket_options,
+ cred, resolve_ctx,
+ ev_ctx, options, session_options,
+ gensec_settings);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Connection to \\\\%s\\%s failed - %s\n",
+ server, share, nt_errstr(status));
+ talloc_free(ctx);
+ return false;
+ }
+
+ return true;
+}
+
+/****************************************************************************
+handle a -L query
+****************************************************************************/
+static int do_host_query(struct loadparm_context *lp_ctx,
+ struct tevent_context *ev_ctx,
+ const char *query_host,
+ const char *workgroup)
+{
+ browse_host(lp_ctx, ev_ctx, query_host);
+ list_servers(workgroup);
+ return(0);
+}
+
+
+/****************************************************************************
+handle a message operation
+****************************************************************************/
+static int do_message_op(const char *netbios_name, const char *desthost,
+ const char **destports, const char *destip,
+ int name_type,
+ struct tevent_context *ev_ctx,
+ struct resolve_context *resolve_ctx,
+ struct smbcli_options *options,
+ const char *socket_options)
+{
+ struct nbt_name called, calling;
+ const char *server_name;
+ struct smbcli_state *cli;
+ bool ok;
+
+ make_nbt_name_client(&calling, netbios_name);
+
+ nbt_choose_called_name(NULL, &called, desthost, name_type);
+
+ server_name = destip ? destip : desthost;
+
+ cli = smbcli_state_init(NULL);
+ if (cli == NULL) {
+ d_printf("smbcli_state_init() failed\n");
+ return 1;
+ }
+
+ ok = smbcli_socket_connect(cli, server_name, destports,
+ ev_ctx, resolve_ctx, options,
+ socket_options,
+ &calling, &called);
+ if (!ok) {
+ d_printf("Connection to %s failed\n", server_name);
+ return 1;
+ }
+
+ send_message(cli, desthost);
+ talloc_free(cli);
+
+ return 0;
+}
+
+
+/****************************************************************************
+ main program
+****************************************************************************/
+int main(int argc, char *argv[])
+{
+ const char **const_argv = discard_const_p(const char *, argv);
+ char *base_directory = NULL;
+ const char *dest_ip = NULL;
+ int opt;
+ const char *query_host = NULL;
+ bool message = false;
+ char *desthost = NULL;
+ poptContext pc;
+ const char *service = NULL;
+ int port = 0;
+ char *p;
+ int rc = 0;
+ int name_type = 0x20;
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev_ctx;
+ struct smbclient_context *ctx;
+ const char *cmdstr = NULL;
+ struct smbcli_options smb_options;
+ struct smbcli_session_options smb_session_options;
+ struct cli_credentials *creds = NULL;
+ struct loadparm_context *lp_ctx = NULL;
+ bool ok;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+
+ {
+ .longName = "message",
+ .shortName = 'M',
+ .argInfo = POPT_ARG_STRING,
+ .arg = NULL,
+ .val = 'M',
+ .descrip = "Send message",
+ .argDescrip = "HOST",
+ },
+ {
+ .longName = "ip-address",
+ .shortName = 'I',
+ .argInfo = POPT_ARG_STRING,
+ .arg = NULL,
+ .val = 'I',
+ .descrip = "Use this IP to connect to",
+ .argDescrip = "IP",
+ },
+ {
+ .longName = "stderr",
+ .shortName = 'E',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'E',
+ .descrip = "Write messages to stderr instead of stdout",
+ },
+ {
+ .longName = "list",
+ .shortName = 'L',
+ .argInfo = POPT_ARG_STRING,
+ .arg = NULL,
+ .val = 'L',
+ .descrip = "Get a list of shares available on a host",
+ .argDescrip = "HOST",
+ },
+ {
+ .longName = "directory",
+ .shortName = 'D',
+ .argInfo = POPT_ARG_STRING,
+ .arg = NULL,
+ .val = 'D',
+ .descrip = "Start from directory",
+ .argDescrip = "DIR",
+ },
+ {
+ .longName = "command",
+ .shortName = 'c',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &cmdstr,
+ .val = 'c',
+ .descrip = "Execute semicolon separated commands",
+ },
+ {
+ .longName = "send-buffer",
+ .shortName = 'b',
+ .argInfo = POPT_ARG_INT,
+ .arg = NULL,
+ .val = 'b',
+ .descrip = "Changes the transmit/send buffer",
+ .argDescrip = "BYTES",
+ },
+ {
+ .longName = "port",
+ .shortName = 'p',
+ .argInfo = POPT_ARG_INT,
+ .arg = &port,
+ .val = 'p',
+ .descrip = "Port to connect to",
+ .argDescrip = "PORT",
+ },
+ POPT_COMMON_SAMBA
+ POPT_COMMON_CONNECTION
+ POPT_COMMON_CREDENTIALS
+ POPT_LEGACY_S4
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+
+ mem_ctx = talloc_init("client.c/main");
+ if (!mem_ctx) {
+ d_printf("\nclient.c: Not enough memory\n");
+ exit(1);
+ }
+
+ ctx = talloc_zero(mem_ctx, struct smbclient_context);
+ ctx->io_bufsize = 64512;
+
+ ok = samba_cmdline_init(mem_ctx,
+ SAMBA_CMDLINE_CONFIG_CLIENT,
+ false /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ TALLOC_FREE(ctx);
+ exit(1);
+ }
+
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ const_argv,
+ long_options,
+ 0);
+ if (pc == NULL) {
+ DBG_ERR("Failed to setup popt context!\n");
+ TALLOC_FREE(ctx);
+ exit(1);
+ }
+ poptSetOtherOptionHelp(pc, "[OPTIONS] service <password>");
+
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ case 'M':
+ /* Messages are sent to NetBIOS name type 0x3
+ * (Messenger Service). Make sure we default
+ * to port 139 instead of port 445. srl,crh
+ */
+ name_type = 0x03;
+ desthost = strdup(poptGetOptArg(pc));
+ if( 0 == port ) port = 139;
+ message = true;
+ break;
+ case 'I':
+ dest_ip = poptGetOptArg(pc);
+ break;
+ case 'L':
+ query_host = strdup(poptGetOptArg(pc));
+ break;
+ case 'D':
+ base_directory = strdup(poptGetOptArg(pc));
+ break;
+ case 'b':
+ ctx->io_bufsize = MAX(1, atoi(poptGetOptArg(pc)));
+ break;
+ case POPT_ERROR_BADOPT:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+
+ gensec_init();
+
+ if(poptPeekArg(pc)) {
+ char *s = strdup(poptGetArg(pc));
+
+ /* Convert any '/' characters in the service name to '\' characters */
+ string_replace(s, '/','\\');
+
+ service = s;
+
+ if (count_chars(s,'\\') < 3) {
+ d_printf("\n%s: Not enough '\\' characters in service\n",s);
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+
+ creds = samba_cmdline_get_creds();
+ lp_ctx = samba_cmdline_get_lp_ctx();
+
+ if (poptPeekArg(pc)) {
+ cli_credentials_set_password(creds,
+ poptGetArg(pc), CRED_SPECIFIED);
+ }
+
+ if (!query_host && !service && !message) {
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+
+ poptFreeContext(pc);
+ samba_cmdline_burn(argc, argv);
+
+ lpcfg_smbcli_options(lp_ctx, &smb_options);
+ lpcfg_smbcli_session_options(lp_ctx, &smb_session_options);
+
+ ev_ctx = s4_event_context_init(ctx);
+
+ DEBUG( 3, ( "Client started (version %s).\n", SAMBA_VERSION_STRING ) );
+
+ if (query_host && (p=strchr_m(query_host,'#'))) {
+ *p = 0;
+ p++;
+ sscanf(p, "%x", &name_type);
+ }
+
+ if (query_host) {
+ rc = do_host_query(lp_ctx, ev_ctx, query_host,
+ lpcfg_workgroup(lp_ctx));
+ return rc;
+ }
+
+ if (message) {
+ rc = do_message_op(lpcfg_netbios_name(lp_ctx), desthost,
+ lpcfg_smb_ports(lp_ctx), dest_ip,
+ name_type, ev_ctx,
+ lpcfg_resolve_context(lp_ctx),
+ &smb_options,
+ lpcfg_socket_options(lp_ctx));
+ return rc;
+ }
+
+ if (!do_connect(ctx, ev_ctx, lpcfg_resolve_context(lp_ctx),
+ desthost, lpcfg_smb_ports(lp_ctx), service,
+ lpcfg_socket_options(lp_ctx),
+ creds,
+ &smb_options, &smb_session_options,
+ lpcfg_gensec_settings(ctx, lp_ctx)))
+ return 1;
+
+ if (base_directory) {
+ do_cd(ctx, base_directory);
+ free(base_directory);
+ }
+
+ if (cmdstr) {
+ rc = process_command_string(ctx, cmdstr);
+ } else {
+ rc = process_stdin(ctx);
+ }
+
+ free(desthost);
+ talloc_free(mem_ctx);
+
+ return rc;
+}
diff --git a/source4/client/tests/test_cifsdd.sh b/source4/client/tests/test_cifsdd.sh
new file mode 100755
index 0000000..21a8840
--- /dev/null
+++ b/source4/client/tests/test_cifsdd.sh
@@ -0,0 +1,77 @@
+#!/bin/sh
+
+# Basic script to make sure that cifsdd can do both local and remote I/O.
+
+if [ $# -lt 4 ]; then
+ cat <<EOF
+Usage: test_cifsdd.sh SERVER USERNAME PASSWORD DOMAIN
+EOF
+ exit 1
+fi
+
+SERVER=$1
+USERNAME=$2
+PASSWORD=$3
+DOMAIN=$4
+
+. $(dirname $0)/../../../testprogs/blackbox/subunit.sh
+
+samba4bindir="$BINDIR"
+DD="$samba4bindir/cifsdd"
+
+SHARE=tmp
+DEBUGLEVEL=1
+
+runcopy()
+{
+ message="$1"
+ shift
+
+ testit "$message" $VALGRIND $DD $CONFIGURATION --debuglevel=$DEBUGLEVEL -W "$DOMAIN" -U "$USERNAME"%"$PASSWORD" \
+ "$@" || failed=$(expr $failed + 1)
+}
+
+compare()
+{
+ testit "$1" cmp "$2" "$3" || failed=$(expr $failed + 1)
+}
+
+sourcefile=tempfile.src.$$
+sourcepath=${SELFTEST_TMPDIR}/$sourcefile
+destfile=tempfile.dst.$$
+destpath=${SELFTEST_TMPDIR}/$destfile
+
+# Create a source file with arbitrary contents
+dd if=$DD of=$sourcepath bs=1024 count=50 >/dev/null
+
+ls -l $sourcepath
+
+for bs in 512 4k 48k; do
+
+ echo "Testing $bs block size ..."
+
+ # Check whether we can do local IO
+ runcopy "Testing local -> local copy" if=$sourcepath of=$destpath bs=$bs
+ compare "Checking local differences" $sourcepath $destpath
+
+ # Check whether we can do a round trip
+ runcopy "Testing local -> remote copy" \
+ if=$sourcepath of=//$SERVER/$SHARE/$sourcefile bs=$bs
+ runcopy "Testing remote -> local copy" \
+ if=//$SERVER/$SHARE/$sourcefile of=$destpath bs=$bs
+ compare "Checking differences" $sourcepath $destpath
+
+ # Check that copying within the remote server works
+ runcopy "Testing local -> remote copy" \
+ if=//$SERVER/$SHARE/$sourcefile of=//$SERVER/$SHARE/$sourcefile bs=$bs
+ runcopy "Testing remote -> remote copy" \
+ if=//$SERVER/$SHARE/$sourcefile of=//$SERVER/$SHARE/$destfile bs=$bs
+ runcopy "Testing remote -> local copy" \
+ if=//$SERVER/$SHARE/$destfile of=$destpath bs=$bs
+ compare "Checking differences" $sourcepath $destpath
+
+done
+
+rm -f $sourcepath $destpath
+
+exit $failed
diff --git a/source4/client/tests/test_smbclient.sh b/source4/client/tests/test_smbclient.sh
new file mode 100755
index 0000000..1d87f90
--- /dev/null
+++ b/source4/client/tests/test_smbclient.sh
@@ -0,0 +1,154 @@
+#!/bin/sh
+# Blackbox tests for smbclient
+# Copyright (C) 2006-2007 Jelmer Vernooij <jelmer@samba.org>
+# Copyright (C) 2006-2007 Andrew Bartlett <abartlet@samba.org>
+
+if [ $# -lt 5 ]; then
+ cat <<EOF
+Usage: test_smbclient.sh SERVER USERNAME PASSWORD DOMAIN PREFIX SMBCLIENT
+EOF
+ exit 1
+fi
+
+SERVER=$1
+USERNAME=$2
+PASSWORD=$3
+DOMAIN=$4
+PREFIX=$5
+smbclient=$6
+shift 6
+failed=0
+
+. $(dirname $0)/../../../testprogs/blackbox/subunit.sh
+
+runcmd()
+{
+ name="$1"
+ cmd="$2"
+ shift
+ shift
+ echo "test: $name"
+ $VALGRIND $smbclient $CONFIGURATION //$SERVER/tmp -c "$cmd" -W "$DOMAIN" -U"$USERNAME%$PASSWORD" $@
+ status=$?
+ if [ x$status = x0 ]; then
+ echo "success: $name"
+ else
+ echo "failure: $name"
+ fi
+ return $status
+}
+
+testit "share and server list" $VALGRIND $smbclient -L $SERVER $CONFIGURATION -W "$DOMAIN" -U"$USERNAME%$PASSWORD" $@ || failed=$(expr $failed + 1)
+
+testit "share and server list anonymously" $VALGRIND $smbclient -N -L $SERVER $CONFIGURATION $@ || failed=$(expr $failed + 1)
+
+# Use the smbclient binary as our test file
+cat $smbclient >$PREFIX/tmpfile
+
+# put that file
+runcmd "MPutting file" "lcd $PREFIX; mput tmpfile" || failed=$(expr $failed + 1)
+# check file info
+runcmd "Getting alternative name" 'altname tmpfile' || failed=$(expr $failed + 1)
+# run allinfo on that file
+runcmd "Checking info on file" 'allinfo tmpfile' || failed=$(expr $failed + 1)
+# get that file
+mv $PREFIX/tmpfile $PREFIX/tmpfile-old
+runcmd "MGetting file" "lcd $PREFIX; mget tmpfile" || failed=$(expr $failed + 1)
+# remove that file
+runcmd "Removing file" 'rm tmpfile' || failed=$(expr $failed + 1)
+# compare locally
+testit "Comparing files" diff $PREFIX/tmpfile-old $PREFIX/tmpfile || failed=$(expr $failed + 1)
+# create directory
+# cd to directory
+# cd to top level directory
+# remove directory
+runcmd "Creating directory, Changing directory, Going back" 'mkdir bla; cd bla; cd ..; rmdir bla' || failed=$(expr $failed + 1)
+# enable recurse, create nested directory
+runcmd "Creating nested directory" 'mkdir bla/bloe' || failed=$(expr $failed + 1)
+# remove child directory
+runcmd "Removing directory" 'rmdir bla/bloe' || failed=$(expr $failed + 1)
+# remove parent directory
+runcmd "Removing directory" 'rmdir bla' || failed=$(expr $failed + 1)
+# enable recurse, create nested directory
+runcmd "Creating nested directory" 'mkdir bla' || failed=$(expr $failed + 1)
+# rename bla to bla2
+runcmd "rename of nested directory" 'rename bla bla2' || failed=$(expr $failed + 1)
+# deltree
+runcmd "deltree of nested directory" 'deltree bla2' || failed=$(expr $failed + 1)
+# run fsinfo
+runcmd "Getting file system info" 'fsinfo allocation' || failed=$(expr $failed + 1)
+runcmd "Getting file system info" 'fsinfo volume' || failed=$(expr $failed + 1)
+runcmd "Getting file system info" 'fsinfo volumeinfo' || failed=$(expr $failed + 1)
+runcmd "Getting file system info" 'fsinfo sizeinfo' || failed=$(expr $failed + 1)
+runcmd "Getting file system info" 'fsinfo deviceinfo' || failed=$(expr $failed + 1)
+runcmd "Getting file system info" 'fsinfo attributeinfo' || failed=$(expr $failed + 1)
+runcmd "Getting file system info" 'fsinfo volume-information' || failed=$(expr $failed + 1)
+runcmd "Getting file system info" 'fsinfo size-information' || failed=$(expr $failed + 1)
+runcmd "Getting file system info" 'fsinfo device-information' || failed=$(expr $failed + 1)
+runcmd "Getting file system info" 'fsinfo attribute-information' || failed=$(expr $failed + 1)
+runcmd "Getting file system info" 'fsinfo quota-information' || failed=$(expr $failed + 1)
+runcmd "Getting file system info" 'fsinfo fullsize-information' || failed=$(expr $failed + 1)
+runcmd "Getting file system info" 'fsinfo objectid' || failed=$(expr $failed + 1)
+
+# put that file
+runcmd "Putting file" "lcd $PREFIX; put tmpfile" || failed=$(expr $failed + 1)
+# get that file
+mv $PREFIX/tmpfile $PREFIX/tmpfile-old
+runcmd "Getting file" "lcd $PREFIX; get tmpfile" || failed=$(expr $failed + 1)
+runcmd "Getting file EA info" 'eainfo tmpfile' || failed=$(expr $failed + 1)
+# remove that file
+runcmd "Removing file" 'rm tmpfile' || failed=$(expr $failed + 1)
+# compare locally
+testit "Comparing files" diff $PREFIX/tmpfile-old $PREFIX/tmpfile || failed=$(expr $failed + 1)
+# put that file
+runcmd "Putting file with different name" "lcd $PREFIX; put tmpfile tmpfilex" || failed=$(expr $failed + 1)
+# get that file
+runcmd "Getting file again" "lcd $PREFIX; get tmpfilex" || failed=$(expr $failed + 1)
+# compare locally
+testit "Comparing files" diff $PREFIX/tmpfilex $PREFIX/tmpfile || failed=$(expr $failed + 1)
+# remove that file
+runcmd "Removing file" 'rm tmpfilex' || failed=$(expr $failed + 1)
+
+runcmd "Lookup name" "lookup $DOMAIN\\$USERNAME" || failed=$(expr $failed + 1)
+
+#Fails unless there are privileges
+#runcmd "Lookup privs of name" "privileges $DOMAIN\\$USERNAME" || failed=`expr $failed + 1`
+
+# do some simple operations using old protocol versions
+runcmd "List directory with LANMAN1" 'ls' -m LANMAN1 --option=clientntlmv2auth=no || failed=$(expr $failed + 1)
+runcmd "List directory with LANMAN2" 'ls' -m LANMAN2 --option=clientntlmv2auth=no || failed=$(expr $failed + 1)
+
+runcmd "Print current working directory" 'pwd' || failed=$(expr $failed + 1)
+
+(
+ echo "password=$PASSWORD"
+ echo "username=$USERNAME"
+ echo "domain=$DOMAIN"
+) >$PREFIX/tmpauthfile
+
+testit "Test login with --authentication-file" $VALGRIND $smbclient -c 'ls' $CONFIGURATION //$SERVER/tmp --authentication-file=$PREFIX/tmpauthfile || failed=$(expr $failed + 1)
+
+PASSWD_FILE="$PREFIX/tmppassfile"
+echo "$PASSWORD" >$PASSWD_FILE
+export PASSWD_FILE
+testit "Test login with PASSWD_FILE" $VALGRIND $smbclient -c 'ls' $CONFIGURATION //$SERVER/tmp -W "$DOMAIN" -U"$USERNAME" || failed=$(expr $failed + 1)
+PASSWD_FILE=""
+export PASSWD_FILE
+unset PASSWD_FILE
+
+PASSWD="$PASSWORD"
+export PASSWD
+testit "Test login with PASSWD" $VALGRIND $smbclient -c 'ls' $CONFIGURATION //$SERVER/tmp -W "$DOMAIN" -U"$USERNAME" || failed=$(expr $failed + 1)
+
+oldUSER=$USER
+USER="$USERNAME"
+export USER
+testit "Test login with USER and PASSWD" $VALGRIND $smbclient --use-kerberos=disabled -c 'ls' $CONFIGURATION //$SERVER/tmp -W "$DOMAIN" || failed=$(expr $failed + 1)
+PASSWD=
+export PASSWD
+unset PASSWD
+USER=$oldUSER
+export USER
+
+rm -f $PREFIX/tmpfile $PREFIX/tmpfile-old $PREFIX/tmpfilex $PREFIX/tmpauthfile $PREFIX/tmppassfile
+exit $failed