X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/putty/blobdiff_plain/9954aaa37368a233dc614d610ca68c2ce5a7e8cd..4ed34d251770cb8dd7f13217976d1dec9452b5e6:/psftp.c diff --git a/psftp.c b/psftp.c index 0ab453c0..aa3c08e1 100644 --- a/psftp.c +++ b/psftp.c @@ -8,6 +8,7 @@ #include #include #include +#include #define PUTTY_DO_GLOBALS #include "putty.h" @@ -16,52 +17,13 @@ #include "sftp.h" #include "int64.h" -/* ---------------------------------------------------------------------- - * String handling routines. +/* + * Since SFTP is a request-response oriented protocol, it requires + * no buffer management: when we send data, we stop and wait for an + * acknowledgement _anyway_, and so we can't possibly overfill our + * send buffer. */ -char *dupstr(char *s) -{ - int len = strlen(s); - char *p = smalloc(len + 1); - strcpy(p, s); - return p; -} - -/* Allocate the concatenation of N strings. Terminate arg list with NULL. */ -char *dupcat(char *s1, ...) -{ - int len; - char *p, *q, *sn; - va_list ap; - - len = strlen(s1); - va_start(ap, s1); - while (1) { - sn = va_arg(ap, char *); - if (!sn) - break; - len += strlen(sn); - } - va_end(ap); - - p = smalloc(len + 1); - strcpy(p, s1); - q = p + strlen(p); - - va_start(ap, s1); - while (1) { - sn = va_arg(ap, char *); - if (!sn) - break; - strcpy(q, sn); - q += strlen(q); - } - va_end(ap); - - return p; -} - /* ---------------------------------------------------------------------- * sftp client state. */ @@ -168,6 +130,30 @@ char *canonify(char *name) } } +/* + * Return a pointer to the portion of str that comes after the last + * slash (or backslash or colon, if `local' is TRUE). + */ +static char *stripslashes(char *str, int local) +{ + char *p; + + if (local) { + p = strchr(str, ':'); + if (p) str = p+1; + } + + p = strrchr(str, '/'); + if (p) str = p+1; + + if (local) { + p = strrchr(str, '\\'); + if (p) str = p+1; + } + + return str; +} + /* ---------------------------------------------------------------------- * Actual sftp commands. */ @@ -314,9 +300,21 @@ int sftp_cmd_cd(struct sftp_command *cmd) } /* - * Get a file and save it at the local end. + * Print current directory. Easy as pie. */ -int sftp_cmd_get(struct sftp_command *cmd) +int sftp_cmd_pwd(struct sftp_command *cmd) +{ + printf("Remote directory is %s\n", pwd); + return 0; +} + +/* + * Get a file and save it at the local end. We have two very + * similar commands here: `get' and `reget', which differ in that + * `reget' checks for the existence of the destination file and + * starts from where a previous aborted transfer left off. + */ +int sftp_general_get(struct sftp_command *cmd, int restart) { struct fxp_handle *fh; char *fname, *outfname; @@ -333,7 +331,8 @@ int sftp_cmd_get(struct sftp_command *cmd) printf("%s: %s\n", cmd->words[1], fxp_error()); return 0; } - outfname = (cmd->nwords == 2 ? cmd->words[1] : cmd->words[2]); + outfname = (cmd->nwords == 2 ? + stripslashes(cmd->words[1], 0) : cmd->words[2]); fh = fxp_open(fname, SSH_FXF_READ); if (!fh) { @@ -341,7 +340,13 @@ int sftp_cmd_get(struct sftp_command *cmd) sfree(fname); return 0; } - fp = fopen(outfname, "wb"); + + if (restart) { + fp = fopen(outfname, "rb+"); + } else { + fp = fopen(outfname, "wb"); + } + if (!fp) { printf("local: unable to open %s\n", outfname); fxp_close(fh); @@ -349,9 +354,17 @@ int sftp_cmd_get(struct sftp_command *cmd) return 0; } - printf("remote:%s => local:%s\n", fname, outfname); + if (restart) { + long posn; + fseek(fp, 0L, SEEK_END); + posn = ftell(fp); + printf("reget: restarting at file position %ld\n", posn); + offset = uint64_make(0, posn); + } else { + offset = uint64_make(0, 0); + } - offset = uint64_make(0, 0); + printf("remote:%s => local:%s\n", fname, outfname); /* * FIXME: we can use FXP_FSTAT here to get the file size, and @@ -390,11 +403,22 @@ int sftp_cmd_get(struct sftp_command *cmd) return 0; } +int sftp_cmd_get(struct sftp_command *cmd) +{ + return sftp_general_get(cmd, 0); +} +int sftp_cmd_reget(struct sftp_command *cmd) +{ + return sftp_general_get(cmd, 1); +} /* - * Send a file and store it at the remote end. + * Send a file and store it at the remote end. We have two very + * similar commands here: `put' and `reput', which differ in that + * `reput' checks for the existence of the destination file and + * starts from where a previous aborted transfer left off. */ -int sftp_cmd_put(struct sftp_command *cmd) +int sftp_general_put(struct sftp_command *cmd, int restart) { struct fxp_handle *fh; char *fname, *origoutfname, *outfname; @@ -407,7 +431,8 @@ int sftp_cmd_put(struct sftp_command *cmd) } fname = cmd->words[1]; - origoutfname = (cmd->nwords == 2 ? cmd->words[1] : cmd->words[2]); + origoutfname = (cmd->nwords == 2 ? + stripslashes(cmd->words[1], 1) : cmd->words[2]); outfname = canonify(origoutfname); if (!outfname) { printf("%s: %s\n", origoutfname, fxp_error()); @@ -420,16 +445,47 @@ int sftp_cmd_put(struct sftp_command *cmd) sfree(outfname); return 0; } - fh = fxp_open(outfname, SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC); + if (restart) { + fh = fxp_open(outfname, + SSH_FXF_WRITE); + } else { + fh = fxp_open(outfname, + SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC); + } if (!fh) { printf("%s: %s\n", outfname, fxp_error()); sfree(outfname); return 0; } - printf("local:%s => remote:%s\n", fname, outfname); + if (restart) { + char decbuf[30]; + struct fxp_attrs attrs; + if (!fxp_fstat(fh, &attrs)) { + printf("read size of %s: %s\n", outfname, fxp_error()); + sfree(outfname); + return 0; + } + if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) { + printf("read size of %s: size was not given\n", outfname); + sfree(outfname); + return 0; + } + offset = attrs.size; + uint64_decimal(offset, decbuf); + printf("reput: restarting at file position %s\n", decbuf); + if (uint64_compare(offset, uint64_make(0, LONG_MAX)) > 0) { + printf("reput: remote file is larger than we can deal with\n"); + sfree(outfname); + return 0; + } + if (fseek(fp, offset.lo, SEEK_SET) != 0) + fseek(fp, 0, SEEK_END); /* *shrug* */ + } else { + offset = uint64_make(0, 0); + } - offset = uint64_make(0, 0); + printf("local:%s => remote:%s\n", fname, outfname); /* * FIXME: we can use FXP_FSTAT here to get the file size, and @@ -459,6 +515,14 @@ int sftp_cmd_put(struct sftp_command *cmd) return 0; } +int sftp_cmd_put(struct sftp_command *cmd) +{ + return sftp_general_put(cmd, 0); +} +int sftp_cmd_reput(struct sftp_command *cmd) +{ + return sftp_general_put(cmd, 1); +} int sftp_cmd_mkdir(struct sftp_command *cmd) { @@ -484,9 +548,8 @@ int sftp_cmd_mkdir(struct sftp_command *cmd) return 0; } - sfree(dir); - return 0; - + sfree(dir); + return 0; } int sftp_cmd_rmdir(struct sftp_command *cmd) @@ -513,9 +576,8 @@ int sftp_cmd_rmdir(struct sftp_command *cmd) return 0; } - sfree(dir); - return 0; - + sfree(dir); + return 0; } int sftp_cmd_rm(struct sftp_command *cmd) @@ -523,7 +585,6 @@ int sftp_cmd_rm(struct sftp_command *cmd) char *fname; int result; - if (cmd->nwords < 2) { printf("rm: expects a filename\n"); return 0; @@ -535,21 +596,252 @@ int sftp_cmd_rm(struct sftp_command *cmd) return 0; } - result = fxp_rm(fname); + result = fxp_remove(fname); if (!result) { printf("rm %s: %s\n", fname, fxp_error()); sfree(fname); return 0; } + sfree(fname); + return 0; + +} + +int sftp_cmd_mv(struct sftp_command *cmd) +{ + char *srcfname, *dstfname; + int result; + + if (cmd->nwords < 3) { + printf("mv: expects two filenames\n"); + return 0; + } + srcfname = canonify(cmd->words[1]); + if (!srcfname) { + printf("%s: %s\n", srcfname, fxp_error()); + return 0; + } + + dstfname = canonify(cmd->words[2]); + if (!dstfname) { + printf("%s: %s\n", dstfname, fxp_error()); + return 0; + } + + result = fxp_rename(srcfname, dstfname); + if (!result) { + char const *error = fxp_error(); + struct fxp_attrs attrs; + + /* + * 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. + */ + result = fxp_stat(dstfname, &attrs); + 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; + result = fxp_rename(srcfname, dstfname); + error = result ? NULL : fxp_error(); + } + } + if (error) { + printf("mv %s %s: %s\n", srcfname, dstfname, error); + sfree(srcfname); + sfree(dstfname); + return 0; + } + } + printf("%s -> %s\n", srcfname, dstfname); + + sfree(srcfname); + sfree(dstfname); + return 0; +} + +int sftp_cmd_chmod(struct sftp_command *cmd) +{ + char *fname, *mode; + int result; + struct fxp_attrs attrs; + unsigned attrs_clr, attrs_xor, oldperms, newperms; + + 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. + */ + attrs_clr = 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; + } + attrs_clr = 07777; + sscanf(mode, "%o", &attrs_xor); + attrs_xor &= 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", + strcspn(modebegin, ","), modebegin, *mode); + return 0; + } + mode++; + } + if (!*mode || *mode == ',') { + printf("chmod: file mode '%.*s' is incomplete\n", + strcspn(modebegin, ","), modebegin); + return 0; + } + action = *mode++; + if (!*mode || *mode == ',') { + printf("chmod: file mode '%.*s' is incomplete\n", + 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", + strcspn(modebegin, ","), modebegin); + return 0; + } + perms |= 06000; + break; + default: + printf("chmod: file mode '%.*s' contains unrecognised" + " permission specifier '%c'\n", + 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", + strcspn(modebegin, ","), modebegin, *mode); + return 0; + } + perms &= subset; + switch (action) { + case '+': + attrs_clr |= perms; + attrs_xor |= perms; + break; + case '-': + attrs_clr |= perms; + attrs_xor &= ~perms; + break; + case '=': + attrs_clr |= subset; + 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; + } + + result = fxp_stat(fname, &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; + + result = fxp_setstat(fname, attrs); + + if (!result) { + printf("set attrs for %s: %s\n", fname, fxp_error()); sfree(fname); return 0; + } + printf("%s: %04o -> %04o\n", fname, oldperms, newperms); + + sfree(fname); + return 0; } +static int sftp_cmd_help(struct sftp_command *cmd); static struct sftp_cmd_lookup { char *name; + /* + * For help purposes, there are two kinds of command: + * + * - primary commands, in which `longhelp' is non-NULL. In + * this case `shorthelp' is descriptive text, and `longhelp' + * is longer descriptive text intended to be printed after + * the command name. + * + * - alias commands, in which `longhelp' is NULL. In this case + * `shorthelp' is the name of a primary command, which + * contains the help that should double up for this command. + */ + char *shorthelp; + char *longhelp; int (*obey) (struct sftp_command *); } sftp_lookup[] = { /* @@ -557,17 +849,218 @@ static struct sftp_cmd_lookup { * in ASCII order. */ { - "bye", sftp_cmd_quit}, { - "cd", sftp_cmd_cd}, { - "dir", sftp_cmd_ls}, { - "exit", sftp_cmd_quit}, { - "get", sftp_cmd_get}, { - "ls", sftp_cmd_ls}, { - "mkdir", sftp_cmd_mkdir}, { - "put", sftp_cmd_put}, { - "quit", sftp_cmd_quit}, { - "rm", sftp_cmd_rm}, { - "rmdir", sftp_cmd_rmdir},}; + "bye", "finish your SFTP session", + "\n" + " Terminates your SFTP session and quits the PSFTP program.\n", + sftp_cmd_quit + }, + { + "cd", "change your remote working directory", + " [ ]\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", + sftp_cmd_cd + }, + { + "chmod", "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" + " 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" + " u-r make file not readable by owning user\n" + " [also u-w, u-x]\n" + " g+r make file readable by members of owning group\n" + " [also g+w, g+x, g-r, g-w, g-x]\n" + " o+r make file readable by all other users\n" + " [also o+w, o+x, o-r, o-w, o-x]\n" + " a+r make file readable by absolutely everybody\n" + " [also a+w, a+x, a-r, a-w, a-x]\n" + " u+s enable the Unix set-user-ID bit\n" + " u-s disable the Unix set-user-ID bit\n" + " g+s enable the Unix set-group-ID bit\n" + " g-s disable the Unix set-group-ID bit\n" + " +t enable the Unix \"sticky bit\"\n" + " You can give more than one modifier for the same user (\"g-rwx\"), and\n" + " more than one user for the same modifier (\"ug+w\"). You can\n" + " use commas to separate different modifiers (\"u+rwx,g+s\").\n", + sftp_cmd_chmod + }, + { + "del", "delete a file", + " \n" + " Delete a file.\n", + sftp_cmd_rm + }, + { + "delete", "delete a file", + "\n" + " Delete a file.\n", + sftp_cmd_rm + }, + { + "dir", "list contents of a remote directory", + " [ ]\n" + " List the contents of a specified directory on the server.\n" + " If is not given, the current working directory\n" + " will be listed.\n", + sftp_cmd_ls + }, + { + "exit", "bye", NULL, sftp_cmd_quit + }, + { + "get", "download a file from the server to your local machine", + " [ ]\n" + " Downloads a file on the server and stores it locally under\n" + " the same name, or under a different one if you supply the\n" + " argument .\n", + sftp_cmd_get + }, + { + "help", "give help", + " [ [ ... ] ]\n" + " Give general help if no commands are specified.\n" + " If one or more commands are specified, give specific help on\n" + " those particular commands.\n", + sftp_cmd_help + }, + { + "ls", "dir", NULL, + sftp_cmd_ls + }, + { + "mkdir", "create a directory on the remote server", + " \n" + " Creates a directory with the given name on the server.\n", + sftp_cmd_mkdir + }, + { + "mv", "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", + sftp_cmd_mv + }, + { + "put", "upload a file from your local machine to the server", + " [ ]\n" + " Uploads a file to the server and stores it there under\n" + " the same name, or under a different one if you supply the\n" + " argument .\n", + sftp_cmd_put + }, + { + "pwd", "print your remote working directory", + "\n" + " Print the current remote working directory for your SFTP session.\n", + sftp_cmd_pwd + }, + { + "quit", "bye", NULL, + sftp_cmd_quit + }, + { + "reget", "continue downloading a file", + " [ ]\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" + " file. This is for resuming a download that was interrupted.\n", + sftp_cmd_reget + }, + { + "ren", "mv", NULL, + sftp_cmd_mv + }, + { + "rename", "mv", NULL, + sftp_cmd_mv + }, + { + "reput", "continue uploading a file", + " [ ]\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" + " file. This is for resuming an upload that was interrupted.\n", + sftp_cmd_reput + }, + { + "rm", "del", NULL, + sftp_cmd_rm + }, + { + "rmdir", "remove a directory 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", + sftp_cmd_rmdir + } +}; + +const struct sftp_cmd_lookup *lookup_command(char *name) +{ + int i, j, k, cmp; + + i = -1; + j = sizeof(sftp_lookup) / sizeof(*sftp_lookup); + while (j - i > 1) { + k = (j + i) / 2; + cmp = strcmp(name, sftp_lookup[k].name); + if (cmp < 0) + j = k; + else if (cmp > 0) + i = k; + else { + return &sftp_lookup[k]; + } + } + return NULL; +} + +static int sftp_cmd_help(struct sftp_command *cmd) +{ + int i; + if (cmd->nwords == 1) { + /* + * Give short help on each command. + */ + int maxlen; + maxlen = 0; + for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) { + int len = strlen(sftp_lookup[i].name); + if (maxlen < len) + maxlen = len; + } + for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) { + const struct sftp_cmd_lookup *lookup; + lookup = &sftp_lookup[i]; + printf("%-*s", maxlen+2, lookup->name); + if (lookup->longhelp == NULL) + lookup = lookup_command(lookup->shorthelp); + printf("%s\n", lookup->shorthelp); + } + } else { + /* + * Give long help on specific commands. + */ + for (i = 1; i < cmd->nwords; i++) { + const struct sftp_cmd_lookup *lookup; + lookup = lookup_command(cmd->words[i]); + if (!lookup) { + printf("help: %s: command not found\n", cmd->words[i]); + } else { + printf("%s", lookup->name); + if (lookup->longhelp == NULL) + lookup = lookup_command(lookup->shorthelp); + printf("%s", lookup->longhelp); + } + } + } + return 0; +} /* ---------------------------------------------------------------------- * Command line reading and parsing. @@ -669,24 +1162,12 @@ struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags) if (cmd->nwords == 0) cmd->obey = sftp_cmd_null; else { - int i, j, k, cmp; - - cmd->obey = sftp_cmd_unknown; - - i = -1; - j = sizeof(sftp_lookup) / sizeof(*sftp_lookup); - while (j - i > 1) { - k = (j + i) / 2; - cmp = strcmp(cmd->words[0], sftp_lookup[k].name); - if (cmp < 0) - j = k; - else if (cmp > 0) - i = k; - else { - cmd->obey = sftp_lookup[k].obey; - break; - } - } + const struct sftp_cmd_lookup *lookup; + lookup = lookup_command(cmd->words[0]); + if (!lookup) + cmd->obey = sftp_cmd_unknown; + else + cmd->obey = lookup->obey; } return cmd; @@ -734,26 +1215,26 @@ void do_sftp(int mode, int modeflags, char *batchfile) break; if (cmd->obey(cmd) < 0) break; - } + } } else { fp = fopen(batchfile, "r"); if (!fp) { - printf("Fatal: unable to open %s\n", batchfile); - return; + printf("Fatal: unable to open %s\n", batchfile); + return; } while (1) { - struct sftp_command *cmd; - cmd = sftp_getcmd(fp, mode, modeflags); - if (!cmd) - break; - if (cmd->obey(cmd) < 0) + struct sftp_command *cmd; + cmd = sftp_getcmd(fp, mode, modeflags); + if (!cmd) + break; + if (cmd->obey(cmd) < 0) + break; + if (fxp_error() != NULL) { + if (!(modeflags & 2)) break; - if (fxp_error() != NULL) { - if (!(modeflags & 2)) - break; - } + } } - fclose(fp); + fclose(fp); } } @@ -841,6 +1322,46 @@ void verify_ssh_host_key(char *host, int port, char *keytype, } /* + * Ask whether the selected cipher is acceptable (since it was + * below the configured 'warn' threshold). + * cs: 0 = both ways, 1 = client->server, 2 = server->client + */ +void askcipher(char *ciphername, int cs) +{ + HANDLE hin; + DWORD savemode, i; + + static const char msg[] = + "The first %scipher supported by the server is\n" + "%s, which is below the configured warning threshold.\n" + "Continue with connection? (y/n) "; + static const char abandoned[] = "Connection abandoned.\n"; + + char line[32]; + + fprintf(stderr, msg, + (cs == 0) ? "" : + (cs == 1) ? "client-to-server " : + "server-to-client ", + ciphername); + fflush(stderr); + + hin = GetStdHandle(STD_INPUT_HANDLE); + GetConsoleMode(hin, &savemode); + SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT | + ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT)); + ReadFile(hin, line, sizeof(line) - 1, &i, NULL); + SetConsoleMode(hin, savemode); + + if (line[0] == 'y' || line[0] == 'Y') { + return; + } else { + fprintf(stderr, abandoned); + exit(0); + } +} + +/* * Print an error message and perform a fatal exit. */ void fatalbox(char *fmt, ...) @@ -874,7 +1395,7 @@ void logevent(char *string) { } -void ldisc_send(char *buf, int len) +void ldisc_send(char *buf, int len, int interactive) { /* * This is only here because of the calls to ldisc_send(NULL, @@ -912,7 +1433,7 @@ static unsigned char *outptr; /* where to put the data */ static unsigned outlen; /* how much data required */ static unsigned char *pending = NULL; /* any spare data */ static unsigned pendlen = 0, pendsize = 0; /* length and phys. size of buffer */ -void from_backend(int is_stderr, char *data, int datalen) +int from_backend(int is_stderr, char *data, int datalen) { unsigned char *p = (unsigned char *) data; unsigned len = (unsigned) datalen; @@ -923,14 +1444,14 @@ void from_backend(int is_stderr, char *data, int datalen) */ if (is_stderr) { fwrite(data, 1, len, stderr); - return; + return 0; } /* * If this is before the real session begins, just return. */ if (!outptr) - return; + return 0; if (outlen > 0) { unsigned used = outlen; @@ -954,6 +1475,8 @@ void from_backend(int is_stderr, char *data, int datalen) memcpy(pending + pendlen, p, len); pendlen += len; } + + return 0; } int sftp_recvdata(char *buf, int len) { @@ -1118,7 +1641,7 @@ int main(int argc, char *argv[]) int modeflags = 0; char *batchfile = NULL; - flags = FLAG_STDERR; + flags = FLAG_STDERR | FLAG_INTERACTIVE; ssh_get_line = &get_line; init_winsock(); sk_init(); @@ -1213,11 +1736,43 @@ int main(int argc, char *argv[]) /* SFTP uses SSH2 by default always */ cfg.sshprot = 2; - /* Set up subsystem name. FIXME: fudge for SSH1. */ + /* + * Disable scary things which shouldn't be enabled for simple + * things like SCP and SFTP: agent forwarding, port forwarding, + * X forwarding. + */ + cfg.x11_forward = 0; + cfg.agentfwd = 0; + cfg.portfwd[0] = cfg.portfwd[1] = '\0'; + + /* Set up subsystem name. */ strcpy(cfg.remote_cmd, "sftp"); cfg.ssh_subsys = TRUE; cfg.nopty = TRUE; + /* + * Set up fallback option, for SSH1 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 + * try to find sftp-server in various places (the obvious + * systemwide spots /usr/lib and /usr/local/lib, and then the + * user's PATH) and finally give up. + * + * test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server + * test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server + * exec sftp-server + * + * the idea being that this will attempt to use either of the + * obvious pathnames and then give up, and when it does give up + * it will print the preferred pathname in the error messages. + */ + cfg.remote_cmd_ptr2 = + "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n" + "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n" + "exec sftp-server"; + cfg.ssh_subsys2 = FALSE; + back = &ssh_backend; err = back->init(cfg.host, cfg.port, &realhost);