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 static int psftp_connect(char *userhost, char *user, int portnumber);
28 static void do_sftp_init(void);
30 /* ----------------------------------------------------------------------
36 /* ----------------------------------------------------------------------
37 * Higher-level helper functions used in commands.
41 * Attempt to canonify a pathname starting from the pwd. If
42 * canonification fails, at least fall back to returning a _valid_
43 * pathname (though it may be ugly, eg /home/simon/../foobar).
45 char *canonify(char *name)
47 char *fullname, *canonname;
50 fullname = dupstr(name);
53 if (pwd[strlen(pwd) - 1] == '/')
57 fullname = dupcat(pwd, slash, name, NULL);
60 canonname = fxp_realpath(fullname);
67 * Attempt number 2. Some FXP_REALPATH implementations
68 * (glibc-based ones, in particular) require the _whole_
69 * path to point to something that exists, whereas others
70 * (BSD-based) only require all but the last component to
71 * exist. So if the first call failed, we should strip off
72 * everything from the last slash onwards and try again,
73 * then put the final component back on.
77 * - if the last component is "/." or "/..", then we don't
78 * bother trying this because there's no way it can work.
80 * - if the thing actually ends with a "/", we remove it
81 * before we start. Except if the string is "/" itself
82 * (although I can't see why we'd have got here if so,
83 * because surely "/" would have worked the first
84 * time?), in which case we don't bother.
86 * - if there's no slash in the string at all, give up in
87 * confusion (we expect at least one because of the way
88 * we constructed the string).
95 if (i > 2 && fullname[i - 1] == '/')
96 fullname[--i] = '\0'; /* strip trailing / unless at pos 0 */
97 while (i > 0 && fullname[--i] != '/');
100 * Give up on special cases.
102 if (fullname[i] != '/' || /* no slash at all */
103 !strcmp(fullname + i, "/.") || /* ends in /. */
104 !strcmp(fullname + i, "/..") || /* ends in /.. */
105 !strcmp(fullname, "/")) {
110 * Now i points at the slash. Deal with the final special
111 * case i==0 (ie the whole path was "/nonexistentfile").
113 fullname[i] = '\0'; /* separate the string */
115 canonname = fxp_realpath("/");
117 canonname = fxp_realpath(fullname);
121 return fullname; /* even that failed; give up */
124 * We have a canonical name for all but the last path
125 * component. Concatenate the last component and return.
127 returnname = dupcat(canonname,
128 canonname[strlen(canonname) - 1] ==
129 '/' ? "" : "/", fullname + i + 1, NULL);
137 * Return a pointer to the portion of str that comes after the last
138 * slash (or backslash or colon, if `local' is TRUE).
140 static char *stripslashes(char *str, int local)
145 p = strchr(str, ':');
149 p = strrchr(str, '/');
153 p = strrchr(str, '\\');
160 /* ----------------------------------------------------------------------
161 * Actual sftp commands.
163 struct sftp_command {
165 int nwords, wordssize;
166 int (*obey) (struct sftp_command *); /* returns <0 to quit */
169 int sftp_cmd_null(struct sftp_command *cmd)
171 return 1; /* success */
174 int sftp_cmd_unknown(struct sftp_command *cmd)
176 printf("psftp: unknown command \"%s\"\n", cmd->words[0]);
177 return 0; /* failure */
180 int sftp_cmd_quit(struct sftp_command *cmd)
186 * List a directory. If no arguments are given, list pwd; otherwise
187 * list the directory given in words[1].
189 static int sftp_ls_compare(const void *av, const void *bv)
191 const struct fxp_name *a = (const struct fxp_name *) av;
192 const struct fxp_name *b = (const struct fxp_name *) bv;
193 return strcmp(a->filename, b->filename);
195 int sftp_cmd_ls(struct sftp_command *cmd)
197 struct fxp_handle *dirh;
198 struct fxp_names *names;
199 struct fxp_name *ournames;
200 int nnames, namesize;
205 printf("psftp: not connected to a host; use \"open host.name\"\n");
214 cdir = canonify(dir);
216 printf("%s: %s\n", dir, fxp_error());
220 printf("Listing directory %s\n", cdir);
222 dirh = fxp_opendir(cdir);
224 printf("Unable to open %s: %s\n", dir, fxp_error());
226 nnames = namesize = 0;
231 names = fxp_readdir(dirh);
233 if (fxp_error_type() == SSH_FX_EOF)
235 printf("Reading directory %s: %s\n", dir, fxp_error());
238 if (names->nnames == 0) {
239 fxp_free_names(names);
243 if (nnames + names->nnames >= namesize) {
244 namesize += names->nnames + 128;
246 srealloc(ournames, namesize * sizeof(*ournames));
249 for (i = 0; i < names->nnames; i++)
250 ournames[nnames++] = names->names[i];
252 names->nnames = 0; /* prevent free_names */
253 fxp_free_names(names);
258 * Now we have our filenames. Sort them by actual file
259 * name, and then output the longname parts.
261 qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare);
266 for (i = 0; i < nnames; i++)
267 printf("%s\n", ournames[i].longname);
276 * Change directories. We do this by canonifying the new name, then
277 * trying to OPENDIR it. Only if that succeeds do we set the new pwd.
279 int sftp_cmd_cd(struct sftp_command *cmd)
281 struct fxp_handle *dirh;
285 printf("psftp: not connected to a host; use \"open host.name\"\n");
290 dir = dupstr(homedir);
292 dir = canonify(cmd->words[1]);
295 printf("%s: %s\n", dir, fxp_error());
299 dirh = fxp_opendir(dir);
301 printf("Directory %s: %s\n", dir, fxp_error());
310 printf("Remote directory is now %s\n", pwd);
316 * Print current directory. Easy as pie.
318 int sftp_cmd_pwd(struct sftp_command *cmd)
321 printf("psftp: not connected to a host; use \"open host.name\"\n");
325 printf("Remote directory is %s\n", pwd);
330 * Get a file and save it at the local end. We have two very
331 * similar commands here: `get' and `reget', which differ in that
332 * `reget' checks for the existence of the destination file and
333 * starts from where a previous aborted transfer left off.
335 int sftp_general_get(struct sftp_command *cmd, int restart)
337 struct fxp_handle *fh;
338 char *fname, *outfname;
344 printf("psftp: not connected to a host; use \"open host.name\"\n");
348 if (cmd->nwords < 2) {
349 printf("get: expects a filename\n");
353 fname = canonify(cmd->words[1]);
355 printf("%s: %s\n", cmd->words[1], fxp_error());
358 outfname = (cmd->nwords == 2 ?
359 stripslashes(cmd->words[1], 0) : cmd->words[2]);
361 fh = fxp_open(fname, SSH_FXF_READ);
363 printf("%s: %s\n", fname, fxp_error());
369 fp = fopen(outfname, "rb+");
371 fp = fopen(outfname, "wb");
375 printf("local: unable to open %s\n", outfname);
383 fseek(fp, 0L, SEEK_END);
385 printf("reget: restarting at file position %ld\n", posn);
386 offset = uint64_make(0, posn);
388 offset = uint64_make(0, 0);
391 printf("remote:%s => local:%s\n", fname, outfname);
394 * FIXME: we can use FXP_FSTAT here to get the file size, and
395 * thus put up a progress bar.
403 len = fxp_read(fh, buffer, offset, sizeof(buffer));
404 if ((len == -1 && fxp_error_type() == SSH_FX_EOF) || len == 0)
407 printf("error while reading: %s\n", fxp_error());
414 wlen = fwrite(buffer, 1, len - wpos, fp);
416 printf("error while writing local file\n");
422 if (wpos < len) { /* we had an error */
426 offset = uint64_add32(offset, len);
435 int sftp_cmd_get(struct sftp_command *cmd)
437 return sftp_general_get(cmd, 0);
439 int sftp_cmd_reget(struct sftp_command *cmd)
441 return sftp_general_get(cmd, 1);
445 * Send a file and store it at the remote end. We have two very
446 * similar commands here: `put' and `reput', which differ in that
447 * `reput' checks for the existence of the destination file and
448 * starts from where a previous aborted transfer left off.
450 int sftp_general_put(struct sftp_command *cmd, int restart)
452 struct fxp_handle *fh;
453 char *fname, *origoutfname, *outfname;
459 printf("psftp: not connected to a host; use \"open host.name\"\n");
463 if (cmd->nwords < 2) {
464 printf("put: expects a filename\n");
468 fname = cmd->words[1];
469 origoutfname = (cmd->nwords == 2 ?
470 stripslashes(cmd->words[1], 1) : cmd->words[2]);
471 outfname = canonify(origoutfname);
473 printf("%s: %s\n", origoutfname, fxp_error());
477 fp = fopen(fname, "rb");
479 printf("local: unable to open %s\n", fname);
484 fh = fxp_open(outfname,
487 fh = fxp_open(outfname,
488 SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC);
491 printf("%s: %s\n", outfname, fxp_error());
498 struct fxp_attrs attrs;
499 if (!fxp_fstat(fh, &attrs)) {
500 printf("read size of %s: %s\n", outfname, fxp_error());
504 if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) {
505 printf("read size of %s: size was not given\n", outfname);
510 uint64_decimal(offset, decbuf);
511 printf("reput: restarting at file position %s\n", decbuf);
512 if (uint64_compare(offset, uint64_make(0, LONG_MAX)) > 0) {
513 printf("reput: remote file is larger than we can deal with\n");
517 if (fseek(fp, offset.lo, SEEK_SET) != 0)
518 fseek(fp, 0, SEEK_END); /* *shrug* */
520 offset = uint64_make(0, 0);
523 printf("local:%s => remote:%s\n", fname, outfname);
526 * FIXME: we can use FXP_FSTAT here to get the file size, and
527 * thus put up a progress bar.
534 len = fread(buffer, 1, sizeof(buffer), fp);
536 printf("error while reading local file\n");
539 } else if (len == 0) {
542 if (!fxp_write(fh, buffer, offset, len)) {
543 printf("error while writing: %s\n", fxp_error());
547 offset = uint64_add32(offset, len);
556 int sftp_cmd_put(struct sftp_command *cmd)
558 return sftp_general_put(cmd, 0);
560 int sftp_cmd_reput(struct sftp_command *cmd)
562 return sftp_general_put(cmd, 1);
565 int sftp_cmd_mkdir(struct sftp_command *cmd)
571 printf("psftp: not connected to a host; use \"open host.name\"\n");
575 if (cmd->nwords < 2) {
576 printf("mkdir: expects a directory\n");
580 dir = canonify(cmd->words[1]);
582 printf("%s: %s\n", dir, fxp_error());
586 result = fxp_mkdir(dir);
588 printf("mkdir %s: %s\n", dir, fxp_error());
597 int sftp_cmd_rmdir(struct sftp_command *cmd)
603 printf("psftp: not connected to a host; use \"open host.name\"\n");
607 if (cmd->nwords < 2) {
608 printf("rmdir: expects a directory\n");
612 dir = canonify(cmd->words[1]);
614 printf("%s: %s\n", dir, fxp_error());
618 result = fxp_rmdir(dir);
620 printf("rmdir %s: %s\n", dir, fxp_error());
629 int sftp_cmd_rm(struct sftp_command *cmd)
635 printf("psftp: not connected to a host; use \"open host.name\"\n");
639 if (cmd->nwords < 2) {
640 printf("rm: expects a filename\n");
644 fname = canonify(cmd->words[1]);
646 printf("%s: %s\n", fname, fxp_error());
650 result = fxp_remove(fname);
652 printf("rm %s: %s\n", fname, fxp_error());
661 int sftp_cmd_mv(struct sftp_command *cmd)
663 char *srcfname, *dstfname;
667 printf("psftp: not connected to a host; use \"open host.name\"\n");
671 if (cmd->nwords < 3) {
672 printf("mv: expects two filenames\n");
675 srcfname = canonify(cmd->words[1]);
677 printf("%s: %s\n", srcfname, fxp_error());
681 dstfname = canonify(cmd->words[2]);
683 printf("%s: %s\n", dstfname, fxp_error());
687 result = fxp_rename(srcfname, dstfname);
689 char const *error = fxp_error();
690 struct fxp_attrs attrs;
693 * The move might have failed because dstfname pointed at a
694 * directory. We check this possibility now: if dstfname
695 * _is_ a directory, we re-attempt the move by appending
696 * the basename of srcfname to dstfname.
698 result = fxp_stat(dstfname, &attrs);
700 (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) &&
701 (attrs.permissions & 0040000)) {
703 char *newname, *newcanon;
704 printf("(destination %s is a directory)\n", dstfname);
705 p = srcfname + strlen(srcfname);
706 while (p > srcfname && p[-1] != '/') p--;
707 newname = dupcat(dstfname, "/", p, NULL);
708 newcanon = canonify(newname);
713 result = fxp_rename(srcfname, dstfname);
714 error = result ? NULL : fxp_error();
718 printf("mv %s %s: %s\n", srcfname, dstfname, error);
724 printf("%s -> %s\n", srcfname, dstfname);
731 int sftp_cmd_chmod(struct sftp_command *cmd)
735 struct fxp_attrs attrs;
736 unsigned attrs_clr, attrs_xor, oldperms, newperms;
739 printf("psftp: not connected to a host; use \"open host.name\"\n");
743 if (cmd->nwords < 3) {
744 printf("chmod: expects a mode specifier and a filename\n");
749 * Attempt to parse the mode specifier in cmd->words[1]. We
750 * don't support the full horror of Unix chmod; instead we
751 * support a much simpler syntax in which the user can either
752 * specify an octal number, or a comma-separated sequence of
753 * [ugoa]*[-+=][rwxst]+. (The initial [ugoa] sequence may
754 * _only_ be omitted if the only attribute mentioned is t,
755 * since all others require a user/group/other specification.
756 * Additionally, the s attribute may not be specified for any
757 * [ugoa] specifications other than exactly u or exactly g.
759 attrs_clr = attrs_xor = 0;
760 mode = cmd->words[1];
761 if (mode[0] >= '0' && mode[0] <= '9') {
762 if (mode[strspn(mode, "01234567")]) {
763 printf("chmod: numeric file modes should"
764 " contain digits 0-7 only\n");
768 sscanf(mode, "%o", &attrs_xor);
769 attrs_xor &= attrs_clr;
772 char *modebegin = mode;
773 unsigned subset, perms;
777 while (*mode && *mode != ',' &&
778 *mode != '+' && *mode != '-' && *mode != '=') {
780 case 'u': subset |= 04700; break; /* setuid, user perms */
781 case 'g': subset |= 02070; break; /* setgid, group perms */
782 case 'o': subset |= 00007; break; /* just other perms */
783 case 'a': subset |= 06777; break; /* all of the above */
785 printf("chmod: file mode '%.*s' contains unrecognised"
786 " user/group/other specifier '%c'\n",
787 strcspn(modebegin, ","), modebegin, *mode);
792 if (!*mode || *mode == ',') {
793 printf("chmod: file mode '%.*s' is incomplete\n",
794 strcspn(modebegin, ","), modebegin);
798 if (!*mode || *mode == ',') {
799 printf("chmod: file mode '%.*s' is incomplete\n",
800 strcspn(modebegin, ","), modebegin);
804 while (*mode && *mode != ',') {
806 case 'r': perms |= 00444; break;
807 case 'w': perms |= 00222; break;
808 case 'x': perms |= 00111; break;
809 case 't': perms |= 01000; subset |= 01000; break;
811 if ((subset & 06777) != 04700 &&
812 (subset & 06777) != 02070) {
813 printf("chmod: file mode '%.*s': set[ug]id bit should"
814 " be used with exactly one of u or g only\n",
815 strcspn(modebegin, ","), modebegin);
821 printf("chmod: file mode '%.*s' contains unrecognised"
822 " permission specifier '%c'\n",
823 strcspn(modebegin, ","), modebegin, *mode);
828 if (!(subset & 06777) && (perms &~ subset)) {
829 printf("chmod: file mode '%.*s' contains no user/group/other"
830 " specifier and permissions other than 't' \n",
831 strcspn(modebegin, ","), modebegin, *mode);
849 if (*mode) mode++; /* eat comma */
853 fname = canonify(cmd->words[2]);
855 printf("%s: %s\n", fname, fxp_error());
859 result = fxp_stat(fname, &attrs);
860 if (!result || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) {
861 printf("get attrs for %s: %s\n", fname,
862 result ? "file permissions not provided" : fxp_error());
867 attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS; /* perms _only_ */
868 oldperms = attrs.permissions & 07777;
869 attrs.permissions &= ~attrs_clr;
870 attrs.permissions ^= attrs_xor;
871 newperms = attrs.permissions & 07777;
873 result = fxp_setstat(fname, attrs);
876 printf("set attrs for %s: %s\n", fname, fxp_error());
881 printf("%s: %04o -> %04o\n", fname, oldperms, newperms);
887 static int sftp_cmd_open(struct sftp_command *cmd)
890 printf("psftp: already connected\n");
894 if (cmd->nwords < 2) {
895 printf("open: expects a host name\n");
899 if (psftp_connect(cmd->words[1], NULL, 0)) {
900 back = NULL; /* connection is already closed */
901 return -1; /* this is fatal */
907 static int sftp_cmd_lcd(struct sftp_command *cmd)
912 if (cmd->nwords < 2) {
913 printf("lcd: expects a local directory name\n");
917 if (!SetCurrentDirectory(cmd->words[1])) {
920 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
921 FORMAT_MESSAGE_FROM_SYSTEM |
922 FORMAT_MESSAGE_IGNORE_INSERTS,
923 NULL, GetLastError(),
924 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
925 (LPTSTR)&message, 0, NULL);
926 i = strcspn((char *)message, "\n");
927 printf("lcd: unable to change directory: %.*s\n", i, (LPCTSTR)message);
932 currdir = smalloc(256);
933 len = GetCurrentDirectory(256, currdir);
935 currdir = srealloc(currdir, len);
936 GetCurrentDirectory(len, currdir);
937 printf("New local directory is %s\n", currdir);
943 static int sftp_cmd_lpwd(struct sftp_command *cmd)
948 currdir = smalloc(256);
949 len = GetCurrentDirectory(256, currdir);
951 currdir = srealloc(currdir, len);
952 GetCurrentDirectory(len, currdir);
953 printf("Current local directory is %s\n", currdir);
959 static int sftp_cmd_pling(struct sftp_command *cmd)
963 exitcode = system(cmd->words[1]);
964 return (exitcode == 0);
967 static int sftp_cmd_help(struct sftp_command *cmd);
969 static struct sftp_cmd_lookup {
972 * For help purposes, there are two kinds of command:
974 * - primary commands, in which `longhelp' is non-NULL. In
975 * this case `shorthelp' is descriptive text, and `longhelp'
976 * is longer descriptive text intended to be printed after
979 * - alias commands, in which `longhelp' is NULL. In this case
980 * `shorthelp' is the name of a primary command, which
981 * contains the help that should double up for this command.
983 int listed; /* do we list this in primary help? */
986 int (*obey) (struct sftp_command *);
989 * List of sftp commands. This is binary-searched so it MUST be
993 "!", TRUE, "run a local Windows command",
995 " Runs a local Windows command. For example, \"!del myfile\".\n",
999 "bye", TRUE, "finish your SFTP session",
1001 " Terminates your SFTP session and quits the PSFTP program.\n",
1005 "cd", TRUE, "change your remote working directory",
1006 " [ <New working directory> ]\n"
1007 " Change the remote working directory for your SFTP session.\n"
1008 " If a new working directory is not supplied, you will be\n"
1009 " returned to your home directory.\n",
1013 "chmod", TRUE, "change file permissions and modes",
1014 " ( <octal-digits> | <modifiers> ) <filename>\n"
1015 " Change the file permissions on a file or directory.\n"
1016 " <octal-digits> can be any octal Unix permission specifier.\n"
1017 " Alternatively, <modifiers> can include:\n"
1018 " u+r make file readable by owning user\n"
1019 " u+w make file writable by owning user\n"
1020 " u+x make file executable by owning user\n"
1021 " u-r make file not readable by owning user\n"
1022 " [also u-w, u-x]\n"
1023 " g+r make file readable by members of owning group\n"
1024 " [also g+w, g+x, g-r, g-w, g-x]\n"
1025 " o+r make file readable by all other users\n"
1026 " [also o+w, o+x, o-r, o-w, o-x]\n"
1027 " a+r make file readable by absolutely everybody\n"
1028 " [also a+w, a+x, a-r, a-w, a-x]\n"
1029 " u+s enable the Unix set-user-ID bit\n"
1030 " u-s disable the Unix set-user-ID bit\n"
1031 " g+s enable the Unix set-group-ID bit\n"
1032 " g-s disable the Unix set-group-ID bit\n"
1033 " +t enable the Unix \"sticky bit\"\n"
1034 " You can give more than one modifier for the same user (\"g-rwx\"), and\n"
1035 " more than one user for the same modifier (\"ug+w\"). You can\n"
1036 " use commas to separate different modifiers (\"u+rwx,g+s\").\n",
1040 "del", TRUE, "delete a file",
1042 " Delete a file.\n",
1046 "delete", FALSE, "del", NULL, sftp_cmd_rm
1049 "dir", TRUE, "list contents of a remote directory",
1050 " [ <directory-name> ]\n"
1051 " List the contents of a specified directory on the server.\n"
1052 " If <directory-name> is not given, the current working directory\n"
1053 " will be listed.\n",
1057 "exit", TRUE, "bye", NULL, sftp_cmd_quit
1060 "get", TRUE, "download a file from the server to your local machine",
1061 " <filename> [ <local-filename> ]\n"
1062 " Downloads a file on the server and stores it locally under\n"
1063 " the same name, or under a different one if you supply the\n"
1064 " argument <local-filename>.\n",
1068 "help", TRUE, "give help",
1069 " [ <command> [ <command> ... ] ]\n"
1070 " Give general help if no commands are specified.\n"
1071 " If one or more commands are specified, give specific help on\n"
1072 " those particular commands.\n",
1076 "lcd", TRUE, "change local working directory",
1077 " <local-directory-name>\n"
1078 " Change the local working directory of the PSFTP program (the\n"
1079 " default location where the \"get\" command will save files).\n",
1083 "lpwd", TRUE, "print local working directory",
1085 " Print the local working directory of the PSFTP program (the\n"
1086 " default location where the \"get\" command will save files).\n",
1090 "ls", TRUE, "dir", NULL,
1094 "mkdir", TRUE, "create a directory on the remote server",
1095 " <directory-name>\n"
1096 " Creates a directory with the given name on the server.\n",
1100 "mv", TRUE, "move or rename a file on the remote server",
1101 " <source-filename> <destination-filename>\n"
1102 " Moves or renames the file <source-filename> on the server,\n"
1103 " so that it is accessible under the name <destination-filename>.\n",
1107 "open", TRUE, "connect to a host",
1108 " [<user>@]<hostname>\n"
1109 " Establishes an SFTP connection to a given host. Only usable\n"
1110 " when you did not already specify a host name on the command\n"
1115 "put", TRUE, "upload a file from your local machine to the server",
1116 " <filename> [ <remote-filename> ]\n"
1117 " Uploads a file to the server and stores it there under\n"
1118 " the same name, or under a different one if you supply the\n"
1119 " argument <remote-filename>.\n",
1123 "pwd", TRUE, "print your remote working directory",
1125 " Print the current remote working directory for your SFTP session.\n",
1129 "quit", TRUE, "bye", NULL,
1133 "reget", TRUE, "continue downloading a file",
1134 " <filename> [ <local-filename> ]\n"
1135 " Works exactly like the \"get\" command, but the local file\n"
1136 " must already exist. The download will begin at the end of the\n"
1137 " file. This is for resuming a download that was interrupted.\n",
1141 "ren", TRUE, "mv", NULL,
1145 "rename", FALSE, "mv", NULL,
1149 "reput", TRUE, "continue uploading a file",
1150 " <filename> [ <remote-filename> ]\n"
1151 " Works exactly like the \"put\" command, but the remote file\n"
1152 " must already exist. The upload will begin at the end of the\n"
1153 " file. This is for resuming an upload that was interrupted.\n",
1157 "rm", TRUE, "del", NULL,
1161 "rmdir", TRUE, "remove a directory on the remote server",
1162 " <directory-name>\n"
1163 " Removes the directory with the given name on the server.\n"
1164 " The directory will not be removed unless it is empty.\n",
1169 const struct sftp_cmd_lookup *lookup_command(char *name)
1174 j = sizeof(sftp_lookup) / sizeof(*sftp_lookup);
1177 cmp = strcmp(name, sftp_lookup[k].name);
1183 return &sftp_lookup[k];
1189 static int sftp_cmd_help(struct sftp_command *cmd)
1192 if (cmd->nwords == 1) {
1194 * Give short help on each command.
1198 for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) {
1200 if (!sftp_lookup[i].listed)
1202 len = strlen(sftp_lookup[i].name);
1206 for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) {
1207 const struct sftp_cmd_lookup *lookup;
1208 if (!sftp_lookup[i].listed)
1210 lookup = &sftp_lookup[i];
1211 printf("%-*s", maxlen+2, lookup->name);
1212 if (lookup->longhelp == NULL)
1213 lookup = lookup_command(lookup->shorthelp);
1214 printf("%s\n", lookup->shorthelp);
1218 * Give long help on specific commands.
1220 for (i = 1; i < cmd->nwords; i++) {
1221 const struct sftp_cmd_lookup *lookup;
1222 lookup = lookup_command(cmd->words[i]);
1224 printf("help: %s: command not found\n", cmd->words[i]);
1226 printf("%s", lookup->name);
1227 if (lookup->longhelp == NULL)
1228 lookup = lookup_command(lookup->shorthelp);
1229 printf("%s", lookup->longhelp);
1236 /* ----------------------------------------------------------------------
1237 * Command line reading and parsing.
1239 struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags)
1242 int linelen, linesize;
1243 struct sftp_command *cmd;
1247 if ((mode == 0) || (modeflags & 1)) {
1252 cmd = smalloc(sizeof(struct sftp_command));
1258 linesize = linelen = 0;
1264 line = srealloc(line, linesize);
1265 ret = fgets(line + linelen, linesize - linelen, fp);
1267 if (!ret || (linelen == 0 && line[0] == '\0')) {
1268 cmd->obey = sftp_cmd_quit;
1269 if ((mode == 0) || (modeflags & 1))
1271 return cmd; /* eof */
1273 len = linelen + strlen(line + linelen);
1275 if (line[linelen - 1] == '\n') {
1277 line[linelen] = '\0';
1281 if (modeflags & 1) {
1282 printf("%s\n", line);
1286 while (*p && (*p == ' ' || *p == '\t'))
1291 * Special case: the ! command. This is always parsed as
1292 * exactly two words: one containing the !, and the second
1293 * containing everything else on the line.
1295 cmd->nwords = cmd->wordssize = 2;
1296 cmd->words = srealloc(cmd->words, cmd->wordssize * sizeof(char *));
1297 cmd->words[0] = "!";
1298 cmd->words[1] = p+1;
1302 * Parse the command line into words. The syntax is:
1303 * - double quotes are removed, but cause spaces within to be
1304 * treated as non-separating.
1305 * - a double-doublequote pair is a literal double quote, inside
1306 * _or_ outside quotes. Like this:
1308 * firstword "second word" "this has ""quotes"" in" and""this""
1314 * >this has "quotes" in<
1318 /* skip whitespace */
1319 while (*p && (*p == ' ' || *p == '\t'))
1321 /* mark start of word */
1322 q = r = p; /* q sits at start, r writes word */
1325 if (!quoting && (*p == ' ' || *p == '\t'))
1326 break; /* reached end of word */
1327 else if (*p == '"' && p[1] == '"')
1328 p += 2, *r++ = '"'; /* a literal quote */
1330 p++, quoting = !quoting;
1335 p++; /* skip over the whitespace */
1337 if (cmd->nwords >= cmd->wordssize) {
1338 cmd->wordssize = cmd->nwords + 16;
1340 srealloc(cmd->words, cmd->wordssize * sizeof(char *));
1342 cmd->words[cmd->nwords++] = q;
1347 * Now parse the first word and assign a function.
1350 if (cmd->nwords == 0)
1351 cmd->obey = sftp_cmd_null;
1353 const struct sftp_cmd_lookup *lookup;
1354 lookup = lookup_command(cmd->words[0]);
1356 cmd->obey = sftp_cmd_unknown;
1358 cmd->obey = lookup->obey;
1364 static void do_sftp_init(void)
1367 * Do protocol initialisation.
1371 "Fatal: unable to initialise SFTP: %s\n", fxp_error());
1376 * Find out where our home directory is.
1378 homedir = fxp_realpath(".");
1381 "Warning: failed to resolve home directory: %s\n",
1383 homedir = dupstr(".");
1385 printf("Remote working directory is %s\n", homedir);
1387 pwd = dupstr(homedir);
1390 void do_sftp(int mode, int modeflags, char *batchfile)
1400 /* ------------------------------------------------------------------
1401 * Now we're ready to do Real Stuff.
1404 struct sftp_command *cmd;
1405 cmd = sftp_getcmd(stdin, 0, 0);
1408 if (cmd->obey(cmd) < 0)
1412 fp = fopen(batchfile, "r");
1414 printf("Fatal: unable to open %s\n", batchfile);
1418 struct sftp_command *cmd;
1419 cmd = sftp_getcmd(fp, mode, modeflags);
1422 ret = cmd->obey(cmd);
1426 if (!(modeflags & 2))
1435 /* ----------------------------------------------------------------------
1436 * Dirty bits: integration with PuTTY.
1439 static int verbose = 0;
1441 void verify_ssh_host_key(char *host, int port, char *keytype,
1442 char *keystr, char *fingerprint)
1448 static const char absentmsg[] =
1449 "The server's host key is not cached in the registry. You\n"
1450 "have no guarantee that the server is the computer you\n"
1452 "The server's key fingerprint is:\n"
1454 "If you trust this host, enter \"y\" to add the key to\n"
1455 "PuTTY's cache and carry on connecting.\n"
1456 "If you want to carry on connecting just once, without\n"
1457 "adding the key to the cache, enter \"n\".\n"
1458 "If you do not trust this host, press Return to abandon the\n"
1460 "Store key in cache? (y/n) ";
1462 static const char wrongmsg[] =
1463 "WARNING - POTENTIAL SECURITY BREACH!\n"
1464 "The server's host key does not match the one PuTTY has\n"
1465 "cached in the registry. This means that either the\n"
1466 "server administrator has changed the host key, or you\n"
1467 "have actually connected to another computer pretending\n"
1468 "to be the server.\n"
1469 "The new key fingerprint is:\n"
1471 "If you were expecting this change and trust the new key,\n"
1472 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
1473 "If you want to carry on connecting but without updating\n"
1474 "the cache, enter \"n\".\n"
1475 "If you want to abandon the connection completely, press\n"
1476 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
1478 "Update cached key? (y/n, Return cancels connection) ";
1480 static const char abandoned[] = "Connection abandoned.\n";
1485 * Verify the key against the registry.
1487 ret = verify_host_key(host, port, keytype, keystr);
1489 if (ret == 0) /* success - key matched OK */
1492 if (ret == 2) { /* key was different */
1493 fprintf(stderr, wrongmsg, fingerprint);
1496 if (ret == 1) { /* key was absent */
1497 fprintf(stderr, absentmsg, fingerprint);
1501 hin = GetStdHandle(STD_INPUT_HANDLE);
1502 GetConsoleMode(hin, &savemode);
1503 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
1504 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
1505 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
1506 SetConsoleMode(hin, savemode);
1508 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
1509 if (line[0] == 'y' || line[0] == 'Y')
1510 store_host_key(host, port, keytype, keystr);
1512 fprintf(stderr, abandoned);
1518 * Ask whether the selected cipher is acceptable (since it was
1519 * below the configured 'warn' threshold).
1520 * cs: 0 = both ways, 1 = client->server, 2 = server->client
1522 void askcipher(char *ciphername, int cs)
1527 static const char msg[] =
1528 "The first %scipher supported by the server is\n"
1529 "%s, which is below the configured warning threshold.\n"
1530 "Continue with connection? (y/n) ";
1531 static const char abandoned[] = "Connection abandoned.\n";
1535 fprintf(stderr, msg,
1537 (cs == 1) ? "client-to-server " :
1538 "server-to-client ",
1542 hin = GetStdHandle(STD_INPUT_HANDLE);
1543 GetConsoleMode(hin, &savemode);
1544 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
1545 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
1546 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
1547 SetConsoleMode(hin, savemode);
1549 if (line[0] == 'y' || line[0] == 'Y') {
1552 fprintf(stderr, abandoned);
1558 * Ask whether to wipe a session log file before writing to it.
1559 * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
1561 int askappend(char *filename)
1566 static const char msgtemplate[] =
1567 "The session log file \"%.*s\" already exists.\n"
1568 "You can overwrite it with a new session log,\n"
1569 "append your session log to the end of it,\n"
1570 "or disable session logging for this session.\n"
1571 "Enter \"y\" to wipe the file, \"n\" to append to it,\n"
1572 "or just press Return to disable logging.\n"
1573 "Wipe the log file? (y/n, Return cancels logging) ";
1577 fprintf(stderr, msgtemplate, FILENAME_MAX, filename);
1580 hin = GetStdHandle(STD_INPUT_HANDLE);
1581 GetConsoleMode(hin, &savemode);
1582 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
1583 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
1584 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
1585 SetConsoleMode(hin, savemode);
1587 if (line[0] == 'y' || line[0] == 'Y')
1589 else if (line[0] == 'n' || line[0] == 'N')
1596 * Warn about the obsolescent key file format.
1598 void old_keyfile_warning(void)
1600 static const char message[] =
1601 "You are loading an SSH 2 private key which has an\n"
1602 "old version of the file format. This means your key\n"
1603 "file is not fully tamperproof. Future versions of\n"
1604 "PuTTY may stop supporting this private key format,\n"
1605 "so we recommend you convert your key to the new\n"
1608 "Once the key is loaded into PuTTYgen, you can perform\n"
1609 "this conversion simply by saving it again.\n";
1611 fputs(message, stderr);
1615 * Print an error message and perform a fatal exit.
1617 void fatalbox(char *fmt, ...)
1619 char str[0x100]; /* Make the size big enough */
1622 strcpy(str, "Fatal:");
1623 vsprintf(str + strlen(str), fmt, ap);
1630 void connection_fatal(char *fmt, ...)
1632 char str[0x100]; /* Make the size big enough */
1635 strcpy(str, "Fatal:");
1636 vsprintf(str + strlen(str), fmt, ap);
1644 void logevent(char *string)
1648 void ldisc_send(char *buf, int len, int interactive)
1651 * This is only here because of the calls to ldisc_send(NULL,
1652 * 0) in ssh.c. Nothing in PSFTP actually needs to use the
1653 * ldisc as an ldisc. So if we get called with any real data, I
1654 * want to know about it.
1660 * Be told what socket we're supposed to be using.
1662 static SOCKET sftp_ssh_socket;
1663 char *do_select(SOCKET skt, int startup)
1666 sftp_ssh_socket = skt;
1668 sftp_ssh_socket = INVALID_SOCKET;
1671 extern int select_result(WPARAM, LPARAM);
1674 * Receive a block of data from the SSH link. Block until all data
1677 * To do this, we repeatedly call the SSH protocol module, with our
1678 * own trap in from_backend() to catch the data that comes back. We
1679 * do this until we have enough data.
1682 static unsigned char *outptr; /* where to put the data */
1683 static unsigned outlen; /* how much data required */
1684 static unsigned char *pending = NULL; /* any spare data */
1685 static unsigned pendlen = 0, pendsize = 0; /* length and phys. size of buffer */
1686 int from_backend(int is_stderr, char *data, int datalen)
1688 unsigned char *p = (unsigned char *) data;
1689 unsigned len = (unsigned) datalen;
1692 * stderr data is just spouted to local stderr and otherwise
1696 fwrite(data, 1, len, stderr);
1701 * If this is before the real session begins, just return.
1707 unsigned used = outlen;
1710 memcpy(outptr, p, used);
1718 if (pendsize < pendlen + len) {
1719 pendsize = pendlen + len + 4096;
1720 pending = (pending ? srealloc(pending, pendsize) :
1723 fatalbox("Out of memory");
1725 memcpy(pending + pendlen, p, len);
1731 int sftp_recvdata(char *buf, int len)
1733 outptr = (unsigned char *) buf;
1737 * See if the pending-input block contains some of what we
1741 unsigned pendused = pendlen;
1742 if (pendused > outlen)
1744 memcpy(outptr, pending, pendused);
1745 memmove(pending, pending + pendused, pendlen - pendused);
1748 pendlen -= pendused;
1758 while (outlen > 0) {
1762 FD_SET(sftp_ssh_socket, &readfds);
1763 if (select(1, &readfds, NULL, NULL, NULL) < 0)
1764 return 0; /* doom */
1765 select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
1770 int sftp_senddata(char *buf, int len)
1772 back->send((unsigned char *) buf, len);
1777 * Loop through the ssh connection and authentication process.
1779 static void ssh_sftp_init(void)
1781 if (sftp_ssh_socket == INVALID_SOCKET)
1783 while (!back->sendok()) {
1786 FD_SET(sftp_ssh_socket, &readfds);
1787 if (select(1, &readfds, NULL, NULL, NULL) < 0)
1789 select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
1793 static char *password = NULL;
1794 static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
1797 DWORD savemode, newmode, i;
1800 static int tried_once = 0;
1805 strncpy(str, password, maxlen);
1806 str[maxlen - 1] = '\0';
1812 hin = GetStdHandle(STD_INPUT_HANDLE);
1813 hout = GetStdHandle(STD_OUTPUT_HANDLE);
1814 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
1815 fprintf(stderr, "Cannot get standard input/output handles\n");
1819 GetConsoleMode(hin, &savemode);
1820 newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
1822 newmode &= ~ENABLE_ECHO_INPUT;
1824 newmode |= ENABLE_ECHO_INPUT;
1825 SetConsoleMode(hin, newmode);
1827 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
1828 ReadFile(hin, str, maxlen - 1, &i, NULL);
1830 SetConsoleMode(hin, savemode);
1832 if ((int) i > maxlen)
1839 WriteFile(hout, "\r\n", 2, &i, NULL);
1845 * Initialize the Win$ock driver.
1847 static void init_winsock(void)
1852 winsock_ver = MAKEWORD(1, 1);
1853 if (WSAStartup(winsock_ver, &wsadata)) {
1854 fprintf(stderr, "Unable to initialise WinSock");
1857 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
1858 fprintf(stderr, "WinSock version is incompatible with 1.1");
1864 * Short description of parameters.
1866 static void usage(void)
1868 printf("PuTTY Secure File Transfer (SFTP) client\n");
1869 printf("%s\n", ver);
1870 printf("Usage: psftp [options] user@host\n");
1871 printf("Options:\n");
1872 printf(" -b file use specified batchfile\n");
1873 printf(" -bc output batchfile commands\n");
1874 printf(" -be don't stop batchfile processing if errors\n");
1875 printf(" -v show verbose messages\n");
1876 printf(" -P port connect to specified port\n");
1877 printf(" -pw passw login with specified password\n");
1882 * Connect to a host.
1884 static int psftp_connect(char *userhost, char *user, int portnumber)
1886 char *host, *realhost;
1889 /* Separate host and username */
1891 host = strrchr(host, '@');
1897 printf("psftp: multiple usernames specified; using \"%s\"\n",
1903 /* Try to load settings for this host */
1904 do_defaults(host, &cfg);
1905 if (cfg.host[0] == '\0') {
1906 /* No settings for this host; use defaults */
1907 do_defaults(NULL, &cfg);
1908 strncpy(cfg.host, host, sizeof(cfg.host) - 1);
1909 cfg.host[sizeof(cfg.host) - 1] = '\0';
1914 * Trim leading whitespace off the hostname if it's there.
1917 int space = strspn(cfg.host, " \t");
1918 memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
1921 /* See if host is of the form user@host */
1922 if (cfg.host[0] != '\0') {
1923 char *atsign = strchr(cfg.host, '@');
1924 /* Make sure we're not overflowing the user field */
1926 if (atsign - cfg.host < sizeof cfg.username) {
1927 strncpy(cfg.username, cfg.host, atsign - cfg.host);
1928 cfg.username[atsign - cfg.host] = '\0';
1930 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
1935 * Trim a colon suffix off the hostname if it's there.
1937 cfg.host[strcspn(cfg.host, ":")] = '\0';
1940 if (user != NULL && user[0] != '\0') {
1941 strncpy(cfg.username, user, sizeof(cfg.username) - 1);
1942 cfg.username[sizeof(cfg.username) - 1] = '\0';
1944 if (!cfg.username[0]) {
1945 printf("login as: ");
1946 if (!fgets(cfg.username, sizeof(cfg.username), stdin)) {
1947 fprintf(stderr, "psftp: aborting\n");
1950 int len = strlen(cfg.username);
1951 if (cfg.username[len - 1] == '\n')
1952 cfg.username[len - 1] = '\0';
1956 if (cfg.protocol != PROT_SSH)
1960 cfg.port = portnumber;
1962 /* SFTP uses SSH2 by default always */
1966 * Disable scary things which shouldn't be enabled for simple
1967 * things like SCP and SFTP: agent forwarding, port forwarding,
1970 cfg.x11_forward = 0;
1972 cfg.portfwd[0] = cfg.portfwd[1] = '\0';
1974 /* Set up subsystem name. */
1975 strcpy(cfg.remote_cmd, "sftp");
1976 cfg.ssh_subsys = TRUE;
1980 * Set up fallback option, for SSH1 servers or servers with the
1981 * sftp subsystem not enabled but the server binary installed
1982 * in the usual place. We only support fallback on Unix
1983 * systems, and we use a kludgy piece of shellery which should
1984 * try to find sftp-server in various places (the obvious
1985 * systemwide spots /usr/lib and /usr/local/lib, and then the
1986 * user's PATH) and finally give up.
1988 * test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server
1989 * test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server
1992 * the idea being that this will attempt to use either of the
1993 * obvious pathnames and then give up, and when it does give up
1994 * it will print the preferred pathname in the error messages.
1996 cfg.remote_cmd_ptr2 =
1997 "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n"
1998 "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n"
2000 cfg.ssh_subsys2 = FALSE;
2002 back = &ssh_backend;
2004 err = back->init(cfg.host, cfg.port, &realhost, 0);
2006 fprintf(stderr, "ssh_init: %s\n", err);
2010 if (verbose && realhost != NULL)
2011 printf("Connected to %s\n", realhost);
2016 * Main program. Parse arguments etc.
2018 int main(int argc, char *argv[])
2022 char *userhost, *user;
2025 char *batchfile = NULL;
2027 flags = FLAG_STDERR | FLAG_INTERACTIVE;
2028 ssh_get_line = &get_line;
2032 userhost = user = NULL;
2034 for (i = 1; i < argc; i++) {
2035 if (argv[i][0] != '-') {
2039 userhost = dupstr(argv[i]);
2040 } else if (strcmp(argv[i], "-v") == 0) {
2041 verbose = 1, flags |= FLAG_VERBOSE;
2042 } else if (strcmp(argv[i], "-h") == 0 ||
2043 strcmp(argv[i], "-?") == 0) {
2045 } else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) {
2047 } else if (strcmp(argv[i], "-P") == 0 && i + 1 < argc) {
2048 portnumber = atoi(argv[++i]);
2049 } else if (strcmp(argv[i], "-pw") == 0 && i + 1 < argc) {
2050 password = argv[++i];
2051 } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) {
2053 batchfile = argv[++i];
2054 } else if (strcmp(argv[i], "-bc") == 0) {
2055 modeflags = modeflags | 1;
2056 } else if (strcmp(argv[i], "-be") == 0) {
2057 modeflags = modeflags | 2;
2058 } else if (strcmp(argv[i], "--") == 0) {
2070 * If a user@host string has already been provided, connect to
2074 if (psftp_connect(userhost, user, portnumber))
2078 printf("psftp: no hostname specified; use \"open host.name\""
2082 do_sftp(mode, modeflags, batchfile);
2084 if (back != NULL && back->socket() != NULL) {
2086 back->special(TS_EOF);
2087 sftp_recvdata(&ch, 1);