+/* ----------------------------------------------------------------------
+ * A remote wildcard matcher, providing a similar interface to the
+ * local one in psftp.h.
+ */
+
+typedef struct SftpWildcardMatcher {
+ struct fxp_handle *dirh;
+ struct fxp_names *names;
+ int namepos;
+ char *wildcard, *prefix;
+} SftpWildcardMatcher;
+
+SftpWildcardMatcher *sftp_begin_wildcard_matching(char *name)
+{
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+ char *wildcard;
+ char *unwcdir, *tmpdir, *cdir;
+ int len, check;
+ SftpWildcardMatcher *swcm;
+ struct fxp_handle *dirh;
+
+ /*
+ * We don't handle multi-level wildcards; so we expect to find
+ * a fully specified directory part, followed by a wildcard
+ * after that.
+ */
+ wildcard = stripslashes(name, 0);
+
+ unwcdir = dupstr(name);
+ len = wildcard - name;
+ unwcdir[len] = '\0';
+ if (len > 0 && unwcdir[len-1] == '/')
+ unwcdir[len-1] = '\0';
+ tmpdir = snewn(1 + len, char);
+ check = wc_unescape(tmpdir, unwcdir);
+ sfree(tmpdir);
+
+ if (!check) {
+ printf("Multiple-level wildcards are not supported\n");
+ sfree(unwcdir);
+ return NULL;
+ }
+
+ cdir = canonify(unwcdir);
+
+ sftp_register(req = fxp_opendir_send(cdir));
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ dirh = fxp_opendir_recv(pktin, rreq);
+
+ if (dirh) {
+ swcm = snew(SftpWildcardMatcher);
+ swcm->dirh = dirh;
+ swcm->names = NULL;
+ swcm->wildcard = dupstr(wildcard);
+ swcm->prefix = unwcdir;
+ } else {
+ printf("Unable to open %s: %s\n", cdir, fxp_error());
+ swcm = NULL;
+ sfree(unwcdir);
+ }
+
+ sfree(cdir);
+
+ return swcm;
+}
+
+char *sftp_wildcard_get_filename(SftpWildcardMatcher *swcm)
+{
+ struct fxp_name *name;
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+
+ while (1) {
+ if (swcm->names && swcm->namepos >= swcm->names->nnames) {
+ fxp_free_names(swcm->names);
+ swcm->names = NULL;
+ }
+
+ if (!swcm->names) {
+ sftp_register(req = fxp_readdir_send(swcm->dirh));
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ swcm->names = fxp_readdir_recv(pktin, rreq);
+
+ if (!swcm->names) {
+ if (fxp_error_type() != SSH_FX_EOF)
+ printf("%s: reading directory: %s\n", swcm->prefix,
+ fxp_error());
+ return NULL;
+ }
+
+ swcm->namepos = 0;
+ }
+
+ assert(swcm->names && swcm->namepos < swcm->names->nnames);
+
+ name = &swcm->names->names[swcm->namepos++];
+
+ if (!strcmp(name->filename, ".") || !strcmp(name->filename, ".."))
+ continue; /* expected bad filenames */
+
+ if (!vet_filename(name->filename)) {
+ printf("ignoring potentially dangerous server-"
+ "supplied filename '%s'\n", name->filename);
+ continue; /* unexpected bad filename */
+ }
+
+ if (!wc_match(swcm->wildcard, name->filename))
+ continue; /* doesn't match the wildcard */
+
+ /*
+ * We have a working filename. Return it.
+ */
+ return dupprintf("%s%s%s", swcm->prefix,
+ (!swcm->prefix[0] ||
+ swcm->prefix[strlen(swcm->prefix)-1]=='/' ?
+ "" : "/"),
+ name->filename);
+ }
+}
+
+void sftp_finish_wildcard_matching(SftpWildcardMatcher *swcm)
+{
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+
+ sftp_register(req = fxp_close_send(swcm->dirh));
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ fxp_close_recv(pktin, rreq);
+
+ if (swcm->names)
+ fxp_free_names(swcm->names);
+
+ sfree(swcm->prefix);
+ sfree(swcm->wildcard);
+
+ sfree(swcm);
+}
+
+/*
+ * General function to match a potential wildcard in a filename
+ * argument and iterate over every matching file. Used in several
+ * PSFTP commands (rmdir, rm, chmod, mv).
+ */
+int wildcard_iterate(char *filename, int (*func)(void *, char *), void *ctx)
+{
+ char *unwcfname, *newname, *cname;
+ int is_wc, ret;
+
+ unwcfname = snewn(strlen(filename)+1, char);
+ is_wc = !wc_unescape(unwcfname, filename);
+
+ if (is_wc) {
+ SftpWildcardMatcher *swcm = sftp_begin_wildcard_matching(filename);
+ int matched = FALSE;
+ sfree(unwcfname);
+
+ if (!swcm)
+ return 0;
+
+ ret = 1;
+
+ while ( (newname = sftp_wildcard_get_filename(swcm)) != NULL ) {
+ cname = canonify(newname);
+ if (!cname) {
+ printf("%s: canonify: %s\n", newname, fxp_error());
+ ret = 0;
+ }
+ matched = TRUE;
+ ret &= func(ctx, cname);
+ sfree(cname);
+ }
+
+ if (!matched) {
+ /* Politely warn the user that nothing matched. */
+ printf("%s: nothing matched\n", filename);
+ }
+
+ sftp_finish_wildcard_matching(swcm);
+ } else {
+ cname = canonify(unwcfname);
+ if (!cname) {
+ printf("%s: canonify: %s\n", filename, fxp_error());
+ ret = 0;
+ }
+ ret = func(ctx, cname);
+ sfree(cname);
+ sfree(unwcfname);
+ }
+
+ return ret;
+}
+
+/*
+ * Handy helper function.
+ */
+int is_wildcard(char *name)
+{
+ char *unwcfname = snewn(strlen(name)+1, char);
+ int is_wc = !wc_unescape(unwcfname, name);
+ sfree(unwcfname);
+ return is_wc;
+}
+
+/* ----------------------------------------------------------------------
+ * Actual sftp commands.
+ */
+struct sftp_command {
+ char **words;
+ int nwords, wordssize;
+ int (*obey) (struct sftp_command *); /* returns <0 to quit */
+};
+
+int sftp_cmd_null(struct sftp_command *cmd)
+{
+ return 1; /* success */
+}
+
+int sftp_cmd_unknown(struct sftp_command *cmd)
+{
+ printf("psftp: unknown command \"%s\"\n", cmd->words[0]);
+ return 0; /* failure */
+}
+
+int sftp_cmd_quit(struct sftp_command *cmd)
+{
+ return -1;
+}
+
+int sftp_cmd_close(struct sftp_command *cmd)
+{
+ if (back == NULL) {
+ not_connected();
+ return 0;
+ }
+
+ if (back != NULL && back->connected(backhandle)) {
+ char ch;
+ back->special(backhandle, TS_EOF);
+ sent_eof = TRUE;
+ sftp_recvdata(&ch, 1);
+ }
+ do_sftp_cleanup();
+
+ return 0;
+}
+
+/*
+ * List a directory. If no arguments are given, list pwd; otherwise
+ * list the directory given in words[1].
+ */
+int sftp_cmd_ls(struct sftp_command *cmd)
+{
+ struct fxp_handle *dirh;
+ struct fxp_names *names;
+ struct fxp_name **ournames;
+ int nnames, namesize;
+ char *dir, *cdir, *unwcdir, *wildcard;
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+ int i;
+
+ if (back == NULL) {
+ not_connected();
+ return 0;
+ }
+
+ if (cmd->nwords < 2)
+ dir = ".";
+ else
+ dir = cmd->words[1];
+
+ unwcdir = snewn(1 + strlen(dir), char);
+ if (wc_unescape(unwcdir, dir)) {
+ dir = unwcdir;
+ wildcard = NULL;
+ } else {
+ char *tmpdir;
+ int len, check;
+
+ wildcard = stripslashes(dir, 0);
+ unwcdir = dupstr(dir);
+ len = wildcard - dir;
+ unwcdir[len] = '\0';
+ if (len > 0 && unwcdir[len-1] == '/')
+ unwcdir[len-1] = '\0';
+ tmpdir = snewn(1 + len, char);
+ check = wc_unescape(tmpdir, unwcdir);
+ sfree(tmpdir);
+ if (!check) {
+ printf("Multiple-level wildcards are not supported\n");
+ sfree(unwcdir);
+ return 0;
+ }
+ dir = unwcdir;
+ }
+
+ cdir = canonify(dir);
+ if (!cdir) {
+ printf("%s: canonify: %s\n", dir, fxp_error());
+ sfree(unwcdir);
+ return 0;
+ }
+
+ printf("Listing directory %s\n", cdir);
+
+ sftp_register(req = fxp_opendir_send(cdir));
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ dirh = fxp_opendir_recv(pktin, rreq);
+
+ if (dirh == NULL) {
+ printf("Unable to open %s: %s\n", dir, fxp_error());
+ } else {
+ nnames = namesize = 0;
+ ournames = NULL;
+
+ while (1) {
+
+ sftp_register(req = fxp_readdir_send(dirh));
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ names = fxp_readdir_recv(pktin, rreq);
+
+ if (names == NULL) {
+ if (fxp_error_type() == SSH_FX_EOF)
+ break;
+ printf("Reading directory %s: %s\n", dir, fxp_error());
+ break;
+ }
+ if (names->nnames == 0) {
+ fxp_free_names(names);
+ break;
+ }
+
+ if (nnames + names->nnames >= namesize) {
+ namesize += names->nnames + 128;
+ ournames = sresize(ournames, namesize, struct fxp_name *);
+ }
+
+ for (i = 0; i < names->nnames; i++)
+ if (!wildcard || wc_match(wildcard, names->names[i].filename))
+ ournames[nnames++] = fxp_dup_name(&names->names[i]);
+
+ fxp_free_names(names);
+ }
+ sftp_register(req = fxp_close_send(dirh));
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ fxp_close_recv(pktin, rreq);
+
+ /*
+ * Now we have our filenames. Sort them by actual file
+ * name, and then output the longname parts.
+ */
+ qsort(ournames, nnames, sizeof(*ournames), sftp_name_compare);
+
+ /*
+ * And print them.
+ */
+ for (i = 0; i < nnames; i++) {
+ printf("%s\n", ournames[i]->longname);
+ fxp_free_name(ournames[i]);
+ }
+ sfree(ournames);
+ }
+
+ sfree(cdir);
+ sfree(unwcdir);
+
+ return 1;
+}
+
+/*
+ * Change directories. We do this by canonifying the new name, then
+ * trying to OPENDIR it. Only if that succeeds do we set the new pwd.
+ */
+int sftp_cmd_cd(struct sftp_command *cmd)
+{
+ struct fxp_handle *dirh;
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+ char *dir;
+
+ if (back == NULL) {
+ not_connected();
+ return 0;
+ }
+
+ if (cmd->nwords < 2)
+ dir = dupstr(homedir);
+ else
+ dir = canonify(cmd->words[1]);
+
+ if (!dir) {
+ printf("%s: canonify: %s\n", dir, fxp_error());
+ return 0;
+ }
+
+ sftp_register(req = fxp_opendir_send(dir));
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ dirh = fxp_opendir_recv(pktin, rreq);
+
+ if (!dirh) {
+ printf("Directory %s: %s\n", dir, fxp_error());
+ sfree(dir);
+ return 0;
+ }
+
+ sftp_register(req = fxp_close_send(dirh));
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ fxp_close_recv(pktin, rreq);
+
+ sfree(pwd);
+ pwd = dir;
+ printf("Remote directory is now %s\n", pwd);
+
+ return 1;
+}
+
+/*
+ * Print current directory. Easy as pie.
+ */
+int sftp_cmd_pwd(struct sftp_command *cmd)
+{
+ if (back == NULL) {
+ not_connected();
+ return 0;
+ }
+
+ printf("Remote directory is %s\n", pwd);
+ return 1;
+}
+
+/*
+ * Get a file and save it at the local end. We have three very
+ * similar commands here. The basic one is `get'; `reget' differs
+ * in that it checks for the existence of the destination file and
+ * starts from where a previous aborted transfer left off; `mget'
+ * differs in that it interprets all its arguments as files to
+ * transfer (never as a different local name for a remote file) and
+ * can handle wildcards.
+ */
+int sftp_general_get(struct sftp_command *cmd, int restart, int multiple)
+{
+ char *fname, *unwcfname, *origfname, *origwfname, *outfname;
+ int i, ret;
+ int recurse = FALSE;
+
+ if (back == NULL) {
+ not_connected();
+ return 0;
+ }
+
+ i = 1;
+ while (i < cmd->nwords && cmd->words[i][0] == '-') {
+ if (!strcmp(cmd->words[i], "--")) {
+ /* finish processing options */
+ i++;
+ break;
+ } else if (!strcmp(cmd->words[i], "-r")) {
+ recurse = TRUE;
+ } else {
+ printf("%s: unrecognised option '%s'\n", cmd->words[0], cmd->words[i]);
+ return 0;
+ }
+ i++;
+ }
+
+ if (i >= cmd->nwords) {
+ printf("%s: expects a filename\n", cmd->words[0]);
+ return 0;
+ }
+
+ ret = 1;
+ do {
+ SftpWildcardMatcher *swcm;
+
+ origfname = cmd->words[i++];
+ unwcfname = snewn(strlen(origfname)+1, char);
+
+ if (multiple && !wc_unescape(unwcfname, origfname)) {
+ swcm = sftp_begin_wildcard_matching(origfname);
+ if (!swcm) {
+ sfree(unwcfname);
+ continue;
+ }
+ origwfname = sftp_wildcard_get_filename(swcm);
+ if (!origwfname) {
+ /* Politely warn the user that nothing matched. */
+ printf("%s: nothing matched\n", origfname);
+ sftp_finish_wildcard_matching(swcm);
+ sfree(unwcfname);
+ continue;
+ }
+ } else {
+ origwfname = origfname;
+ swcm = NULL;
+ }
+
+ while (origwfname) {
+ fname = canonify(origwfname);
+
+ if (!fname) {
+ printf("%s: canonify: %s\n", origwfname, fxp_error());
+ sfree(unwcfname);
+ return 0;
+ }
+
+ if (!multiple && i < cmd->nwords)
+ outfname = cmd->words[i++];
+ else
+ outfname = stripslashes(origwfname, 0);
+
+ ret = sftp_get_file(fname, outfname, recurse, restart);
+
+ sfree(fname);
+
+ if (swcm) {
+ sfree(origwfname);
+ origwfname = sftp_wildcard_get_filename(swcm);
+ } else {
+ origwfname = NULL;
+ }
+ }
+ sfree(unwcfname);
+ if (swcm)
+ sftp_finish_wildcard_matching(swcm);
+ if (!ret)
+ return ret;
+
+ } while (multiple && i < cmd->nwords);
+
+ return ret;
+}
+int sftp_cmd_get(struct sftp_command *cmd)
+{
+ return sftp_general_get(cmd, 0, 0);
+}
+int sftp_cmd_mget(struct sftp_command *cmd)
+{
+ return sftp_general_get(cmd, 0, 1);
+}
+int sftp_cmd_reget(struct sftp_command *cmd)
+{
+ return sftp_general_get(cmd, 1, 0);
+}
+
+/*
+ * Send a file and store it at the remote end. We have three very
+ * similar commands here. The basic one is `put'; `reput' differs
+ * in that it checks for the existence of the destination file and
+ * starts from where a previous aborted transfer left off; `mput'
+ * differs in that it interprets all its arguments as files to
+ * transfer (never as a different remote name for a local file) and
+ * can handle wildcards.
+ */
+int sftp_general_put(struct sftp_command *cmd, int restart, int multiple)
+{
+ char *fname, *wfname, *origoutfname, *outfname;
+ int i, ret;
+ int recurse = FALSE;
+
+ if (back == NULL) {
+ not_connected();
+ return 0;
+ }
+
+ i = 1;
+ while (i < cmd->nwords && cmd->words[i][0] == '-') {
+ if (!strcmp(cmd->words[i], "--")) {
+ /* finish processing options */
+ i++;
+ break;
+ } else if (!strcmp(cmd->words[i], "-r")) {
+ recurse = TRUE;
+ } else {
+ printf("%s: unrecognised option '%s'\n", cmd->words[0], cmd->words[i]);
+ return 0;
+ }
+ i++;
+ }
+
+ if (i >= cmd->nwords) {
+ printf("%s: expects a filename\n", cmd->words[0]);
+ return 0;
+ }
+
+ ret = 1;
+ do {
+ WildcardMatcher *wcm;
+ fname = cmd->words[i++];
+
+ if (multiple && test_wildcard(fname, FALSE) == WCTYPE_WILDCARD) {
+ wcm = begin_wildcard_matching(fname);
+ wfname = wildcard_get_filename(wcm);
+ if (!wfname) {
+ /* Politely warn the user that nothing matched. */
+ printf("%s: nothing matched\n", fname);
+ finish_wildcard_matching(wcm);
+ continue;
+ }
+ } else {
+ wfname = fname;
+ wcm = NULL;
+ }
+
+ while (wfname) {
+ if (!multiple && i < cmd->nwords)
+ origoutfname = cmd->words[i++];
+ else
+ origoutfname = stripslashes(wfname, 1);
+
+ outfname = canonify(origoutfname);
+ if (!outfname) {
+ printf("%s: canonify: %s\n", origoutfname, fxp_error());
+ if (wcm) {
+ sfree(wfname);
+ finish_wildcard_matching(wcm);
+ }
+ return 0;
+ }
+ ret = sftp_put_file(wfname, outfname, recurse, restart);
+ sfree(outfname);
+
+ if (wcm) {
+ sfree(wfname);
+ wfname = wildcard_get_filename(wcm);
+ } else {
+ wfname = NULL;
+ }
+ }
+
+ if (wcm)
+ finish_wildcard_matching(wcm);
+
+ if (!ret)
+ return ret;
+
+ } while (multiple && i < cmd->nwords);
+
+ return ret;
+}
+int sftp_cmd_put(struct sftp_command *cmd)
+{
+ return sftp_general_put(cmd, 0, 0);
+}
+int sftp_cmd_mput(struct sftp_command *cmd)
+{
+ return sftp_general_put(cmd, 0, 1);
+}
+int sftp_cmd_reput(struct sftp_command *cmd)
+{
+ return sftp_general_put(cmd, 1, 0);
+}
+
+int sftp_cmd_mkdir(struct sftp_command *cmd)
+{
+ char *dir;
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+ int result;
+ int i, ret;
+
+ if (back == NULL) {
+ not_connected();