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_password(const char *prompt
, char *str
, int maxlen
)
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 SetConsoleMode(hin
, (savemode
& (~ENABLE_ECHO_INPUT
)) |
890 ENABLE_PROCESSED_INPUT
| ENABLE_LINE_INPUT
);
892 WriteFile(hout
, prompt
, strlen(prompt
), &i
, NULL
);
893 ReadFile(hin
, str
, maxlen
-1, &i
, NULL
);
895 SetConsoleMode(hin
, savemode
);
897 if ((int)i
> maxlen
) i
= maxlen
-1; else i
= i
- 2;
900 WriteFile(hout
, "\r\n", 2, &i
, NULL
);
906 * Initialize the Win$ock driver.
908 static void init_winsock(void)
913 winsock_ver
= MAKEWORD(1, 1);
914 if (WSAStartup(winsock_ver
, &wsadata
)) {
915 fprintf(stderr
, "Unable to initialise WinSock");
918 if (LOBYTE(wsadata
.wVersion
) != 1 ||
919 HIBYTE(wsadata
.wVersion
) != 1) {
920 fprintf(stderr
, "WinSock version is incompatible with 1.1");
926 * Short description of parameters.
928 static void usage(void)
930 printf("PuTTY Secure File Transfer (SFTP) client\n");
932 printf("Usage: psftp [options] user@host\n");
933 printf("Options:\n");
934 printf(" -v show verbose messages\n");
935 printf(" -P port connect to specified port\n");
936 printf(" -pw passw login with specified password\n");
941 * Main program. Parse arguments etc.
943 int main(int argc
, char *argv
[])
947 char *user
, *host
, *userhost
, *realhost
;
951 ssh_get_password
= &get_password
;
955 userhost
= user
= NULL
;
957 for (i
= 1; i
< argc
; i
++) {
958 if (argv
[i
][0] != '-') {
962 userhost
= dupstr(argv
[i
]);
963 } else if (strcmp(argv
[i
], "-v") == 0) {
964 verbose
= 1, flags
|= FLAG_VERBOSE
;
965 } else if (strcmp(argv
[i
], "-h") == 0 ||
966 strcmp(argv
[i
], "-?") == 0) {
968 } else if (strcmp(argv
[i
], "-l") == 0 && i
+1 < argc
) {
970 } else if (strcmp(argv
[i
], "-P") == 0 && i
+1 < argc
) {
971 portnumber
= atoi(argv
[++i
]);
972 } else if (strcmp(argv
[i
], "-pw") == 0 && i
+1 < argc
) {
973 password
= argv
[++i
];
974 } else if (strcmp(argv
[i
], "--") == 0) {
985 if (argc
> 0 || !userhost
)
988 /* Separate host and username */
990 host
= strrchr(host
, '@');
996 printf("psftp: multiple usernames specified; using \"%s\"\n", user
);
1001 /* Try to load settings for this host */
1002 do_defaults(host
, &cfg
);
1003 if (cfg
.host
[0] == '\0') {
1004 /* No settings for this host; use defaults */
1005 do_defaults(NULL
, &cfg
);
1006 strncpy(cfg
.host
, host
, sizeof(cfg
.host
)-1);
1007 cfg
.host
[sizeof(cfg
.host
)-1] = '\0';
1012 if (user
!= NULL
&& user
[0] != '\0') {
1013 strncpy(cfg
.username
, user
, sizeof(cfg
.username
)-1);
1014 cfg
.username
[sizeof(cfg
.username
)-1] = '\0';
1016 if (!cfg
.username
[0]) {
1017 printf("login as: ");
1018 if (!fgets(cfg
.username
, sizeof(cfg
.username
), stdin
)) {
1019 fprintf(stderr
, "psftp: aborting\n");
1022 int len
= strlen(cfg
.username
);
1023 if (cfg
.username
[len
-1] == '\n')
1024 cfg
.username
[len
-1] = '\0';
1028 if (cfg
.protocol
!= PROT_SSH
)
1032 cfg
.port
= portnumber
;
1034 /* SFTP uses SSH2 by default always */
1037 /* Set up subsystem name. FIXME: fudge for SSH1. */
1038 strcpy(cfg
.remote_cmd
, "sftp");
1039 cfg
.ssh_subsys
= TRUE
;
1042 back
= &ssh_backend
;
1044 err
= back
->init(cfg
.host
, cfg
.port
, &realhost
);
1046 fprintf(stderr
, "ssh_init: %s", err
);
1050 if (verbose
&& realhost
!= NULL
)
1051 printf("Connected to %s\n", realhost
);
1055 if (back
!= NULL
&& back
->socket() != NULL
) {
1057 back
->special(TS_EOF
);
1058 sftp_recvdata(&ch
, 1);