X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/putty/blobdiff_plain/055817455466c8eb60392f30bb7c689763962e17..615cdc1fec47b39c0b272e564e5199d0060850d0:/psftp.c diff --git a/psftp.c b/psftp.c index ab076795..272079e3 100644 --- a/psftp.c +++ b/psftp.c @@ -189,6 +189,11 @@ static int bare_name_compare(const void *av, const void *bv) return strcmp(*a, *b); } +static void not_connected(void) +{ + printf("psftp: not connected to a host; use \"open host.name\"\n"); +} + /* ---------------------------------------------------------------------- * The meat of the `get' and `put' commands. */ @@ -817,7 +822,9 @@ char *sftp_wildcard_get_filename(SftpWildcardMatcher *swcm) * We have a working filename. Return it. */ return dupprintf("%s%s%s", swcm->prefix, - swcm->prefix[strlen(swcm->prefix)-1]=='/' ? "" : "/", + (!swcm->prefix[0] || + swcm->prefix[strlen(swcm->prefix)-1]=='/' ? + "" : "/"), name->filename); } } @@ -841,6 +848,71 @@ void sftp_finish_wildcard_matching(SftpWildcardMatcher *swcm) 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. */ @@ -869,7 +941,7 @@ int sftp_cmd_quit(struct sftp_command *cmd) int sftp_cmd_close(struct sftp_command *cmd) { if (back == NULL) { - printf("psftp: not connected to a host; use \"open host.name\"\n"); + not_connected(); return 0; } @@ -899,7 +971,7 @@ int sftp_cmd_ls(struct sftp_command *cmd) int i; if (back == NULL) { - printf("psftp: not connected to a host; use \"open host.name\"\n"); + not_connected(); return 0; } @@ -1021,7 +1093,7 @@ int sftp_cmd_cd(struct sftp_command *cmd) char *dir; if (back == NULL) { - printf("psftp: not connected to a host; use \"open host.name\"\n"); + not_connected(); return 0; } @@ -1064,7 +1136,7 @@ int sftp_cmd_cd(struct sftp_command *cmd) int sftp_cmd_pwd(struct sftp_command *cmd) { if (back == NULL) { - printf("psftp: not connected to a host; use \"open host.name\"\n"); + not_connected(); return 0; } @@ -1088,7 +1160,7 @@ int sftp_general_get(struct sftp_command *cmd, int restart, int multiple) int recurse = FALSE; if (back == NULL) { - printf("psftp: not connected to a host; use \"open host.name\"\n"); + not_connected(); return 0; } @@ -1202,7 +1274,7 @@ int sftp_general_put(struct sftp_command *cmd, int restart, int multiple) int recurse = FALSE; if (back == NULL) { - printf("psftp: not connected to a host; use \"open host.name\"\n"); + not_connected(); return 0; } @@ -1300,9 +1372,10 @@ int sftp_cmd_mkdir(struct sftp_command *cmd) struct sftp_packet *pktin; struct sftp_request *req, *rreq; int result; + int i, ret; if (back == NULL) { - printf("psftp: not connected to a host; use \"open host.name\"\n"); + not_connected(); return 0; } @@ -1311,36 +1384,59 @@ int sftp_cmd_mkdir(struct sftp_command *cmd) return 0; } - dir = canonify(cmd->words[1]); - if (!dir) { - printf("%s: %s\n", dir, fxp_error()); - 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()); + sfree(dir); + ret = 0; + } else + printf("mkdir %s: OK\n", dir); + + sfree(dir); } - sftp_register(req = fxp_mkdir_send(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_mkdir_recv(pktin, rreq); + result = fxp_rmdir_recv(pktin, rreq); if (!result) { - printf("mkdir %s: %s\n", dir, fxp_error()); - sfree(dir); + printf("rmdir %s: %s\n", dir, fxp_error()); return 0; } - sfree(dir); + printf("rmdir %s: OK\n", dir); + return 1; } int sftp_cmd_rmdir(struct sftp_command *cmd) { - char *dir; - struct sftp_packet *pktin; - struct sftp_request *req, *rreq; - int result; + int i, ret; if (back == NULL) { - printf("psftp: not connected to a host; use \"open host.name\"\n"); + not_connected(); return 0; } @@ -1349,36 +1445,40 @@ int sftp_cmd_rmdir(struct sftp_command *cmd) return 0; } - dir = canonify(cmd->words[1]); - if (!dir) { - printf("%s: %s\n", dir, fxp_error()); - return 0; - } + ret = 1; + for (i = 1; i < cmd->nwords; i++) + ret &= wildcard_iterate(cmd->words[i], sftp_action_rmdir, NULL); - sftp_register(req = fxp_rmdir_send(dir)); + 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_rmdir_recv(pktin, rreq); + result = fxp_remove_recv(pktin, rreq); if (!result) { - printf("rmdir %s: %s\n", dir, fxp_error()); - sfree(dir); + printf("rm %s: %s\n", fname, fxp_error()); return 0; } - sfree(dir); + printf("rm %s: OK\n", fname); + return 1; } int sftp_cmd_rm(struct sftp_command *cmd) { - char *fname; - struct sftp_packet *pktin; - struct sftp_request *req, *rreq; - int result; + int i, ret; if (back == NULL) { - printf("psftp: not connected to a host; use \"open host.name\"\n"); + not_connected(); return 0; } @@ -1387,36 +1487,93 @@ int sftp_cmd_rm(struct sftp_command *cmd) return 0; } - fname = canonify(cmd->words[1]); - if (!fname) { - printf("%s: %s\n", fname, fxp_error()); - 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_remove_send(fname)); + sftp_register(req = fxp_rename_send(srcfname, finalfname)); rreq = sftp_find_request(pktin = sftp_recv()); assert(rreq == req); - result = fxp_remove_recv(pktin, rreq); + result = fxp_rename_recv(pktin, rreq); - if (!result) { - printf("rm %s: %s\n", fname, fxp_error()); - sfree(fname); - return 0; + 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(fname); - return 1; + sfree(newcanon); + return ret; } int sftp_cmd_mv(struct sftp_command *cmd) { - char *srcfname, *dstfname; - struct sftp_packet *pktin; - struct sftp_request *req, *rreq; - int result; + struct sftp_context_mv actx, *ctx = &actx; + int i, ret; if (back == NULL) { - printf("psftp: not connected to a host; use \"open host.name\"\n"); + not_connected(); return 0; } @@ -1424,86 +1581,93 @@ int sftp_cmd_mv(struct sftp_command *cmd) printf("mv: expects two filenames\n"); return 0; } - srcfname = canonify(cmd->words[1]); - if (!srcfname) { - printf("%s: %s\n", srcfname, fxp_error()); + + ctx->dstfname = canonify(cmd->words[cmd->nwords-1]); + if (!ctx->dstfname) { + printf("%s: %s\n", ctx->dstfname, fxp_error()); return 0; } - dstfname = canonify(cmd->words[2]); - if (!dstfname) { - printf("%s: %s\n", dstfname, fxp_error()); + /* + * 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; } - sftp_register(req = fxp_rename_send(srcfname, dstfname)); + /* + * 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_rename_recv(pktin, rreq); + result = fxp_stat_recv(pktin, rreq, &attrs); - if (!result) { - char const *error = fxp_error(); - struct fxp_attrs 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; + } - /* - * The move might have failed because dstfname pointed at a - * directory. We check this possibility now: if dstfname - * _is_ a directory, we re-attempt the move by appending - * the basename of srcfname to dstfname. - */ - sftp_register(req = fxp_stat_send(dstfname)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - result = fxp_stat_recv(pktin, rreq, &attrs); + 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 (result && - (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) && - (attrs.permissions & 0040000)) { - char *p; - char *newname, *newcanon; - printf("(destination %s is a directory)\n", dstfname); - p = srcfname + strlen(srcfname); - while (p > srcfname && p[-1] != '/') p--; - newname = dupcat(dstfname, "/", p, NULL); - newcanon = canonify(newname); - sfree(newname); - if (newcanon) { - sfree(dstfname); - dstfname = newcanon; + if (oldperms == newperms) + return 1; /* no need to do anything! */ - sftp_register(req = fxp_rename_send(srcfname, dstfname)); - rreq = sftp_find_request(pktin = sftp_recv()); - assert(rreq == req); - result = fxp_rename_recv(pktin, rreq); + sftp_register(req = fxp_setstat_send(fname, attrs)); + rreq = sftp_find_request(pktin = sftp_recv()); + assert(rreq == req); + result = fxp_setstat_recv(pktin, rreq); - error = result ? NULL : fxp_error(); - } - } - if (error) { - printf("mv %s %s: %s\n", srcfname, dstfname, error); - sfree(srcfname); - sfree(dstfname); - return 0; - } + if (!result) { + printf("set attrs for %s: %s\n", fname, fxp_error()); + return 0; } - printf("%s -> %s\n", srcfname, dstfname); - sfree(srcfname); - sfree(dstfname); + printf("%s: %04o -> %04o\n", fname, oldperms, newperms); + return 1; } int sftp_cmd_chmod(struct sftp_command *cmd) { - char *fname, *mode; - int result; - struct fxp_attrs attrs; - unsigned attrs_clr, attrs_xor, oldperms, newperms; - struct sftp_packet *pktin; - struct sftp_request *req, *rreq; + char *mode; + int i, ret; + struct sftp_context_chmod actx, *ctx = &actx; if (back == NULL) { - printf("psftp: not connected to a host; use \"open host.name\"\n"); + not_connected(); return 0; } @@ -1523,7 +1687,7 @@ int sftp_cmd_chmod(struct sftp_command *cmd) * Additionally, the s attribute may not be specified for any * [ugoa] specifications other than exactly u or exactly g. */ - attrs_clr = attrs_xor = 0; + ctx->attrs_clr = ctx->attrs_xor = 0; mode = cmd->words[1]; if (mode[0] >= '0' && mode[0] <= '9') { if (mode[strspn(mode, "01234567")]) { @@ -1531,9 +1695,9 @@ int sftp_cmd_chmod(struct sftp_command *cmd) " contain digits 0-7 only\n"); return 0; } - attrs_clr = 07777; - sscanf(mode, "%o", &attrs_xor); - attrs_xor &= attrs_clr; + ctx->attrs_clr = 07777; + sscanf(mode, "%o", &ctx->attrs_xor); + ctx->attrs_xor &= ctx->attrs_clr; } else { while (*mode) { char *modebegin = mode; @@ -1601,61 +1765,27 @@ int sftp_cmd_chmod(struct sftp_command *cmd) perms &= subset; switch (action) { case '+': - attrs_clr |= perms; - attrs_xor |= perms; + ctx->attrs_clr |= perms; + ctx->attrs_xor |= perms; break; case '-': - attrs_clr |= perms; - attrs_xor &= ~perms; + ctx->attrs_clr |= perms; + ctx->attrs_xor &= ~perms; break; case '=': - attrs_clr |= subset; - attrs_xor |= perms; + ctx->attrs_clr |= subset; + ctx->attrs_xor |= perms; break; } if (*mode) mode++; /* eat comma */ } } - fname = canonify(cmd->words[2]); - if (!fname) { - printf("%s: %s\n", fname, fxp_error()); - return 0; - } - - 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()); - sfree(fname); - return 0; - } - - attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS; /* perms _only_ */ - oldperms = attrs.permissions & 07777; - attrs.permissions &= ~attrs_clr; - attrs.permissions ^= attrs_xor; - newperms = attrs.permissions & 07777; - - 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()); - sfree(fname); - return 0; - } - - printf("%s: %04o -> %04o\n", fname, oldperms, newperms); + ret = 1; + for (i = 2; i < cmd->nwords; i++) + ret &= wildcard_iterate(cmd->words[i], sftp_action_chmod, ctx); - sfree(fname); - return 1; + return ret; } static int sftp_cmd_open(struct sftp_command *cmd) @@ -1771,7 +1901,7 @@ static struct sftp_cmd_lookup { }, { "cd", TRUE, "change your remote working directory", - " [ ]\n" + " [ ]\n" " Change the remote working directory for your SFTP session.\n" " If a new working directory is not supplied, you will be\n" " returned to your home directory.\n", @@ -1779,10 +1909,11 @@ static struct sftp_cmd_lookup { }, { "chmod", TRUE, "change file permissions and modes", - " ( | ) \n" - " Change the file permissions on a file or directory.\n" - " can be any octal Unix permission specifier.\n" - " Alternatively, can include:\n" + " [ ... ]\n" + " Change the file permissions on one or more remote files or\n" + " directories.\n" + " can be any octal Unix permission specifier.\n" + " Alternatively, can include the following modifiers:\n" " u+r make file readable by owning user\n" " u+w make file writable by owning user\n" " u+x make file executable by owning user\n" @@ -1813,16 +1944,16 @@ static struct sftp_cmd_lookup { sftp_cmd_close }, { - "del", TRUE, "delete a file", - " \n" - " Delete a file.\n", + "del", TRUE, "delete files on the remote server", + " [ ... ]\n" + " Delete a file or files from the server.\n", sftp_cmd_rm }, { "delete", FALSE, "del", NULL, sftp_cmd_rm }, { - "dir", TRUE, "list contents of a remote directory", + "dir", TRUE, "list remote files", " [ ]/[ ]\n" " List the contents of a specified directory on the server.\n" " If is not given, the current working directory\n" @@ -1879,9 +2010,9 @@ static struct sftp_cmd_lookup { sftp_cmd_mget }, { - "mkdir", TRUE, "create a directory on the remote server", - " \n" - " Creates a directory with the given name on the server.\n", + "mkdir", TRUE, "create directories on the remote server", + " [ ... ]\n" + " Creates directories with the given names on the server.\n", sftp_cmd_mkdir }, { @@ -1894,18 +2025,22 @@ static struct sftp_cmd_lookup { sftp_cmd_mput }, { - "mv", TRUE, "move or rename a file on the remote server", - " \n" - " Moves or renames the file on the server,\n" - " so that it is accessible under the name .\n", + "mv", TRUE, "move or rename file(s) on the remote server", + " [ ... ] \n" + " Moves or renames (s) on the server to ,\n" + " also on the server.\n" + " If specifies an existing directory, then \n" + " may be a wildcard, and multiple s may be given; all\n" + " source files are moved into .\n" + " Otherwise, must specify a single file, which is moved\n" + " or renamed so that it is accessible under the name .\n", sftp_cmd_mv }, { "open", TRUE, "connect to a host", " [@] []\n" " Establishes an SFTP connection to a given host. Only usable\n" - " when you did not already specify a host name on the command\n" - " line.\n", + " when you are not already connected to a server.\n", sftp_cmd_open }, { @@ -1928,7 +2063,7 @@ static struct sftp_cmd_lookup { sftp_cmd_quit }, { - "reget", TRUE, "continue downloading a file", + "reget", TRUE, "continue downloading files", " [ -r ] [ -- ] [ ]\n" " Works exactly like the \"get\" command, but the local file\n" " must already exist. The download will begin at the end of the\n" @@ -1945,7 +2080,7 @@ static struct sftp_cmd_lookup { sftp_cmd_mv }, { - "reput", TRUE, "continue uploading a file", + "reput", TRUE, "continue uploading files", " [ -r ] [ -- ] [ ]\n" " Works exactly like the \"put\" command, but the remote file\n" " must already exist. The upload will begin at the end of the\n" @@ -1958,10 +2093,11 @@ static struct sftp_cmd_lookup { sftp_cmd_rm }, { - "rmdir", TRUE, "remove a directory on the remote server", - " \n" + "rmdir", TRUE, "remove directories on the remote server", + " [ ... ]\n" " Removes the directory with the given name on the server.\n" - " The directory will not be removed unless it is empty.\n", + " The directory will not be removed unless it is empty.\n" + " Wildcards may be used to specify multiple directories.\n", sftp_cmd_rmdir } }; @@ -2607,7 +2743,7 @@ static int psftp_connect(char *userhost, char *user, int portnumber) cfg.nopty = TRUE; /* - * Set up fallback option, for SSH1 servers or servers with the + * Set up fallback option, for SSH-1 servers or servers with the * sftp subsystem not enabled but the server binary installed * in the usual place. We only support fallback on Unix * systems, and we use a kludgy piece of shellery which should