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);
134 * Return a pointer to the portion of str that comes after the last
135 * slash (or backslash or colon, if `local' is TRUE).
137 static char *stripslashes(char *str, int local)
142 p = strchr(str, ':');
146 p = strrchr(str, '/');
150 p = strrchr(str, '\\');
157 /* ----------------------------------------------------------------------
158 * Actual sftp commands.
160 struct sftp_command {
162 int nwords, wordssize;
163 int (*obey) (struct sftp_command *); /* returns <0 to quit */
166 int sftp_cmd_null(struct sftp_command *cmd)
171 int sftp_cmd_unknown(struct sftp_command *cmd)
173 printf("psftp: unknown command \"%s\"\n", cmd->words[0]);
177 int sftp_cmd_quit(struct sftp_command *cmd)
183 * List a directory. If no arguments are given, list pwd; otherwise
184 * list the directory given in words[1].
186 static int sftp_ls_compare(const void *av, const void *bv)
188 const struct fxp_name *a = (const struct fxp_name *) av;
189 const struct fxp_name *b = (const struct fxp_name *) bv;
190 return strcmp(a->filename, b->filename);
192 int sftp_cmd_ls(struct sftp_command *cmd)
194 struct fxp_handle *dirh;
195 struct fxp_names *names;
196 struct fxp_name *ournames;
197 int nnames, namesize;
206 cdir = canonify(dir);
208 printf("%s: %s\n", dir, fxp_error());
212 printf("Listing directory %s\n", cdir);
214 dirh = fxp_opendir(cdir);
216 printf("Unable to open %s: %s\n", dir, fxp_error());
218 nnames = namesize = 0;
223 names = fxp_readdir(dirh);
225 if (fxp_error_type() == SSH_FX_EOF)
227 printf("Reading directory %s: %s\n", dir, fxp_error());
230 if (names->nnames == 0) {
231 fxp_free_names(names);
235 if (nnames + names->nnames >= namesize) {
236 namesize += names->nnames + 128;
238 srealloc(ournames, namesize * sizeof(*ournames));
241 for (i = 0; i < names->nnames; i++)
242 ournames[nnames++] = names->names[i];
244 names->nnames = 0; /* prevent free_names */
245 fxp_free_names(names);
250 * Now we have our filenames. Sort them by actual file
251 * name, and then output the longname parts.
253 qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare);
258 for (i = 0; i < nnames; i++)
259 printf("%s\n", ournames[i].longname);
268 * Change directories. We do this by canonifying the new name, then
269 * trying to OPENDIR it. Only if that succeeds do we set the new pwd.
271 int sftp_cmd_cd(struct sftp_command *cmd)
273 struct fxp_handle *dirh;
277 dir = dupstr(homedir);
279 dir = canonify(cmd->words[1]);
282 printf("%s: %s\n", dir, fxp_error());
286 dirh = fxp_opendir(dir);
288 printf("Directory %s: %s\n", dir, fxp_error());
297 printf("Remote directory is now %s\n", pwd);
303 * Print current directory. Easy as pie.
305 int sftp_cmd_pwd(struct sftp_command *cmd)
307 printf("Remote directory is %s\n", pwd);
312 * Get a file and save it at the local end. We have two very
313 * similar commands here: `get' and `reget', which differ in that
314 * `reget' checks for the existence of the destination file and
315 * starts from where a previous aborted transfer left off.
317 int sftp_general_get(struct sftp_command *cmd, int restart)
319 struct fxp_handle *fh;
320 char *fname, *outfname;
324 if (cmd->nwords < 2) {
325 printf("get: expects a filename\n");
329 fname = canonify(cmd->words[1]);
331 printf("%s: %s\n", cmd->words[1], fxp_error());
334 outfname = (cmd->nwords == 2 ?
335 stripslashes(cmd->words[1], 0) : cmd->words[2]);
337 fh = fxp_open(fname, SSH_FXF_READ);
339 printf("%s: %s\n", fname, fxp_error());
345 fp = fopen(outfname, "rb+");
347 fp = fopen(outfname, "wb");
351 printf("local: unable to open %s\n", outfname);
359 fseek(fp, 0L, SEEK_END);
361 printf("reget: restarting at file position %ld\n", posn);
362 offset = uint64_make(0, posn);
364 offset = uint64_make(0, 0);
367 printf("remote:%s => local:%s\n", fname, outfname);
370 * FIXME: we can use FXP_FSTAT here to get the file size, and
371 * thus put up a progress bar.
378 len = fxp_read(fh, buffer, offset, sizeof(buffer));
379 if ((len == -1 && fxp_error_type() == SSH_FX_EOF) || len == 0)
382 printf("error while reading: %s\n", fxp_error());
388 wlen = fwrite(buffer, 1, len - wpos, fp);
390 printf("error while writing local file\n");
395 if (wpos < len) /* we had an error */
397 offset = uint64_add32(offset, len);
406 int sftp_cmd_get(struct sftp_command *cmd)
408 return sftp_general_get(cmd, 0);
410 int sftp_cmd_reget(struct sftp_command *cmd)
412 return sftp_general_get(cmd, 1);
416 * Send a file and store it at the remote end. We have two very
417 * similar commands here: `put' and `reput', which differ in that
418 * `reput' checks for the existence of the destination file and
419 * starts from where a previous aborted transfer left off.
421 int sftp_general_put(struct sftp_command *cmd, int restart)
423 struct fxp_handle *fh;
424 char *fname, *origoutfname, *outfname;
428 if (cmd->nwords < 2) {
429 printf("put: expects a filename\n");
433 fname = cmd->words[1];
434 origoutfname = (cmd->nwords == 2 ?
435 stripslashes(cmd->words[1], 1) : cmd->words[2]);
436 outfname = canonify(origoutfname);
438 printf("%s: %s\n", origoutfname, fxp_error());
442 fp = fopen(fname, "rb");
444 printf("local: unable to open %s\n", fname);
449 fh = fxp_open(outfname,
452 fh = fxp_open(outfname,
453 SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC);
456 printf("%s: %s\n", outfname, fxp_error());
463 struct fxp_attrs attrs;
464 if (!fxp_fstat(fh, &attrs)) {
465 printf("read size of %s: %s\n", outfname, fxp_error());
469 if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) {
470 printf("read size of %s: size was not given\n", outfname);
475 uint64_decimal(offset, decbuf);
476 printf("reput: restarting at file position %s\n", decbuf);
477 if (uint64_compare(offset, uint64_make(0, LONG_MAX)) > 0) {
478 printf("reput: remote file is larger than we can deal with\n");
482 if (fseek(fp, offset.lo, SEEK_SET) != 0)
483 fseek(fp, 0, SEEK_END); /* *shrug* */
485 offset = uint64_make(0, 0);
488 printf("local:%s => remote:%s\n", fname, outfname);
491 * FIXME: we can use FXP_FSTAT here to get the file size, and
492 * thus put up a progress bar.
498 len = fread(buffer, 1, sizeof(buffer), fp);
500 printf("error while reading local file\n");
502 } else if (len == 0) {
505 if (!fxp_write(fh, buffer, offset, len)) {
506 printf("error while writing: %s\n", fxp_error());
509 offset = uint64_add32(offset, len);
518 int sftp_cmd_put(struct sftp_command *cmd)
520 return sftp_general_put(cmd, 0);
522 int sftp_cmd_reput(struct sftp_command *cmd)
524 return sftp_general_put(cmd, 1);
527 int sftp_cmd_mkdir(struct sftp_command *cmd)
533 if (cmd->nwords < 2) {
534 printf("mkdir: expects a directory\n");
538 dir = canonify(cmd->words[1]);
540 printf("%s: %s\n", dir, fxp_error());
544 result = fxp_mkdir(dir);
546 printf("mkdir %s: %s\n", dir, fxp_error());
555 int sftp_cmd_rmdir(struct sftp_command *cmd)
561 if (cmd->nwords < 2) {
562 printf("rmdir: expects a directory\n");
566 dir = canonify(cmd->words[1]);
568 printf("%s: %s\n", dir, fxp_error());
572 result = fxp_rmdir(dir);
574 printf("rmdir %s: %s\n", dir, fxp_error());
583 int sftp_cmd_rm(struct sftp_command *cmd)
588 if (cmd->nwords < 2) {
589 printf("rm: expects a filename\n");
593 fname = canonify(cmd->words[1]);
595 printf("%s: %s\n", fname, fxp_error());
599 result = fxp_remove(fname);
601 printf("rm %s: %s\n", fname, fxp_error());
611 int sftp_cmd_mv(struct sftp_command *cmd)
613 char *srcfname, *dstfname;
616 if (cmd->nwords < 3) {
617 printf("mv: expects two filenames\n");
620 srcfname = canonify(cmd->words[1]);
622 printf("%s: %s\n", srcfname, fxp_error());
626 dstfname = canonify(cmd->words[2]);
628 printf("%s: %s\n", dstfname, fxp_error());
632 result = fxp_rename(srcfname, dstfname);
634 char const *error = fxp_error();
635 struct fxp_attrs attrs;
638 * The move might have failed because dstfname pointed at a
639 * directory. We check this possibility now: if dstfname
640 * _is_ a directory, we re-attempt the move by appending
641 * the basename of srcfname to dstfname.
643 result = fxp_stat(dstfname, &attrs);
645 (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) &&
646 (attrs.permissions & 0040000)) {
648 char *newname, *newcanon;
649 printf("(destination %s is a directory)\n", dstfname);
650 p = srcfname + strlen(srcfname);
651 while (p > srcfname && p[-1] != '/') p--;
652 newname = dupcat(dstfname, "/", p, NULL);
653 newcanon = canonify(newname);
658 result = fxp_rename(srcfname, dstfname);
659 error = result ? NULL : fxp_error();
663 printf("mv %s %s: %s\n", srcfname, dstfname, error);
669 printf("%s -> %s\n", srcfname, dstfname);
676 int sftp_cmd_chmod(struct sftp_command *cmd)
680 struct fxp_attrs attrs;
681 unsigned attrs_clr, attrs_xor, oldperms, newperms;
683 if (cmd->nwords < 3) {
684 printf("chmod: expects a mode specifier and a filename\n");
689 * Attempt to parse the mode specifier in cmd->words[1]. We
690 * don't support the full horror of Unix chmod; instead we
691 * support a much simpler syntax in which the user can either
692 * specify an octal number, or a comma-separated sequence of
693 * [ugoa]*[-+=][rwxst]+. (The initial [ugoa] sequence may
694 * _only_ be omitted if the only attribute mentioned is t,
695 * since all others require a user/group/other specification.
696 * Additionally, the s attribute may not be specified for any
697 * [ugoa] specifications other than exactly u or exactly g.
699 attrs_clr = attrs_xor = 0;
700 mode = cmd->words[1];
701 if (mode[0] >= '0' && mode[0] <= '9') {
702 if (mode[strspn(mode, "01234567")]) {
703 printf("chmod: numeric file modes should"
704 " contain digits 0-7 only\n");
708 sscanf(mode, "%o", &attrs_xor);
709 attrs_xor &= attrs_clr;
712 char *modebegin = mode;
713 unsigned subset, perms;
717 while (*mode && *mode != ',' &&
718 *mode != '+' && *mode != '-' && *mode != '=') {
720 case 'u': subset |= 04700; break; /* setuid, user perms */
721 case 'g': subset |= 02070; break; /* setgid, group perms */
722 case 'o': subset |= 00007; break; /* just other perms */
723 case 'a': subset |= 06777; break; /* all of the above */
725 printf("chmod: file mode '%.*s' contains unrecognised"
726 " user/group/other specifier '%c'\n",
727 strcspn(modebegin, ","), modebegin, *mode);
732 if (!*mode || *mode == ',') {
733 printf("chmod: file mode '%.*s' is incomplete\n",
734 strcspn(modebegin, ","), modebegin);
738 if (!*mode || *mode == ',') {
739 printf("chmod: file mode '%.*s' is incomplete\n",
740 strcspn(modebegin, ","), modebegin);
744 while (*mode && *mode != ',') {
746 case 'r': perms |= 00444; break;
747 case 'w': perms |= 00222; break;
748 case 'x': perms |= 00111; break;
749 case 't': perms |= 01000; subset |= 01000; break;
751 if ((subset & 06777) != 04700 &&
752 (subset & 06777) != 02070) {
753 printf("chmod: file mode '%.*s': set[ug]id bit should"
754 " be used with exactly one of u or g only\n",
755 strcspn(modebegin, ","), modebegin);
761 printf("chmod: file mode '%.*s' contains unrecognised"
762 " permission specifier '%c'\n",
763 strcspn(modebegin, ","), modebegin, *mode);
768 if (!(subset & 06777) && (perms &~ subset)) {
769 printf("chmod: file mode '%.*s' contains no user/group/other"
770 " specifier and permissions other than 't' \n",
771 strcspn(modebegin, ","), modebegin, *mode);
789 if (*mode) mode++; /* eat comma */
793 fname = canonify(cmd->words[2]);
795 printf("%s: %s\n", fname, fxp_error());
799 result = fxp_stat(fname, &attrs);
800 if (!result || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) {
801 printf("get attrs for %s: %s\n", fname,
802 result ? "file permissions not provided" : fxp_error());
807 attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS; /* perms _only_ */
808 oldperms = attrs.permissions & 07777;
809 attrs.permissions &= ~attrs_clr;
810 attrs.permissions ^= attrs_xor;
811 newperms = attrs.permissions & 07777;
813 result = fxp_setstat(fname, attrs);
816 printf("set attrs for %s: %s\n", fname, fxp_error());
821 printf("%s: %04o -> %04o\n", fname, oldperms, newperms);
827 static int sftp_cmd_help(struct sftp_command *cmd);
829 static struct sftp_cmd_lookup {
832 * For help purposes, there are two kinds of command:
834 * - primary commands, in which `longhelp' is non-NULL. In
835 * this case `shorthelp' is descriptive text, and `longhelp'
836 * is longer descriptive text intended to be printed after
839 * - alias commands, in which `longhelp' is NULL. In this case
840 * `shorthelp' is the name of a primary command, which
841 * contains the help that should double up for this command.
845 int (*obey) (struct sftp_command *);
848 * List of sftp commands. This is binary-searched so it MUST be
852 "bye", "finish your SFTP session",
854 " Terminates your SFTP session and quits the PSFTP program.\n",
858 "cd", "change your remote working directory",
859 " [ <New working directory> ]\n"
860 " Change the remote working directory for your SFTP session.\n"
861 " If a new working directory is not supplied, you will be\n"
862 " returned to your home directory.\n",
866 "chmod", "change file permissions and modes",
867 " ( <octal-digits> | <modifiers> ) <filename>\n"
868 " Change the file permissions on a file or directory.\n"
869 " <octal-digits> can be any octal Unix permission specifier.\n"
870 " Alternatively, <modifiers> can include:\n"
871 " u+r make file readable by owning user\n"
872 " u+w make file writable by owning user\n"
873 " u+x make file executable by owning user\n"
874 " u-r make file not readable by owning user\n"
876 " g+r make file readable by members of owning group\n"
877 " [also g+w, g+x, g-r, g-w, g-x]\n"
878 " o+r make file readable by all other users\n"
879 " [also o+w, o+x, o-r, o-w, o-x]\n"
880 " a+r make file readable by absolutely everybody\n"
881 " [also a+w, a+x, a-r, a-w, a-x]\n"
882 " u+s enable the Unix set-user-ID bit\n"
883 " u-s disable the Unix set-user-ID bit\n"
884 " g+s enable the Unix set-group-ID bit\n"
885 " g-s disable the Unix set-group-ID bit\n"
886 " +t enable the Unix \"sticky bit\"\n"
887 " You can give more than one modifier for the same user (\"g-rwx\"), and\n"
888 " more than one user for the same modifier (\"ug+w\"). You can\n"
889 " use commas to separate different modifiers (\"u+rwx,g+s\").\n",
893 "del", "delete a file",
899 "delete", "delete a file",
905 "dir", "list contents of a remote directory",
906 " [ <directory-name> ]\n"
907 " List the contents of a specified directory on the server.\n"
908 " If <directory-name> is not given, the current working directory\n"
909 " will be listed.\n",
913 "exit", "bye", NULL, sftp_cmd_quit
916 "get", "download a file from the server to your local machine",
917 " <filename> [ <local-filename> ]\n"
918 " Downloads a file on the server and stores it locally under\n"
919 " the same name, or under a different one if you supply the\n"
920 " argument <local-filename>.\n",
925 " [ <command> [ <command> ... ] ]\n"
926 " Give general help if no commands are specified.\n"
927 " If one or more commands are specified, give specific help on\n"
928 " those particular commands.\n",
936 "mkdir", "create a directory on the remote server",
937 " <directory-name>\n"
938 " Creates a directory with the given name on the server.\n",
942 "mv", "move or rename a file on the remote server",
943 " <source-filename> <destination-filename>\n"
944 " Moves or renames the file <source-filename> on the server,\n"
945 " so that it is accessible under the name <destination-filename>.\n",
949 "put", "upload a file from your local machine to the server",
950 " <filename> [ <remote-filename> ]\n"
951 " Uploads a file to the server and stores it there under\n"
952 " the same name, or under a different one if you supply the\n"
953 " argument <remote-filename>.\n",
957 "pwd", "print your remote working directory",
959 " Print the current remote working directory for your SFTP session.\n",
967 "reget", "continue downloading a file",
968 " <filename> [ <local-filename> ]\n"
969 " Works exactly like the \"get\" command, but the local file\n"
970 " must already exist. The download will begin at the end of the\n"
971 " file. This is for resuming a download that was interrupted.\n",
979 "rename", "mv", NULL,
983 "reput", "continue uploading a file",
984 " <filename> [ <remote-filename> ]\n"
985 " Works exactly like the \"put\" command, but the remote file\n"
986 " must already exist. The upload will begin at the end of the\n"
987 " file. This is for resuming an upload that was interrupted.\n",
995 "rmdir", "remove a directory on the remote server",
996 " <directory-name>\n"
997 " Removes the directory with the given name on the server.\n"
998 " The directory will not be removed unless it is empty.\n",
1003 const struct sftp_cmd_lookup *lookup_command(char *name)
1008 j = sizeof(sftp_lookup) / sizeof(*sftp_lookup);
1011 cmp = strcmp(name, sftp_lookup[k].name);
1017 return &sftp_lookup[k];
1023 static int sftp_cmd_help(struct sftp_command *cmd)
1026 if (cmd->nwords == 1) {
1028 * Give short help on each command.
1032 for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) {
1033 int len = strlen(sftp_lookup[i].name);
1037 for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) {
1038 const struct sftp_cmd_lookup *lookup;
1039 lookup = &sftp_lookup[i];
1040 printf("%-*s", maxlen+2, lookup->name);
1041 if (lookup->longhelp == NULL)
1042 lookup = lookup_command(lookup->shorthelp);
1043 printf("%s\n", lookup->shorthelp);
1047 * Give long help on specific commands.
1049 for (i = 1; i < cmd->nwords; i++) {
1050 const struct sftp_cmd_lookup *lookup;
1051 lookup = lookup_command(cmd->words[i]);
1053 printf("help: %s: command not found\n", cmd->words[i]);
1055 printf("%s", lookup->name);
1056 if (lookup->longhelp == NULL)
1057 lookup = lookup_command(lookup->shorthelp);
1058 printf("%s", lookup->longhelp);
1065 /* ----------------------------------------------------------------------
1066 * Command line reading and parsing.
1068 struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags)
1071 int linelen, linesize;
1072 struct sftp_command *cmd;
1076 if ((mode == 0) || (modeflags & 1)) {
1081 cmd = smalloc(sizeof(struct sftp_command));
1087 linesize = linelen = 0;
1093 line = srealloc(line, linesize);
1094 ret = fgets(line + linelen, linesize - linelen, fp);
1095 if (modeflags & 1) {
1099 if (!ret || (linelen == 0 && line[0] == '\0')) {
1100 cmd->obey = sftp_cmd_quit;
1102 return cmd; /* eof */
1104 len = linelen + strlen(line + linelen);
1106 if (line[linelen - 1] == '\n') {
1108 line[linelen] = '\0';
1114 * Parse the command line into words. The syntax is:
1115 * - double quotes are removed, but cause spaces within to be
1116 * treated as non-separating.
1117 * - a double-doublequote pair is a literal double quote, inside
1118 * _or_ outside quotes. Like this:
1120 * firstword "second word" "this has ""quotes"" in" sodoes""this""
1126 * >this has "quotes" in<
1131 /* skip whitespace */
1132 while (*p && (*p == ' ' || *p == '\t'))
1134 /* mark start of word */
1135 q = r = p; /* q sits at start, r writes word */
1138 if (!quoting && (*p == ' ' || *p == '\t'))
1139 break; /* reached end of word */
1140 else if (*p == '"' && p[1] == '"')
1141 p += 2, *r++ = '"'; /* a literal quote */
1143 p++, quoting = !quoting;
1148 p++; /* skip over the whitespace */
1150 if (cmd->nwords >= cmd->wordssize) {
1151 cmd->wordssize = cmd->nwords + 16;
1153 srealloc(cmd->words, cmd->wordssize * sizeof(char *));
1155 cmd->words[cmd->nwords++] = q;
1159 * Now parse the first word and assign a function.
1162 if (cmd->nwords == 0)
1163 cmd->obey = sftp_cmd_null;
1165 const struct sftp_cmd_lookup *lookup;
1166 lookup = lookup_command(cmd->words[0]);
1168 cmd->obey = sftp_cmd_unknown;
1170 cmd->obey = lookup->obey;
1176 void do_sftp(int mode, int modeflags, char *batchfile)
1181 * Do protocol initialisation.
1185 "Fatal: unable to initialise SFTP: %s\n", fxp_error());
1190 * Find out where our home directory is.
1192 homedir = fxp_realpath(".");
1195 "Warning: failed to resolve home directory: %s\n",
1197 homedir = dupstr(".");
1199 printf("Remote working directory is %s\n", homedir);
1201 pwd = dupstr(homedir);
1208 /* ------------------------------------------------------------------
1209 * Now we're ready to do Real Stuff.
1212 struct sftp_command *cmd;
1213 cmd = sftp_getcmd(stdin, 0, 0);
1216 if (cmd->obey(cmd) < 0)
1220 fp = fopen(batchfile, "r");
1222 printf("Fatal: unable to open %s\n", batchfile);
1226 struct sftp_command *cmd;
1227 cmd = sftp_getcmd(fp, mode, modeflags);
1230 if (cmd->obey(cmd) < 0)
1232 if (fxp_error() != NULL) {
1233 if (!(modeflags & 2))
1242 /* ----------------------------------------------------------------------
1243 * Dirty bits: integration with PuTTY.
1246 static int verbose = 0;
1248 void verify_ssh_host_key(char *host, int port, char *keytype,
1249 char *keystr, char *fingerprint)
1255 static const char absentmsg[] =
1256 "The server's host key is not cached in the registry. You\n"
1257 "have no guarantee that the server is the computer you\n"
1259 "The server's key fingerprint is:\n"
1261 "If you trust this host, enter \"y\" to add the key to\n"
1262 "PuTTY's cache and carry on connecting.\n"
1263 "If you want to carry on connecting just once, without\n"
1264 "adding the key to the cache, enter \"n\".\n"
1265 "If you do not trust this host, press Return to abandon the\n"
1267 "Store key in cache? (y/n) ";
1269 static const char wrongmsg[] =
1270 "WARNING - POTENTIAL SECURITY BREACH!\n"
1271 "The server's host key does not match the one PuTTY has\n"
1272 "cached in the registry. This means that either the\n"
1273 "server administrator has changed the host key, or you\n"
1274 "have actually connected to another computer pretending\n"
1275 "to be the server.\n"
1276 "The new key fingerprint is:\n"
1278 "If you were expecting this change and trust the new key,\n"
1279 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
1280 "If you want to carry on connecting but without updating\n"
1281 "the cache, enter \"n\".\n"
1282 "If you want to abandon the connection completely, press\n"
1283 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
1285 "Update cached key? (y/n, Return cancels connection) ";
1287 static const char abandoned[] = "Connection abandoned.\n";
1292 * Verify the key against the registry.
1294 ret = verify_host_key(host, port, keytype, keystr);
1296 if (ret == 0) /* success - key matched OK */
1299 if (ret == 2) { /* key was different */
1300 fprintf(stderr, wrongmsg, fingerprint);
1303 if (ret == 1) { /* key was absent */
1304 fprintf(stderr, absentmsg, fingerprint);
1308 hin = GetStdHandle(STD_INPUT_HANDLE);
1309 GetConsoleMode(hin, &savemode);
1310 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
1311 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
1312 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
1313 SetConsoleMode(hin, savemode);
1315 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
1316 if (line[0] == 'y' || line[0] == 'Y')
1317 store_host_key(host, port, keytype, keystr);
1319 fprintf(stderr, abandoned);
1325 * Ask whether the selected cipher is acceptable (since it was
1326 * below the configured 'warn' threshold).
1327 * cs: 0 = both ways, 1 = client->server, 2 = server->client
1329 void askcipher(char *ciphername, int cs)
1334 static const char msg[] =
1335 "The first %scipher supported by the server is\n"
1336 "%s, which is below the configured warning threshold.\n"
1337 "Continue with connection? (y/n) ";
1338 static const char abandoned[] = "Connection abandoned.\n";
1342 fprintf(stderr, msg,
1344 (cs == 1) ? "client-to-server " :
1345 "server-to-client ",
1349 hin = GetStdHandle(STD_INPUT_HANDLE);
1350 GetConsoleMode(hin, &savemode);
1351 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
1352 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
1353 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
1354 SetConsoleMode(hin, savemode);
1356 if (line[0] == 'y' || line[0] == 'Y') {
1359 fprintf(stderr, abandoned);
1365 * Warn about the obsolescent key file format.
1367 void old_keyfile_warning(void)
1369 static const char message[] =
1370 "You are loading an SSH 2 private key which has an\n"
1371 "old version of the file format. This means your key\n"
1372 "file is not fully tamperproof. Future versions of\n"
1373 "PuTTY may stop supporting this private key format,\n"
1374 "so we recommend you convert your key to the new\n"
1377 "Once the key is loaded into PuTTYgen, you can perform\n"
1378 "this conversion simply by saving it again.\n";
1380 fputs(message, stderr);
1384 * Print an error message and perform a fatal exit.
1386 void fatalbox(char *fmt, ...)
1388 char str[0x100]; /* Make the size big enough */
1391 strcpy(str, "Fatal:");
1392 vsprintf(str + strlen(str), fmt, ap);
1395 fprintf(stderr, str);
1399 void connection_fatal(char *fmt, ...)
1401 char str[0x100]; /* Make the size big enough */
1404 strcpy(str, "Fatal:");
1405 vsprintf(str + strlen(str), fmt, ap);
1408 fprintf(stderr, str);
1413 void logevent(char *string)
1417 void ldisc_send(char *buf, int len, int interactive)
1420 * This is only here because of the calls to ldisc_send(NULL,
1421 * 0) in ssh.c. Nothing in PSFTP actually needs to use the
1422 * ldisc as an ldisc. So if we get called with any real data, I
1423 * want to know about it.
1429 * Be told what socket we're supposed to be using.
1431 static SOCKET sftp_ssh_socket;
1432 char *do_select(SOCKET skt, int startup)
1435 sftp_ssh_socket = skt;
1437 sftp_ssh_socket = INVALID_SOCKET;
1440 extern int select_result(WPARAM, LPARAM);
1443 * Receive a block of data from the SSH link. Block until all data
1446 * To do this, we repeatedly call the SSH protocol module, with our
1447 * own trap in from_backend() to catch the data that comes back. We
1448 * do this until we have enough data.
1451 static unsigned char *outptr; /* where to put the data */
1452 static unsigned outlen; /* how much data required */
1453 static unsigned char *pending = NULL; /* any spare data */
1454 static unsigned pendlen = 0, pendsize = 0; /* length and phys. size of buffer */
1455 int from_backend(int is_stderr, char *data, int datalen)
1457 unsigned char *p = (unsigned char *) data;
1458 unsigned len = (unsigned) datalen;
1461 * stderr data is just spouted to local stderr and otherwise
1465 fwrite(data, 1, len, stderr);
1470 * If this is before the real session begins, just return.
1476 unsigned used = outlen;
1479 memcpy(outptr, p, used);
1487 if (pendsize < pendlen + len) {
1488 pendsize = pendlen + len + 4096;
1489 pending = (pending ? srealloc(pending, pendsize) :
1492 fatalbox("Out of memory");
1494 memcpy(pending + pendlen, p, len);
1500 int sftp_recvdata(char *buf, int len)
1502 outptr = (unsigned char *) buf;
1506 * See if the pending-input block contains some of what we
1510 unsigned pendused = pendlen;
1511 if (pendused > outlen)
1513 memcpy(outptr, pending, pendused);
1514 memmove(pending, pending + pendused, pendlen - pendused);
1517 pendlen -= pendused;
1527 while (outlen > 0) {
1531 FD_SET(sftp_ssh_socket, &readfds);
1532 if (select(1, &readfds, NULL, NULL, NULL) < 0)
1533 return 0; /* doom */
1534 select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
1539 int sftp_senddata(char *buf, int len)
1541 back->send((unsigned char *) buf, len);
1546 * Loop through the ssh connection and authentication process.
1548 static void ssh_sftp_init(void)
1550 if (sftp_ssh_socket == INVALID_SOCKET)
1552 while (!back->sendok()) {
1555 FD_SET(sftp_ssh_socket, &readfds);
1556 if (select(1, &readfds, NULL, NULL, NULL) < 0)
1558 select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
1562 static char *password = NULL;
1563 static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
1566 DWORD savemode, newmode, i;
1569 static int tried_once = 0;
1574 strncpy(str, password, maxlen);
1575 str[maxlen - 1] = '\0';
1581 hin = GetStdHandle(STD_INPUT_HANDLE);
1582 hout = GetStdHandle(STD_OUTPUT_HANDLE);
1583 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
1584 fprintf(stderr, "Cannot get standard input/output handles\n");
1588 GetConsoleMode(hin, &savemode);
1589 newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
1591 newmode &= ~ENABLE_ECHO_INPUT;
1593 newmode |= ENABLE_ECHO_INPUT;
1594 SetConsoleMode(hin, newmode);
1596 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
1597 ReadFile(hin, str, maxlen - 1, &i, NULL);
1599 SetConsoleMode(hin, savemode);
1601 if ((int) i > maxlen)
1608 WriteFile(hout, "\r\n", 2, &i, NULL);
1614 * Initialize the Win$ock driver.
1616 static void init_winsock(void)
1621 winsock_ver = MAKEWORD(1, 1);
1622 if (WSAStartup(winsock_ver, &wsadata)) {
1623 fprintf(stderr, "Unable to initialise WinSock");
1626 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
1627 fprintf(stderr, "WinSock version is incompatible with 1.1");
1633 * Short description of parameters.
1635 static void usage(void)
1637 printf("PuTTY Secure File Transfer (SFTP) client\n");
1638 printf("%s\n", ver);
1639 printf("Usage: psftp [options] user@host\n");
1640 printf("Options:\n");
1641 printf(" -b file use specified batchfile\n");
1642 printf(" -bc output batchfile commands\n");
1643 printf(" -be don't stop batchfile processing if errors\n");
1644 printf(" -v show verbose messages\n");
1645 printf(" -P port connect to specified port\n");
1646 printf(" -pw passw login with specified password\n");
1651 * Main program. Parse arguments etc.
1653 int main(int argc, char *argv[])
1657 char *user, *host, *userhost, *realhost;
1661 char *batchfile = NULL;
1663 flags = FLAG_STDERR | FLAG_INTERACTIVE;
1664 ssh_get_line = &get_line;
1668 userhost = user = NULL;
1670 for (i = 1; i < argc; i++) {
1671 if (argv[i][0] != '-') {
1675 userhost = dupstr(argv[i]);
1676 } else if (strcmp(argv[i], "-v") == 0) {
1677 verbose = 1, flags |= FLAG_VERBOSE;
1678 } else if (strcmp(argv[i], "-h") == 0 ||
1679 strcmp(argv[i], "-?") == 0) {
1681 } else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) {
1683 } else if (strcmp(argv[i], "-P") == 0 && i + 1 < argc) {
1684 portnumber = atoi(argv[++i]);
1685 } else if (strcmp(argv[i], "-pw") == 0 && i + 1 < argc) {
1686 password = argv[++i];
1687 } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) {
1689 batchfile = argv[++i];
1690 } else if (strcmp(argv[i], "-bc") == 0 && i + 1 < argc) {
1691 modeflags = modeflags | 1;
1692 } else if (strcmp(argv[i], "-be") == 0 && i + 1 < argc) {
1693 modeflags = modeflags | 2;
1694 } else if (strcmp(argv[i], "--") == 0) {
1705 if (argc > 0 || !userhost)
1708 /* Separate host and username */
1710 host = strrchr(host, '@');
1716 printf("psftp: multiple usernames specified; using \"%s\"\n",
1722 /* Try to load settings for this host */
1723 do_defaults(host, &cfg);
1724 if (cfg.host[0] == '\0') {
1725 /* No settings for this host; use defaults */
1726 do_defaults(NULL, &cfg);
1727 strncpy(cfg.host, host, sizeof(cfg.host) - 1);
1728 cfg.host[sizeof(cfg.host) - 1] = '\0';
1733 * Trim leading whitespace off the hostname if it's there.
1736 int space = strspn(cfg.host, " \t");
1737 memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
1740 /* See if host is of the form user@host */
1741 if (cfg.host[0] != '\0') {
1742 char *atsign = strchr(cfg.host, '@');
1743 /* Make sure we're not overflowing the user field */
1745 if (atsign - cfg.host < sizeof cfg.username) {
1746 strncpy(cfg.username, cfg.host, atsign - cfg.host);
1747 cfg.username[atsign - cfg.host] = '\0';
1749 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
1754 * Trim a colon suffix off the hostname if it's there.
1756 cfg.host[strcspn(cfg.host, ":")] = '\0';
1759 if (user != NULL && user[0] != '\0') {
1760 strncpy(cfg.username, user, sizeof(cfg.username) - 1);
1761 cfg.username[sizeof(cfg.username) - 1] = '\0';
1763 if (!cfg.username[0]) {
1764 printf("login as: ");
1765 if (!fgets(cfg.username, sizeof(cfg.username), stdin)) {
1766 fprintf(stderr, "psftp: aborting\n");
1769 int len = strlen(cfg.username);
1770 if (cfg.username[len - 1] == '\n')
1771 cfg.username[len - 1] = '\0';
1775 if (cfg.protocol != PROT_SSH)
1779 cfg.port = portnumber;
1781 /* SFTP uses SSH2 by default always */
1785 * Disable scary things which shouldn't be enabled for simple
1786 * things like SCP and SFTP: agent forwarding, port forwarding,
1789 cfg.x11_forward = 0;
1791 cfg.portfwd[0] = cfg.portfwd[1] = '\0';
1793 /* Set up subsystem name. */
1794 strcpy(cfg.remote_cmd, "sftp");
1795 cfg.ssh_subsys = TRUE;
1799 * Set up fallback option, for SSH1 servers or servers with the
1800 * sftp subsystem not enabled but the server binary installed
1801 * in the usual place. We only support fallback on Unix
1802 * systems, and we use a kludgy piece of shellery which should
1803 * try to find sftp-server in various places (the obvious
1804 * systemwide spots /usr/lib and /usr/local/lib, and then the
1805 * user's PATH) and finally give up.
1807 * test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server
1808 * test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server
1811 * the idea being that this will attempt to use either of the
1812 * obvious pathnames and then give up, and when it does give up
1813 * it will print the preferred pathname in the error messages.
1815 cfg.remote_cmd_ptr2 =
1816 "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n"
1817 "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n"
1819 cfg.ssh_subsys2 = FALSE;
1821 back = &ssh_backend;
1823 err = back->init(cfg.host, cfg.port, &realhost, 0);
1825 fprintf(stderr, "ssh_init: %s", err);
1829 if (verbose && realhost != NULL)
1830 printf("Connected to %s\n", realhost);
1832 do_sftp(mode, modeflags, batchfile);
1834 if (back != NULL && back->socket() != NULL) {
1836 back->special(TS_EOF);
1837 sftp_recvdata(&ch, 1);