2 * psftp.c: front end for PSFTP.
12 #define PUTTY_DO_GLOBALS
19 /* ----------------------------------------------------------------------
20 * String handling routines.
23 char *dupstr(char *s) {
25 char *p = smalloc(len+1);
30 /* Allocate the concatenation of N strings. Terminate arg list with NULL. */
31 char *dupcat(char *s1, ...) {
39 sn = va_arg(ap, char *);
52 sn = va_arg(ap, char *);
63 /* ----------------------------------------------------------------------
69 /* ----------------------------------------------------------------------
70 * Higher-level helper functions used in commands.
74 * Attempt to canonify a pathname starting from the pwd. If
75 * canonification fails, at least fall back to returning a _valid_
76 * pathname (though it may be ugly, eg /home/simon/../foobar).
78 char *canonify(char *name) {
79 char *fullname, *canonname;
82 fullname = dupstr(name);
85 if (pwd[strlen(pwd)-1] == '/')
89 fullname = dupcat(pwd, slash, name, NULL);
92 canonname = fxp_realpath(fullname);
99 * Attempt number 2. Some FXP_REALPATH implementations
100 * (glibc-based ones, in particular) require the _whole_
101 * path to point to something that exists, whereas others
102 * (BSD-based) only require all but the last component to
103 * exist. So if the first call failed, we should strip off
104 * everything from the last slash onwards and try again,
105 * then put the final component back on.
109 * - if the last component is "/." or "/..", then we don't
110 * bother trying this because there's no way it can work.
112 * - if the thing actually ends with a "/", we remove it
113 * before we start. Except if the string is "/" itself
114 * (although I can't see why we'd have got here if so,
115 * because surely "/" would have worked the first
116 * time?), in which case we don't bother.
118 * - if there's no slash in the string at all, give up in
119 * confusion (we expect at least one because of the way
120 * we constructed the string).
126 i = strlen(fullname);
127 if (i > 2 && fullname[i-1] == '/')
128 fullname[--i] = '\0'; /* strip trailing / unless at pos 0 */
129 while (i > 0 && fullname[--i] != '/');
132 * Give up on special cases.
134 if (fullname[i] != '/' || /* no slash at all */
135 !strcmp(fullname+i, "/.") || /* ends in /. */
136 !strcmp(fullname+i, "/..") || /* ends in /.. */
137 !strcmp(fullname, "/")) {
142 * Now i points at the slash. Deal with the final special
143 * case i==0 (ie the whole path was "/nonexistentfile").
145 fullname[i] = '\0'; /* separate the string */
147 canonname = fxp_realpath("/");
149 canonname = fxp_realpath(fullname);
153 return fullname; /* even that failed; give up */
156 * We have a canonical name for all but the last path
157 * component. Concatenate the last component and return.
159 returnname = dupcat(canonname,
160 canonname[strlen(canonname)-1] == '/' ? "" : "/",
168 /* ----------------------------------------------------------------------
169 * Actual sftp commands.
171 struct sftp_command {
173 int nwords, wordssize;
174 int (*obey)(struct sftp_command *);/* returns <0 to quit */
177 int sftp_cmd_null(struct sftp_command *cmd) {
181 int sftp_cmd_unknown(struct sftp_command *cmd) {
182 printf("psftp: unknown command \"%s\"\n", cmd->words[0]);
186 int sftp_cmd_quit(struct sftp_command *cmd) {
191 * List a directory. If no arguments are given, list pwd; otherwise
192 * list the directory given in words[1].
194 static int sftp_ls_compare(const void *av, const void *bv) {
195 const struct fxp_name *a = (const struct fxp_name *)av;
196 const struct fxp_name *b = (const struct fxp_name *)bv;
197 return strcmp(a->filename, b->filename);
199 int sftp_cmd_ls(struct sftp_command *cmd) {
200 struct fxp_handle *dirh;
201 struct fxp_names *names;
202 struct fxp_name *ournames;
203 int nnames, namesize;
212 cdir = canonify(dir);
214 printf("%s: %s\n", dir, fxp_error());
218 printf("Listing directory %s\n", cdir);
220 dirh = fxp_opendir(cdir);
222 printf("Unable to open %s: %s\n", dir, fxp_error());
224 nnames = namesize = 0;
229 names = fxp_readdir(dirh);
231 if (fxp_error_type() == SSH_FX_EOF)
233 printf("Reading directory %s: %s\n", dir, fxp_error());
236 if (names->nnames == 0) {
237 fxp_free_names(names);
241 if (nnames + names->nnames >= namesize) {
242 namesize += names->nnames + 128;
243 ournames = srealloc(ournames, namesize * sizeof(*ournames));
246 for (i = 0; i < names->nnames; i++)
247 ournames[nnames++] = names->names[i];
249 names->nnames = 0; /* prevent free_names */
250 fxp_free_names(names);
255 * Now we have our filenames. Sort them by actual file
256 * name, and then output the longname parts.
258 qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare);
263 for (i = 0; i < nnames; i++)
264 printf("%s\n", ournames[i].longname);
273 * Change directories. We do this by canonifying the new name, then
274 * trying to OPENDIR it. Only if that succeeds do we set the new pwd.
276 int sftp_cmd_cd(struct sftp_command *cmd) {
277 struct fxp_handle *dirh;
281 dir = dupstr(homedir);
283 dir = canonify(cmd->words[1]);
286 printf("%s: %s\n", dir, fxp_error());
290 dirh = fxp_opendir(dir);
292 printf("Directory %s: %s\n", dir, fxp_error());
301 printf("Remote directory is now %s\n", pwd);
307 * Get a file and save it at the local end.
309 int sftp_cmd_get(struct sftp_command *cmd) {
310 struct fxp_handle *fh;
311 char *fname, *outfname;
315 if (cmd->nwords < 2) {
316 printf("get: expects a filename\n");
320 fname = canonify(cmd->words[1]);
322 printf("%s: %s\n", cmd->words[1], fxp_error());
325 outfname = (cmd->nwords == 2 ? cmd->words[1] : cmd->words[2]);
327 fh = fxp_open(fname, SSH_FXF_READ);
329 printf("%s: %s\n", fname, fxp_error());
333 fp = fopen(outfname, "wb");
335 printf("local: unable to open %s\n", outfname);
341 printf("remote:%s => local:%s\n", fname, outfname);
343 offset = uint64_make(0,0);
346 * FIXME: we can use FXP_FSTAT here to get the file size, and
347 * thus put up a progress bar.
354 len = fxp_read(fh, buffer, offset, sizeof(buffer));
355 if ((len == -1 && fxp_error_type() == SSH_FX_EOF) ||
359 printf("error while reading: %s\n", fxp_error());
365 wlen = fwrite(buffer, 1, len-wpos, fp);
367 printf("error while writing local file\n");
372 if (wpos < len) /* we had an error */
374 offset = uint64_add32(offset, len);
385 * Send a file and store it at the remote end.
387 int sftp_cmd_put(struct sftp_command *cmd) {
388 struct fxp_handle *fh;
389 char *fname, *origoutfname, *outfname;
393 if (cmd->nwords < 2) {
394 printf("put: expects a filename\n");
398 fname = cmd->words[1];
399 origoutfname = (cmd->nwords == 2 ? cmd->words[1] : cmd->words[2]);
400 outfname = canonify(origoutfname);
402 printf("%s: %s\n", origoutfname, fxp_error());
406 fp = fopen(fname, "rb");
408 printf("local: unable to open %s\n", fname);
412 fh = fxp_open(outfname, SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC);
414 printf("%s: %s\n", outfname, fxp_error());
419 printf("local:%s => remote:%s\n", fname, outfname);
421 offset = uint64_make(0,0);
424 * FIXME: we can use FXP_FSTAT here to get the file size, and
425 * thus put up a progress bar.
431 len = fread(buffer, 1, sizeof(buffer), fp);
433 printf("error while reading local file\n");
435 } else if (len == 0) {
438 if (!fxp_write(fh, buffer, offset, len)) {
439 printf("error while writing: %s\n", fxp_error());
442 offset = uint64_add32(offset, len);
452 static struct sftp_cmd_lookup {
454 int (*obey)(struct sftp_command *);
457 * List of sftp commands. This is binary-searched so it MUST be
460 {"bye", sftp_cmd_quit},
462 {"dir", sftp_cmd_ls},
463 {"exit", sftp_cmd_quit},
464 {"get", sftp_cmd_get},
466 {"put", sftp_cmd_put},
467 {"quit", sftp_cmd_quit},
470 /* ----------------------------------------------------------------------
471 * Command line reading and parsing.
473 struct sftp_command *sftp_getcmd(void) {
475 int linelen, linesize;
476 struct sftp_command *cmd;
483 cmd = smalloc(sizeof(struct sftp_command));
489 linesize = linelen = 0;
495 line = srealloc(line, linesize);
496 ret = fgets(line+linelen, linesize-linelen, stdin);
498 if (!ret || (linelen == 0 && line[0] == '\0')) {
499 cmd->obey = sftp_cmd_quit;
501 return cmd; /* eof */
503 len = linelen + strlen(line+linelen);
505 if (line[linelen-1] == '\n') {
507 line[linelen] = '\0';
513 * Parse the command line into words. The syntax is:
514 * - double quotes are removed, but cause spaces within to be
515 * treated as non-separating.
516 * - a double-doublequote pair is a literal double quote, inside
517 * _or_ outside quotes. Like this:
519 * firstword "second word" "this has ""quotes"" in" sodoes""this""
525 * >this has "quotes" in<
530 /* skip whitespace */
531 while (*p && (*p == ' ' || *p == '\t')) p++;
532 /* mark start of word */
533 q = r = p; /* q sits at start, r writes word */
536 if (!quoting && (*p == ' ' || *p == '\t'))
537 break; /* reached end of word */
538 else if (*p == '"' && p[1] == '"')
539 p+=2, *r++ = '"'; /* a literal quote */
541 p++, quoting = !quoting;
545 if (*p) p++; /* skip over the whitespace */
547 if (cmd->nwords >= cmd->wordssize) {
548 cmd->wordssize = cmd->nwords + 16;
549 cmd->words = srealloc(cmd->words, cmd->wordssize*sizeof(char *));
551 cmd->words[cmd->nwords++] = q;
555 * Now parse the first word and assign a function.
558 if (cmd->nwords == 0)
559 cmd->obey = sftp_cmd_null;
563 cmd->obey = sftp_cmd_unknown;
566 j = sizeof(sftp_lookup) / sizeof(*sftp_lookup);
569 cmp = strcmp(cmd->words[0], sftp_lookup[k].name);
575 cmd->obey = sftp_lookup[k].obey;
586 * Do protocol initialisation.
590 "Fatal: unable to initialise SFTP: %s\n",
596 * Find out where our home directory is.
598 homedir = fxp_realpath(".");
601 "Warning: failed to resolve home directory: %s\n",
603 homedir = dupstr(".");
605 printf("Remote working directory is %s\n", homedir);
607 pwd = dupstr(homedir);
609 /* ------------------------------------------------------------------
610 * Now we're ready to do Real Stuff.
613 struct sftp_command *cmd;
617 if (cmd->obey(cmd) < 0)
622 /* ----------------------------------------------------------------------
623 * Dirty bits: integration with PuTTY.
626 static int verbose = 0;
628 void verify_ssh_host_key(char *host, int port, char *keytype,
629 char *keystr, char *fingerprint) {
632 static const char absentmsg[] =
633 "The server's host key is not cached in the registry. You\n"
634 "have no guarantee that the server is the computer you\n"
636 "The server's key fingerprint is:\n"
638 "If you trust this host, enter \"y\" to add the key to\n"
639 "PuTTY's cache and carry on connecting.\n"
640 "If you do not trust this host, enter \"n\" to abandon the\n"
642 "Continue connecting? (y/n) ";
644 static const char wrongmsg[] =
645 "WARNING - POTENTIAL SECURITY BREACH!\n"
646 "The server's host key does not match the one PuTTY has\n"
647 "cached in the registry. This means that either the\n"
648 "server administrator has changed the host key, or you\n"
649 "have actually connected to another computer pretending\n"
650 "to be the server.\n"
651 "The new key fingerprint is:\n"
653 "If you were expecting this change and trust the new key,\n"
654 "enter Yes to update PuTTY's cache and continue connecting.\n"
655 "If you want to carry on connecting but without updating\n"
656 "the cache, enter No.\n"
657 "If you want to abandon the connection completely, press\n"
658 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
660 "Update cached key? (y/n, Return cancels connection) ";
662 static const char abandoned[] = "Connection abandoned.\n";
667 * Verify the key against the registry.
669 ret = verify_host_key(host, port, keytype, keystr);
671 if (ret == 0) /* success - key matched OK */
673 if (ret == 2) { /* key was different */
674 fprintf(stderr, wrongmsg, fingerprint);
675 if (fgets(line, sizeof(line), stdin) &&
676 line[0] != '\0' && line[0] != '\n') {
677 if (line[0] == 'y' || line[0] == 'Y')
678 store_host_key(host, port, keytype, keystr);
680 fprintf(stderr, abandoned);
684 if (ret == 1) { /* key was absent */
685 fprintf(stderr, absentmsg, fingerprint);
686 if (fgets(line, sizeof(line), stdin) &&
687 (line[0] == 'y' || line[0] == 'Y'))
688 store_host_key(host, port, keytype, keystr);
690 fprintf(stderr, abandoned);
697 * Print an error message and perform a fatal exit.
699 void fatalbox(char *fmt, ...)
701 char str[0x100]; /* Make the size big enough */
704 strcpy(str, "Fatal:");
705 vsprintf(str+strlen(str), fmt, ap);
708 fprintf(stderr, str);
712 void connection_fatal(char *fmt, ...)
714 char str[0x100]; /* Make the size big enough */
717 strcpy(str, "Fatal:");
718 vsprintf(str+strlen(str), fmt, ap);
721 fprintf(stderr, str);
726 void logevent(char *string) { }
728 void ldisc_send(char *buf, int len) {
730 * This is only here because of the calls to ldisc_send(NULL,
731 * 0) in ssh.c. Nothing in PSFTP actually needs to use the
732 * ldisc as an ldisc. So if we get called with any real data, I
733 * want to know about it.
739 * Be told what socket we're supposed to be using.
741 static SOCKET sftp_ssh_socket;
742 char *do_select(SOCKET skt, int startup) {
744 sftp_ssh_socket = skt;
746 sftp_ssh_socket = INVALID_SOCKET;
749 extern int select_result(WPARAM, LPARAM);
752 * Receive a block of data from the SSH link. Block until all data
755 * To do this, we repeatedly call the SSH protocol module, with our
756 * own trap in from_backend() to catch the data that comes back. We
757 * do this until we have enough data.
760 static unsigned char *outptr; /* where to put the data */
761 static unsigned outlen; /* how much data required */
762 static unsigned char *pending = NULL; /* any spare data */
763 static unsigned pendlen=0, pendsize=0; /* length and phys. size of buffer */
764 void from_backend(int is_stderr, char *data, int datalen) {
765 unsigned char *p = (unsigned char *)data;
766 unsigned len = (unsigned)datalen;
769 * stderr data is just spouted to local stderr and otherwise
773 fwrite(data, 1, len, stderr);
778 * If this is before the real session begins, just return.
784 unsigned used = outlen;
785 if (used > len) used = len;
786 memcpy(outptr, p, used);
787 outptr += used; outlen -= used;
788 p += used; len -= used;
792 if (pendsize < pendlen + len) {
793 pendsize = pendlen + len + 4096;
794 pending = (pending ? srealloc(pending, pendsize) :
797 fatalbox("Out of memory");
799 memcpy(pending+pendlen, p, len);
803 int sftp_recvdata(char *buf, int len) {
804 outptr = (unsigned char *)buf;
808 * See if the pending-input block contains some of what we
812 unsigned pendused = pendlen;
813 if (pendused > outlen)
815 memcpy(outptr, pending, pendused);
816 memmove(pending, pending+pendused, pendlen-pendused);
833 FD_SET(sftp_ssh_socket, &readfds);
834 if (select(1, &readfds, NULL, NULL, NULL) < 0)
836 select_result((WPARAM)sftp_ssh_socket, (LPARAM)FD_READ);
841 int sftp_senddata(char *buf, int len) {
842 back->send((unsigned char *)buf, len);
847 * Loop through the ssh connection and authentication process.
849 static void ssh_sftp_init(void) {
850 if (sftp_ssh_socket == INVALID_SOCKET)
852 while (!back->sendok()) {
855 FD_SET(sftp_ssh_socket, &readfds);
856 if (select(1, &readfds, NULL, NULL, NULL) < 0)
858 select_result((WPARAM)sftp_ssh_socket, (LPARAM)FD_READ);
862 static char *password = NULL;
863 static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
866 DWORD savemode, newmode, i;
869 static int tried_once = 0;
874 strncpy(str, password, maxlen);
875 str[maxlen-1] = '\0';
881 hin = GetStdHandle(STD_INPUT_HANDLE);
882 hout = GetStdHandle(STD_OUTPUT_HANDLE);
883 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
884 fprintf(stderr, "Cannot get standard input/output handles\n");
888 GetConsoleMode(hin, &savemode);
889 newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
891 newmode &= ~ENABLE_ECHO_INPUT;
893 newmode |= ENABLE_ECHO_INPUT;
894 SetConsoleMode(hin, newmode);
896 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
897 ReadFile(hin, str, maxlen-1, &i, NULL);
899 SetConsoleMode(hin, savemode);
901 if ((int)i > maxlen) i = maxlen-1; else i = i - 2;
905 WriteFile(hout, "\r\n", 2, &i, NULL);
911 * Initialize the Win$ock driver.
913 static void init_winsock(void)
918 winsock_ver = MAKEWORD(1, 1);
919 if (WSAStartup(winsock_ver, &wsadata)) {
920 fprintf(stderr, "Unable to initialise WinSock");
923 if (LOBYTE(wsadata.wVersion) != 1 ||
924 HIBYTE(wsadata.wVersion) != 1) {
925 fprintf(stderr, "WinSock version is incompatible with 1.1");
931 * Short description of parameters.
933 static void usage(void)
935 printf("PuTTY Secure File Transfer (SFTP) client\n");
937 printf("Usage: psftp [options] user@host\n");
938 printf("Options:\n");
939 printf(" -v show verbose messages\n");
940 printf(" -P port connect to specified port\n");
941 printf(" -pw passw login with specified password\n");
946 * Main program. Parse arguments etc.
948 int main(int argc, char *argv[])
952 char *user, *host, *userhost, *realhost;
956 ssh_get_line = &get_line;
960 userhost = user = NULL;
962 for (i = 1; i < argc; i++) {
963 if (argv[i][0] != '-') {
967 userhost = dupstr(argv[i]);
968 } else if (strcmp(argv[i], "-v") == 0) {
969 verbose = 1, flags |= FLAG_VERBOSE;
970 } else if (strcmp(argv[i], "-h") == 0 ||
971 strcmp(argv[i], "-?") == 0) {
973 } else if (strcmp(argv[i], "-l") == 0 && i+1 < argc) {
975 } else if (strcmp(argv[i], "-P") == 0 && i+1 < argc) {
976 portnumber = atoi(argv[++i]);
977 } else if (strcmp(argv[i], "-pw") == 0 && i+1 < argc) {
978 password = argv[++i];
979 } else if (strcmp(argv[i], "--") == 0) {
990 if (argc > 0 || !userhost)
993 /* Separate host and username */
995 host = strrchr(host, '@');
1001 printf("psftp: multiple usernames specified; using \"%s\"\n", user);
1006 /* Try to load settings for this host */
1007 do_defaults(host, &cfg);
1008 if (cfg.host[0] == '\0') {
1009 /* No settings for this host; use defaults */
1010 do_defaults(NULL, &cfg);
1011 strncpy(cfg.host, host, sizeof(cfg.host)-1);
1012 cfg.host[sizeof(cfg.host)-1] = '\0';
1017 if (user != NULL && user[0] != '\0') {
1018 strncpy(cfg.username, user, sizeof(cfg.username)-1);
1019 cfg.username[sizeof(cfg.username)-1] = '\0';
1021 if (!cfg.username[0]) {
1022 printf("login as: ");
1023 if (!fgets(cfg.username, sizeof(cfg.username), stdin)) {
1024 fprintf(stderr, "psftp: aborting\n");
1027 int len = strlen(cfg.username);
1028 if (cfg.username[len-1] == '\n')
1029 cfg.username[len-1] = '\0';
1033 if (cfg.protocol != PROT_SSH)
1037 cfg.port = portnumber;
1039 /* SFTP uses SSH2 by default always */
1042 /* Set up subsystem name. FIXME: fudge for SSH1. */
1043 strcpy(cfg.remote_cmd, "sftp");
1044 cfg.ssh_subsys = TRUE;
1047 back = &ssh_backend;
1049 err = back->init(cfg.host, cfg.port, &realhost);
1051 fprintf(stderr, "ssh_init: %s", err);
1055 if (verbose && realhost != NULL)
1056 printf("Connected to %s\n", realhost);
1060 if (back != NULL && back->socket() != NULL) {
1062 back->special(TS_EOF);
1063 sftp_recvdata(&ch, 1);