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)
174 int sftp_cmd_unknown(struct sftp_command *cmd)
176 printf("psftp: unknown command \"%s\"\n", cmd->words[0]);
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;
343 printf("psftp: not connected to a host; use \"open host.name\"\n");
347 if (cmd->nwords < 2) {
348 printf("get: expects a filename\n");
352 fname = canonify(cmd->words[1]);
354 printf("%s: %s\n", cmd->words[1], fxp_error());
357 outfname = (cmd->nwords == 2 ?
358 stripslashes(cmd->words[1], 0) : cmd->words[2]);
360 fh = fxp_open(fname, SSH_FXF_READ);
362 printf("%s: %s\n", fname, fxp_error());
368 fp = fopen(outfname, "rb+");
370 fp = fopen(outfname, "wb");
374 printf("local: unable to open %s\n", outfname);
382 fseek(fp, 0L, SEEK_END);
384 printf("reget: restarting at file position %ld\n", posn);
385 offset = uint64_make(0, posn);
387 offset = uint64_make(0, 0);
390 printf("remote:%s => local:%s\n", fname, outfname);
393 * FIXME: we can use FXP_FSTAT here to get the file size, and
394 * thus put up a progress bar.
401 len = fxp_read(fh, buffer, offset, sizeof(buffer));
402 if ((len == -1 && fxp_error_type() == SSH_FX_EOF) || len == 0)
405 printf("error while reading: %s\n", fxp_error());
411 wlen = fwrite(buffer, 1, len - wpos, fp);
413 printf("error while writing local file\n");
418 if (wpos < len) /* we had an error */
420 offset = uint64_add32(offset, len);
429 int sftp_cmd_get(struct sftp_command *cmd)
431 return sftp_general_get(cmd, 0);
433 int sftp_cmd_reget(struct sftp_command *cmd)
435 return sftp_general_get(cmd, 1);
439 * Send a file and store it at the remote end. We have two very
440 * similar commands here: `put' and `reput', which differ in that
441 * `reput' checks for the existence of the destination file and
442 * starts from where a previous aborted transfer left off.
444 int sftp_general_put(struct sftp_command *cmd, int restart)
446 struct fxp_handle *fh;
447 char *fname, *origoutfname, *outfname;
452 printf("psftp: not connected to a host; use \"open host.name\"\n");
456 if (cmd->nwords < 2) {
457 printf("put: expects a filename\n");
461 fname = cmd->words[1];
462 origoutfname = (cmd->nwords == 2 ?
463 stripslashes(cmd->words[1], 1) : cmd->words[2]);
464 outfname = canonify(origoutfname);
466 printf("%s: %s\n", origoutfname, fxp_error());
470 fp = fopen(fname, "rb");
472 printf("local: unable to open %s\n", fname);
477 fh = fxp_open(outfname,
480 fh = fxp_open(outfname,
481 SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC);
484 printf("%s: %s\n", outfname, fxp_error());
491 struct fxp_attrs attrs;
492 if (!fxp_fstat(fh, &attrs)) {
493 printf("read size of %s: %s\n", outfname, fxp_error());
497 if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) {
498 printf("read size of %s: size was not given\n", outfname);
503 uint64_decimal(offset, decbuf);
504 printf("reput: restarting at file position %s\n", decbuf);
505 if (uint64_compare(offset, uint64_make(0, LONG_MAX)) > 0) {
506 printf("reput: remote file is larger than we can deal with\n");
510 if (fseek(fp, offset.lo, SEEK_SET) != 0)
511 fseek(fp, 0, SEEK_END); /* *shrug* */
513 offset = uint64_make(0, 0);
516 printf("local:%s => remote:%s\n", fname, outfname);
519 * FIXME: we can use FXP_FSTAT here to get the file size, and
520 * thus put up a progress bar.
526 len = fread(buffer, 1, sizeof(buffer), fp);
528 printf("error while reading local file\n");
530 } else if (len == 0) {
533 if (!fxp_write(fh, buffer, offset, len)) {
534 printf("error while writing: %s\n", fxp_error());
537 offset = uint64_add32(offset, len);
546 int sftp_cmd_put(struct sftp_command *cmd)
548 return sftp_general_put(cmd, 0);
550 int sftp_cmd_reput(struct sftp_command *cmd)
552 return sftp_general_put(cmd, 1);
555 int sftp_cmd_mkdir(struct sftp_command *cmd)
561 printf("psftp: not connected to a host; use \"open host.name\"\n");
565 if (cmd->nwords < 2) {
566 printf("mkdir: expects a directory\n");
570 dir = canonify(cmd->words[1]);
572 printf("%s: %s\n", dir, fxp_error());
576 result = fxp_mkdir(dir);
578 printf("mkdir %s: %s\n", dir, fxp_error());
587 int sftp_cmd_rmdir(struct sftp_command *cmd)
593 printf("psftp: not connected to a host; use \"open host.name\"\n");
597 if (cmd->nwords < 2) {
598 printf("rmdir: expects a directory\n");
602 dir = canonify(cmd->words[1]);
604 printf("%s: %s\n", dir, fxp_error());
608 result = fxp_rmdir(dir);
610 printf("rmdir %s: %s\n", dir, fxp_error());
619 int sftp_cmd_rm(struct sftp_command *cmd)
625 printf("psftp: not connected to a host; use \"open host.name\"\n");
629 if (cmd->nwords < 2) {
630 printf("rm: expects a filename\n");
634 fname = canonify(cmd->words[1]);
636 printf("%s: %s\n", fname, fxp_error());
640 result = fxp_remove(fname);
642 printf("rm %s: %s\n", fname, fxp_error());
652 int sftp_cmd_mv(struct sftp_command *cmd)
654 char *srcfname, *dstfname;
658 printf("psftp: not connected to a host; use \"open host.name\"\n");
662 if (cmd->nwords < 3) {
663 printf("mv: expects two filenames\n");
666 srcfname = canonify(cmd->words[1]);
668 printf("%s: %s\n", srcfname, fxp_error());
672 dstfname = canonify(cmd->words[2]);
674 printf("%s: %s\n", dstfname, fxp_error());
678 result = fxp_rename(srcfname, dstfname);
680 char const *error = fxp_error();
681 struct fxp_attrs attrs;
684 * The move might have failed because dstfname pointed at a
685 * directory. We check this possibility now: if dstfname
686 * _is_ a directory, we re-attempt the move by appending
687 * the basename of srcfname to dstfname.
689 result = fxp_stat(dstfname, &attrs);
691 (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) &&
692 (attrs.permissions & 0040000)) {
694 char *newname, *newcanon;
695 printf("(destination %s is a directory)\n", dstfname);
696 p = srcfname + strlen(srcfname);
697 while (p > srcfname && p[-1] != '/') p--;
698 newname = dupcat(dstfname, "/", p, NULL);
699 newcanon = canonify(newname);
704 result = fxp_rename(srcfname, dstfname);
705 error = result ? NULL : fxp_error();
709 printf("mv %s %s: %s\n", srcfname, dstfname, error);
715 printf("%s -> %s\n", srcfname, dstfname);
722 int sftp_cmd_chmod(struct sftp_command *cmd)
726 struct fxp_attrs attrs;
727 unsigned attrs_clr, attrs_xor, oldperms, newperms;
730 printf("psftp: not connected to a host; use \"open host.name\"\n");
734 if (cmd->nwords < 3) {
735 printf("chmod: expects a mode specifier and a filename\n");
740 * Attempt to parse the mode specifier in cmd->words[1]. We
741 * don't support the full horror of Unix chmod; instead we
742 * support a much simpler syntax in which the user can either
743 * specify an octal number, or a comma-separated sequence of
744 * [ugoa]*[-+=][rwxst]+. (The initial [ugoa] sequence may
745 * _only_ be omitted if the only attribute mentioned is t,
746 * since all others require a user/group/other specification.
747 * Additionally, the s attribute may not be specified for any
748 * [ugoa] specifications other than exactly u or exactly g.
750 attrs_clr = attrs_xor = 0;
751 mode = cmd->words[1];
752 if (mode[0] >= '0' && mode[0] <= '9') {
753 if (mode[strspn(mode, "01234567")]) {
754 printf("chmod: numeric file modes should"
755 " contain digits 0-7 only\n");
759 sscanf(mode, "%o", &attrs_xor);
760 attrs_xor &= attrs_clr;
763 char *modebegin = mode;
764 unsigned subset, perms;
768 while (*mode && *mode != ',' &&
769 *mode != '+' && *mode != '-' && *mode != '=') {
771 case 'u': subset |= 04700; break; /* setuid, user perms */
772 case 'g': subset |= 02070; break; /* setgid, group perms */
773 case 'o': subset |= 00007; break; /* just other perms */
774 case 'a': subset |= 06777; break; /* all of the above */
776 printf("chmod: file mode '%.*s' contains unrecognised"
777 " user/group/other specifier '%c'\n",
778 strcspn(modebegin, ","), modebegin, *mode);
783 if (!*mode || *mode == ',') {
784 printf("chmod: file mode '%.*s' is incomplete\n",
785 strcspn(modebegin, ","), modebegin);
789 if (!*mode || *mode == ',') {
790 printf("chmod: file mode '%.*s' is incomplete\n",
791 strcspn(modebegin, ","), modebegin);
795 while (*mode && *mode != ',') {
797 case 'r': perms |= 00444; break;
798 case 'w': perms |= 00222; break;
799 case 'x': perms |= 00111; break;
800 case 't': perms |= 01000; subset |= 01000; break;
802 if ((subset & 06777) != 04700 &&
803 (subset & 06777) != 02070) {
804 printf("chmod: file mode '%.*s': set[ug]id bit should"
805 " be used with exactly one of u or g only\n",
806 strcspn(modebegin, ","), modebegin);
812 printf("chmod: file mode '%.*s' contains unrecognised"
813 " permission specifier '%c'\n",
814 strcspn(modebegin, ","), modebegin, *mode);
819 if (!(subset & 06777) && (perms &~ subset)) {
820 printf("chmod: file mode '%.*s' contains no user/group/other"
821 " specifier and permissions other than 't' \n",
822 strcspn(modebegin, ","), modebegin, *mode);
840 if (*mode) mode++; /* eat comma */
844 fname = canonify(cmd->words[2]);
846 printf("%s: %s\n", fname, fxp_error());
850 result = fxp_stat(fname, &attrs);
851 if (!result || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) {
852 printf("get attrs for %s: %s\n", fname,
853 result ? "file permissions not provided" : fxp_error());
858 attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS; /* perms _only_ */
859 oldperms = attrs.permissions & 07777;
860 attrs.permissions &= ~attrs_clr;
861 attrs.permissions ^= attrs_xor;
862 newperms = attrs.permissions & 07777;
864 result = fxp_setstat(fname, attrs);
867 printf("set attrs for %s: %s\n", fname, fxp_error());
872 printf("%s: %04o -> %04o\n", fname, oldperms, newperms);
878 static int sftp_cmd_open(struct sftp_command *cmd)
881 printf("psftp: already connected\n");
885 if (cmd->nwords < 2) {
886 printf("open: expects a host name\n");
890 if (psftp_connect(cmd->words[1], NULL, 0)) {
891 back = NULL; /* connection is already closed */
892 return -1; /* this is fatal */
897 static int sftp_cmd_help(struct sftp_command *cmd);
899 static struct sftp_cmd_lookup {
902 * For help purposes, there are two kinds of command:
904 * - primary commands, in which `longhelp' is non-NULL. In
905 * this case `shorthelp' is descriptive text, and `longhelp'
906 * is longer descriptive text intended to be printed after
909 * - alias commands, in which `longhelp' is NULL. In this case
910 * `shorthelp' is the name of a primary command, which
911 * contains the help that should double up for this command.
915 int (*obey) (struct sftp_command *);
918 * List of sftp commands. This is binary-searched so it MUST be
922 "bye", "finish your SFTP session",
924 " Terminates your SFTP session and quits the PSFTP program.\n",
928 "cd", "change your remote working directory",
929 " [ <New working directory> ]\n"
930 " Change the remote working directory for your SFTP session.\n"
931 " If a new working directory is not supplied, you will be\n"
932 " returned to your home directory.\n",
936 "chmod", "change file permissions and modes",
937 " ( <octal-digits> | <modifiers> ) <filename>\n"
938 " Change the file permissions on a file or directory.\n"
939 " <octal-digits> can be any octal Unix permission specifier.\n"
940 " Alternatively, <modifiers> can include:\n"
941 " u+r make file readable by owning user\n"
942 " u+w make file writable by owning user\n"
943 " u+x make file executable by owning user\n"
944 " u-r make file not readable by owning user\n"
946 " g+r make file readable by members of owning group\n"
947 " [also g+w, g+x, g-r, g-w, g-x]\n"
948 " o+r make file readable by all other users\n"
949 " [also o+w, o+x, o-r, o-w, o-x]\n"
950 " a+r make file readable by absolutely everybody\n"
951 " [also a+w, a+x, a-r, a-w, a-x]\n"
952 " u+s enable the Unix set-user-ID bit\n"
953 " u-s disable the Unix set-user-ID bit\n"
954 " g+s enable the Unix set-group-ID bit\n"
955 " g-s disable the Unix set-group-ID bit\n"
956 " +t enable the Unix \"sticky bit\"\n"
957 " You can give more than one modifier for the same user (\"g-rwx\"), and\n"
958 " more than one user for the same modifier (\"ug+w\"). You can\n"
959 " use commas to separate different modifiers (\"u+rwx,g+s\").\n",
963 "del", "delete a file",
969 "delete", "delete a file",
975 "dir", "list contents of a remote directory",
976 " [ <directory-name> ]\n"
977 " List the contents of a specified directory on the server.\n"
978 " If <directory-name> is not given, the current working directory\n"
979 " will be listed.\n",
983 "exit", "bye", NULL, sftp_cmd_quit
986 "get", "download a file from the server to your local machine",
987 " <filename> [ <local-filename> ]\n"
988 " Downloads a file on the server and stores it locally under\n"
989 " the same name, or under a different one if you supply the\n"
990 " argument <local-filename>.\n",
995 " [ <command> [ <command> ... ] ]\n"
996 " Give general help if no commands are specified.\n"
997 " If one or more commands are specified, give specific help on\n"
998 " those particular commands.\n",
1006 "mkdir", "create a directory on the remote server",
1007 " <directory-name>\n"
1008 " Creates a directory with the given name on the server.\n",
1012 "mv", "move or rename a file on the remote server",
1013 " <source-filename> <destination-filename>\n"
1014 " Moves or renames the file <source-filename> on the server,\n"
1015 " so that it is accessible under the name <destination-filename>.\n",
1019 "put", "upload a file from your local machine to the server",
1020 " <filename> [ <remote-filename> ]\n"
1021 " Uploads a file to the server and stores it there under\n"
1022 " the same name, or under a different one if you supply the\n"
1023 " argument <remote-filename>.\n",
1027 "open", "connect to a host",
1028 " [<user>@]<hostname>\n"
1029 " Establishes an SFTP connection to a given host. Only usable\n"
1030 " when you did not already specify a host name on the command\n"
1035 "pwd", "print your remote working directory",
1037 " Print the current remote working directory for your SFTP session.\n",
1041 "quit", "bye", NULL,
1045 "reget", "continue downloading a file",
1046 " <filename> [ <local-filename> ]\n"
1047 " Works exactly like the \"get\" command, but the local file\n"
1048 " must already exist. The download will begin at the end of the\n"
1049 " file. This is for resuming a download that was interrupted.\n",
1057 "rename", "mv", NULL,
1061 "reput", "continue uploading a file",
1062 " <filename> [ <remote-filename> ]\n"
1063 " Works exactly like the \"put\" command, but the remote file\n"
1064 " must already exist. The upload will begin at the end of the\n"
1065 " file. This is for resuming an upload that was interrupted.\n",
1073 "rmdir", "remove a directory on the remote server",
1074 " <directory-name>\n"
1075 " Removes the directory with the given name on the server.\n"
1076 " The directory will not be removed unless it is empty.\n",
1081 const struct sftp_cmd_lookup *lookup_command(char *name)
1086 j = sizeof(sftp_lookup) / sizeof(*sftp_lookup);
1089 cmp = strcmp(name, sftp_lookup[k].name);
1095 return &sftp_lookup[k];
1101 static int sftp_cmd_help(struct sftp_command *cmd)
1104 if (cmd->nwords == 1) {
1106 * Give short help on each command.
1110 for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) {
1111 int len = strlen(sftp_lookup[i].name);
1115 for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) {
1116 const struct sftp_cmd_lookup *lookup;
1117 lookup = &sftp_lookup[i];
1118 printf("%-*s", maxlen+2, lookup->name);
1119 if (lookup->longhelp == NULL)
1120 lookup = lookup_command(lookup->shorthelp);
1121 printf("%s\n", lookup->shorthelp);
1125 * Give long help on specific commands.
1127 for (i = 1; i < cmd->nwords; i++) {
1128 const struct sftp_cmd_lookup *lookup;
1129 lookup = lookup_command(cmd->words[i]);
1131 printf("help: %s: command not found\n", cmd->words[i]);
1133 printf("%s", lookup->name);
1134 if (lookup->longhelp == NULL)
1135 lookup = lookup_command(lookup->shorthelp);
1136 printf("%s", lookup->longhelp);
1143 /* ----------------------------------------------------------------------
1144 * Command line reading and parsing.
1146 struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags)
1149 int linelen, linesize;
1150 struct sftp_command *cmd;
1154 if ((mode == 0) || (modeflags & 1)) {
1159 cmd = smalloc(sizeof(struct sftp_command));
1165 linesize = linelen = 0;
1171 line = srealloc(line, linesize);
1172 ret = fgets(line + linelen, linesize - linelen, fp);
1173 if (modeflags & 1) {
1177 if (!ret || (linelen == 0 && line[0] == '\0')) {
1178 cmd->obey = sftp_cmd_quit;
1180 return cmd; /* eof */
1182 len = linelen + strlen(line + linelen);
1184 if (line[linelen - 1] == '\n') {
1186 line[linelen] = '\0';
1192 * Parse the command line into words. The syntax is:
1193 * - double quotes are removed, but cause spaces within to be
1194 * treated as non-separating.
1195 * - a double-doublequote pair is a literal double quote, inside
1196 * _or_ outside quotes. Like this:
1198 * firstword "second word" "this has ""quotes"" in" sodoes""this""
1204 * >this has "quotes" in<
1209 /* skip whitespace */
1210 while (*p && (*p == ' ' || *p == '\t'))
1212 /* mark start of word */
1213 q = r = p; /* q sits at start, r writes word */
1216 if (!quoting && (*p == ' ' || *p == '\t'))
1217 break; /* reached end of word */
1218 else if (*p == '"' && p[1] == '"')
1219 p += 2, *r++ = '"'; /* a literal quote */
1221 p++, quoting = !quoting;
1226 p++; /* skip over the whitespace */
1228 if (cmd->nwords >= cmd->wordssize) {
1229 cmd->wordssize = cmd->nwords + 16;
1231 srealloc(cmd->words, cmd->wordssize * sizeof(char *));
1233 cmd->words[cmd->nwords++] = q;
1237 * Now parse the first word and assign a function.
1240 if (cmd->nwords == 0)
1241 cmd->obey = sftp_cmd_null;
1243 const struct sftp_cmd_lookup *lookup;
1244 lookup = lookup_command(cmd->words[0]);
1246 cmd->obey = sftp_cmd_unknown;
1248 cmd->obey = lookup->obey;
1254 static void do_sftp_init(void)
1257 * Do protocol initialisation.
1261 "Fatal: unable to initialise SFTP: %s\n", fxp_error());
1266 * Find out where our home directory is.
1268 homedir = fxp_realpath(".");
1271 "Warning: failed to resolve home directory: %s\n",
1273 homedir = dupstr(".");
1275 printf("Remote working directory is %s\n", homedir);
1277 pwd = dupstr(homedir);
1280 void do_sftp(int mode, int modeflags, char *batchfile)
1289 /* ------------------------------------------------------------------
1290 * Now we're ready to do Real Stuff.
1293 struct sftp_command *cmd;
1294 cmd = sftp_getcmd(stdin, 0, 0);
1297 if (cmd->obey(cmd) < 0)
1301 fp = fopen(batchfile, "r");
1303 printf("Fatal: unable to open %s\n", batchfile);
1307 struct sftp_command *cmd;
1308 cmd = sftp_getcmd(fp, mode, modeflags);
1311 if (cmd->obey(cmd) < 0)
1313 if (fxp_error() != NULL) {
1314 if (!(modeflags & 2))
1323 /* ----------------------------------------------------------------------
1324 * Dirty bits: integration with PuTTY.
1327 static int verbose = 0;
1329 void verify_ssh_host_key(char *host, int port, char *keytype,
1330 char *keystr, char *fingerprint)
1336 static const char absentmsg[] =
1337 "The server's host key is not cached in the registry. You\n"
1338 "have no guarantee that the server is the computer you\n"
1340 "The server's key fingerprint is:\n"
1342 "If you trust this host, enter \"y\" to add the key to\n"
1343 "PuTTY's cache and carry on connecting.\n"
1344 "If you want to carry on connecting just once, without\n"
1345 "adding the key to the cache, enter \"n\".\n"
1346 "If you do not trust this host, press Return to abandon the\n"
1348 "Store key in cache? (y/n) ";
1350 static const char wrongmsg[] =
1351 "WARNING - POTENTIAL SECURITY BREACH!\n"
1352 "The server's host key does not match the one PuTTY has\n"
1353 "cached in the registry. This means that either the\n"
1354 "server administrator has changed the host key, or you\n"
1355 "have actually connected to another computer pretending\n"
1356 "to be the server.\n"
1357 "The new key fingerprint is:\n"
1359 "If you were expecting this change and trust the new key,\n"
1360 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
1361 "If you want to carry on connecting but without updating\n"
1362 "the cache, enter \"n\".\n"
1363 "If you want to abandon the connection completely, press\n"
1364 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
1366 "Update cached key? (y/n, Return cancels connection) ";
1368 static const char abandoned[] = "Connection abandoned.\n";
1373 * Verify the key against the registry.
1375 ret = verify_host_key(host, port, keytype, keystr);
1377 if (ret == 0) /* success - key matched OK */
1380 if (ret == 2) { /* key was different */
1381 fprintf(stderr, wrongmsg, fingerprint);
1384 if (ret == 1) { /* key was absent */
1385 fprintf(stderr, absentmsg, fingerprint);
1389 hin = GetStdHandle(STD_INPUT_HANDLE);
1390 GetConsoleMode(hin, &savemode);
1391 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
1392 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
1393 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
1394 SetConsoleMode(hin, savemode);
1396 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
1397 if (line[0] == 'y' || line[0] == 'Y')
1398 store_host_key(host, port, keytype, keystr);
1400 fprintf(stderr, abandoned);
1406 * Ask whether the selected cipher is acceptable (since it was
1407 * below the configured 'warn' threshold).
1408 * cs: 0 = both ways, 1 = client->server, 2 = server->client
1410 void askcipher(char *ciphername, int cs)
1415 static const char msg[] =
1416 "The first %scipher supported by the server is\n"
1417 "%s, which is below the configured warning threshold.\n"
1418 "Continue with connection? (y/n) ";
1419 static const char abandoned[] = "Connection abandoned.\n";
1423 fprintf(stderr, msg,
1425 (cs == 1) ? "client-to-server " :
1426 "server-to-client ",
1430 hin = GetStdHandle(STD_INPUT_HANDLE);
1431 GetConsoleMode(hin, &savemode);
1432 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
1433 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
1434 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
1435 SetConsoleMode(hin, savemode);
1437 if (line[0] == 'y' || line[0] == 'Y') {
1440 fprintf(stderr, abandoned);
1446 * Warn about the obsolescent key file format.
1448 void old_keyfile_warning(void)
1450 static const char message[] =
1451 "You are loading an SSH 2 private key which has an\n"
1452 "old version of the file format. This means your key\n"
1453 "file is not fully tamperproof. Future versions of\n"
1454 "PuTTY may stop supporting this private key format,\n"
1455 "so we recommend you convert your key to the new\n"
1458 "Once the key is loaded into PuTTYgen, you can perform\n"
1459 "this conversion simply by saving it again.\n";
1461 fputs(message, stderr);
1465 * Print an error message and perform a fatal exit.
1467 void fatalbox(char *fmt, ...)
1469 char str[0x100]; /* Make the size big enough */
1472 strcpy(str, "Fatal:");
1473 vsprintf(str + strlen(str), fmt, ap);
1480 void connection_fatal(char *fmt, ...)
1482 char str[0x100]; /* Make the size big enough */
1485 strcpy(str, "Fatal:");
1486 vsprintf(str + strlen(str), fmt, ap);
1494 void logevent(char *string)
1498 void ldisc_send(char *buf, int len, int interactive)
1501 * This is only here because of the calls to ldisc_send(NULL,
1502 * 0) in ssh.c. Nothing in PSFTP actually needs to use the
1503 * ldisc as an ldisc. So if we get called with any real data, I
1504 * want to know about it.
1510 * Be told what socket we're supposed to be using.
1512 static SOCKET sftp_ssh_socket;
1513 char *do_select(SOCKET skt, int startup)
1516 sftp_ssh_socket = skt;
1518 sftp_ssh_socket = INVALID_SOCKET;
1521 extern int select_result(WPARAM, LPARAM);
1524 * Receive a block of data from the SSH link. Block until all data
1527 * To do this, we repeatedly call the SSH protocol module, with our
1528 * own trap in from_backend() to catch the data that comes back. We
1529 * do this until we have enough data.
1532 static unsigned char *outptr; /* where to put the data */
1533 static unsigned outlen; /* how much data required */
1534 static unsigned char *pending = NULL; /* any spare data */
1535 static unsigned pendlen = 0, pendsize = 0; /* length and phys. size of buffer */
1536 int from_backend(int is_stderr, char *data, int datalen)
1538 unsigned char *p = (unsigned char *) data;
1539 unsigned len = (unsigned) datalen;
1542 * stderr data is just spouted to local stderr and otherwise
1546 fwrite(data, 1, len, stderr);
1551 * If this is before the real session begins, just return.
1557 unsigned used = outlen;
1560 memcpy(outptr, p, used);
1568 if (pendsize < pendlen + len) {
1569 pendsize = pendlen + len + 4096;
1570 pending = (pending ? srealloc(pending, pendsize) :
1573 fatalbox("Out of memory");
1575 memcpy(pending + pendlen, p, len);
1581 int sftp_recvdata(char *buf, int len)
1583 outptr = (unsigned char *) buf;
1587 * See if the pending-input block contains some of what we
1591 unsigned pendused = pendlen;
1592 if (pendused > outlen)
1594 memcpy(outptr, pending, pendused);
1595 memmove(pending, pending + pendused, pendlen - pendused);
1598 pendlen -= pendused;
1608 while (outlen > 0) {
1612 FD_SET(sftp_ssh_socket, &readfds);
1613 if (select(1, &readfds, NULL, NULL, NULL) < 0)
1614 return 0; /* doom */
1615 select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
1620 int sftp_senddata(char *buf, int len)
1622 back->send((unsigned char *) buf, len);
1627 * Loop through the ssh connection and authentication process.
1629 static void ssh_sftp_init(void)
1631 if (sftp_ssh_socket == INVALID_SOCKET)
1633 while (!back->sendok()) {
1636 FD_SET(sftp_ssh_socket, &readfds);
1637 if (select(1, &readfds, NULL, NULL, NULL) < 0)
1639 select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
1643 static char *password = NULL;
1644 static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
1647 DWORD savemode, newmode, i;
1650 static int tried_once = 0;
1655 strncpy(str, password, maxlen);
1656 str[maxlen - 1] = '\0';
1662 hin = GetStdHandle(STD_INPUT_HANDLE);
1663 hout = GetStdHandle(STD_OUTPUT_HANDLE);
1664 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
1665 fprintf(stderr, "Cannot get standard input/output handles\n");
1669 GetConsoleMode(hin, &savemode);
1670 newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
1672 newmode &= ~ENABLE_ECHO_INPUT;
1674 newmode |= ENABLE_ECHO_INPUT;
1675 SetConsoleMode(hin, newmode);
1677 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
1678 ReadFile(hin, str, maxlen - 1, &i, NULL);
1680 SetConsoleMode(hin, savemode);
1682 if ((int) i > maxlen)
1689 WriteFile(hout, "\r\n", 2, &i, NULL);
1695 * Initialize the Win$ock driver.
1697 static void init_winsock(void)
1702 winsock_ver = MAKEWORD(1, 1);
1703 if (WSAStartup(winsock_ver, &wsadata)) {
1704 fprintf(stderr, "Unable to initialise WinSock");
1707 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
1708 fprintf(stderr, "WinSock version is incompatible with 1.1");
1714 * Short description of parameters.
1716 static void usage(void)
1718 printf("PuTTY Secure File Transfer (SFTP) client\n");
1719 printf("%s\n", ver);
1720 printf("Usage: psftp [options] user@host\n");
1721 printf("Options:\n");
1722 printf(" -b file use specified batchfile\n");
1723 printf(" -bc output batchfile commands\n");
1724 printf(" -be don't stop batchfile processing if errors\n");
1725 printf(" -v show verbose messages\n");
1726 printf(" -P port connect to specified port\n");
1727 printf(" -pw passw login with specified password\n");
1732 * Connect to a host.
1734 static int psftp_connect(char *userhost, char *user, int portnumber)
1736 char *host, *realhost;
1739 /* Separate host and username */
1741 host = strrchr(host, '@');
1747 printf("psftp: multiple usernames specified; using \"%s\"\n",
1753 /* Try to load settings for this host */
1754 do_defaults(host, &cfg);
1755 if (cfg.host[0] == '\0') {
1756 /* No settings for this host; use defaults */
1757 do_defaults(NULL, &cfg);
1758 strncpy(cfg.host, host, sizeof(cfg.host) - 1);
1759 cfg.host[sizeof(cfg.host) - 1] = '\0';
1764 * Trim leading whitespace off the hostname if it's there.
1767 int space = strspn(cfg.host, " \t");
1768 memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
1771 /* See if host is of the form user@host */
1772 if (cfg.host[0] != '\0') {
1773 char *atsign = strchr(cfg.host, '@');
1774 /* Make sure we're not overflowing the user field */
1776 if (atsign - cfg.host < sizeof cfg.username) {
1777 strncpy(cfg.username, cfg.host, atsign - cfg.host);
1778 cfg.username[atsign - cfg.host] = '\0';
1780 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
1785 * Trim a colon suffix off the hostname if it's there.
1787 cfg.host[strcspn(cfg.host, ":")] = '\0';
1790 if (user != NULL && user[0] != '\0') {
1791 strncpy(cfg.username, user, sizeof(cfg.username) - 1);
1792 cfg.username[sizeof(cfg.username) - 1] = '\0';
1794 if (!cfg.username[0]) {
1795 printf("login as: ");
1796 if (!fgets(cfg.username, sizeof(cfg.username), stdin)) {
1797 fprintf(stderr, "psftp: aborting\n");
1800 int len = strlen(cfg.username);
1801 if (cfg.username[len - 1] == '\n')
1802 cfg.username[len - 1] = '\0';
1806 if (cfg.protocol != PROT_SSH)
1810 cfg.port = portnumber;
1812 /* SFTP uses SSH2 by default always */
1816 * Disable scary things which shouldn't be enabled for simple
1817 * things like SCP and SFTP: agent forwarding, port forwarding,
1820 cfg.x11_forward = 0;
1822 cfg.portfwd[0] = cfg.portfwd[1] = '\0';
1824 /* Set up subsystem name. */
1825 strcpy(cfg.remote_cmd, "sftp");
1826 cfg.ssh_subsys = TRUE;
1830 * Set up fallback option, for SSH1 servers or servers with the
1831 * sftp subsystem not enabled but the server binary installed
1832 * in the usual place. We only support fallback on Unix
1833 * systems, and we use a kludgy piece of shellery which should
1834 * try to find sftp-server in various places (the obvious
1835 * systemwide spots /usr/lib and /usr/local/lib, and then the
1836 * user's PATH) and finally give up.
1838 * test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server
1839 * test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server
1842 * the idea being that this will attempt to use either of the
1843 * obvious pathnames and then give up, and when it does give up
1844 * it will print the preferred pathname in the error messages.
1846 cfg.remote_cmd_ptr2 =
1847 "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n"
1848 "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n"
1850 cfg.ssh_subsys2 = FALSE;
1852 back = &ssh_backend;
1854 err = back->init(cfg.host, cfg.port, &realhost, 0);
1856 fprintf(stderr, "ssh_init: %s\n", err);
1860 if (verbose && realhost != NULL)
1861 printf("Connected to %s\n", realhost);
1866 * Main program. Parse arguments etc.
1868 int main(int argc, char *argv[])
1872 char *userhost, *user;
1875 char *batchfile = NULL;
1877 flags = FLAG_STDERR | FLAG_INTERACTIVE;
1878 ssh_get_line = &get_line;
1882 userhost = user = NULL;
1884 for (i = 1; i < argc; i++) {
1885 if (argv[i][0] != '-') {
1889 userhost = dupstr(argv[i]);
1890 } else if (strcmp(argv[i], "-v") == 0) {
1891 verbose = 1, flags |= FLAG_VERBOSE;
1892 } else if (strcmp(argv[i], "-h") == 0 ||
1893 strcmp(argv[i], "-?") == 0) {
1895 } else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) {
1897 } else if (strcmp(argv[i], "-P") == 0 && i + 1 < argc) {
1898 portnumber = atoi(argv[++i]);
1899 } else if (strcmp(argv[i], "-pw") == 0 && i + 1 < argc) {
1900 password = argv[++i];
1901 } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) {
1903 batchfile = argv[++i];
1904 } else if (strcmp(argv[i], "-bc") == 0 && i + 1 < argc) {
1905 modeflags = modeflags | 1;
1906 } else if (strcmp(argv[i], "-be") == 0 && i + 1 < argc) {
1907 modeflags = modeflags | 2;
1908 } else if (strcmp(argv[i], "--") == 0) {
1920 * If a user@host string has already been provided, connect to
1924 if (psftp_connect(userhost, user, portnumber))
1928 printf("psftp: no hostname specified; use \"open host.name\""
1932 do_sftp(mode, modeflags, batchfile);
1934 if (back != NULL && back->socket() != NULL) {
1936 back->special(TS_EOF);
1937 sftp_recvdata(&ch, 1);