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);
413 fh = fxp_open(outfname, SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC);
415 printf("%s: %s\n", outfname, fxp_error());
420 printf("local:%s => remote:%s\n", fname, outfname);
422 offset = uint64_make(0,0);
425 * FIXME: we can use FXP_FSTAT here to get the file size, and
426 * thus put up a progress bar.
432 len = fread(buffer, 1, sizeof(buffer), fp);
434 printf("error while reading local file\n");
436 } else if (len == 0) {
439 if (!fxp_write(fh, buffer, offset, len)) {
440 printf("error while writing: %s\n", fxp_error());
443 offset = uint64_add32(offset, len);
453 static struct sftp_cmd_lookup {
455 int (*obey)(struct sftp_command *);
458 * List of sftp commands. This is binary-searched so it MUST be
461 {"bye", sftp_cmd_quit},
463 {"dir", sftp_cmd_ls},
464 {"exit", sftp_cmd_quit},
465 {"get", sftp_cmd_get},
467 {"put", sftp_cmd_put},
468 {"quit", sftp_cmd_quit},
471 /* ----------------------------------------------------------------------
472 * Command line reading and parsing.
474 struct sftp_command *sftp_getcmd(void) {
476 int linelen, linesize;
477 struct sftp_command *cmd;
484 cmd = smalloc(sizeof(struct sftp_command));
490 linesize = linelen = 0;
496 line = srealloc(line, linesize);
497 ret = fgets(line+linelen, linesize-linelen, stdin);
499 if (!ret || (linelen == 0 && line[0] == '\0')) {
500 cmd->obey = sftp_cmd_quit;
502 return cmd; /* eof */
504 len = linelen + strlen(line+linelen);
506 if (line[linelen-1] == '\n') {
508 line[linelen] = '\0';
514 * Parse the command line into words. The syntax is:
515 * - double quotes are removed, but cause spaces within to be
516 * treated as non-separating.
517 * - a double-doublequote pair is a literal double quote, inside
518 * _or_ outside quotes. Like this:
520 * firstword "second word" "this has ""quotes"" in" sodoes""this""
526 * >this has "quotes" in<
531 /* skip whitespace */
532 while (*p && (*p == ' ' || *p == '\t')) p++;
533 /* mark start of word */
534 q = r = p; /* q sits at start, r writes word */
537 if (!quoting && (*p == ' ' || *p == '\t'))
538 break; /* reached end of word */
539 else if (*p == '"' && p[1] == '"')
540 p+=2, *r++ = '"'; /* a literal quote */
542 p++, quoting = !quoting;
546 if (*p) p++; /* skip over the whitespace */
548 if (cmd->nwords >= cmd->wordssize) {
549 cmd->wordssize = cmd->nwords + 16;
550 cmd->words = srealloc(cmd->words, cmd->wordssize*sizeof(char *));
552 cmd->words[cmd->nwords++] = q;
556 * Now parse the first word and assign a function.
559 if (cmd->nwords == 0)
560 cmd->obey = sftp_cmd_null;
564 cmd->obey = sftp_cmd_unknown;
567 j = sizeof(sftp_lookup) / sizeof(*sftp_lookup);
570 cmp = strcmp(cmd->words[0], sftp_lookup[k].name);
576 cmd->obey = sftp_lookup[k].obey;
587 * Do protocol initialisation.
591 "Fatal: unable to initialise SFTP: %s\n",
597 * Find out where our home directory is.
599 homedir = fxp_realpath(".");
602 "Warning: failed to resolve home directory: %s\n",
604 homedir = dupstr(".");
606 printf("Remote working directory is %s\n", homedir);
608 pwd = dupstr(homedir);
610 /* ------------------------------------------------------------------
611 * Now we're ready to do Real Stuff.
614 struct sftp_command *cmd;
618 if (cmd->obey(cmd) < 0)
623 /* ----------------------------------------------------------------------
624 * Dirty bits: integration with PuTTY.
627 static int verbose = 0;
629 void verify_ssh_host_key(char *host, int port, char *keytype,
630 char *keystr, char *fingerprint) {
633 static const char absentmsg[] =
634 "The server's host key is not cached in the registry. You\n"
635 "have no guarantee that the server is the computer you\n"
637 "The server's key fingerprint is:\n"
639 "If you trust this host, enter \"y\" to add the key to\n"
640 "PuTTY's cache and carry on connecting.\n"
641 "If you do not trust this host, enter \"n\" to abandon the\n"
643 "Continue connecting? (y/n) ";
645 static const char wrongmsg[] =
646 "WARNING - POTENTIAL SECURITY BREACH!\n"
647 "The server's host key does not match the one PuTTY has\n"
648 "cached in the registry. This means that either the\n"
649 "server administrator has changed the host key, or you\n"
650 "have actually connected to another computer pretending\n"
651 "to be the server.\n"
652 "The new key fingerprint is:\n"
654 "If you were expecting this change and trust the new key,\n"
655 "enter Yes to update PuTTY's cache and continue connecting.\n"
656 "If you want to carry on connecting but without updating\n"
657 "the cache, enter No.\n"
658 "If you want to abandon the connection completely, press\n"
659 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
661 "Update cached key? (y/n, Return cancels connection) ";
663 static const char abandoned[] = "Connection abandoned.\n";
668 * Verify the key against the registry.
670 ret = verify_host_key(host, port, keytype, keystr);
672 if (ret == 0) /* success - key matched OK */
674 if (ret == 2) { /* key was different */
675 fprintf(stderr, wrongmsg, fingerprint);
676 if (fgets(line, sizeof(line), stdin) &&
677 line[0] != '\0' && line[0] != '\n') {
678 if (line[0] == 'y' || line[0] == 'Y')
679 store_host_key(host, port, keytype, keystr);
681 fprintf(stderr, abandoned);
685 if (ret == 1) { /* key was absent */
686 fprintf(stderr, absentmsg, fingerprint);
687 if (fgets(line, sizeof(line), stdin) &&
688 (line[0] == 'y' || line[0] == 'Y'))
689 store_host_key(host, port, keytype, keystr);
691 fprintf(stderr, abandoned);
698 * Print an error message and perform a fatal exit.
700 void fatalbox(char *fmt, ...)
702 char str[0x100]; /* Make the size big enough */
705 strcpy(str, "Fatal:");
706 vsprintf(str+strlen(str), fmt, ap);
709 fprintf(stderr, str);
713 void connection_fatal(char *fmt, ...)
715 char str[0x100]; /* Make the size big enough */
718 strcpy(str, "Fatal:");
719 vsprintf(str+strlen(str), fmt, ap);
722 fprintf(stderr, str);
727 void logevent(char *string) { }
729 void ldisc_send(char *buf, int len) {
731 * This is only here because of the calls to ldisc_send(NULL,
732 * 0) in ssh.c. Nothing in PSFTP actually needs to use the
733 * ldisc as an ldisc. So if we get called with any real data, I
734 * want to know about it.
740 * Be told what socket we're supposed to be using.
742 static SOCKET sftp_ssh_socket;
743 char *do_select(SOCKET skt, int startup) {
745 sftp_ssh_socket = skt;
747 sftp_ssh_socket = INVALID_SOCKET;
750 extern int select_result(WPARAM, LPARAM);
753 * Receive a block of data from the SSH link. Block until all data
756 * To do this, we repeatedly call the SSH protocol module, with our
757 * own trap in from_backend() to catch the data that comes back. We
758 * do this until we have enough data.
761 static unsigned char *outptr; /* where to put the data */
762 static unsigned outlen; /* how much data required */
763 static unsigned char *pending = NULL; /* any spare data */
764 static unsigned pendlen=0, pendsize=0; /* length and phys. size of buffer */
765 void from_backend(int is_stderr, char *data, int datalen) {
766 unsigned char *p = (unsigned char *)data;
767 unsigned len = (unsigned)datalen;
770 * stderr data is just spouted to local stderr and otherwise
774 fwrite(data, 1, len, stderr);
779 * If this is before the real session begins, just return.
785 unsigned used = outlen;
786 if (used > len) used = len;
787 memcpy(outptr, p, used);
788 outptr += used; outlen -= used;
789 p += used; len -= used;
793 if (pendsize < pendlen + len) {
794 pendsize = pendlen + len + 4096;
795 pending = (pending ? srealloc(pending, pendsize) :
798 fatalbox("Out of memory");
800 memcpy(pending+pendlen, p, len);
804 int sftp_recvdata(char *buf, int len) {
805 outptr = (unsigned char *)buf;
809 * See if the pending-input block contains some of what we
813 unsigned pendused = pendlen;
814 if (pendused > outlen)
816 memcpy(outptr, pending, pendused);
817 memmove(pending, pending+pendused, pendlen-pendused);
834 FD_SET(sftp_ssh_socket, &readfds);
835 if (select(1, &readfds, NULL, NULL, NULL) < 0)
837 select_result((WPARAM)sftp_ssh_socket, (LPARAM)FD_READ);
842 int sftp_senddata(char *buf, int len) {
843 back->send((unsigned char *)buf, len);
848 * Loop through the ssh connection and authentication process.
850 static void ssh_sftp_init(void) {
851 if (sftp_ssh_socket == INVALID_SOCKET)
853 while (!back->sendok()) {
856 FD_SET(sftp_ssh_socket, &readfds);
857 if (select(1, &readfds, NULL, NULL, NULL) < 0)
859 select_result((WPARAM)sftp_ssh_socket, (LPARAM)FD_READ);
863 static char *password = NULL;
864 static int get_password(const char *prompt, char *str, int maxlen)
870 static int tried_once = 0;
875 strncpy(str, password, maxlen);
876 str[maxlen-1] = '\0';
882 hin = GetStdHandle(STD_INPUT_HANDLE);
883 hout = GetStdHandle(STD_OUTPUT_HANDLE);
884 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
885 fprintf(stderr, "Cannot get standard input/output handles\n");
889 GetConsoleMode(hin, &savemode);
890 SetConsoleMode(hin, (savemode & (~ENABLE_ECHO_INPUT)) |
891 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT);
893 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
894 ReadFile(hin, str, maxlen-1, &i, NULL);
896 SetConsoleMode(hin, savemode);
898 if ((int)i > maxlen) i = maxlen-1; else i = i - 2;
901 WriteFile(hout, "\r\n", 2, &i, NULL);
907 * Initialize the Win$ock driver.
909 static void init_winsock(void)
914 winsock_ver = MAKEWORD(1, 1);
915 if (WSAStartup(winsock_ver, &wsadata)) {
916 fprintf(stderr, "Unable to initialise WinSock");
919 if (LOBYTE(wsadata.wVersion) != 1 ||
920 HIBYTE(wsadata.wVersion) != 1) {
921 fprintf(stderr, "WinSock version is incompatible with 1.1");
927 * Short description of parameters.
929 static void usage(void)
931 printf("PuTTY Secure File Transfer (SFTP) client\n");
933 printf("Usage: psftp [options] user@host\n");
934 printf("Options:\n");
935 printf(" -v show verbose messages\n");
936 printf(" -P port connect to specified port\n");
937 printf(" -pw passw login with specified password\n");
942 * Main program. Parse arguments etc.
944 int main(int argc, char *argv[])
948 char *user, *host, *userhost, *realhost;
952 ssh_get_password = &get_password;
956 userhost = user = NULL;
958 for (i = 1; i < argc; i++) {
959 if (argv[i][0] != '-') {
963 userhost = dupstr(argv[i]);
964 } else if (strcmp(argv[i], "-v") == 0) {
965 verbose = 1, flags |= FLAG_VERBOSE;
966 } else if (strcmp(argv[i], "-h") == 0 ||
967 strcmp(argv[i], "-?") == 0) {
969 } else if (strcmp(argv[i], "-l") == 0 && i+1 < argc) {
971 } else if (strcmp(argv[i], "-P") == 0 && i+1 < argc) {
972 portnumber = atoi(argv[++i]);
973 } else if (strcmp(argv[i], "-pw") == 0 && i+1 < argc) {
974 password = argv[++i];
975 } else if (strcmp(argv[i], "--") == 0) {
986 if (argc > 0 || !userhost)
989 /* Separate host and username */
991 host = strrchr(host, '@');
997 printf("psftp: multiple usernames specified; using \"%s\"\n", user);
1002 /* Try to load settings for this host */
1003 do_defaults(host, &cfg);
1004 if (cfg.host[0] == '\0') {
1005 /* No settings for this host; use defaults */
1006 do_defaults(NULL, &cfg);
1007 strncpy(cfg.host, host, sizeof(cfg.host)-1);
1008 cfg.host[sizeof(cfg.host)-1] = '\0';
1013 if (user != NULL && user[0] != '\0') {
1014 strncpy(cfg.username, user, sizeof(cfg.username)-1);
1015 cfg.username[sizeof(cfg.username)-1] = '\0';
1017 if (!cfg.username[0]) {
1018 printf("login as: ");
1019 if (!fgets(cfg.username, sizeof(cfg.username), stdin)) {
1020 fprintf(stderr, "psftp: aborting\n");
1023 int len = strlen(cfg.username);
1024 if (cfg.username[len-1] == '\n')
1025 cfg.username[len-1] = '\0';
1029 if (cfg.protocol != PROT_SSH)
1033 cfg.port = portnumber;
1035 /* SFTP uses SSH2 by default always */
1038 /* Set up subsystem name. FIXME: fudge for SSH1. */
1039 strcpy(cfg.remote_cmd, "sftp");
1040 cfg.ssh_subsys = TRUE;
1043 back = &ssh_backend;
1045 err = back->init(cfg.host, cfg.port, &realhost);
1047 fprintf(stderr, "ssh_init: %s", err);
1051 if (verbose && realhost != NULL)
1052 printf("Connected to %s\n", realhost);
1056 if (back != NULL && back->socket() != NULL) {
1058 back->special(TS_EOF);
1059 sftp_recvdata(&ch, 1);