+ 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: %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: %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->socket(backhandle) != NULL) {
+ char ch;
+ back->special(backhandle, TS_EOF);
+ 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: %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: %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: %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: %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();
+ return 0;
+ }
+
+ if (cmd->nwords < 2) {
+ printf("mkdir: expects a directory\n");
+ return 0;
+ }
+
+ ret = 1;
+ for (i = 1; i < cmd->nwords; i++) {
+ dir = canonify(cmd->words[i]);
+ if (!dir) {
+ printf("%s: %s\n", dir, fxp_error());
+ return 0;
+ }
+
+ sftp_register(req = fxp_mkdir_send(dir));
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ result = fxp_mkdir_recv(pktin, rreq);
+
+ if (!result) {
+ printf("mkdir %s: %s\n", dir, fxp_error());
+ ret = 0;
+ } else
+ printf("mkdir %s: OK\n", dir);
+
+ sfree(dir);
+ }
+
+ return ret;
+}
+
+static int sftp_action_rmdir(void *vctx, char *dir)
+{
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+ int result;
+
+ sftp_register(req = fxp_rmdir_send(dir));
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ result = fxp_rmdir_recv(pktin, rreq);
+
+ if (!result) {
+ printf("rmdir %s: %s\n", dir, fxp_error());
+ return 0;
+ }
+
+ printf("rmdir %s: OK\n", dir);
+
+ return 1;
+}
+
+int sftp_cmd_rmdir(struct sftp_command *cmd)
+{
+ int i, ret;
+
+ if (back == NULL) {
+ not_connected();
+ return 0;
+ }
+
+ if (cmd->nwords < 2) {
+ printf("rmdir: expects a directory\n");
+ return 0;
+ }
+
+ ret = 1;
+ for (i = 1; i < cmd->nwords; i++)
+ ret &= wildcard_iterate(cmd->words[i], sftp_action_rmdir, NULL);
+
+ return ret;
+}
+
+static int sftp_action_rm(void *vctx, char *fname)
+{
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+ int result;
+
+ sftp_register(req = fxp_remove_send(fname));
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ result = fxp_remove_recv(pktin, rreq);
+
+ if (!result) {
+ printf("rm %s: %s\n", fname, fxp_error());
+ return 0;
+ }
+
+ printf("rm %s: OK\n", fname);
+
+ return 1;
+}
+
+int sftp_cmd_rm(struct sftp_command *cmd)
+{
+ int i, ret;
+
+ if (back == NULL) {
+ not_connected();
+ return 0;
+ }
+
+ if (cmd->nwords < 2) {
+ printf("rm: expects a filename\n");
+ return 0;
+ }
+
+ ret = 1;
+ for (i = 1; i < cmd->nwords; i++)
+ ret &= wildcard_iterate(cmd->words[i], sftp_action_rm, NULL);
+
+ return ret;
+}
+
+static int check_is_dir(char *dstfname)
+{
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+ struct fxp_attrs attrs;
+ int result;
+
+ sftp_register(req = fxp_stat_send(dstfname));
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ result = fxp_stat_recv(pktin, rreq, &attrs);
+
+ if (result &&
+ (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) &&
+ (attrs.permissions & 0040000))
+ return TRUE;
+ else
+ return FALSE;
+}
+
+struct sftp_context_mv {
+ char *dstfname;
+ int dest_is_dir;
+};
+
+static int sftp_action_mv(void *vctx, char *srcfname)
+{
+ struct sftp_context_mv *ctx = (struct sftp_context_mv *)vctx;
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+ const char *error;
+ char *finalfname, *newcanon = NULL;
+ int ret, result;
+
+ if (ctx->dest_is_dir) {
+ char *p;
+ char *newname;
+
+ p = srcfname + strlen(srcfname);
+ while (p > srcfname && p[-1] != '/') p--;
+ newname = dupcat(ctx->dstfname, "/", p, NULL);
+ newcanon = canonify(newname);
+ if (!newcanon) {
+ printf("%s: %s\n", newname, fxp_error());
+ sfree(newname);
+ return 0;
+ }
+ sfree(newname);
+
+ finalfname = newcanon;
+ } else {
+ finalfname = ctx->dstfname;
+ }
+
+ sftp_register(req = fxp_rename_send(srcfname, finalfname));
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ result = fxp_rename_recv(pktin, rreq);
+
+ error = result ? NULL : fxp_error();
+
+ if (error) {
+ printf("mv %s %s: %s\n", srcfname, finalfname, error);
+ ret = 0;
+ } else {
+ printf("%s -> %s\n", srcfname, finalfname);
+ ret = 1;
+ }
+
+ sfree(newcanon);
+ return ret;
+}
+
+int sftp_cmd_mv(struct sftp_command *cmd)
+{
+ struct sftp_context_mv actx, *ctx = &actx;
+ int i, ret;
+
+ if (back == NULL) {
+ not_connected();
+ return 0;
+ }
+
+ if (cmd->nwords < 3) {
+ printf("mv: expects two filenames\n");
+ return 0;
+ }
+
+ ctx->dstfname = canonify(cmd->words[cmd->nwords-1]);
+ if (!ctx->dstfname) {
+ printf("%s: %s\n", ctx->dstfname, fxp_error());
+ return 0;
+ }
+
+ /*
+ * If there's more than one source argument, or one source
+ * argument which is a wildcard, we _require_ that the
+ * destination is a directory.
+ */
+ ctx->dest_is_dir = check_is_dir(ctx->dstfname);
+ if ((cmd->nwords > 3 || is_wildcard(cmd->words[1])) && !ctx->dest_is_dir) {
+ printf("mv: multiple or wildcard arguments require the destination"
+ " to be a directory\n");
+ sfree(ctx->dstfname);
+ return 0;
+ }
+
+ /*
+ * Now iterate over the source arguments.
+ */
+ ret = 1;
+ for (i = 1; i < cmd->nwords-1; i++)
+ ret &= wildcard_iterate(cmd->words[i], sftp_action_mv, ctx);
+
+ sfree(ctx->dstfname);
+ return ret;
+}
+
+struct sftp_context_chmod {
+ unsigned attrs_clr, attrs_xor;
+};
+
+static int sftp_action_chmod(void *vctx, char *fname)
+{
+ struct fxp_attrs attrs;
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+ int result;
+ unsigned oldperms, newperms;
+ struct sftp_context_chmod *ctx = (struct sftp_context_chmod *)vctx;
+
+ sftp_register(req = fxp_stat_send(fname));
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ result = fxp_stat_recv(pktin, rreq, &attrs);
+
+ if (!result || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) {
+ printf("get attrs for %s: %s\n", fname,
+ result ? "file permissions not provided" : fxp_error());
+ return 0;
+ }
+
+ attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS; /* perms _only_ */
+ oldperms = attrs.permissions & 07777;
+ attrs.permissions &= ~ctx->attrs_clr;
+ attrs.permissions ^= ctx->attrs_xor;
+ newperms = attrs.permissions & 07777;
+
+ if (oldperms == newperms)
+ return 1; /* no need to do anything! */
+
+ sftp_register(req = fxp_setstat_send(fname, attrs));
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ result = fxp_setstat_recv(pktin, rreq);
+
+ if (!result) {
+ printf("set attrs for %s: %s\n", fname, fxp_error());
+ return 0;
+ }
+
+ printf("%s: %04o -> %04o\n", fname, oldperms, newperms);
+
+ return 1;
+}
+
+int sftp_cmd_chmod(struct sftp_command *cmd)
+{
+ char *mode;
+ int i, ret;
+ struct sftp_context_chmod actx, *ctx = &actx;
+
+ if (back == NULL) {
+ not_connected();
+ return 0;
+ }
+
+ if (cmd->nwords < 3) {
+ printf("chmod: expects a mode specifier and a filename\n");
+ return 0;
+ }
+
+ /*
+ * Attempt to parse the mode specifier in cmd->words[1]. We
+ * don't support the full horror of Unix chmod; instead we
+ * support a much simpler syntax in which the user can either
+ * specify an octal number, or a comma-separated sequence of
+ * [ugoa]*[-+=][rwxst]+. (The initial [ugoa] sequence may
+ * _only_ be omitted if the only attribute mentioned is t,
+ * since all others require a user/group/other specification.
+ * Additionally, the s attribute may not be specified for any
+ * [ugoa] specifications other than exactly u or exactly g.
+ */
+ ctx->attrs_clr = ctx->attrs_xor = 0;
+ mode = cmd->words[1];
+ if (mode[0] >= '0' && mode[0] <= '9') {
+ if (mode[strspn(mode, "01234567")]) {
+ printf("chmod: numeric file modes should"
+ " contain digits 0-7 only\n");
+ return 0;
+ }
+ ctx->attrs_clr = 07777;
+ sscanf(mode, "%o", &ctx->attrs_xor);
+ ctx->attrs_xor &= ctx->attrs_clr;
+ } else {
+ while (*mode) {
+ char *modebegin = mode;
+ unsigned subset, perms;
+ int action;
+
+ subset = 0;
+ while (*mode && *mode != ',' &&
+ *mode != '+' && *mode != '-' && *mode != '=') {
+ switch (*mode) {
+ case 'u': subset |= 04700; break; /* setuid, user perms */
+ case 'g': subset |= 02070; break; /* setgid, group perms */
+ case 'o': subset |= 00007; break; /* just other perms */
+ case 'a': subset |= 06777; break; /* all of the above */
+ default:
+ printf("chmod: file mode '%.*s' contains unrecognised"
+ " user/group/other specifier '%c'\n",
+ (int)strcspn(modebegin, ","), modebegin, *mode);
+ return 0;
+ }
+ mode++;
+ }
+ if (!*mode || *mode == ',') {
+ printf("chmod: file mode '%.*s' is incomplete\n",
+ (int)strcspn(modebegin, ","), modebegin);
+ return 0;
+ }
+ action = *mode++;
+ if (!*mode || *mode == ',') {
+ printf("chmod: file mode '%.*s' is incomplete\n",
+ (int)strcspn(modebegin, ","), modebegin);
+ return 0;
+ }
+ perms = 0;
+ while (*mode && *mode != ',') {
+ switch (*mode) {
+ case 'r': perms |= 00444; break;
+ case 'w': perms |= 00222; break;
+ case 'x': perms |= 00111; break;
+ case 't': perms |= 01000; subset |= 01000; break;
+ case 's':
+ if ((subset & 06777) != 04700 &&
+ (subset & 06777) != 02070) {
+ printf("chmod: file mode '%.*s': set[ug]id bit should"
+ " be used with exactly one of u or g only\n",
+ (int)strcspn(modebegin, ","), modebegin);
+ return 0;
+ }
+ perms |= 06000;
+ break;
+ default:
+ printf("chmod: file mode '%.*s' contains unrecognised"
+ " permission specifier '%c'\n",
+ (int)strcspn(modebegin, ","), modebegin, *mode);
+ return 0;
+ }
+ mode++;
+ }
+ if (!(subset & 06777) && (perms &~ subset)) {
+ printf("chmod: file mode '%.*s' contains no user/group/other"
+ " specifier and permissions other than 't' \n",
+ (int)strcspn(modebegin, ","), modebegin);
+ return 0;
+ }
+ perms &= subset;
+ switch (action) {
+ case '+':
+ ctx->attrs_clr |= perms;
+ ctx->attrs_xor |= perms;
+ break;
+ case '-':
+ ctx->attrs_clr |= perms;
+ ctx->attrs_xor &= ~perms;
+ break;
+ case '=':
+ ctx->attrs_clr |= subset;
+ ctx->attrs_xor |= perms;
+ break;
+ }
+ if (*mode) mode++; /* eat comma */
+ }
+ }
+
+ ret = 1;
+ for (i = 2; i < cmd->nwords; i++)
+ ret &= wildcard_iterate(cmd->words[i], sftp_action_chmod, ctx);
+
+ return ret;
+}
+
+static int sftp_cmd_open(struct sftp_command *cmd)
+{
+ int portnumber;
+
+ if (back != NULL) {
+ printf("psftp: already connected\n");
+ return 0;
+ }
+
+ if (cmd->nwords < 2) {
+ printf("open: expects a host name\n");
+ return 0;
+ }
+
+ if (cmd->nwords > 2) {
+ portnumber = atoi(cmd->words[2]);
+ if (portnumber == 0) {
+ printf("open: invalid port number\n");
+ return 0;
+ }
+ } else
+ portnumber = 0;
+
+ if (psftp_connect(cmd->words[1], NULL, portnumber)) {
+ back = NULL; /* connection is already closed */
+ return -1; /* this is fatal */
+ }
+ do_sftp_init();
+ return 1;
+}
+
+static int sftp_cmd_lcd(struct sftp_command *cmd)
+{
+ char *currdir, *errmsg;
+
+ if (cmd->nwords < 2) {
+ printf("lcd: expects a local directory name\n");
+ return 0;
+ }
+
+ errmsg = psftp_lcd(cmd->words[1]);
+ if (errmsg) {
+ printf("lcd: unable to change directory: %s\n", errmsg);
+ sfree(errmsg);
+ return 0;
+ }
+
+ currdir = psftp_getcwd();
+ printf("New local directory is %s\n", currdir);
+ sfree(currdir);
+
+ return 1;
+}
+
+static int sftp_cmd_lpwd(struct sftp_command *cmd)
+{
+ char *currdir;
+
+ currdir = psftp_getcwd();
+ printf("Current local directory is %s\n", currdir);
+ sfree(currdir);
+
+ return 1;
+}
+
+static int sftp_cmd_pling(struct sftp_command *cmd)
+{
+ int exitcode;
+
+ exitcode = system(cmd->words[1]);
+ return (exitcode == 0);
+}
+
+static int sftp_cmd_help(struct sftp_command *cmd);
+
+static struct sftp_cmd_lookup {
+ char *name;