2 * psftp.c: front end for PSFTP.
13 #define PUTTY_DO_GLOBALS
21 * Since SFTP is a request-response oriented protocol, it requires
22 * no buffer management: when we send data, we stop and wait for an
23 * acknowledgement _anyway_, and so we can't possibly overfill our
27 /* ----------------------------------------------------------------------
33 /* ----------------------------------------------------------------------
34 * Higher-level helper functions used in commands.
38 * Attempt to canonify a pathname starting from the pwd. If
39 * canonification fails, at least fall back to returning a _valid_
40 * pathname (though it may be ugly, eg /home/simon/../foobar).
42 char *canonify(char *name
)
44 char *fullname
, *canonname
;
47 fullname
= dupstr(name
);
50 if (pwd
[strlen(pwd
) - 1] == '/')
54 fullname
= dupcat(pwd
, slash
, name
, NULL
);
57 canonname
= fxp_realpath(fullname
);
64 * Attempt number 2. Some FXP_REALPATH implementations
65 * (glibc-based ones, in particular) require the _whole_
66 * path to point to something that exists, whereas others
67 * (BSD-based) only require all but the last component to
68 * exist. So if the first call failed, we should strip off
69 * everything from the last slash onwards and try again,
70 * then put the final component back on.
74 * - if the last component is "/." or "/..", then we don't
75 * bother trying this because there's no way it can work.
77 * - if the thing actually ends with a "/", we remove it
78 * before we start. Except if the string is "/" itself
79 * (although I can't see why we'd have got here if so,
80 * because surely "/" would have worked the first
81 * time?), in which case we don't bother.
83 * - if there's no slash in the string at all, give up in
84 * confusion (we expect at least one because of the way
85 * we constructed the string).
92 if (i
> 2 && fullname
[i
- 1] == '/')
93 fullname
[--i
] = '\0'; /* strip trailing / unless at pos 0 */
94 while (i
> 0 && fullname
[--i
] != '/');
97 * Give up on special cases.
99 if (fullname
[i
] != '/' || /* no slash at all */
100 !strcmp(fullname
+ i
, "/.") || /* ends in /. */
101 !strcmp(fullname
+ i
, "/..") || /* ends in /.. */
102 !strcmp(fullname
, "/")) {
107 * Now i points at the slash. Deal with the final special
108 * case i==0 (ie the whole path was "/nonexistentfile").
110 fullname
[i
] = '\0'; /* separate the string */
112 canonname
= fxp_realpath("/");
114 canonname
= fxp_realpath(fullname
);
118 return fullname
; /* even that failed; give up */
121 * We have a canonical name for all but the last path
122 * component. Concatenate the last component and return.
124 returnname
= dupcat(canonname
,
125 canonname
[strlen(canonname
) - 1] ==
126 '/' ?
"" : "/", fullname
+ i
+ 1, NULL
);
133 /* ----------------------------------------------------------------------
134 * Actual sftp commands.
136 struct sftp_command
{
138 int nwords
, wordssize
;
139 int (*obey
) (struct sftp_command
*); /* returns <0 to quit */
142 int sftp_cmd_null(struct sftp_command
*cmd
)
147 int sftp_cmd_unknown(struct sftp_command
*cmd
)
149 printf("psftp: unknown command \"%s\"\n", cmd
->words
[0]);
153 int sftp_cmd_quit(struct sftp_command
*cmd
)
159 * List a directory. If no arguments are given, list pwd; otherwise
160 * list the directory given in words[1].
162 static int sftp_ls_compare(const void *av
, const void *bv
)
164 const struct fxp_name
*a
= (const struct fxp_name
*) av
;
165 const struct fxp_name
*b
= (const struct fxp_name
*) bv
;
166 return strcmp(a
->filename
, b
->filename
);
168 int sftp_cmd_ls(struct sftp_command
*cmd
)
170 struct fxp_handle
*dirh
;
171 struct fxp_names
*names
;
172 struct fxp_name
*ournames
;
173 int nnames
, namesize
;
182 cdir
= canonify(dir
);
184 printf("%s: %s\n", dir
, fxp_error());
188 printf("Listing directory %s\n", cdir
);
190 dirh
= fxp_opendir(cdir
);
192 printf("Unable to open %s: %s\n", dir
, fxp_error());
194 nnames
= namesize
= 0;
199 names
= fxp_readdir(dirh
);
201 if (fxp_error_type() == SSH_FX_EOF
)
203 printf("Reading directory %s: %s\n", dir
, fxp_error());
206 if (names
->nnames
== 0) {
207 fxp_free_names(names
);
211 if (nnames
+ names
->nnames
>= namesize
) {
212 namesize
+= names
->nnames
+ 128;
214 srealloc(ournames
, namesize
* sizeof(*ournames
));
217 for (i
= 0; i
< names
->nnames
; i
++)
218 ournames
[nnames
++] = names
->names
[i
];
220 names
->nnames
= 0; /* prevent free_names */
221 fxp_free_names(names
);
226 * Now we have our filenames. Sort them by actual file
227 * name, and then output the longname parts.
229 qsort(ournames
, nnames
, sizeof(*ournames
), sftp_ls_compare
);
234 for (i
= 0; i
< nnames
; i
++)
235 printf("%s\n", ournames
[i
].longname
);
244 * Change directories. We do this by canonifying the new name, then
245 * trying to OPENDIR it. Only if that succeeds do we set the new pwd.
247 int sftp_cmd_cd(struct sftp_command
*cmd
)
249 struct fxp_handle
*dirh
;
253 dir
= dupstr(homedir
);
255 dir
= canonify(cmd
->words
[1]);
258 printf("%s: %s\n", dir
, fxp_error());
262 dirh
= fxp_opendir(dir
);
264 printf("Directory %s: %s\n", dir
, fxp_error());
273 printf("Remote directory is now %s\n", pwd
);
279 * Print current directory. Easy as pie.
281 int sftp_cmd_pwd(struct sftp_command
*cmd
)
283 printf("Remote directory is %s\n", pwd
);
288 * Get a file and save it at the local end. We have two very
289 * similar commands here: `get' and `reget', which differ in that
290 * `reget' checks for the existence of the destination file and
291 * starts from where a previous aborted transfer left off.
293 int sftp_general_get(struct sftp_command
*cmd
, int restart
)
295 struct fxp_handle
*fh
;
296 char *fname
, *outfname
;
300 if (cmd
->nwords
< 2) {
301 printf("get: expects a filename\n");
305 fname
= canonify(cmd
->words
[1]);
307 printf("%s: %s\n", cmd
->words
[1], fxp_error());
310 outfname
= (cmd
->nwords
== 2 ? cmd
->words
[1] : cmd
->words
[2]);
312 fh
= fxp_open(fname
, SSH_FXF_READ
);
314 printf("%s: %s\n", fname
, fxp_error());
320 fp
= fopen(outfname
, "rb+");
322 fp
= fopen(outfname
, "wb");
326 printf("local: unable to open %s\n", outfname
);
334 fseek(fp
, 0L, SEEK_END
);
336 printf("reget: restarting at file position %ld\n", posn
);
337 offset
= uint64_make(0, posn
);
339 offset
= uint64_make(0, 0);
342 printf("remote:%s => local:%s\n", fname
, outfname
);
345 * FIXME: we can use FXP_FSTAT here to get the file size, and
346 * thus put up a progress bar.
353 len
= fxp_read(fh
, buffer
, offset
, sizeof(buffer
));
354 if ((len
== -1 && fxp_error_type() == SSH_FX_EOF
) || len
== 0)
357 printf("error while reading: %s\n", fxp_error());
363 wlen
= fwrite(buffer
, 1, len
- wpos
, fp
);
365 printf("error while writing local file\n");
370 if (wpos
< len
) /* we had an error */
372 offset
= uint64_add32(offset
, len
);
381 int sftp_cmd_get(struct sftp_command
*cmd
)
383 return sftp_general_get(cmd
, 0);
385 int sftp_cmd_reget(struct sftp_command
*cmd
)
387 return sftp_general_get(cmd
, 1);
391 * Send a file and store it at the remote end. We have two very
392 * similar commands here: `put' and `reput', which differ in that
393 * `reput' checks for the existence of the destination file and
394 * starts from where a previous aborted transfer left off.
396 int sftp_general_put(struct sftp_command
*cmd
, int restart
)
398 struct fxp_handle
*fh
;
399 char *fname
, *origoutfname
, *outfname
;
403 if (cmd
->nwords
< 2) {
404 printf("put: expects a filename\n");
408 fname
= cmd
->words
[1];
409 origoutfname
= (cmd
->nwords
== 2 ? cmd
->words
[1] : cmd
->words
[2]);
410 outfname
= canonify(origoutfname
);
412 printf("%s: %s\n", origoutfname
, fxp_error());
416 fp
= fopen(fname
, "rb");
418 printf("local: unable to open %s\n", fname
);
423 fh
= fxp_open(outfname
,
426 fh
= fxp_open(outfname
,
427 SSH_FXF_WRITE
| SSH_FXF_CREAT
| SSH_FXF_TRUNC
);
430 printf("%s: %s\n", outfname
, fxp_error());
437 struct fxp_attrs attrs
;
438 if (!fxp_fstat(fh
, &attrs
)) {
439 printf("read size of %s: %s\n", outfname
, fxp_error());
443 if (!(attrs
.flags
& SSH_FILEXFER_ATTR_SIZE
)) {
444 printf("read size of %s: size was not given\n", outfname
);
449 uint64_decimal(offset
, decbuf
);
450 printf("reput: restarting at file position %s\n", decbuf
);
451 if (uint64_compare(offset
, uint64_make(0, LONG_MAX
)) > 0) {
452 printf("reput: remote file is larger than we can deal with\n");
456 if (fseek(fp
, offset
.lo
, SEEK_SET
) != 0)
457 fseek(fp
, 0, SEEK_END
); /* *shrug* */
459 offset
= uint64_make(0, 0);
462 printf("local:%s => remote:%s\n", fname
, outfname
);
465 * FIXME: we can use FXP_FSTAT here to get the file size, and
466 * thus put up a progress bar.
472 len
= fread(buffer
, 1, sizeof(buffer
), fp
);
474 printf("error while reading local file\n");
476 } else if (len
== 0) {
479 if (!fxp_write(fh
, buffer
, offset
, len
)) {
480 printf("error while writing: %s\n", fxp_error());
483 offset
= uint64_add32(offset
, len
);
492 int sftp_cmd_put(struct sftp_command
*cmd
)
494 return sftp_general_put(cmd
, 0);
496 int sftp_cmd_reput(struct sftp_command
*cmd
)
498 return sftp_general_put(cmd
, 1);
501 int sftp_cmd_mkdir(struct sftp_command
*cmd
)
507 if (cmd
->nwords
< 2) {
508 printf("mkdir: expects a directory\n");
512 dir
= canonify(cmd
->words
[1]);
514 printf("%s: %s\n", dir
, fxp_error());
518 result
= fxp_mkdir(dir
);
520 printf("mkdir %s: %s\n", dir
, fxp_error());
529 int sftp_cmd_rmdir(struct sftp_command
*cmd
)
535 if (cmd
->nwords
< 2) {
536 printf("rmdir: expects a directory\n");
540 dir
= canonify(cmd
->words
[1]);
542 printf("%s: %s\n", dir
, fxp_error());
546 result
= fxp_rmdir(dir
);
548 printf("rmdir %s: %s\n", dir
, fxp_error());
557 int sftp_cmd_rm(struct sftp_command
*cmd
)
562 if (cmd
->nwords
< 2) {
563 printf("rm: expects a filename\n");
567 fname
= canonify(cmd
->words
[1]);
569 printf("%s: %s\n", fname
, fxp_error());
573 result
= fxp_remove(fname
);
575 printf("rm %s: %s\n", fname
, fxp_error());
585 int sftp_cmd_mv(struct sftp_command
*cmd
)
587 char *srcfname
, *dstfname
;
590 if (cmd
->nwords
< 3) {
591 printf("mv: expects two filenames\n");
594 srcfname
= canonify(cmd
->words
[1]);
596 printf("%s: %s\n", srcfname
, fxp_error());
600 dstfname
= canonify(cmd
->words
[2]);
602 printf("%s: %s\n", dstfname
, fxp_error());
606 result
= fxp_rename(srcfname
, dstfname
);
608 char const *error
= fxp_error();
609 struct fxp_attrs attrs
;
612 * The move might have failed because dstfname pointed at a
613 * directory. We check this possibility now: if dstfname
614 * _is_ a directory, we re-attempt the move by appending
615 * the basename of srcfname to dstfname.
617 result
= fxp_stat(dstfname
, &attrs
);
619 (attrs
.flags
& SSH_FILEXFER_ATTR_PERMISSIONS
) &&
620 (attrs
.permissions
& 0040000)) {
622 char *newname
, *newcanon
;
623 printf("(destination %s is a directory)\n", dstfname
);
624 p
= srcfname
+ strlen(srcfname
);
625 while (p
> srcfname
&& p
[-1] != '/') p
--;
626 newname
= dupcat(dstfname
, "/", p
, NULL
);
627 newcanon
= canonify(newname
);
632 result
= fxp_rename(srcfname
, dstfname
);
633 error
= result ? NULL
: fxp_error();
637 printf("mv %s %s: %s\n", srcfname
, dstfname
, error
);
643 printf("%s -> %s\n", srcfname
, dstfname
);
650 int sftp_cmd_chmod(struct sftp_command
*cmd
)
654 struct fxp_attrs attrs
;
655 unsigned attrs_clr
, attrs_xor
, oldperms
, newperms
;
657 if (cmd
->nwords
< 3) {
658 printf("chmod: expects a mode specifier and a filename\n");
663 * Attempt to parse the mode specifier in cmd->words[1]. We
664 * don't support the full horror of Unix chmod; instead we
665 * support a much simpler syntax in which the user can either
666 * specify an octal number, or a comma-separated sequence of
667 * [ugoa]*[-+=][rwxst]+. (The initial [ugoa] sequence may
668 * _only_ be omitted if the only attribute mentioned is t,
669 * since all others require a user/group/other specification.
670 * Additionally, the s attribute may not be specified for any
671 * [ugoa] specifications other than exactly u or exactly g.
673 attrs_clr
= attrs_xor
= 0;
674 mode
= cmd
->words
[1];
675 if (mode
[0] >= '0' && mode
[0] <= '9') {
676 if (mode
[strspn(mode
, "01234567")]) {
677 printf("chmod: numeric file modes should"
678 " contain digits 0-7 only\n");
682 sscanf(mode
, "%o", &attrs_xor
);
683 attrs_xor
&= attrs_clr
;
686 char *modebegin
= mode
;
687 unsigned subset
, perms
;
691 while (*mode
&& *mode
!= ',' &&
692 *mode
!= '+' && *mode
!= '-' && *mode
!= '=') {
694 case 'u': subset
|= 04700; break; /* setuid, user perms */
695 case 'g': subset
|= 02070; break; /* setgid, group perms */
696 case 'o': subset
|= 00007; break; /* just other perms */
697 case 'a': subset
|= 06777; break; /* all of the above */
699 printf("chmod: file mode '%.*s' contains unrecognised"
700 " user/group/other specifier '%c'\n",
701 strcspn(modebegin
, ","), modebegin
, *mode
);
706 if (!*mode
|| *mode
== ',') {
707 printf("chmod: file mode '%.*s' is incomplete\n",
708 strcspn(modebegin
, ","), modebegin
);
712 if (!*mode
|| *mode
== ',') {
713 printf("chmod: file mode '%.*s' is incomplete\n",
714 strcspn(modebegin
, ","), modebegin
);
718 while (*mode
&& *mode
!= ',') {
720 case 'r': perms
|= 00444; break;
721 case 'w': perms
|= 00222; break;
722 case 'x': perms
|= 00111; break;
723 case 't': perms
|= 01000; subset
|= 01000; break;
725 if ((subset
& 06777) != 04700 &&
726 (subset
& 06777) != 02070) {
727 printf("chmod: file mode '%.*s': set[ug]id bit should"
728 " be used with exactly one of u or g only\n",
729 strcspn(modebegin
, ","), modebegin
);
735 printf("chmod: file mode '%.*s' contains unrecognised"
736 " permission specifier '%c'\n",
737 strcspn(modebegin
, ","), modebegin
, *mode
);
742 if (!(subset
& 06777) && (perms
&~ subset
)) {
743 printf("chmod: file mode '%.*s' contains no user/group/other"
744 " specifier and permissions other than 't' \n",
745 strcspn(modebegin
, ","), modebegin
, *mode
);
763 if (*mode
) mode
++; /* eat comma */
767 fname
= canonify(cmd
->words
[2]);
769 printf("%s: %s\n", fname
, fxp_error());
773 result
= fxp_stat(fname
, &attrs
);
774 if (!result
|| !(attrs
.flags
& SSH_FILEXFER_ATTR_PERMISSIONS
)) {
775 printf("get attrs for %s: %s\n", fname
,
776 result ?
"file permissions not provided" : fxp_error());
781 attrs
.flags
= SSH_FILEXFER_ATTR_PERMISSIONS
; /* perms _only_ */
782 oldperms
= attrs
.permissions
& 07777;
783 attrs
.permissions
&= ~attrs_clr
;
784 attrs
.permissions
^= attrs_xor
;
785 newperms
= attrs
.permissions
& 07777;
787 result
= fxp_setstat(fname
, attrs
);
790 printf("set attrs for %s: %s\n", fname
, fxp_error());
795 printf("%s: %04o -> %04o\n", fname
, oldperms
, newperms
);
801 static int sftp_cmd_help(struct sftp_command
*cmd
);
803 static struct sftp_cmd_lookup
{
806 * For help purposes, there are two kinds of command:
808 * - primary commands, in which `longhelp' is non-NULL. In
809 * this case `shorthelp' is descriptive text, and `longhelp'
810 * is longer descriptive text intended to be printed after
813 * - alias commands, in which `longhelp' is NULL. In this case
814 * `shorthelp' is the name of a primary command, which
815 * contains the help that should double up for this command.
819 int (*obey
) (struct sftp_command
*);
822 * List of sftp commands. This is binary-searched so it MUST be
826 "bye", "finish your SFTP session",
828 " Terminates your SFTP session and quits the PSFTP program.\n",
832 "cd", "change your remote working directory",
833 " [ <New working directory> ]\n"
834 " Change the remote working directory for your SFTP session.\n"
835 " If a new working directory is not supplied, you will be\n"
836 " returned to your home directory.\n",
840 "chmod", "change file permissions and modes",
841 " ( <octal-digits> | <modifiers> ) <filename>\n"
842 " Change the file permissions on a file or directory.\n"
843 " <octal-digits> can be any octal Unix permission specifier.\n"
844 " Alternatively, <modifiers> can include:\n"
845 " u+r make file readable by owning user\n"
846 " u+w make file writable by owning user\n"
847 " u+x make file executable by owning user\n"
848 " u-r make file not readable by owning user\n"
850 " g+r make file readable by members of owning group\n"
851 " [also g+w, g+x, g-r, g-w, g-x]\n"
852 " o+r make file readable by all other users\n"
853 " [also o+w, o+x, o-r, o-w, o-x]\n"
854 " a+r make file readable by absolutely everybody\n"
855 " [also a+w, a+x, a-r, a-w, a-x]\n"
856 " u+s enable the Unix set-user-ID bit\n"
857 " u-s disable the Unix set-user-ID bit\n"
858 " g+s enable the Unix set-group-ID bit\n"
859 " g-s disable the Unix set-group-ID bit\n"
860 " +t enable the Unix \"sticky bit\"\n"
861 " You can give more than one modifier for the same user (\"g-rwx\"), and\n"
862 " more than one user for the same modifier (\"ug+w\"). You can\n"
863 " use commas to separate different modifiers (\"u+rwx,g+s\").\n",
867 "del", "delete a file",
873 "delete", "delete a file",
879 "dir", "list contents of a remote directory",
880 " [ <directory-name> ]\n"
881 " List the contents of a specified directory on the server.\n"
882 " If <directory-name> is not given, the current working directory\n"
883 " will be listed.\n",
887 "exit", "bye", NULL
, sftp_cmd_quit
890 "get", "download a file from the server to your local machine",
891 " <filename> [ <local-filename> ]\n"
892 " Downloads a file on the server and stores it locally under\n"
893 " the same name, or under a different one if you supply the\n"
894 " argument <local-filename>.\n",
899 " [ <command> [ <command> ... ] ]\n"
900 " Give general help if no commands are specified.\n"
901 " If one or more commands are specified, give specific help on\n"
902 " those particular commands.\n",
910 "mkdir", "create a directory on the remote server",
911 " <directory-name>\n"
912 " Creates a directory with the given name on the server.\n",
916 "mv", "move or rename a file on the remote server",
917 " <source-filename> <destination-filename>\n"
918 " Moves or renames the file <source-filename> on the server,\n"
919 " so that it is accessible under the name <destination-filename>.\n",
923 "put", "upload a file from your local machine to the server",
924 " <filename> [ <remote-filename> ]\n"
925 " Uploads a file to the server and stores it there under\n"
926 " the same name, or under a different one if you supply the\n"
927 " argument <remote-filename>.\n",
931 "pwd", "print your remote working directory",
933 " Print the current remote working directory for your SFTP session.\n",
941 "reget", "continue downloading a file",
942 " <filename> [ <local-filename> ]\n"
943 " Works exactly like the \"get\" command, but the local file\n"
944 " must already exist. The download will begin at the end of the\n"
945 " file. This is for resuming a download that was interrupted.\n",
953 "rename", "mv", NULL
,
957 "reput", "continue uploading a file",
958 " <filename> [ <remote-filename> ]\n"
959 " Works exactly like the \"put\" command, but the remote file\n"
960 " must already exist. The upload will begin at the end of the\n"
961 " file. This is for resuming an upload that was interrupted.\n",
969 "rmdir", "remove a directory on the remote server",
970 " <directory-name>\n"
971 " Removes the directory with the given name on the server.\n"
972 " The directory will not be removed unless it is empty.\n",
977 const struct sftp_cmd_lookup
*lookup_command(char *name
)
982 j
= sizeof(sftp_lookup
) / sizeof(*sftp_lookup
);
985 cmp
= strcmp(name
, sftp_lookup
[k
].name
);
991 return &sftp_lookup
[k
];
997 static int sftp_cmd_help(struct sftp_command
*cmd
)
1000 if (cmd
->nwords
== 1) {
1002 * Give short help on each command.
1006 for (i
= 0; i
< sizeof(sftp_lookup
) / sizeof(*sftp_lookup
); i
++) {
1007 int len
= strlen(sftp_lookup
[i
].name
);
1011 for (i
= 0; i
< sizeof(sftp_lookup
) / sizeof(*sftp_lookup
); i
++) {
1012 const struct sftp_cmd_lookup
*lookup
;
1013 lookup
= &sftp_lookup
[i
];
1014 printf("%-*s", maxlen
+2, lookup
->name
);
1015 if (lookup
->longhelp
== NULL
)
1016 lookup
= lookup_command(lookup
->shorthelp
);
1017 printf("%s\n", lookup
->shorthelp
);
1021 * Give long help on specific commands.
1023 for (i
= 1; i
< cmd
->nwords
; i
++) {
1024 const struct sftp_cmd_lookup
*lookup
;
1025 lookup
= lookup_command(cmd
->words
[i
]);
1027 printf("help: %s: command not found\n", cmd
->words
[i
]);
1029 printf("%s", lookup
->name
);
1030 if (lookup
->longhelp
== NULL
)
1031 lookup
= lookup_command(lookup
->shorthelp
);
1032 printf("%s", lookup
->longhelp
);
1039 /* ----------------------------------------------------------------------
1040 * Command line reading and parsing.
1042 struct sftp_command
*sftp_getcmd(FILE *fp
, int mode
, int modeflags
)
1045 int linelen
, linesize
;
1046 struct sftp_command
*cmd
;
1050 if ((mode
== 0) || (modeflags
& 1)) {
1055 cmd
= smalloc(sizeof(struct sftp_command
));
1061 linesize
= linelen
= 0;
1067 line
= srealloc(line
, linesize
);
1068 ret
= fgets(line
+ linelen
, linesize
- linelen
, fp
);
1069 if (modeflags
& 1) {
1073 if (!ret
|| (linelen
== 0 && line
[0] == '\0')) {
1074 cmd
->obey
= sftp_cmd_quit
;
1076 return cmd
; /* eof */
1078 len
= linelen
+ strlen(line
+ linelen
);
1080 if (line
[linelen
- 1] == '\n') {
1082 line
[linelen
] = '\0';
1088 * Parse the command line into words. The syntax is:
1089 * - double quotes are removed, but cause spaces within to be
1090 * treated as non-separating.
1091 * - a double-doublequote pair is a literal double quote, inside
1092 * _or_ outside quotes. Like this:
1094 * firstword "second word" "this has ""quotes"" in" sodoes""this""
1100 * >this has "quotes" in<
1105 /* skip whitespace */
1106 while (*p
&& (*p
== ' ' || *p
== '\t'))
1108 /* mark start of word */
1109 q
= r
= p
; /* q sits at start, r writes word */
1112 if (!quoting
&& (*p
== ' ' || *p
== '\t'))
1113 break; /* reached end of word */
1114 else if (*p
== '"' && p
[1] == '"')
1115 p
+= 2, *r
++ = '"'; /* a literal quote */
1117 p
++, quoting
= !quoting
;
1122 p
++; /* skip over the whitespace */
1124 if (cmd
->nwords
>= cmd
->wordssize
) {
1125 cmd
->wordssize
= cmd
->nwords
+ 16;
1127 srealloc(cmd
->words
, cmd
->wordssize
* sizeof(char *));
1129 cmd
->words
[cmd
->nwords
++] = q
;
1133 * Now parse the first word and assign a function.
1136 if (cmd
->nwords
== 0)
1137 cmd
->obey
= sftp_cmd_null
;
1139 const struct sftp_cmd_lookup
*lookup
;
1140 lookup
= lookup_command(cmd
->words
[0]);
1142 cmd
->obey
= sftp_cmd_unknown
;
1144 cmd
->obey
= lookup
->obey
;
1150 void do_sftp(int mode
, int modeflags
, char *batchfile
)
1155 * Do protocol initialisation.
1159 "Fatal: unable to initialise SFTP: %s\n", fxp_error());
1164 * Find out where our home directory is.
1166 homedir
= fxp_realpath(".");
1169 "Warning: failed to resolve home directory: %s\n",
1171 homedir
= dupstr(".");
1173 printf("Remote working directory is %s\n", homedir
);
1175 pwd
= dupstr(homedir
);
1182 /* ------------------------------------------------------------------
1183 * Now we're ready to do Real Stuff.
1186 struct sftp_command
*cmd
;
1187 cmd
= sftp_getcmd(stdin
, 0, 0);
1190 if (cmd
->obey(cmd
) < 0)
1194 fp
= fopen(batchfile
, "r");
1196 printf("Fatal: unable to open %s\n", batchfile
);
1200 struct sftp_command
*cmd
;
1201 cmd
= sftp_getcmd(fp
, mode
, modeflags
);
1204 if (cmd
->obey(cmd
) < 0)
1206 if (fxp_error() != NULL
) {
1207 if (!(modeflags
& 2))
1216 /* ----------------------------------------------------------------------
1217 * Dirty bits: integration with PuTTY.
1220 static int verbose
= 0;
1222 void verify_ssh_host_key(char *host
, int port
, char *keytype
,
1223 char *keystr
, char *fingerprint
)
1229 static const char absentmsg
[] =
1230 "The server's host key is not cached in the registry. You\n"
1231 "have no guarantee that the server is the computer you\n"
1233 "The server's key fingerprint is:\n"
1235 "If you trust this host, enter \"y\" to add the key to\n"
1236 "PuTTY's cache and carry on connecting.\n"
1237 "If you want to carry on connecting just once, without\n"
1238 "adding the key to the cache, enter \"n\".\n"
1239 "If you do not trust this host, press Return to abandon the\n"
1241 "Store key in cache? (y/n) ";
1243 static const char wrongmsg
[] =
1244 "WARNING - POTENTIAL SECURITY BREACH!\n"
1245 "The server's host key does not match the one PuTTY has\n"
1246 "cached in the registry. This means that either the\n"
1247 "server administrator has changed the host key, or you\n"
1248 "have actually connected to another computer pretending\n"
1249 "to be the server.\n"
1250 "The new key fingerprint is:\n"
1252 "If you were expecting this change and trust the new key,\n"
1253 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
1254 "If you want to carry on connecting but without updating\n"
1255 "the cache, enter \"n\".\n"
1256 "If you want to abandon the connection completely, press\n"
1257 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
1259 "Update cached key? (y/n, Return cancels connection) ";
1261 static const char abandoned
[] = "Connection abandoned.\n";
1266 * Verify the key against the registry.
1268 ret
= verify_host_key(host
, port
, keytype
, keystr
);
1270 if (ret
== 0) /* success - key matched OK */
1273 if (ret
== 2) { /* key was different */
1274 fprintf(stderr
, wrongmsg
, fingerprint
);
1277 if (ret
== 1) { /* key was absent */
1278 fprintf(stderr
, absentmsg
, fingerprint
);
1282 hin
= GetStdHandle(STD_INPUT_HANDLE
);
1283 GetConsoleMode(hin
, &savemode
);
1284 SetConsoleMode(hin
, (savemode
| ENABLE_ECHO_INPUT
|
1285 ENABLE_PROCESSED_INPUT
| ENABLE_LINE_INPUT
));
1286 ReadFile(hin
, line
, sizeof(line
) - 1, &i
, NULL
);
1287 SetConsoleMode(hin
, savemode
);
1289 if (line
[0] != '\0' && line
[0] != '\r' && line
[0] != '\n') {
1290 if (line
[0] == 'y' || line
[0] == 'Y')
1291 store_host_key(host
, port
, keytype
, keystr
);
1293 fprintf(stderr
, abandoned
);
1299 * Ask whether the selected cipher is acceptable (since it was
1300 * below the configured 'warn' threshold).
1301 * cs: 0 = both ways, 1 = client->server, 2 = server->client
1303 void askcipher(char *ciphername
, int cs
)
1308 static const char msg
[] =
1309 "The first %scipher supported by the server is\n"
1310 "%s, which is below the configured warning threshold.\n"
1311 "Continue with connection? (y/n) ";
1312 static const char abandoned
[] = "Connection abandoned.\n";
1316 fprintf(stderr
, msg
,
1318 (cs
== 1) ?
"client-to-server " :
1319 "server-to-client ",
1323 hin
= GetStdHandle(STD_INPUT_HANDLE
);
1324 GetConsoleMode(hin
, &savemode
);
1325 SetConsoleMode(hin
, (savemode
| ENABLE_ECHO_INPUT
|
1326 ENABLE_PROCESSED_INPUT
| ENABLE_LINE_INPUT
));
1327 ReadFile(hin
, line
, sizeof(line
) - 1, &i
, NULL
);
1328 SetConsoleMode(hin
, savemode
);
1330 if (line
[0] == 'y' || line
[0] == 'Y') {
1333 fprintf(stderr
, abandoned
);
1339 * Print an error message and perform a fatal exit.
1341 void fatalbox(char *fmt
, ...)
1343 char str
[0x100]; /* Make the size big enough */
1346 strcpy(str
, "Fatal:");
1347 vsprintf(str
+ strlen(str
), fmt
, ap
);
1350 fprintf(stderr
, str
);
1354 void connection_fatal(char *fmt
, ...)
1356 char str
[0x100]; /* Make the size big enough */
1359 strcpy(str
, "Fatal:");
1360 vsprintf(str
+ strlen(str
), fmt
, ap
);
1363 fprintf(stderr
, str
);
1368 void logevent(char *string
)
1372 void ldisc_send(char *buf
, int len
)
1375 * This is only here because of the calls to ldisc_send(NULL,
1376 * 0) in ssh.c. Nothing in PSFTP actually needs to use the
1377 * ldisc as an ldisc. So if we get called with any real data, I
1378 * want to know about it.
1384 * Be told what socket we're supposed to be using.
1386 static SOCKET sftp_ssh_socket
;
1387 char *do_select(SOCKET skt
, int startup
)
1390 sftp_ssh_socket
= skt
;
1392 sftp_ssh_socket
= INVALID_SOCKET
;
1395 extern int select_result(WPARAM
, LPARAM
);
1398 * Receive a block of data from the SSH link. Block until all data
1401 * To do this, we repeatedly call the SSH protocol module, with our
1402 * own trap in from_backend() to catch the data that comes back. We
1403 * do this until we have enough data.
1406 static unsigned char *outptr
; /* where to put the data */
1407 static unsigned outlen
; /* how much data required */
1408 static unsigned char *pending
= NULL
; /* any spare data */
1409 static unsigned pendlen
= 0, pendsize
= 0; /* length and phys. size of buffer */
1410 int from_backend(int is_stderr
, char *data
, int datalen
)
1412 unsigned char *p
= (unsigned char *) data
;
1413 unsigned len
= (unsigned) datalen
;
1416 * stderr data is just spouted to local stderr and otherwise
1420 fwrite(data
, 1, len
, stderr
);
1425 * If this is before the real session begins, just return.
1431 unsigned used
= outlen
;
1434 memcpy(outptr
, p
, used
);
1442 if (pendsize
< pendlen
+ len
) {
1443 pendsize
= pendlen
+ len
+ 4096;
1444 pending
= (pending ?
srealloc(pending
, pendsize
) :
1447 fatalbox("Out of memory");
1449 memcpy(pending
+ pendlen
, p
, len
);
1455 int sftp_recvdata(char *buf
, int len
)
1457 outptr
= (unsigned char *) buf
;
1461 * See if the pending-input block contains some of what we
1465 unsigned pendused
= pendlen
;
1466 if (pendused
> outlen
)
1468 memcpy(outptr
, pending
, pendused
);
1469 memmove(pending
, pending
+ pendused
, pendlen
- pendused
);
1472 pendlen
-= pendused
;
1482 while (outlen
> 0) {
1486 FD_SET(sftp_ssh_socket
, &readfds
);
1487 if (select(1, &readfds
, NULL
, NULL
, NULL
) < 0)
1488 return 0; /* doom */
1489 select_result((WPARAM
) sftp_ssh_socket
, (LPARAM
) FD_READ
);
1494 int sftp_senddata(char *buf
, int len
)
1496 back
->send((unsigned char *) buf
, len
);
1501 * Loop through the ssh connection and authentication process.
1503 static void ssh_sftp_init(void)
1505 if (sftp_ssh_socket
== INVALID_SOCKET
)
1507 while (!back
->sendok()) {
1510 FD_SET(sftp_ssh_socket
, &readfds
);
1511 if (select(1, &readfds
, NULL
, NULL
, NULL
) < 0)
1513 select_result((WPARAM
) sftp_ssh_socket
, (LPARAM
) FD_READ
);
1517 static char *password
= NULL
;
1518 static int get_line(const char *prompt
, char *str
, int maxlen
, int is_pw
)
1521 DWORD savemode
, newmode
, i
;
1524 static int tried_once
= 0;
1529 strncpy(str
, password
, maxlen
);
1530 str
[maxlen
- 1] = '\0';
1536 hin
= GetStdHandle(STD_INPUT_HANDLE
);
1537 hout
= GetStdHandle(STD_OUTPUT_HANDLE
);
1538 if (hin
== INVALID_HANDLE_VALUE
|| hout
== INVALID_HANDLE_VALUE
) {
1539 fprintf(stderr
, "Cannot get standard input/output handles\n");
1543 GetConsoleMode(hin
, &savemode
);
1544 newmode
= savemode
| ENABLE_PROCESSED_INPUT
| ENABLE_LINE_INPUT
;
1546 newmode
&= ~ENABLE_ECHO_INPUT
;
1548 newmode
|= ENABLE_ECHO_INPUT
;
1549 SetConsoleMode(hin
, newmode
);
1551 WriteFile(hout
, prompt
, strlen(prompt
), &i
, NULL
);
1552 ReadFile(hin
, str
, maxlen
- 1, &i
, NULL
);
1554 SetConsoleMode(hin
, savemode
);
1556 if ((int) i
> maxlen
)
1563 WriteFile(hout
, "\r\n", 2, &i
, NULL
);
1569 * Initialize the Win$ock driver.
1571 static void init_winsock(void)
1576 winsock_ver
= MAKEWORD(1, 1);
1577 if (WSAStartup(winsock_ver
, &wsadata
)) {
1578 fprintf(stderr
, "Unable to initialise WinSock");
1581 if (LOBYTE(wsadata
.wVersion
) != 1 || HIBYTE(wsadata
.wVersion
) != 1) {
1582 fprintf(stderr
, "WinSock version is incompatible with 1.1");
1588 * Short description of parameters.
1590 static void usage(void)
1592 printf("PuTTY Secure File Transfer (SFTP) client\n");
1593 printf("%s\n", ver
);
1594 printf("Usage: psftp [options] user@host\n");
1595 printf("Options:\n");
1596 printf(" -b file use specified batchfile\n");
1597 printf(" -bc output batchfile commands\n");
1598 printf(" -be don't stop batchfile processing if errors\n");
1599 printf(" -v show verbose messages\n");
1600 printf(" -P port connect to specified port\n");
1601 printf(" -pw passw login with specified password\n");
1606 * Main program. Parse arguments etc.
1608 int main(int argc
, char *argv
[])
1612 char *user
, *host
, *userhost
, *realhost
;
1616 char *batchfile
= NULL
;
1618 flags
= FLAG_STDERR
| FLAG_INTERACTIVE
;
1619 ssh_get_line
= &get_line
;
1623 userhost
= user
= NULL
;
1625 for (i
= 1; i
< argc
; i
++) {
1626 if (argv
[i
][0] != '-') {
1630 userhost
= dupstr(argv
[i
]);
1631 } else if (strcmp(argv
[i
], "-v") == 0) {
1632 verbose
= 1, flags
|= FLAG_VERBOSE
;
1633 } else if (strcmp(argv
[i
], "-h") == 0 ||
1634 strcmp(argv
[i
], "-?") == 0) {
1636 } else if (strcmp(argv
[i
], "-l") == 0 && i
+ 1 < argc
) {
1638 } else if (strcmp(argv
[i
], "-P") == 0 && i
+ 1 < argc
) {
1639 portnumber
= atoi(argv
[++i
]);
1640 } else if (strcmp(argv
[i
], "-pw") == 0 && i
+ 1 < argc
) {
1641 password
= argv
[++i
];
1642 } else if (strcmp(argv
[i
], "-b") == 0 && i
+ 1 < argc
) {
1644 batchfile
= argv
[++i
];
1645 } else if (strcmp(argv
[i
], "-bc") == 0 && i
+ 1 < argc
) {
1646 modeflags
= modeflags
| 1;
1647 } else if (strcmp(argv
[i
], "-be") == 0 && i
+ 1 < argc
) {
1648 modeflags
= modeflags
| 2;
1649 } else if (strcmp(argv
[i
], "--") == 0) {
1660 if (argc
> 0 || !userhost
)
1663 /* Separate host and username */
1665 host
= strrchr(host
, '@');
1671 printf("psftp: multiple usernames specified; using \"%s\"\n",
1677 /* Try to load settings for this host */
1678 do_defaults(host
, &cfg
);
1679 if (cfg
.host
[0] == '\0') {
1680 /* No settings for this host; use defaults */
1681 do_defaults(NULL
, &cfg
);
1682 strncpy(cfg
.host
, host
, sizeof(cfg
.host
) - 1);
1683 cfg
.host
[sizeof(cfg
.host
) - 1] = '\0';
1688 if (user
!= NULL
&& user
[0] != '\0') {
1689 strncpy(cfg
.username
, user
, sizeof(cfg
.username
) - 1);
1690 cfg
.username
[sizeof(cfg
.username
) - 1] = '\0';
1692 if (!cfg
.username
[0]) {
1693 printf("login as: ");
1694 if (!fgets(cfg
.username
, sizeof(cfg
.username
), stdin
)) {
1695 fprintf(stderr
, "psftp: aborting\n");
1698 int len
= strlen(cfg
.username
);
1699 if (cfg
.username
[len
- 1] == '\n')
1700 cfg
.username
[len
- 1] = '\0';
1704 if (cfg
.protocol
!= PROT_SSH
)
1708 cfg
.port
= portnumber
;
1710 /* SFTP uses SSH2 by default always */
1713 /* Set up subsystem name. */
1714 strcpy(cfg
.remote_cmd
, "sftp");
1715 cfg
.ssh_subsys
= TRUE
;
1719 * Set up fallback option, for SSH1 servers or servers with the
1720 * sftp subsystem not enabled but the server binary installed
1721 * in the usual place. We only support fallback on Unix
1722 * systems, and we use a kludgy piece of shellery which should
1723 * try to find sftp-server in various places (the obvious
1724 * systemwide spots /usr/lib and /usr/local/lib, and then the
1725 * user's PATH) and finally give up.
1727 * test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server
1728 * test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server
1731 * the idea being that this will attempt to use either of the
1732 * obvious pathnames and then give up, and when it does give up
1733 * it will print the preferred pathname in the error messages.
1735 cfg
.remote_cmd_ptr2
=
1736 "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n"
1737 "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n"
1739 cfg
.ssh_subsys2
= FALSE
;
1741 back
= &ssh_backend
;
1743 err
= back
->init(cfg
.host
, cfg
.port
, &realhost
);
1745 fprintf(stderr
, "ssh_init: %s", err
);
1749 if (verbose
&& realhost
!= NULL
)
1750 printf("Connected to %s\n", realhost
);
1752 do_sftp(mode
, modeflags
, batchfile
);
1754 if (back
!= NULL
&& back
->socket() != NULL
) {
1756 back
->special(TS_EOF
);
1757 sftp_recvdata(&ch
, 1);