X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/putty/blobdiff_plain/d92624dccee63e8bee8653e8ae845ffad3490b67..d13c2ee931d199203803d9ce1a0029329512f79a:/psftp.c diff --git a/psftp.c b/psftp.c index edc4bed0..feefbbc0 100644 --- a/psftp.c +++ b/psftp.c @@ -24,51 +24,8 @@ * send buffer. */ -/* ---------------------------------------------------------------------- - * String handling routines. - */ - -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; -} +static int psftp_connect(char *userhost, char *user, int portnumber); +static void do_sftp_init(void); /* ---------------------------------------------------------------------- * sftp client state. @@ -176,6 +133,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. */ @@ -187,13 +168,13 @@ struct sftp_command { int sftp_cmd_null(struct sftp_command *cmd) { - return 0; + return 1; /* success */ } int sftp_cmd_unknown(struct sftp_command *cmd) { printf("psftp: unknown command \"%s\"\n", cmd->words[0]); - return 0; + return 0; /* failure */ } int sftp_cmd_quit(struct sftp_command *cmd) @@ -220,6 +201,11 @@ int sftp_cmd_ls(struct sftp_command *cmd) char *dir, *cdir; int i; + if (back == NULL) { + printf("psftp: not connected to a host; use \"open host.name\"\n"); + return 0; + } + if (cmd->nwords < 2) dir = "."; else @@ -283,7 +269,7 @@ int sftp_cmd_ls(struct sftp_command *cmd) sfree(cdir); - return 0; + return 1; } /* @@ -295,6 +281,11 @@ int sftp_cmd_cd(struct sftp_command *cmd) struct fxp_handle *dirh; char *dir; + if (back == NULL) { + printf("psftp: not connected to a host; use \"open host.name\"\n"); + return 0; + } + if (cmd->nwords < 2) dir = dupstr(homedir); else @@ -318,7 +309,21 @@ int sftp_cmd_cd(struct sftp_command *cmd) pwd = dir; printf("Remote directory is now %s\n", pwd); - return 0; + return 1; +} + +/* + * Print current directory. Easy as pie. + */ +int sftp_cmd_pwd(struct sftp_command *cmd) +{ + if (back == NULL) { + printf("psftp: not connected to a host; use \"open host.name\"\n"); + return 0; + } + + printf("Remote directory is %s\n", pwd); + return 1; } /* @@ -333,6 +338,12 @@ int sftp_general_get(struct sftp_command *cmd, int restart) char *fname, *outfname; uint64 offset; FILE *fp; + int ret; + + if (back == NULL) { + printf("psftp: not connected to a host; use \"open host.name\"\n"); + return 0; + } if (cmd->nwords < 2) { printf("get: expects a filename\n"); @@ -344,7 +355,8 @@ int sftp_general_get(struct sftp_command *cmd, int restart) 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) { @@ -382,6 +394,7 @@ int sftp_general_get(struct sftp_command *cmd, int restart) * FIXME: we can use FXP_FSTAT here to get the file size, and * thus put up a progress bar. */ + ret = 1; while (1) { char buffer[4096]; int len; @@ -392,6 +405,7 @@ int sftp_general_get(struct sftp_command *cmd, int restart) break; if (len == -1) { printf("error while reading: %s\n", fxp_error()); + ret = 0; break; } @@ -400,12 +414,15 @@ int sftp_general_get(struct sftp_command *cmd, int restart) wlen = fwrite(buffer, 1, len - wpos, fp); if (wlen <= 0) { printf("error while writing local file\n"); + ret = 0; break; } wpos += wlen; } - if (wpos < len) /* we had an error */ + if (wpos < len) { /* we had an error */ + ret = 0; break; + } offset = uint64_add32(offset, len); } @@ -413,7 +430,7 @@ int sftp_general_get(struct sftp_command *cmd, int restart) fxp_close(fh); sfree(fname); - return 0; + return ret; } int sftp_cmd_get(struct sftp_command *cmd) { @@ -436,6 +453,12 @@ int sftp_general_put(struct sftp_command *cmd, int restart) char *fname, *origoutfname, *outfname; uint64 offset; FILE *fp; + int ret; + + if (back == NULL) { + printf("psftp: not connected to a host; use \"open host.name\"\n"); + return 0; + } if (cmd->nwords < 2) { printf("put: expects a filename\n"); @@ -443,7 +466,8 @@ int sftp_general_put(struct sftp_command *cmd, int restart) } 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()); @@ -502,6 +526,7 @@ int sftp_general_put(struct sftp_command *cmd, int restart) * FIXME: we can use FXP_FSTAT here to get the file size, and * thus put up a progress bar. */ + ret = 1; while (1) { char buffer[4096]; int len; @@ -509,12 +534,14 @@ int sftp_general_put(struct sftp_command *cmd, int restart) len = fread(buffer, 1, sizeof(buffer), fp); if (len == -1) { printf("error while reading local file\n"); + ret = 0; break; } else if (len == 0) { break; } if (!fxp_write(fh, buffer, offset, len)) { printf("error while writing: %s\n", fxp_error()); + ret = 0; break; } offset = uint64_add32(offset, len); @@ -524,7 +551,7 @@ int sftp_general_put(struct sftp_command *cmd, int restart) fclose(fp); sfree(outfname); - return 0; + return ret; } int sftp_cmd_put(struct sftp_command *cmd) { @@ -540,6 +567,10 @@ int sftp_cmd_mkdir(struct sftp_command *cmd) char *dir; int result; + if (back == NULL) { + printf("psftp: not connected to a host; use \"open host.name\"\n"); + return 0; + } if (cmd->nwords < 2) { printf("mkdir: expects a directory\n"); @@ -560,7 +591,7 @@ int sftp_cmd_mkdir(struct sftp_command *cmd) } sfree(dir); - return 0; + return 1; } int sftp_cmd_rmdir(struct sftp_command *cmd) @@ -568,6 +599,10 @@ int sftp_cmd_rmdir(struct sftp_command *cmd) char *dir; int result; + if (back == NULL) { + printf("psftp: not connected to a host; use \"open host.name\"\n"); + return 0; + } if (cmd->nwords < 2) { printf("rmdir: expects a directory\n"); @@ -588,7 +623,7 @@ int sftp_cmd_rmdir(struct sftp_command *cmd) } sfree(dir); - return 0; + return 1; } int sftp_cmd_rm(struct sftp_command *cmd) @@ -596,6 +631,11 @@ int sftp_cmd_rm(struct sftp_command *cmd) char *fname; int result; + if (back == NULL) { + printf("psftp: not connected to a host; use \"open host.name\"\n"); + return 0; + } + if (cmd->nwords < 2) { printf("rm: expects a filename\n"); return 0; @@ -615,8 +655,7 @@ int sftp_cmd_rm(struct sftp_command *cmd) } sfree(fname); - return 0; - + return 1; } int sftp_cmd_mv(struct sftp_command *cmd) @@ -624,6 +663,11 @@ int sftp_cmd_mv(struct sftp_command *cmd) char *srcfname, *dstfname; int result; + if (back == NULL) { + printf("psftp: not connected to a host; use \"open host.name\"\n"); + return 0; + } + if (cmd->nwords < 3) { printf("mv: expects two filenames\n"); return 0; @@ -681,7 +725,7 @@ int sftp_cmd_mv(struct sftp_command *cmd) sfree(srcfname); sfree(dstfname); - return 0; + return 1; } int sftp_cmd_chmod(struct sftp_command *cmd) @@ -691,6 +735,11 @@ int sftp_cmd_chmod(struct sftp_command *cmd) struct fxp_attrs attrs; unsigned attrs_clr, attrs_xor, oldperms, newperms; + if (back == NULL) { + printf("psftp: not connected to a host; use \"open host.name\"\n"); + return 0; + } + if (cmd->nwords < 3) { printf("chmod: expects a mode specifier and a filename\n"); return 0; @@ -832,11 +881,47 @@ int sftp_cmd_chmod(struct sftp_command *cmd) printf("%s: %04o -> %04o\n", fname, oldperms, newperms); sfree(fname); - return 0; + return 1; +} + +static int sftp_cmd_open(struct sftp_command *cmd) +{ + if (back != NULL) { + printf("psftp: already connected\n"); + return 0; + } + + if (cmd->nwords < 2) { + printf("open: expects a host name\n"); + return 0; + } + + if (psftp_connect(cmd->words[1], NULL, 0)) { + back = NULL; /* connection is already closed */ + return -1; /* this is fatal */ + } + do_sftp_init(); + return 1; } +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[] = { /* @@ -844,25 +929,226 @@ static struct sftp_cmd_lookup { * in ASCII order. */ { - "bye", sftp_cmd_quit}, { - "cd", sftp_cmd_cd}, { - "chmod", sftp_cmd_chmod}, { - "del", sftp_cmd_rm}, { - "delete", sftp_cmd_rm}, { - "dir", sftp_cmd_ls}, { - "exit", sftp_cmd_quit}, { - "get", sftp_cmd_get}, { - "ls", sftp_cmd_ls}, { - "mkdir", sftp_cmd_mkdir}, { - "mv", sftp_cmd_mv}, { - "put", sftp_cmd_put}, { - "quit", sftp_cmd_quit}, { - "reget", sftp_cmd_reget}, { - "ren", sftp_cmd_mv}, { - "rename", sftp_cmd_mv}, { - "reput", sftp_cmd_reput}, { - "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 + }, + { + "open", "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", + sftp_cmd_open + }, + { + "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 1; +} /* ---------------------------------------------------------------------- * Command line reading and parsing. @@ -875,9 +1161,9 @@ struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags) char *p, *q, *r; int quoting; - if ((mode == 0) || (modeflags & 1)) { - printf("psftp> "); - } + if ((mode == 0) || (modeflags & 1)) { + printf("psftp> "); + } fflush(stdout); cmd = smalloc(sizeof(struct sftp_command)); @@ -894,13 +1180,11 @@ struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags) linesize += 512; line = srealloc(line, linesize); ret = fgets(line + linelen, linesize - linelen, fp); - if (modeflags & 1) { - printf("%s", ret); - } if (!ret || (linelen == 0 && line[0] == '\0')) { cmd->obey = sftp_cmd_quit; - printf("quit\n"); + if ((mode == 0) || (modeflags & 1)) + printf("quit\n"); return cmd; /* eof */ } len = linelen + strlen(line + linelen); @@ -911,6 +1195,9 @@ struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags) break; } } + if (modeflags & 1) { + printf("%s\n", line); + } /* * Parse the command line into words. The syntax is: @@ -964,33 +1251,19 @@ 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; } -void do_sftp(int mode, int modeflags, char *batchfile) +static void do_sftp_init(void) { - FILE *fp; - /* * Do protocol initialisation. */ @@ -1013,6 +1286,12 @@ void do_sftp(int mode, int modeflags, char *batchfile) printf("Remote working directory is %s\n", homedir); } pwd = dupstr(homedir); +} + +void do_sftp(int mode, int modeflags, char *batchfile) +{ + FILE *fp; + int ret; /* * Batch mode? @@ -1023,32 +1302,33 @@ void do_sftp(int mode, int modeflags, char *batchfile) * Now we're ready to do Real Stuff. */ while (1) { - struct sftp_command *cmd; - cmd = sftp_getcmd(stdin, 0, 0); - if (!cmd) - break; - if (cmd->obey(cmd) < 0) - break; - } + struct sftp_command *cmd; + cmd = sftp_getcmd(stdin, 0, 0); + if (!cmd) + 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; + ret = cmd->obey(cmd); + if (ret < 0) + break; + if (ret == 0) { + if (!(modeflags & 2)) break; - if (fxp_error() != NULL) { - if (!(modeflags & 2)) - break; - } + } } - fclose(fp); + fclose(fp); } } @@ -1176,6 +1456,25 @@ void askcipher(char *ciphername, int cs) } /* + * Warn about the obsolescent key file format. + */ +void old_keyfile_warning(void) +{ + static const char message[] = + "You are loading an SSH 2 private key which has an\n" + "old version of the file format. This means your key\n" + "file is not fully tamperproof. Future versions of\n" + "PuTTY may stop supporting this private key format,\n" + "so we recommend you convert your key to the new\n" + "format.\n" + "\n" + "Once the key is loaded into PuTTYgen, you can perform\n" + "this conversion simply by saving it again.\n"; + + fputs(message, stderr); +} + +/* * Print an error message and perform a fatal exit. */ void fatalbox(char *fmt, ...) @@ -1187,7 +1486,7 @@ void fatalbox(char *fmt, ...) vsprintf(str + strlen(str), fmt, ap); va_end(ap); strcat(str, "\n"); - fprintf(stderr, str); + fputs(stderr, str); exit(1); } @@ -1200,7 +1499,7 @@ void connection_fatal(char *fmt, ...) vsprintf(str + strlen(str), fmt, ap); va_end(ap); strcat(str, "\n"); - fprintf(stderr, str); + fputs(stderr, str); exit(1); } @@ -1209,7 +1508,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, @@ -1443,62 +1742,12 @@ static void usage(void) } /* - * Main program. Parse arguments etc. + * Connect to a host. */ -int main(int argc, char *argv[]) +static int psftp_connect(char *userhost, char *user, int portnumber) { - int i; - int portnumber = 0; - char *user, *host, *userhost, *realhost; + char *host, *realhost; char *err; - int mode = 0; - int modeflags = 0; - char *batchfile = NULL; - - flags = FLAG_STDERR; - ssh_get_line = &get_line; - init_winsock(); - sk_init(); - - userhost = user = NULL; - - for (i = 1; i < argc; i++) { - if (argv[i][0] != '-') { - if (userhost) - usage(); - else - userhost = dupstr(argv[i]); - } else if (strcmp(argv[i], "-v") == 0) { - verbose = 1, flags |= FLAG_VERBOSE; - } else if (strcmp(argv[i], "-h") == 0 || - strcmp(argv[i], "-?") == 0) { - usage(); - } else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) { - user = argv[++i]; - } else if (strcmp(argv[i], "-P") == 0 && i + 1 < argc) { - portnumber = atoi(argv[++i]); - } else if (strcmp(argv[i], "-pw") == 0 && i + 1 < argc) { - password = argv[++i]; - } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) { - mode = 1; - batchfile = argv[++i]; - } else if (strcmp(argv[i], "-bc") == 0 && i + 1 < argc) { - modeflags = modeflags | 1; - } else if (strcmp(argv[i], "-be") == 0 && i + 1 < argc) { - modeflags = modeflags | 2; - } else if (strcmp(argv[i], "--") == 0) { - i++; - break; - } else { - usage(); - } - } - argc -= i; - argv += i; - back = NULL; - - if (argc > 0 || !userhost) - usage(); /* Separate host and username */ host = userhost; @@ -1524,6 +1773,32 @@ int main(int argc, char *argv[]) cfg.port = 22; } + /* + * Trim leading whitespace off the hostname if it's there. + */ + { + int space = strspn(cfg.host, " \t"); + memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space); + } + + /* See if host is of the form user@host */ + if (cfg.host[0] != '\0') { + char *atsign = strchr(cfg.host, '@'); + /* Make sure we're not overflowing the user field */ + if (atsign) { + if (atsign - cfg.host < sizeof cfg.username) { + strncpy(cfg.username, cfg.host, atsign - cfg.host); + cfg.username[atsign - cfg.host] = '\0'; + } + memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1)); + } + } + + /* + * Trim a colon suffix off the hostname if it's there. + */ + cfg.host[strcspn(cfg.host, ":")] = '\0'; + /* Set username */ if (user != NULL && user[0] != '\0') { strncpy(cfg.username, user, sizeof(cfg.username) - 1); @@ -1550,21 +1825,122 @@ 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); + err = back->init(cfg.host, cfg.port, &realhost, 0); if (err != NULL) { - fprintf(stderr, "ssh_init: %s", err); + fprintf(stderr, "ssh_init: %s\n", err); return 1; } ssh_sftp_init(); if (verbose && realhost != NULL) printf("Connected to %s\n", realhost); + return 0; +} + +/* + * Main program. Parse arguments etc. + */ +int main(int argc, char *argv[]) +{ + int i; + int portnumber = 0; + char *userhost, *user; + int mode = 0; + int modeflags = 0; + char *batchfile = NULL; + + flags = FLAG_STDERR | FLAG_INTERACTIVE; + ssh_get_line = &get_line; + init_winsock(); + sk_init(); + + userhost = user = NULL; + + for (i = 1; i < argc; i++) { + if (argv[i][0] != '-') { + if (userhost) + usage(); + else + userhost = dupstr(argv[i]); + } else if (strcmp(argv[i], "-v") == 0) { + verbose = 1, flags |= FLAG_VERBOSE; + } else if (strcmp(argv[i], "-h") == 0 || + strcmp(argv[i], "-?") == 0) { + usage(); + } else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) { + user = argv[++i]; + } else if (strcmp(argv[i], "-P") == 0 && i + 1 < argc) { + portnumber = atoi(argv[++i]); + } else if (strcmp(argv[i], "-pw") == 0 && i + 1 < argc) { + password = argv[++i]; + } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) { + mode = 1; + batchfile = argv[++i]; + } else if (strcmp(argv[i], "-bc") == 0) { + modeflags = modeflags | 1; + } else if (strcmp(argv[i], "-be") == 0) { + modeflags = modeflags | 2; + } else if (strcmp(argv[i], "--") == 0) { + i++; + break; + } else { + usage(); + } + } + argc -= i; + argv += i; + back = NULL; + + /* + * If a user@host string has already been provided, connect to + * it now. + */ + if (userhost) { + if (psftp_connect(userhost, user, portnumber)) + return 1; + do_sftp_init(); + } else { + printf("psftp: no hostname specified; use \"open host.name\"" + " to connect\n"); + } do_sftp(mode, modeflags, batchfile);