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 * Get a file and save it at the local end. We have two very
280 * similar commands here: `get' and `reget', which differ in that
281 * `reget' checks for the existence of the destination file and
282 * starts from where a previous aborted transfer left off.
284 int sftp_general_get(struct sftp_command *cmd, int restart)
286 struct fxp_handle *fh;
287 char *fname, *outfname;
291 if (cmd->nwords < 2) {
292 printf("get: expects a filename\n");
296 fname = canonify(cmd->words[1]);
298 printf("%s: %s\n", cmd->words[1], fxp_error());
301 outfname = (cmd->nwords == 2 ? cmd->words[1] : cmd->words[2]);
303 fh = fxp_open(fname, SSH_FXF_READ);
305 printf("%s: %s\n", fname, fxp_error());
311 fp = fopen(outfname, "rb+");
313 fp = fopen(outfname, "wb");
317 printf("local: unable to open %s\n", outfname);
325 fseek(fp, 0L, SEEK_END);
327 printf("reget: restarting at file position %ld\n", posn);
328 offset = uint64_make(0, posn);
330 offset = uint64_make(0, 0);
333 printf("remote:%s => local:%s\n", fname, outfname);
336 * FIXME: we can use FXP_FSTAT here to get the file size, and
337 * thus put up a progress bar.
344 len = fxp_read(fh, buffer, offset, sizeof(buffer));
345 if ((len == -1 && fxp_error_type() == SSH_FX_EOF) || len == 0)
348 printf("error while reading: %s\n", fxp_error());
354 wlen = fwrite(buffer, 1, len - wpos, fp);
356 printf("error while writing local file\n");
361 if (wpos < len) /* we had an error */
363 offset = uint64_add32(offset, len);
372 int sftp_cmd_get(struct sftp_command *cmd)
374 return sftp_general_get(cmd, 0);
376 int sftp_cmd_reget(struct sftp_command *cmd)
378 return sftp_general_get(cmd, 1);
382 * Send a file and store it at the remote end. We have two very
383 * similar commands here: `put' and `reput', which differ in that
384 * `reput' checks for the existence of the destination file and
385 * starts from where a previous aborted transfer left off.
387 int sftp_general_put(struct sftp_command *cmd, int restart)
389 struct fxp_handle *fh;
390 char *fname, *origoutfname, *outfname;
394 if (cmd->nwords < 2) {
395 printf("put: expects a filename\n");
399 fname = cmd->words[1];
400 origoutfname = (cmd->nwords == 2 ? cmd->words[1] : cmd->words[2]);
401 outfname = canonify(origoutfname);
403 printf("%s: %s\n", origoutfname, fxp_error());
407 fp = fopen(fname, "rb");
409 printf("local: unable to open %s\n", fname);
414 fh = fxp_open(outfname,
417 fh = fxp_open(outfname,
418 SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC);
421 printf("%s: %s\n", outfname, fxp_error());
428 struct fxp_attrs attrs;
429 if (!fxp_fstat(fh, &attrs)) {
430 printf("read size of %s: %s\n", outfname, fxp_error());
434 if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) {
435 printf("read size of %s: size was not given\n", outfname);
440 uint64_decimal(offset, decbuf);
441 printf("reput: restarting at file position %s\n", decbuf);
442 if (uint64_compare(offset, uint64_make(0, LONG_MAX)) > 0) {
443 printf("reput: remote file is larger than we can deal with\n");
447 if (fseek(fp, offset.lo, SEEK_SET) != 0)
448 fseek(fp, 0, SEEK_END); /* *shrug* */
450 offset = uint64_make(0, 0);
453 printf("local:%s => remote:%s\n", fname, outfname);
456 * FIXME: we can use FXP_FSTAT here to get the file size, and
457 * thus put up a progress bar.
463 len = fread(buffer, 1, sizeof(buffer), fp);
465 printf("error while reading local file\n");
467 } else if (len == 0) {
470 if (!fxp_write(fh, buffer, offset, len)) {
471 printf("error while writing: %s\n", fxp_error());
474 offset = uint64_add32(offset, len);
483 int sftp_cmd_put(struct sftp_command *cmd)
485 return sftp_general_put(cmd, 0);
487 int sftp_cmd_reput(struct sftp_command *cmd)
489 return sftp_general_put(cmd, 1);
492 int sftp_cmd_mkdir(struct sftp_command *cmd)
498 if (cmd->nwords < 2) {
499 printf("mkdir: expects a directory\n");
503 dir = canonify(cmd->words[1]);
505 printf("%s: %s\n", dir, fxp_error());
509 result = fxp_mkdir(dir);
511 printf("mkdir %s: %s\n", dir, fxp_error());
520 int sftp_cmd_rmdir(struct sftp_command *cmd)
526 if (cmd->nwords < 2) {
527 printf("rmdir: expects a directory\n");
531 dir = canonify(cmd->words[1]);
533 printf("%s: %s\n", dir, fxp_error());
537 result = fxp_rmdir(dir);
539 printf("rmdir %s: %s\n", dir, fxp_error());
548 int sftp_cmd_rm(struct sftp_command *cmd)
553 if (cmd->nwords < 2) {
554 printf("rm: expects a filename\n");
558 fname = canonify(cmd->words[1]);
560 printf("%s: %s\n", fname, fxp_error());
564 result = fxp_remove(fname);
566 printf("rm %s: %s\n", fname, fxp_error());
576 int sftp_cmd_mv(struct sftp_command *cmd)
578 char *srcfname, *dstfname;
581 if (cmd->nwords < 3) {
582 printf("mv: expects two filenames\n");
585 srcfname = canonify(cmd->words[1]);
587 printf("%s: %s\n", srcfname, fxp_error());
591 dstfname = canonify(cmd->words[2]);
593 printf("%s: %s\n", dstfname, fxp_error());
597 result = fxp_rename(srcfname, dstfname);
599 char const *error = fxp_error();
600 struct fxp_attrs attrs;
603 * The move might have failed because dstfname pointed at a
604 * directory. We check this possibility now: if dstfname
605 * _is_ a directory, we re-attempt the move by appending
606 * the basename of srcfname to dstfname.
608 result = fxp_stat(dstfname, &attrs);
610 (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) &&
611 (attrs.permissions & 0040000)) {
613 char *newname, *newcanon;
614 printf("(destination %s is a directory)\n", dstfname);
615 p = srcfname + strlen(srcfname);
616 while (p > srcfname && p[-1] != '/') p--;
617 newname = dupcat(dstfname, "/", p, NULL);
618 newcanon = canonify(newname);
623 result = fxp_rename(srcfname, dstfname);
624 error = result ? NULL : fxp_error();
628 printf("mv %s %s: %s\n", srcfname, dstfname, error);
634 printf("%s -> %s\n", srcfname, dstfname);
641 int sftp_cmd_chmod(struct sftp_command *cmd)
645 struct fxp_attrs attrs;
646 unsigned attrs_clr, attrs_xor, oldperms, newperms;
648 if (cmd->nwords < 3) {
649 printf("chmod: expects a mode specifier and a filename\n");
654 * Attempt to parse the mode specifier in cmd->words[1]. We
655 * don't support the full horror of Unix chmod; instead we
656 * support a much simpler syntax in which the user can either
657 * specify an octal number, or a comma-separated sequence of
658 * [ugoa]*[-+=][rwxst]+. (The initial [ugoa] sequence may
659 * _only_ be omitted if the only attribute mentioned is t,
660 * since all others require a user/group/other specification.
661 * Additionally, the s attribute may not be specified for any
662 * [ugoa] specifications other than exactly u or exactly g.
664 attrs_clr = attrs_xor = 0;
665 mode = cmd->words[1];
666 if (mode[0] >= '0' && mode[0] <= '9') {
667 if (mode[strspn(mode, "01234567")]) {
668 printf("chmod: numeric file modes should"
669 " contain digits 0-7 only\n");
673 sscanf(mode, "%o", &attrs_xor);
674 attrs_xor &= attrs_clr;
677 char *modebegin = mode;
678 unsigned subset, perms;
682 while (*mode && *mode != ',' &&
683 *mode != '+' && *mode != '-' && *mode != '=') {
685 case 'u': subset |= 04700; break; /* setuid, user perms */
686 case 'g': subset |= 02070; break; /* setgid, group perms */
687 case 'o': subset |= 00007; break; /* just other perms */
688 case 'a': subset |= 06777; break; /* all of the above */
690 printf("chmod: file mode '%.*s' contains unrecognised"
691 " user/group/other specifier '%c'\n",
692 strcspn(modebegin, ","), modebegin, *mode);
697 if (!*mode || *mode == ',') {
698 printf("chmod: file mode '%.*s' is incomplete\n",
699 strcspn(modebegin, ","), modebegin);
703 if (!*mode || *mode == ',') {
704 printf("chmod: file mode '%.*s' is incomplete\n",
705 strcspn(modebegin, ","), modebegin);
709 while (*mode && *mode != ',') {
711 case 'r': perms |= 00444; break;
712 case 'w': perms |= 00222; break;
713 case 'x': perms |= 00111; break;
714 case 't': perms |= 01000; subset |= 01000; break;
716 if ((subset & 06777) != 04700 &&
717 (subset & 06777) != 02070) {
718 printf("chmod: file mode '%.*s': set[ug]id bit should"
719 " be used with exactly one of u or g only\n",
720 strcspn(modebegin, ","), modebegin);
726 printf("chmod: file mode '%.*s' contains unrecognised"
727 " permission specifier '%c'\n",
728 strcspn(modebegin, ","), modebegin, *mode);
733 if (!(subset & 06777) && (perms &~ subset)) {
734 printf("chmod: file mode '%.*s' contains no user/group/other"
735 " specifier and permissions other than 't' \n",
736 strcspn(modebegin, ","), modebegin, *mode);
754 if (*mode) mode++; /* eat comma */
758 fname = canonify(cmd->words[2]);
760 printf("%s: %s\n", fname, fxp_error());
764 result = fxp_stat(fname, &attrs);
765 if (!result || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) {
766 printf("get attrs for %s: %s\n", fname,
767 result ? "file permissions not provided" : fxp_error());
772 attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS; /* perms _only_ */
773 oldperms = attrs.permissions & 07777;
774 attrs.permissions &= ~attrs_clr;
775 attrs.permissions ^= attrs_xor;
776 newperms = attrs.permissions & 07777;
778 result = fxp_setstat(fname, attrs);
781 printf("set attrs for %s: %s\n", fname, fxp_error());
786 printf("%s: %04o -> %04o\n", fname, oldperms, newperms);
792 static int sftp_cmd_help(struct sftp_command *cmd);
794 static struct sftp_cmd_lookup {
797 * For help purposes, there are two kinds of command:
799 * - primary commands, in which `longhelp' is non-NULL. In
800 * this case `shorthelp' is descriptive text, and `longhelp'
801 * is longer descriptive text intended to be printed after
804 * - alias commands, in which `longhelp' is NULL. In this case
805 * `shorthelp' is the name of a primary command, which
806 * contains the help that should double up for this command.
810 int (*obey) (struct sftp_command *);
813 * List of sftp commands. This is binary-searched so it MUST be
817 "bye", "finish your SFTP session",
819 " Terminates your SFTP session and quits the PSFTP program.\n",
823 "cd", "change your remote working directory",
824 " [ <New working directory> ]\n"
825 " Change the remote working directory for your SFTP session.\n"
826 " If a new working directory is not supplied, you will be\n"
827 " returned to your home directory.\n",
831 "chmod", "change file permissions and modes",
832 " ( <octal-digits> | <modifiers> ) <filename>\n"
833 " Change the file permissions on a file or directory.\n"
834 " <octal-digits> can be any octal Unix permission specifier.\n"
835 " Alternatively, <modifiers> can include:\n"
836 " u+r make file readable by owning user\n"
837 " u+w make file writable by owning user\n"
838 " u+x make file executable by owning user\n"
839 " u-r make file not readable by owning user\n"
841 " g+r make file readable by members of owning group\n"
842 " [also g+w, g+x, g-r, g-w, g-x]\n"
843 " o+r make file readable by all other users\n"
844 " [also o+w, o+x, o-r, o-w, o-x]\n"
845 " a+r make file readable by absolutely everybody\n"
846 " [also a+w, a+x, a-r, a-w, a-x]\n"
847 " u+s enable the Unix set-user-ID bit\n"
848 " u-s disable the Unix set-user-ID bit\n"
849 " g+s enable the Unix set-group-ID bit\n"
850 " g-s disable the Unix set-group-ID bit\n"
851 " +t enable the Unix \"sticky bit\"\n"
852 " You can give more than one modifier for the same user (\"g-rwx\"), and\n"
853 " more than one user for the same modifier (\"ug+w\"). You can\n"
854 " use commas to separate different modifiers (\"u+rwx,g+s\").\n",
858 "del", "delete a file",
864 "delete", "delete a file",
870 "dir", "list contents of a remote directory",
871 " [ <directory-name> ]\n"
872 " List the contents of a specified directory on the server.\n"
873 " If <directory-name> is not given, the current working directory\n"
874 " will be listed.\n",
878 "exit", "bye", NULL, sftp_cmd_quit
881 "get", "download a file from the server to your local machine",
882 " <filename> [ <local-filename> ]\n"
883 " Downloads a file on the server and stores it locally under\n"
884 " the same name, or under a different one if you supply the\n"
885 " argument <local-filename>.\n",
890 " [ <command> [ <command> ... ] ]\n"
891 " Give general help if no commands are specified.\n"
892 " If one or more commands are specified, give specific help on\n"
893 " those particular commands.\n",
901 "mkdir", "create a directory on the remote server",
902 " <directory-name>\n"
903 " Creates a directory with the given name on the server.\n",
907 "mv", "move or rename a file on the remote server",
908 " <source-filename> <destination-filename>\n"
909 " Moves or renames the file <source-filename> on the server,\n"
910 " so that it is accessible under the name <destination-filename>.\n",
914 "put", "upload a file from your local machine to the server",
915 " <filename> [ <remote-filename> ]\n"
916 " Uploads a file to the server and stores it there under\n"
917 " the same name, or under a different one if you supply the\n"
918 " argument <remote-filename>.\n",
926 "reget", "continue downloading a file",
927 " <filename> [ <local-filename> ]\n"
928 " Works exactly like the \"get\" command, but the local file\n"
929 " must already exist. The download will begin at the end of the\n"
930 " file. This is for resuming a download that was interrupted.\n",
938 "rename", "mv", NULL,
942 "reput", "continue uploading a file",
943 " <filename> [ <remote-filename> ]\n"
944 " Works exactly like the \"put\" command, but the remote file\n"
945 " must already exist. The upload will begin at the end of the\n"
946 " file. This is for resuming an upload that was interrupted.\n",
954 "rmdir", "remove a directory on the remote server",
955 " <directory-name>\n"
956 " Removes the directory with the given name on the server.\n"
957 " The directory will not be removed unless it is empty.\n",
962 const struct sftp_cmd_lookup *lookup_command(char *name)
967 j = sizeof(sftp_lookup) / sizeof(*sftp_lookup);
970 cmp = strcmp(name, sftp_lookup[k].name);
976 return &sftp_lookup[k];
982 static int sftp_cmd_help(struct sftp_command *cmd)
985 if (cmd->nwords == 1) {
987 * Give short help on each command.
991 for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) {
992 int len = strlen(sftp_lookup[i].name);
996 for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) {
997 const struct sftp_cmd_lookup *lookup;
998 lookup = &sftp_lookup[i];
999 printf("%-*s", maxlen+2, lookup->name);
1000 if (lookup->longhelp == NULL)
1001 lookup = lookup_command(lookup->shorthelp);
1002 printf("%s\n", lookup->shorthelp);
1006 * Give long help on specific commands.
1008 for (i = 1; i < cmd->nwords; i++) {
1009 const struct sftp_cmd_lookup *lookup;
1010 lookup = lookup_command(cmd->words[i]);
1012 printf("help: %s: command not found\n", cmd->words[i]);
1014 printf("%s", lookup->name);
1015 if (lookup->longhelp == NULL)
1016 lookup = lookup_command(lookup->shorthelp);
1017 printf("%s", lookup->longhelp);
1024 /* ----------------------------------------------------------------------
1025 * Command line reading and parsing.
1027 struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags)
1030 int linelen, linesize;
1031 struct sftp_command *cmd;
1035 if ((mode == 0) || (modeflags & 1)) {
1040 cmd = smalloc(sizeof(struct sftp_command));
1046 linesize = linelen = 0;
1052 line = srealloc(line, linesize);
1053 ret = fgets(line + linelen, linesize - linelen, fp);
1054 if (modeflags & 1) {
1058 if (!ret || (linelen == 0 && line[0] == '\0')) {
1059 cmd->obey = sftp_cmd_quit;
1061 return cmd; /* eof */
1063 len = linelen + strlen(line + linelen);
1065 if (line[linelen - 1] == '\n') {
1067 line[linelen] = '\0';
1073 * Parse the command line into words. The syntax is:
1074 * - double quotes are removed, but cause spaces within to be
1075 * treated as non-separating.
1076 * - a double-doublequote pair is a literal double quote, inside
1077 * _or_ outside quotes. Like this:
1079 * firstword "second word" "this has ""quotes"" in" sodoes""this""
1085 * >this has "quotes" in<
1090 /* skip whitespace */
1091 while (*p && (*p == ' ' || *p == '\t'))
1093 /* mark start of word */
1094 q = r = p; /* q sits at start, r writes word */
1097 if (!quoting && (*p == ' ' || *p == '\t'))
1098 break; /* reached end of word */
1099 else if (*p == '"' && p[1] == '"')
1100 p += 2, *r++ = '"'; /* a literal quote */
1102 p++, quoting = !quoting;
1107 p++; /* skip over the whitespace */
1109 if (cmd->nwords >= cmd->wordssize) {
1110 cmd->wordssize = cmd->nwords + 16;
1112 srealloc(cmd->words, cmd->wordssize * sizeof(char *));
1114 cmd->words[cmd->nwords++] = q;
1118 * Now parse the first word and assign a function.
1121 if (cmd->nwords == 0)
1122 cmd->obey = sftp_cmd_null;
1124 const struct sftp_cmd_lookup *lookup;
1125 lookup = lookup_command(cmd->words[0]);
1127 cmd->obey = sftp_cmd_unknown;
1129 cmd->obey = lookup->obey;
1135 void do_sftp(int mode, int modeflags, char *batchfile)
1140 * Do protocol initialisation.
1144 "Fatal: unable to initialise SFTP: %s\n", fxp_error());
1149 * Find out where our home directory is.
1151 homedir = fxp_realpath(".");
1154 "Warning: failed to resolve home directory: %s\n",
1156 homedir = dupstr(".");
1158 printf("Remote working directory is %s\n", homedir);
1160 pwd = dupstr(homedir);
1167 /* ------------------------------------------------------------------
1168 * Now we're ready to do Real Stuff.
1171 struct sftp_command *cmd;
1172 cmd = sftp_getcmd(stdin, 0, 0);
1175 if (cmd->obey(cmd) < 0)
1179 fp = fopen(batchfile, "r");
1181 printf("Fatal: unable to open %s\n", batchfile);
1185 struct sftp_command *cmd;
1186 cmd = sftp_getcmd(fp, mode, modeflags);
1189 if (cmd->obey(cmd) < 0)
1191 if (fxp_error() != NULL) {
1192 if (!(modeflags & 2))
1201 /* ----------------------------------------------------------------------
1202 * Dirty bits: integration with PuTTY.
1205 static int verbose = 0;
1207 void verify_ssh_host_key(char *host, int port, char *keytype,
1208 char *keystr, char *fingerprint)
1214 static const char absentmsg[] =
1215 "The server's host key is not cached in the registry. You\n"
1216 "have no guarantee that the server is the computer you\n"
1218 "The server's key fingerprint is:\n"
1220 "If you trust this host, enter \"y\" to add the key to\n"
1221 "PuTTY's cache and carry on connecting.\n"
1222 "If you want to carry on connecting just once, without\n"
1223 "adding the key to the cache, enter \"n\".\n"
1224 "If you do not trust this host, press Return to abandon the\n"
1226 "Store key in cache? (y/n) ";
1228 static const char wrongmsg[] =
1229 "WARNING - POTENTIAL SECURITY BREACH!\n"
1230 "The server's host key does not match the one PuTTY has\n"
1231 "cached in the registry. This means that either the\n"
1232 "server administrator has changed the host key, or you\n"
1233 "have actually connected to another computer pretending\n"
1234 "to be the server.\n"
1235 "The new key fingerprint is:\n"
1237 "If you were expecting this change and trust the new key,\n"
1238 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
1239 "If you want to carry on connecting but without updating\n"
1240 "the cache, enter \"n\".\n"
1241 "If you want to abandon the connection completely, press\n"
1242 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
1244 "Update cached key? (y/n, Return cancels connection) ";
1246 static const char abandoned[] = "Connection abandoned.\n";
1251 * Verify the key against the registry.
1253 ret = verify_host_key(host, port, keytype, keystr);
1255 if (ret == 0) /* success - key matched OK */
1258 if (ret == 2) { /* key was different */
1259 fprintf(stderr, wrongmsg, fingerprint);
1262 if (ret == 1) { /* key was absent */
1263 fprintf(stderr, absentmsg, fingerprint);
1267 hin = GetStdHandle(STD_INPUT_HANDLE);
1268 GetConsoleMode(hin, &savemode);
1269 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
1270 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
1271 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
1272 SetConsoleMode(hin, savemode);
1274 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
1275 if (line[0] == 'y' || line[0] == 'Y')
1276 store_host_key(host, port, keytype, keystr);
1278 fprintf(stderr, abandoned);
1284 * Ask whether the selected cipher is acceptable (since it was
1285 * below the configured 'warn' threshold).
1286 * cs: 0 = both ways, 1 = client->server, 2 = server->client
1288 void askcipher(char *ciphername, int cs)
1293 static const char msg[] =
1294 "The first %scipher supported by the server is\n"
1295 "%s, which is below the configured warning threshold.\n"
1296 "Continue with connection? (y/n) ";
1297 static const char abandoned[] = "Connection abandoned.\n";
1301 fprintf(stderr, msg,
1303 (cs == 1) ? "client-to-server " :
1304 "server-to-client ",
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] == 'y' || line[0] == 'Y') {
1318 fprintf(stderr, abandoned);
1324 * Print an error message and perform a fatal exit.
1326 void fatalbox(char *fmt, ...)
1328 char str[0x100]; /* Make the size big enough */
1331 strcpy(str, "Fatal:");
1332 vsprintf(str + strlen(str), fmt, ap);
1335 fprintf(stderr, str);
1339 void connection_fatal(char *fmt, ...)
1341 char str[0x100]; /* Make the size big enough */
1344 strcpy(str, "Fatal:");
1345 vsprintf(str + strlen(str), fmt, ap);
1348 fprintf(stderr, str);
1353 void logevent(char *string)
1357 void ldisc_send(char *buf, int len)
1360 * This is only here because of the calls to ldisc_send(NULL,
1361 * 0) in ssh.c. Nothing in PSFTP actually needs to use the
1362 * ldisc as an ldisc. So if we get called with any real data, I
1363 * want to know about it.
1369 * Be told what socket we're supposed to be using.
1371 static SOCKET sftp_ssh_socket;
1372 char *do_select(SOCKET skt, int startup)
1375 sftp_ssh_socket = skt;
1377 sftp_ssh_socket = INVALID_SOCKET;
1380 extern int select_result(WPARAM, LPARAM);
1383 * Receive a block of data from the SSH link. Block until all data
1386 * To do this, we repeatedly call the SSH protocol module, with our
1387 * own trap in from_backend() to catch the data that comes back. We
1388 * do this until we have enough data.
1391 static unsigned char *outptr; /* where to put the data */
1392 static unsigned outlen; /* how much data required */
1393 static unsigned char *pending = NULL; /* any spare data */
1394 static unsigned pendlen = 0, pendsize = 0; /* length and phys. size of buffer */
1395 int from_backend(int is_stderr, char *data, int datalen)
1397 unsigned char *p = (unsigned char *) data;
1398 unsigned len = (unsigned) datalen;
1401 * stderr data is just spouted to local stderr and otherwise
1405 fwrite(data, 1, len, stderr);
1410 * If this is before the real session begins, just return.
1416 unsigned used = outlen;
1419 memcpy(outptr, p, used);
1427 if (pendsize < pendlen + len) {
1428 pendsize = pendlen + len + 4096;
1429 pending = (pending ? srealloc(pending, pendsize) :
1432 fatalbox("Out of memory");
1434 memcpy(pending + pendlen, p, len);
1440 int sftp_recvdata(char *buf, int len)
1442 outptr = (unsigned char *) buf;
1446 * See if the pending-input block contains some of what we
1450 unsigned pendused = pendlen;
1451 if (pendused > outlen)
1453 memcpy(outptr, pending, pendused);
1454 memmove(pending, pending + pendused, pendlen - pendused);
1457 pendlen -= pendused;
1467 while (outlen > 0) {
1471 FD_SET(sftp_ssh_socket, &readfds);
1472 if (select(1, &readfds, NULL, NULL, NULL) < 0)
1473 return 0; /* doom */
1474 select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
1479 int sftp_senddata(char *buf, int len)
1481 back->send((unsigned char *) buf, len);
1486 * Loop through the ssh connection and authentication process.
1488 static void ssh_sftp_init(void)
1490 if (sftp_ssh_socket == INVALID_SOCKET)
1492 while (!back->sendok()) {
1495 FD_SET(sftp_ssh_socket, &readfds);
1496 if (select(1, &readfds, NULL, NULL, NULL) < 0)
1498 select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
1502 static char *password = NULL;
1503 static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
1506 DWORD savemode, newmode, i;
1509 static int tried_once = 0;
1514 strncpy(str, password, maxlen);
1515 str[maxlen - 1] = '\0';
1521 hin = GetStdHandle(STD_INPUT_HANDLE);
1522 hout = GetStdHandle(STD_OUTPUT_HANDLE);
1523 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
1524 fprintf(stderr, "Cannot get standard input/output handles\n");
1528 GetConsoleMode(hin, &savemode);
1529 newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
1531 newmode &= ~ENABLE_ECHO_INPUT;
1533 newmode |= ENABLE_ECHO_INPUT;
1534 SetConsoleMode(hin, newmode);
1536 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
1537 ReadFile(hin, str, maxlen - 1, &i, NULL);
1539 SetConsoleMode(hin, savemode);
1541 if ((int) i > maxlen)
1548 WriteFile(hout, "\r\n", 2, &i, NULL);
1554 * Initialize the Win$ock driver.
1556 static void init_winsock(void)
1561 winsock_ver = MAKEWORD(1, 1);
1562 if (WSAStartup(winsock_ver, &wsadata)) {
1563 fprintf(stderr, "Unable to initialise WinSock");
1566 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
1567 fprintf(stderr, "WinSock version is incompatible with 1.1");
1573 * Short description of parameters.
1575 static void usage(void)
1577 printf("PuTTY Secure File Transfer (SFTP) client\n");
1578 printf("%s\n", ver);
1579 printf("Usage: psftp [options] user@host\n");
1580 printf("Options:\n");
1581 printf(" -b file use specified batchfile\n");
1582 printf(" -bc output batchfile commands\n");
1583 printf(" -be don't stop batchfile processing if errors\n");
1584 printf(" -v show verbose messages\n");
1585 printf(" -P port connect to specified port\n");
1586 printf(" -pw passw login with specified password\n");
1591 * Main program. Parse arguments etc.
1593 int main(int argc, char *argv[])
1597 char *user, *host, *userhost, *realhost;
1601 char *batchfile = NULL;
1603 flags = FLAG_STDERR | FLAG_INTERACTIVE;
1604 ssh_get_line = &get_line;
1608 userhost = user = NULL;
1610 for (i = 1; i < argc; i++) {
1611 if (argv[i][0] != '-') {
1615 userhost = dupstr(argv[i]);
1616 } else if (strcmp(argv[i], "-v") == 0) {
1617 verbose = 1, flags |= FLAG_VERBOSE;
1618 } else if (strcmp(argv[i], "-h") == 0 ||
1619 strcmp(argv[i], "-?") == 0) {
1621 } else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) {
1623 } else if (strcmp(argv[i], "-P") == 0 && i + 1 < argc) {
1624 portnumber = atoi(argv[++i]);
1625 } else if (strcmp(argv[i], "-pw") == 0 && i + 1 < argc) {
1626 password = argv[++i];
1627 } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) {
1629 batchfile = argv[++i];
1630 } else if (strcmp(argv[i], "-bc") == 0 && i + 1 < argc) {
1631 modeflags = modeflags | 1;
1632 } else if (strcmp(argv[i], "-be") == 0 && i + 1 < argc) {
1633 modeflags = modeflags | 2;
1634 } else if (strcmp(argv[i], "--") == 0) {
1645 if (argc > 0 || !userhost)
1648 /* Separate host and username */
1650 host = strrchr(host, '@');
1656 printf("psftp: multiple usernames specified; using \"%s\"\n",
1662 /* Try to load settings for this host */
1663 do_defaults(host, &cfg);
1664 if (cfg.host[0] == '\0') {
1665 /* No settings for this host; use defaults */
1666 do_defaults(NULL, &cfg);
1667 strncpy(cfg.host, host, sizeof(cfg.host) - 1);
1668 cfg.host[sizeof(cfg.host) - 1] = '\0';
1673 if (user != NULL && user[0] != '\0') {
1674 strncpy(cfg.username, user, sizeof(cfg.username) - 1);
1675 cfg.username[sizeof(cfg.username) - 1] = '\0';
1677 if (!cfg.username[0]) {
1678 printf("login as: ");
1679 if (!fgets(cfg.username, sizeof(cfg.username), stdin)) {
1680 fprintf(stderr, "psftp: aborting\n");
1683 int len = strlen(cfg.username);
1684 if (cfg.username[len - 1] == '\n')
1685 cfg.username[len - 1] = '\0';
1689 if (cfg.protocol != PROT_SSH)
1693 cfg.port = portnumber;
1695 /* SFTP uses SSH2 by default always */
1698 /* Set up subsystem name. */
1699 strcpy(cfg.remote_cmd, "sftp");
1700 cfg.ssh_subsys = TRUE;
1704 * Set up fallback option, for SSH1 servers or servers with the
1705 * sftp subsystem not enabled but the server binary installed
1706 * in the usual place. We only support fallback on Unix
1707 * systems, and we use a kludgy piece of shellery which should
1708 * try to find sftp-server in various places (the obvious
1709 * systemwide spots /usr/lib and /usr/local/lib, and then the
1710 * user's PATH) and finally give up.
1712 * test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server
1713 * test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server
1716 * the idea being that this will attempt to use either of the
1717 * obvious pathnames and then give up, and when it does give up
1718 * it will print the preferred pathname in the error messages.
1720 cfg.remote_cmd_ptr2 =
1721 "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n"
1722 "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n"
1724 cfg.ssh_subsys2 = FALSE;
1726 back = &ssh_backend;
1728 err = back->init(cfg.host, cfg.port, &realhost);
1730 fprintf(stderr, "ssh_init: %s", err);
1734 if (verbose && realhost != NULL)
1735 printf("Connected to %s\n", realhost);
1737 do_sftp(mode, modeflags, batchfile);
1739 if (back != NULL && back->socket() != NULL) {
1741 back->special(TS_EOF);
1742 sftp_recvdata(&ch, 1);