+
+ 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;
+ 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: canonify: %s\n", dir, fxp_error());
+ return 0;
+ }
+
+ req = fxp_mkdir_send(dir);
+ pktin = sftp_wait_for_reply(req);
+ result = fxp_mkdir_recv(pktin, req);
+
+ 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;
+ int result;
+
+ req = fxp_rmdir_send(dir);
+ pktin = sftp_wait_for_reply(req);
+ result = fxp_rmdir_recv(pktin, req);
+
+ 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;
+ int result;
+
+ req = fxp_remove_send(fname);
+ pktin = sftp_wait_for_reply(req);
+ result = fxp_remove_recv(pktin, req);
+
+ 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;
+ struct fxp_attrs attrs;
+ int result;
+
+ req = fxp_stat_send(dstfname);
+ pktin = sftp_wait_for_reply(req);
+ result = fxp_stat_recv(pktin, req, &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;
+ 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: canonify: %s\n", newname, fxp_error());
+ sfree(newname);
+ return 0;
+ }
+ sfree(newname);
+
+ finalfname = newcanon;
+ } else {
+ finalfname = ctx->dstfname;
+ }
+
+ req = fxp_rename_send(srcfname, finalfname);
+ pktin = sftp_wait_for_reply(req);
+ result = fxp_rename_recv(pktin, req);
+
+ 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: canonify: %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;
+ int result;
+ unsigned oldperms, newperms;
+ struct sftp_context_chmod *ctx = (struct sftp_context_chmod *)vctx;
+
+ req = fxp_stat_send(fname);
+ pktin = sftp_wait_for_reply(req);
+ result = fxp_stat_recv(pktin, req, &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! */
+
+ req = fxp_setstat_send(fname, attrs);
+ pktin = sftp_wait_for_reply(req);
+ result = fxp_setstat_recv(pktin, req);
+
+ 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;