Allow PSFTP to be run with no arguments, in which case it enters the
[u/mdw/putty] / psftp.c
CommitLineData
4c7f0d61 1/*
2 * psftp.c: front end for PSFTP.
3 */
4
4a8fc3c4 5#include <windows.h>
6
4c7f0d61 7#include <stdio.h>
8#include <stdlib.h>
f9e162aa 9#include <stdarg.h>
4c7f0d61 10#include <assert.h>
d92624dc 11#include <limits.h>
4c7f0d61 12
4a8fc3c4 13#define PUTTY_DO_GLOBALS
14#include "putty.h"
15#include "storage.h"
16#include "ssh.h"
4c7f0d61 17#include "sftp.h"
18#include "int64.h"
19
5471d09a 20/*
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
24 * send buffer.
25 */
26
fa3db767 27static int psftp_connect(char *userhost, char *user, int portnumber);
28static void do_sftp_init(void);
29
4c7f0d61 30/* ----------------------------------------------------------------------
4c7f0d61 31 * sftp client state.
32 */
33
34char *pwd, *homedir;
35
36/* ----------------------------------------------------------------------
37 * Higher-level helper functions used in commands.
38 */
39
40/*
f9e162aa 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).
4c7f0d61 44 */
32874aea 45char *canonify(char *name)
46{
f9e162aa 47 char *fullname, *canonname;
4a8fc3c4 48
f9e162aa 49 if (name[0] == '/') {
50 fullname = dupstr(name);
51 } else {
4a8fc3c4 52 char *slash;
32874aea 53 if (pwd[strlen(pwd) - 1] == '/')
4a8fc3c4 54 slash = "";
55 else
56 slash = "/";
57 fullname = dupcat(pwd, slash, name, NULL);
f9e162aa 58 }
4a8fc3c4 59
60 canonname = fxp_realpath(fullname);
61
f9e162aa 62 if (canonname) {
63 sfree(fullname);
64 return canonname;
50d7e054 65 } else {
32874aea 66 /*
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.
74 *
75 * Special cases:
76 *
77 * - if the last component is "/." or "/..", then we don't
78 * bother trying this because there's no way it can work.
79 *
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.
85 *
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).
89 */
90
91 int i;
92 char *returnname;
93
94 i = strlen(fullname);
95 if (i > 2 && fullname[i - 1] == '/')
96 fullname[--i] = '\0'; /* strip trailing / unless at pos 0 */
97 while (i > 0 && fullname[--i] != '/');
98
99 /*
100 * Give up on special cases.
101 */
102 if (fullname[i] != '/' || /* no slash at all */
103 !strcmp(fullname + i, "/.") || /* ends in /. */
104 !strcmp(fullname + i, "/..") || /* ends in /.. */
105 !strcmp(fullname, "/")) {
106 return fullname;
107 }
108
109 /*
110 * Now i points at the slash. Deal with the final special
111 * case i==0 (ie the whole path was "/nonexistentfile").
112 */
113 fullname[i] = '\0'; /* separate the string */
114 if (i == 0) {
115 canonname = fxp_realpath("/");
116 } else {
117 canonname = fxp_realpath(fullname);
118 }
119
120 if (!canonname)
121 return fullname; /* even that failed; give up */
122
123 /*
124 * We have a canonical name for all but the last path
125 * component. Concatenate the last component and return.
126 */
127 returnname = dupcat(canonname,
128 canonname[strlen(canonname) - 1] ==
129 '/' ? "" : "/", fullname + i + 1, NULL);
130 sfree(fullname);
131 sfree(canonname);
132 return returnname;
50d7e054 133 }
4c7f0d61 134}
135
dcf8495c 136/*
137 * Return a pointer to the portion of str that comes after the last
138 * slash (or backslash or colon, if `local' is TRUE).
139 */
140static char *stripslashes(char *str, int local)
141{
142 char *p;
143
144 if (local) {
145 p = strchr(str, ':');
146 if (p) str = p+1;
147 }
148
149 p = strrchr(str, '/');
150 if (p) str = p+1;
151
152 if (local) {
153 p = strrchr(str, '\\');
154 if (p) str = p+1;
155 }
156
157 return str;
158}
159
4c7f0d61 160/* ----------------------------------------------------------------------
161 * Actual sftp commands.
162 */
163struct sftp_command {
164 char **words;
165 int nwords, wordssize;
32874aea 166 int (*obey) (struct sftp_command *); /* returns <0 to quit */
4c7f0d61 167};
168
32874aea 169int sftp_cmd_null(struct sftp_command *cmd)
170{
4c7f0d61 171 return 0;
172}
173
32874aea 174int sftp_cmd_unknown(struct sftp_command *cmd)
175{
4c7f0d61 176 printf("psftp: unknown command \"%s\"\n", cmd->words[0]);
177 return 0;
178}
179
32874aea 180int sftp_cmd_quit(struct sftp_command *cmd)
181{
4c7f0d61 182 return -1;
183}
184
185/*
186 * List a directory. If no arguments are given, list pwd; otherwise
187 * list the directory given in words[1].
188 */
32874aea 189static int sftp_ls_compare(const void *av, const void *bv)
190{
191 const struct fxp_name *a = (const struct fxp_name *) av;
192 const struct fxp_name *b = (const struct fxp_name *) bv;
4c7f0d61 193 return strcmp(a->filename, b->filename);
194}
32874aea 195int sftp_cmd_ls(struct sftp_command *cmd)
196{
4c7f0d61 197 struct fxp_handle *dirh;
198 struct fxp_names *names;
199 struct fxp_name *ournames;
200 int nnames, namesize;
201 char *dir, *cdir;
202 int i;
203
fa3db767 204 if (back == NULL) {
205 printf("psftp: not connected to a host; use \"open host.name\"\n");
206 return 0;
207 }
208
4c7f0d61 209 if (cmd->nwords < 2)
210 dir = ".";
211 else
212 dir = cmd->words[1];
213
214 cdir = canonify(dir);
215 if (!cdir) {
216 printf("%s: %s\n", dir, fxp_error());
217 return 0;
218 }
219
220 printf("Listing directory %s\n", cdir);
221
222 dirh = fxp_opendir(cdir);
223 if (dirh == NULL) {
224 printf("Unable to open %s: %s\n", dir, fxp_error());
225 } else {
226 nnames = namesize = 0;
227 ournames = NULL;
228
229 while (1) {
230
231 names = fxp_readdir(dirh);
232 if (names == NULL) {
233 if (fxp_error_type() == SSH_FX_EOF)
234 break;
235 printf("Reading directory %s: %s\n", dir, fxp_error());
236 break;
237 }
238 if (names->nnames == 0) {
239 fxp_free_names(names);
240 break;
241 }
242
243 if (nnames + names->nnames >= namesize) {
244 namesize += names->nnames + 128;
32874aea 245 ournames =
246 srealloc(ournames, namesize * sizeof(*ournames));
4c7f0d61 247 }
248
249 for (i = 0; i < names->nnames; i++)
250 ournames[nnames++] = names->names[i];
251
252 names->nnames = 0; /* prevent free_names */
253 fxp_free_names(names);
254 }
255 fxp_close(dirh);
256
257 /*
258 * Now we have our filenames. Sort them by actual file
259 * name, and then output the longname parts.
260 */
261 qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare);
262
263 /*
264 * And print them.
265 */
266 for (i = 0; i < nnames; i++)
267 printf("%s\n", ournames[i].longname);
268 }
269
270 sfree(cdir);
271
272 return 0;
273}
274
275/*
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.
278 */
32874aea 279int sftp_cmd_cd(struct sftp_command *cmd)
280{
4c7f0d61 281 struct fxp_handle *dirh;
282 char *dir;
283
fa3db767 284 if (back == NULL) {
285 printf("psftp: not connected to a host; use \"open host.name\"\n");
286 return 0;
287 }
288
4c7f0d61 289 if (cmd->nwords < 2)
f9e162aa 290 dir = dupstr(homedir);
4c7f0d61 291 else
292 dir = canonify(cmd->words[1]);
293
294 if (!dir) {
295 printf("%s: %s\n", dir, fxp_error());
296 return 0;
297 }
298
299 dirh = fxp_opendir(dir);
300 if (!dirh) {
301 printf("Directory %s: %s\n", dir, fxp_error());
302 sfree(dir);
303 return 0;
304 }
305
306 fxp_close(dirh);
307
308 sfree(pwd);
309 pwd = dir;
310 printf("Remote directory is now %s\n", pwd);
311
312 return 0;
313}
314
315/*
4f2b387f 316 * Print current directory. Easy as pie.
317 */
318int sftp_cmd_pwd(struct sftp_command *cmd)
319{
fa3db767 320 if (back == NULL) {
321 printf("psftp: not connected to a host; use \"open host.name\"\n");
322 return 0;
323 }
324
4f2b387f 325 printf("Remote directory is %s\n", pwd);
326 return 0;
327}
328
329/*
d92624dc 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.
4c7f0d61 334 */
d92624dc 335int sftp_general_get(struct sftp_command *cmd, int restart)
32874aea 336{
4c7f0d61 337 struct fxp_handle *fh;
338 char *fname, *outfname;
339 uint64 offset;
340 FILE *fp;
341
fa3db767 342 if (back == NULL) {
343 printf("psftp: not connected to a host; use \"open host.name\"\n");
344 return 0;
345 }
346
4c7f0d61 347 if (cmd->nwords < 2) {
348 printf("get: expects a filename\n");
349 return 0;
350 }
351
352 fname = canonify(cmd->words[1]);
353 if (!fname) {
354 printf("%s: %s\n", cmd->words[1], fxp_error());
355 return 0;
356 }
dcf8495c 357 outfname = (cmd->nwords == 2 ?
358 stripslashes(cmd->words[1], 0) : cmd->words[2]);
4c7f0d61 359
360 fh = fxp_open(fname, SSH_FXF_READ);
361 if (!fh) {
362 printf("%s: %s\n", fname, fxp_error());
363 sfree(fname);
364 return 0;
365 }
d92624dc 366
367 if (restart) {
368 fp = fopen(outfname, "rb+");
369 } else {
370 fp = fopen(outfname, "wb");
371 }
372
4c7f0d61 373 if (!fp) {
374 printf("local: unable to open %s\n", outfname);
32874aea 375 fxp_close(fh);
4c7f0d61 376 sfree(fname);
377 return 0;
378 }
379
d92624dc 380 if (restart) {
381 long posn;
382 fseek(fp, 0L, SEEK_END);
383 posn = ftell(fp);
384 printf("reget: restarting at file position %ld\n", posn);
385 offset = uint64_make(0, posn);
386 } else {
387 offset = uint64_make(0, 0);
388 }
4c7f0d61 389
d92624dc 390 printf("remote:%s => local:%s\n", fname, outfname);
4c7f0d61 391
392 /*
393 * FIXME: we can use FXP_FSTAT here to get the file size, and
394 * thus put up a progress bar.
395 */
396 while (1) {
397 char buffer[4096];
398 int len;
399 int wpos, wlen;
400
401 len = fxp_read(fh, buffer, offset, sizeof(buffer));
32874aea 402 if ((len == -1 && fxp_error_type() == SSH_FX_EOF) || len == 0)
4c7f0d61 403 break;
404 if (len == -1) {
405 printf("error while reading: %s\n", fxp_error());
406 break;
407 }
32874aea 408
4c7f0d61 409 wpos = 0;
410 while (wpos < len) {
32874aea 411 wlen = fwrite(buffer, 1, len - wpos, fp);
4c7f0d61 412 if (wlen <= 0) {
413 printf("error while writing local file\n");
414 break;
415 }
416 wpos += wlen;
417 }
418 if (wpos < len) /* we had an error */
419 break;
420 offset = uint64_add32(offset, len);
421 }
422
423 fclose(fp);
424 fxp_close(fh);
425 sfree(fname);
426
427 return 0;
428}
d92624dc 429int sftp_cmd_get(struct sftp_command *cmd)
430{
431 return sftp_general_get(cmd, 0);
432}
433int sftp_cmd_reget(struct sftp_command *cmd)
434{
435 return sftp_general_get(cmd, 1);
436}
4c7f0d61 437
438/*
d92624dc 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.
4c7f0d61 443 */
d92624dc 444int sftp_general_put(struct sftp_command *cmd, int restart)
32874aea 445{
4c7f0d61 446 struct fxp_handle *fh;
447 char *fname, *origoutfname, *outfname;
448 uint64 offset;
449 FILE *fp;
450
fa3db767 451 if (back == NULL) {
452 printf("psftp: not connected to a host; use \"open host.name\"\n");
453 return 0;
454 }
455
4c7f0d61 456 if (cmd->nwords < 2) {
457 printf("put: expects a filename\n");
458 return 0;
459 }
460
461 fname = cmd->words[1];
dcf8495c 462 origoutfname = (cmd->nwords == 2 ?
463 stripslashes(cmd->words[1], 1) : cmd->words[2]);
4c7f0d61 464 outfname = canonify(origoutfname);
f9e162aa 465 if (!outfname) {
4c7f0d61 466 printf("%s: %s\n", origoutfname, fxp_error());
467 return 0;
468 }
469
470 fp = fopen(fname, "rb");
471 if (!fp) {
472 printf("local: unable to open %s\n", fname);
4c7f0d61 473 sfree(outfname);
474 return 0;
475 }
d92624dc 476 if (restart) {
477 fh = fxp_open(outfname,
478 SSH_FXF_WRITE);
479 } else {
480 fh = fxp_open(outfname,
481 SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC);
482 }
4c7f0d61 483 if (!fh) {
484 printf("%s: %s\n", outfname, fxp_error());
485 sfree(outfname);
486 return 0;
487 }
488
d92624dc 489 if (restart) {
490 char decbuf[30];
491 struct fxp_attrs attrs;
492 if (!fxp_fstat(fh, &attrs)) {
493 printf("read size of %s: %s\n", outfname, fxp_error());
494 sfree(outfname);
495 return 0;
496 }
497 if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) {
498 printf("read size of %s: size was not given\n", outfname);
499 sfree(outfname);
500 return 0;
501 }
502 offset = attrs.size;
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");
507 sfree(outfname);
508 return 0;
509 }
510 if (fseek(fp, offset.lo, SEEK_SET) != 0)
511 fseek(fp, 0, SEEK_END); /* *shrug* */
512 } else {
513 offset = uint64_make(0, 0);
514 }
4c7f0d61 515
d92624dc 516 printf("local:%s => remote:%s\n", fname, outfname);
4c7f0d61 517
518 /*
519 * FIXME: we can use FXP_FSTAT here to get the file size, and
520 * thus put up a progress bar.
521 */
522 while (1) {
523 char buffer[4096];
524 int len;
525
f9e162aa 526 len = fread(buffer, 1, sizeof(buffer), fp);
4c7f0d61 527 if (len == -1) {
528 printf("error while reading local file\n");
529 break;
530 } else if (len == 0) {
531 break;
532 }
f9e162aa 533 if (!fxp_write(fh, buffer, offset, len)) {
4c7f0d61 534 printf("error while writing: %s\n", fxp_error());
535 break;
536 }
537 offset = uint64_add32(offset, len);
538 }
539
540 fxp_close(fh);
541 fclose(fp);
542 sfree(outfname);
543
544 return 0;
545}
d92624dc 546int sftp_cmd_put(struct sftp_command *cmd)
547{
548 return sftp_general_put(cmd, 0);
549}
550int sftp_cmd_reput(struct sftp_command *cmd)
551{
552 return sftp_general_put(cmd, 1);
553}
4c7f0d61 554
9954aaa3 555int sftp_cmd_mkdir(struct sftp_command *cmd)
556{
557 char *dir;
558 int result;
559
fa3db767 560 if (back == NULL) {
561 printf("psftp: not connected to a host; use \"open host.name\"\n");
562 return 0;
563 }
9954aaa3 564
565 if (cmd->nwords < 2) {
566 printf("mkdir: expects a directory\n");
567 return 0;
568 }
569
570 dir = canonify(cmd->words[1]);
571 if (!dir) {
572 printf("%s: %s\n", dir, fxp_error());
573 return 0;
574 }
575
576 result = fxp_mkdir(dir);
577 if (!result) {
578 printf("mkdir %s: %s\n", dir, fxp_error());
579 sfree(dir);
580 return 0;
581 }
582
d92624dc 583 sfree(dir);
584 return 0;
9954aaa3 585}
586
587int sftp_cmd_rmdir(struct sftp_command *cmd)
588{
589 char *dir;
590 int result;
591
fa3db767 592 if (back == NULL) {
593 printf("psftp: not connected to a host; use \"open host.name\"\n");
594 return 0;
595 }
9954aaa3 596
597 if (cmd->nwords < 2) {
598 printf("rmdir: expects a directory\n");
599 return 0;
600 }
601
602 dir = canonify(cmd->words[1]);
603 if (!dir) {
604 printf("%s: %s\n", dir, fxp_error());
605 return 0;
606 }
607
608 result = fxp_rmdir(dir);
609 if (!result) {
610 printf("rmdir %s: %s\n", dir, fxp_error());
611 sfree(dir);
612 return 0;
613 }
614
d92624dc 615 sfree(dir);
616 return 0;
9954aaa3 617}
618
619int sftp_cmd_rm(struct sftp_command *cmd)
620{
621 char *fname;
622 int result;
623
fa3db767 624 if (back == NULL) {
625 printf("psftp: not connected to a host; use \"open host.name\"\n");
626 return 0;
627 }
628
9954aaa3 629 if (cmd->nwords < 2) {
630 printf("rm: expects a filename\n");
631 return 0;
632 }
633
634 fname = canonify(cmd->words[1]);
635 if (!fname) {
636 printf("%s: %s\n", fname, fxp_error());
637 return 0;
638 }
639
d92624dc 640 result = fxp_remove(fname);
9954aaa3 641 if (!result) {
642 printf("rm %s: %s\n", fname, fxp_error());
643 sfree(fname);
644 return 0;
645 }
646
d92624dc 647 sfree(fname);
648 return 0;
649
650}
651
652int sftp_cmd_mv(struct sftp_command *cmd)
653{
654 char *srcfname, *dstfname;
655 int result;
656
fa3db767 657 if (back == NULL) {
658 printf("psftp: not connected to a host; use \"open host.name\"\n");
659 return 0;
660 }
661
d92624dc 662 if (cmd->nwords < 3) {
663 printf("mv: expects two filenames\n");
9954aaa3 664 return 0;
d92624dc 665 }
666 srcfname = canonify(cmd->words[1]);
667 if (!srcfname) {
668 printf("%s: %s\n", srcfname, fxp_error());
669 return 0;
670 }
671
672 dstfname = canonify(cmd->words[2]);
673 if (!dstfname) {
674 printf("%s: %s\n", dstfname, fxp_error());
675 return 0;
676 }
9954aaa3 677
d92624dc 678 result = fxp_rename(srcfname, dstfname);
679 if (!result) {
680 char const *error = fxp_error();
681 struct fxp_attrs attrs;
682
683 /*
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.
688 */
689 result = fxp_stat(dstfname, &attrs);
690 if (result &&
691 (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) &&
692 (attrs.permissions & 0040000)) {
693 char *p;
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);
700 sfree(newname);
701 if (newcanon) {
702 sfree(dstfname);
703 dstfname = newcanon;
704 result = fxp_rename(srcfname, dstfname);
705 error = result ? NULL : fxp_error();
706 }
707 }
708 if (error) {
709 printf("mv %s %s: %s\n", srcfname, dstfname, error);
710 sfree(srcfname);
711 sfree(dstfname);
712 return 0;
713 }
714 }
715 printf("%s -> %s\n", srcfname, dstfname);
716
717 sfree(srcfname);
718 sfree(dstfname);
719 return 0;
9954aaa3 720}
721
d92624dc 722int sftp_cmd_chmod(struct sftp_command *cmd)
723{
724 char *fname, *mode;
725 int result;
726 struct fxp_attrs attrs;
727 unsigned attrs_clr, attrs_xor, oldperms, newperms;
728
fa3db767 729 if (back == NULL) {
730 printf("psftp: not connected to a host; use \"open host.name\"\n");
731 return 0;
732 }
733
d92624dc 734 if (cmd->nwords < 3) {
735 printf("chmod: expects a mode specifier and a filename\n");
736 return 0;
737 }
738
739 /*
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.
749 */
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");
756 return 0;
757 }
758 attrs_clr = 07777;
759 sscanf(mode, "%o", &attrs_xor);
760 attrs_xor &= attrs_clr;
761 } else {
762 while (*mode) {
763 char *modebegin = mode;
764 unsigned subset, perms;
765 int action;
766
767 subset = 0;
768 while (*mode && *mode != ',' &&
769 *mode != '+' && *mode != '-' && *mode != '=') {
770 switch (*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 */
775 default:
776 printf("chmod: file mode '%.*s' contains unrecognised"
777 " user/group/other specifier '%c'\n",
778 strcspn(modebegin, ","), modebegin, *mode);
779 return 0;
780 }
781 mode++;
782 }
783 if (!*mode || *mode == ',') {
784 printf("chmod: file mode '%.*s' is incomplete\n",
785 strcspn(modebegin, ","), modebegin);
786 return 0;
787 }
788 action = *mode++;
789 if (!*mode || *mode == ',') {
790 printf("chmod: file mode '%.*s' is incomplete\n",
791 strcspn(modebegin, ","), modebegin);
792 return 0;
793 }
794 perms = 0;
795 while (*mode && *mode != ',') {
796 switch (*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;
801 case 's':
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);
807 return 0;
808 }
809 perms |= 06000;
810 break;
811 default:
812 printf("chmod: file mode '%.*s' contains unrecognised"
813 " permission specifier '%c'\n",
814 strcspn(modebegin, ","), modebegin, *mode);
815 return 0;
816 }
817 mode++;
818 }
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);
823 return 0;
824 }
825 perms &= subset;
826 switch (action) {
827 case '+':
828 attrs_clr |= perms;
829 attrs_xor |= perms;
830 break;
831 case '-':
832 attrs_clr |= perms;
833 attrs_xor &= ~perms;
834 break;
835 case '=':
836 attrs_clr |= subset;
837 attrs_xor |= perms;
838 break;
839 }
840 if (*mode) mode++; /* eat comma */
841 }
842 }
843
844 fname = canonify(cmd->words[2]);
845 if (!fname) {
846 printf("%s: %s\n", fname, fxp_error());
847 return 0;
848 }
849
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());
854 sfree(fname);
855 return 0;
856 }
857
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;
863
864 result = fxp_setstat(fname, attrs);
865
866 if (!result) {
867 printf("set attrs for %s: %s\n", fname, fxp_error());
868 sfree(fname);
869 return 0;
870 }
871
872 printf("%s: %04o -> %04o\n", fname, oldperms, newperms);
873
874 sfree(fname);
875 return 0;
876}
9954aaa3 877
fa3db767 878static int sftp_cmd_open(struct sftp_command *cmd)
879{
880 if (back != NULL) {
881 printf("psftp: already connected\n");
882 return 0;
883 }
884
885 if (cmd->nwords < 2) {
886 printf("open: expects a host name\n");
887 return 0;
888 }
889
890 if (psftp_connect(cmd->words[1], NULL, 0)) {
891 back = NULL; /* connection is already closed */
892 return -1; /* this is fatal */
893 }
894 do_sftp_init();
895}
896
bf5240cd 897static int sftp_cmd_help(struct sftp_command *cmd);
898
4c7f0d61 899static struct sftp_cmd_lookup {
900 char *name;
bf5240cd 901 /*
902 * For help purposes, there are two kinds of command:
903 *
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
907 * the command name.
908 *
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.
912 */
913 char *shorthelp;
914 char *longhelp;
32874aea 915 int (*obey) (struct sftp_command *);
4c7f0d61 916} sftp_lookup[] = {
917 /*
918 * List of sftp commands. This is binary-searched so it MUST be
919 * in ASCII order.
920 */
32874aea 921 {
bf5240cd 922 "bye", "finish your SFTP session",
923 "\n"
924 " Terminates your SFTP session and quits the PSFTP program.\n",
925 sftp_cmd_quit
926 },
927 {
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",
933 sftp_cmd_cd
934 },
935 {
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"
945 " [also u-w, u-x]\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",
960 sftp_cmd_chmod
961 },
962 {
963 "del", "delete a file",
964 " <filename>\n"
965 " Delete a file.\n",
966 sftp_cmd_rm
967 },
968 {
969 "delete", "delete a file",
970 "\n"
971 " Delete a file.\n",
972 sftp_cmd_rm
973 },
974 {
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",
980 sftp_cmd_ls
981 },
982 {
983 "exit", "bye", NULL, sftp_cmd_quit
984 },
985 {
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",
991 sftp_cmd_get
992 },
993 {
994 "help", "give help",
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",
999 sftp_cmd_help
1000 },
1001 {
1002 "ls", "dir", NULL,
1003 sftp_cmd_ls
1004 },
1005 {
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",
1009 sftp_cmd_mkdir
1010 },
1011 {
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",
1016 sftp_cmd_mv
1017 },
1018 {
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",
1024 sftp_cmd_put
1025 },
1026 {
fa3db767 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"
1031 " line.\n",
1032 sftp_cmd_open
1033 },
1034 {
4f2b387f 1035 "pwd", "print your remote working directory",
1036 "\n"
1037 " Print the current remote working directory for your SFTP session.\n",
1038 sftp_cmd_pwd
1039 },
1040 {
bf5240cd 1041 "quit", "bye", NULL,
1042 sftp_cmd_quit
1043 },
1044 {
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",
1050 sftp_cmd_reget
1051 },
1052 {
1053 "ren", "mv", NULL,
1054 sftp_cmd_mv
1055 },
1056 {
1057 "rename", "mv", NULL,
1058 sftp_cmd_mv
1059 },
1060 {
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",
1066 sftp_cmd_reput
1067 },
1068 {
1069 "rm", "del", NULL,
1070 sftp_cmd_rm
1071 },
1072 {
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",
1077 sftp_cmd_rmdir
1078 }
1079};
1080
1081const struct sftp_cmd_lookup *lookup_command(char *name)
1082{
1083 int i, j, k, cmp;
1084
1085 i = -1;
1086 j = sizeof(sftp_lookup) / sizeof(*sftp_lookup);
1087 while (j - i > 1) {
1088 k = (j + i) / 2;
1089 cmp = strcmp(name, sftp_lookup[k].name);
1090 if (cmp < 0)
1091 j = k;
1092 else if (cmp > 0)
1093 i = k;
1094 else {
1095 return &sftp_lookup[k];
1096 }
1097 }
1098 return NULL;
1099}
1100
1101static int sftp_cmd_help(struct sftp_command *cmd)
1102{
1103 int i;
1104 if (cmd->nwords == 1) {
1105 /*
1106 * Give short help on each command.
1107 */
1108 int maxlen;
1109 maxlen = 0;
1110 for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) {
1111 int len = strlen(sftp_lookup[i].name);
1112 if (maxlen < len)
1113 maxlen = len;
1114 }
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);
1122 }
1123 } else {
1124 /*
1125 * Give long help on specific commands.
1126 */
1127 for (i = 1; i < cmd->nwords; i++) {
1128 const struct sftp_cmd_lookup *lookup;
1129 lookup = lookup_command(cmd->words[i]);
1130 if (!lookup) {
1131 printf("help: %s: command not found\n", cmd->words[i]);
1132 } else {
1133 printf("%s", lookup->name);
1134 if (lookup->longhelp == NULL)
1135 lookup = lookup_command(lookup->shorthelp);
1136 printf("%s", lookup->longhelp);
1137 }
1138 }
1139 }
1140 return 0;
1141}
4c7f0d61 1142
1143/* ----------------------------------------------------------------------
1144 * Command line reading and parsing.
1145 */
9954aaa3 1146struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags)
32874aea 1147{
4c7f0d61 1148 char *line;
1149 int linelen, linesize;
1150 struct sftp_command *cmd;
1151 char *p, *q, *r;
1152 int quoting;
1153
9954aaa3 1154 if ((mode == 0) || (modeflags & 1)) {
1155 printf("psftp> ");
1156 }
4c7f0d61 1157 fflush(stdout);
1158
1159 cmd = smalloc(sizeof(struct sftp_command));
1160 cmd->words = NULL;
1161 cmd->nwords = 0;
1162 cmd->wordssize = 0;
1163
1164 line = NULL;
1165 linesize = linelen = 0;
1166 while (1) {
1167 int len;
1168 char *ret;
1169
1170 linesize += 512;
1171 line = srealloc(line, linesize);
9954aaa3 1172 ret = fgets(line + linelen, linesize - linelen, fp);
1173 if (modeflags & 1) {
1174 printf("%s", ret);
1175 }
4c7f0d61 1176
1177 if (!ret || (linelen == 0 && line[0] == '\0')) {
1178 cmd->obey = sftp_cmd_quit;
1179 printf("quit\n");
1180 return cmd; /* eof */
1181 }
32874aea 1182 len = linelen + strlen(line + linelen);
4c7f0d61 1183 linelen += len;
32874aea 1184 if (line[linelen - 1] == '\n') {
4c7f0d61 1185 linelen--;
1186 line[linelen] = '\0';
1187 break;
1188 }
1189 }
1190
1191 /*
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:
1197 *
1198 * firstword "second word" "this has ""quotes"" in" sodoes""this""
1199 *
1200 * becomes
1201 *
1202 * >firstword<
1203 * >second word<
1204 * >this has "quotes" in<
1205 * >sodoes"this"<
1206 */
1207 p = line;
1208 while (*p) {
1209 /* skip whitespace */
32874aea 1210 while (*p && (*p == ' ' || *p == '\t'))
1211 p++;
4c7f0d61 1212 /* mark start of word */
1213 q = r = p; /* q sits at start, r writes word */
1214 quoting = 0;
1215 while (*p) {
1216 if (!quoting && (*p == ' ' || *p == '\t'))
1217 break; /* reached end of word */
1218 else if (*p == '"' && p[1] == '"')
32874aea 1219 p += 2, *r++ = '"'; /* a literal quote */
4c7f0d61 1220 else if (*p == '"')
1221 p++, quoting = !quoting;
1222 else
1223 *r++ = *p++;
1224 }
32874aea 1225 if (*p)
1226 p++; /* skip over the whitespace */
4c7f0d61 1227 *r = '\0';
1228 if (cmd->nwords >= cmd->wordssize) {
1229 cmd->wordssize = cmd->nwords + 16;
32874aea 1230 cmd->words =
1231 srealloc(cmd->words, cmd->wordssize * sizeof(char *));
4c7f0d61 1232 }
1233 cmd->words[cmd->nwords++] = q;
1234 }
1235
1236 /*
1237 * Now parse the first word and assign a function.
1238 */
1239
1240 if (cmd->nwords == 0)
1241 cmd->obey = sftp_cmd_null;
1242 else {
bf5240cd 1243 const struct sftp_cmd_lookup *lookup;
1244 lookup = lookup_command(cmd->words[0]);
1245 if (!lookup)
1246 cmd->obey = sftp_cmd_unknown;
1247 else
1248 cmd->obey = lookup->obey;
4c7f0d61 1249 }
1250
1251 return cmd;
1252}
1253
fa3db767 1254static void do_sftp_init(void)
32874aea 1255{
4c7f0d61 1256 /*
1257 * Do protocol initialisation.
1258 */
1259 if (!fxp_init()) {
1260 fprintf(stderr,
32874aea 1261 "Fatal: unable to initialise SFTP: %s\n", fxp_error());
0d694692 1262 return;
4c7f0d61 1263 }
1264
1265 /*
1266 * Find out where our home directory is.
1267 */
f9e162aa 1268 homedir = fxp_realpath(".");
4c7f0d61 1269 if (!homedir) {
1270 fprintf(stderr,
1271 "Warning: failed to resolve home directory: %s\n",
1272 fxp_error());
1273 homedir = dupstr(".");
1274 } else {
1275 printf("Remote working directory is %s\n", homedir);
1276 }
1277 pwd = dupstr(homedir);
fa3db767 1278}
1279
1280void do_sftp(int mode, int modeflags, char *batchfile)
1281{
1282 FILE *fp;
4c7f0d61 1283
9954aaa3 1284 /*
1285 * Batch mode?
4c7f0d61 1286 */
9954aaa3 1287 if (mode == 0) {
1288
1289 /* ------------------------------------------------------------------
1290 * Now we're ready to do Real Stuff.
1291 */
1292 while (1) {
1293 struct sftp_command *cmd;
1294 cmd = sftp_getcmd(stdin, 0, 0);
1295 if (!cmd)
1296 break;
1297 if (cmd->obey(cmd) < 0)
1298 break;
bf5240cd 1299 }
9954aaa3 1300 } else {
1301 fp = fopen(batchfile, "r");
1302 if (!fp) {
bf5240cd 1303 printf("Fatal: unable to open %s\n", batchfile);
1304 return;
9954aaa3 1305 }
1306 while (1) {
bf5240cd 1307 struct sftp_command *cmd;
1308 cmd = sftp_getcmd(fp, mode, modeflags);
1309 if (!cmd)
1310 break;
1311 if (cmd->obey(cmd) < 0)
1312 break;
1313 if (fxp_error() != NULL) {
1314 if (!(modeflags & 2))
9954aaa3 1315 break;
bf5240cd 1316 }
9954aaa3 1317 }
bf5240cd 1318 fclose(fp);
9954aaa3 1319
4c7f0d61 1320 }
4a8fc3c4 1321}
4c7f0d61 1322
4a8fc3c4 1323/* ----------------------------------------------------------------------
1324 * Dirty bits: integration with PuTTY.
1325 */
1326
1327static int verbose = 0;
1328
1329void verify_ssh_host_key(char *host, int port, char *keytype,
32874aea 1330 char *keystr, char *fingerprint)
1331{
4a8fc3c4 1332 int ret;
d0718310 1333 HANDLE hin;
1334 DWORD savemode, i;
4a8fc3c4 1335
1336 static const char absentmsg[] =
32874aea 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"
1339 "think it is.\n"
1340 "The server's key fingerprint is:\n"
1341 "%s\n"
1342 "If you trust this host, enter \"y\" to add the key to\n"
1343 "PuTTY's cache and carry on connecting.\n"
d0718310 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"
1347 "connection.\n"
1348 "Store key in cache? (y/n) ";
4a8fc3c4 1349
1350 static const char wrongmsg[] =
32874aea 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"
1358 "%s\n"
1359 "If you were expecting this change and trust the new key,\n"
d0718310 1360 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
32874aea 1361 "If you want to carry on connecting but without updating\n"
d0718310 1362 "the cache, enter \"n\".\n"
32874aea 1363 "If you want to abandon the connection completely, press\n"
1364 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
1365 "safe choice.\n"
1366 "Update cached key? (y/n, Return cancels connection) ";
4a8fc3c4 1367
1368 static const char abandoned[] = "Connection abandoned.\n";
1369
1370 char line[32];
1371
1372 /*
1373 * Verify the key against the registry.
1374 */
1375 ret = verify_host_key(host, port, keytype, keystr);
1376
32874aea 1377 if (ret == 0) /* success - key matched OK */
1378 return;
d0718310 1379
32874aea 1380 if (ret == 2) { /* key was different */
1381 fprintf(stderr, wrongmsg, fingerprint);
d0718310 1382 fflush(stderr);
4a8fc3c4 1383 }
32874aea 1384 if (ret == 1) { /* key was absent */
1385 fprintf(stderr, absentmsg, fingerprint);
d0718310 1386 fflush(stderr);
1387 }
1388
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);
1395
1396 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
1397 if (line[0] == 'y' || line[0] == 'Y')
32874aea 1398 store_host_key(host, port, keytype, keystr);
d0718310 1399 } else {
1400 fprintf(stderr, abandoned);
1401 exit(0);
4a8fc3c4 1402 }
1403}
1404
1405/*
ca20bfcf 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
1409 */
1410void askcipher(char *ciphername, int cs)
1411{
1412 HANDLE hin;
1413 DWORD savemode, i;
1414
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";
1420
1421 char line[32];
1422
1423 fprintf(stderr, msg,
1424 (cs == 0) ? "" :
1425 (cs == 1) ? "client-to-server " :
1426 "server-to-client ",
1427 ciphername);
1428 fflush(stderr);
1429
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);
1436
1437 if (line[0] == 'y' || line[0] == 'Y') {
1438 return;
1439 } else {
1440 fprintf(stderr, abandoned);
1441 exit(0);
1442 }
1443}
1444
1445/*
7bedb13c 1446 * Warn about the obsolescent key file format.
1447 */
1448void old_keyfile_warning(void)
1449{
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"
1456 "format.\n"
1457 "\n"
1458 "Once the key is loaded into PuTTYgen, you can perform\n"
1459 "this conversion simply by saving it again.\n";
1460
1461 fputs(message, stderr);
1462}
1463
1464/*
4a8fc3c4 1465 * Print an error message and perform a fatal exit.
1466 */
1467void fatalbox(char *fmt, ...)
1468{
32874aea 1469 char str[0x100]; /* Make the size big enough */
4a8fc3c4 1470 va_list ap;
1471 va_start(ap, fmt);
1472 strcpy(str, "Fatal:");
32874aea 1473 vsprintf(str + strlen(str), fmt, ap);
4a8fc3c4 1474 va_end(ap);
1475 strcat(str, "\n");
fa3db767 1476 fputs(stderr, str);
4a8fc3c4 1477
1478 exit(1);
1479}
1480void connection_fatal(char *fmt, ...)
1481{
32874aea 1482 char str[0x100]; /* Make the size big enough */
4a8fc3c4 1483 va_list ap;
1484 va_start(ap, fmt);
1485 strcpy(str, "Fatal:");
32874aea 1486 vsprintf(str + strlen(str), fmt, ap);
4a8fc3c4 1487 va_end(ap);
1488 strcat(str, "\n");
fa3db767 1489 fputs(stderr, str);
4a8fc3c4 1490
1491 exit(1);
1492}
1493
32874aea 1494void logevent(char *string)
1495{
1496}
4a8fc3c4 1497
760e88b2 1498void ldisc_send(char *buf, int len, int interactive)
32874aea 1499{
4a8fc3c4 1500 /*
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.
4c7f0d61 1505 */
4a8fc3c4 1506 assert(len == 0);
1507}
1508
1509/*
1510 * Be told what socket we're supposed to be using.
1511 */
1512static SOCKET sftp_ssh_socket;
32874aea 1513char *do_select(SOCKET skt, int startup)
1514{
4a8fc3c4 1515 if (startup)
1516 sftp_ssh_socket = skt;
1517 else
1518 sftp_ssh_socket = INVALID_SOCKET;
1519 return NULL;
4c7f0d61 1520}
4a8fc3c4 1521extern int select_result(WPARAM, LPARAM);
1522
1523/*
1524 * Receive a block of data from the SSH link. Block until all data
1525 * is available.
1526 *
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.
1530 */
1531
32874aea 1532static unsigned char *outptr; /* where to put the data */
1533static unsigned outlen; /* how much data required */
4a8fc3c4 1534static unsigned char *pending = NULL; /* any spare data */
32874aea 1535static unsigned pendlen = 0, pendsize = 0; /* length and phys. size of buffer */
5471d09a 1536int from_backend(int is_stderr, char *data, int datalen)
32874aea 1537{
1538 unsigned char *p = (unsigned char *) data;
1539 unsigned len = (unsigned) datalen;
4a8fc3c4 1540
1541 /*
1542 * stderr data is just spouted to local stderr and otherwise
1543 * ignored.
1544 */
1545 if (is_stderr) {
1546 fwrite(data, 1, len, stderr);
5471d09a 1547 return 0;
4a8fc3c4 1548 }
1549
1550 /*
1551 * If this is before the real session begins, just return.
1552 */
1553 if (!outptr)
5471d09a 1554 return 0;
4a8fc3c4 1555
1556 if (outlen > 0) {
32874aea 1557 unsigned used = outlen;
1558 if (used > len)
1559 used = len;
1560 memcpy(outptr, p, used);
1561 outptr += used;
1562 outlen -= used;
1563 p += used;
1564 len -= used;
4a8fc3c4 1565 }
1566
1567 if (len > 0) {
32874aea 1568 if (pendsize < pendlen + len) {
1569 pendsize = pendlen + len + 4096;
1570 pending = (pending ? srealloc(pending, pendsize) :
1571 smalloc(pendsize));
1572 if (!pending)
1573 fatalbox("Out of memory");
1574 }
1575 memcpy(pending + pendlen, p, len);
1576 pendlen += len;
4a8fc3c4 1577 }
5471d09a 1578
1579 return 0;
4a8fc3c4 1580}
32874aea 1581int sftp_recvdata(char *buf, int len)
1582{
1583 outptr = (unsigned char *) buf;
4a8fc3c4 1584 outlen = len;
1585
1586 /*
1587 * See if the pending-input block contains some of what we
1588 * need.
1589 */
1590 if (pendlen > 0) {
32874aea 1591 unsigned pendused = pendlen;
1592 if (pendused > outlen)
1593 pendused = outlen;
4a8fc3c4 1594 memcpy(outptr, pending, pendused);
32874aea 1595 memmove(pending, pending + pendused, pendlen - pendused);
4a8fc3c4 1596 outptr += pendused;
1597 outlen -= pendused;
32874aea 1598 pendlen -= pendused;
1599 if (pendlen == 0) {
1600 pendsize = 0;
1601 sfree(pending);
1602 pending = NULL;
1603 }
1604 if (outlen == 0)
1605 return 1;
4a8fc3c4 1606 }
1607
1608 while (outlen > 0) {
32874aea 1609 fd_set readfds;
4a8fc3c4 1610
32874aea 1611 FD_ZERO(&readfds);
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);
4a8fc3c4 1616 }
1617
1618 return 1;
1619}
32874aea 1620int sftp_senddata(char *buf, int len)
1621{
1622 back->send((unsigned char *) buf, len);
4a8fc3c4 1623 return 1;
1624}
1625
1626/*
1627 * Loop through the ssh connection and authentication process.
1628 */
32874aea 1629static void ssh_sftp_init(void)
1630{
4a8fc3c4 1631 if (sftp_ssh_socket == INVALID_SOCKET)
1632 return;
1633 while (!back->sendok()) {
32874aea 1634 fd_set readfds;
1635 FD_ZERO(&readfds);
1636 FD_SET(sftp_ssh_socket, &readfds);
1637 if (select(1, &readfds, NULL, NULL, NULL) < 0)
1638 return; /* doom */
1639 select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
4a8fc3c4 1640 }
1641}
1642
1643static char *password = NULL;
fa17a66e 1644static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
4a8fc3c4 1645{
1646 HANDLE hin, hout;
fa17a66e 1647 DWORD savemode, newmode, i;
4a8fc3c4 1648
1649 if (password) {
32874aea 1650 static int tried_once = 0;
1651
1652 if (tried_once) {
1653 return 0;
1654 } else {
1655 strncpy(str, password, maxlen);
1656 str[maxlen - 1] = '\0';
1657 tried_once = 1;
1658 return 1;
1659 }
4a8fc3c4 1660 }
1661
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");
1666 exit(1);
1667 }
1668
1669 GetConsoleMode(hin, &savemode);
fa17a66e 1670 newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
1671 if (is_pw)
32874aea 1672 newmode &= ~ENABLE_ECHO_INPUT;
fa17a66e 1673 else
32874aea 1674 newmode |= ENABLE_ECHO_INPUT;
fa17a66e 1675 SetConsoleMode(hin, newmode);
4a8fc3c4 1676
1677 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
32874aea 1678 ReadFile(hin, str, maxlen - 1, &i, NULL);
4a8fc3c4 1679
1680 SetConsoleMode(hin, savemode);
1681
32874aea 1682 if ((int) i > maxlen)
1683 i = maxlen - 1;
1684 else
1685 i = i - 2;
4a8fc3c4 1686 str[i] = '\0';
1687
fa17a66e 1688 if (is_pw)
32874aea 1689 WriteFile(hout, "\r\n", 2, &i, NULL);
4a8fc3c4 1690
1691 return 1;
1692}
1693
1694/*
1695 * Initialize the Win$ock driver.
1696 */
1697static void init_winsock(void)
1698{
1699 WORD winsock_ver;
1700 WSADATA wsadata;
1701
1702 winsock_ver = MAKEWORD(1, 1);
1703 if (WSAStartup(winsock_ver, &wsadata)) {
1704 fprintf(stderr, "Unable to initialise WinSock");
1705 exit(1);
1706 }
32874aea 1707 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
4a8fc3c4 1708 fprintf(stderr, "WinSock version is incompatible with 1.1");
1709 exit(1);
1710 }
1711}
1712
1713/*
1714 * Short description of parameters.
1715 */
1716static void usage(void)
1717{
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");
9954aaa3 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");
4a8fc3c4 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");
1728 exit(1);
1729}
1730
1731/*
fa3db767 1732 * Connect to a host.
4a8fc3c4 1733 */
fa3db767 1734static int psftp_connect(char *userhost, char *user, int portnumber)
4a8fc3c4 1735{
fa3db767 1736 char *host, *realhost;
4a8fc3c4 1737 char *err;
4a8fc3c4 1738
1739 /* Separate host and username */
1740 host = userhost;
1741 host = strrchr(host, '@');
1742 if (host == NULL) {
1743 host = userhost;
1744 } else {
1745 *host++ = '\0';
1746 if (user) {
32874aea 1747 printf("psftp: multiple usernames specified; using \"%s\"\n",
1748 user);
4a8fc3c4 1749 } else
1750 user = userhost;
1751 }
1752
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 */
32874aea 1757 do_defaults(NULL, &cfg);
1758 strncpy(cfg.host, host, sizeof(cfg.host) - 1);
1759 cfg.host[sizeof(cfg.host) - 1] = '\0';
4a8fc3c4 1760 cfg.port = 22;
1761 }
1762
449925a6 1763 /*
1764 * Trim leading whitespace off the hostname if it's there.
1765 */
1766 {
1767 int space = strspn(cfg.host, " \t");
1768 memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
1769 }
1770
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 */
1775 if (atsign) {
1776 if (atsign - cfg.host < sizeof cfg.username) {
1777 strncpy(cfg.username, cfg.host, atsign - cfg.host);
1778 cfg.username[atsign - cfg.host] = '\0';
1779 }
1780 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
1781 }
1782 }
1783
1784 /*
1785 * Trim a colon suffix off the hostname if it's there.
1786 */
1787 cfg.host[strcspn(cfg.host, ":")] = '\0';
1788
4a8fc3c4 1789 /* Set username */
1790 if (user != NULL && user[0] != '\0') {
32874aea 1791 strncpy(cfg.username, user, sizeof(cfg.username) - 1);
1792 cfg.username[sizeof(cfg.username) - 1] = '\0';
4a8fc3c4 1793 }
1794 if (!cfg.username[0]) {
1795 printf("login as: ");
1796 if (!fgets(cfg.username, sizeof(cfg.username), stdin)) {
1797 fprintf(stderr, "psftp: aborting\n");
1798 exit(1);
1799 } else {
1800 int len = strlen(cfg.username);
32874aea 1801 if (cfg.username[len - 1] == '\n')
1802 cfg.username[len - 1] = '\0';
4a8fc3c4 1803 }
1804 }
1805
1806 if (cfg.protocol != PROT_SSH)
1807 cfg.port = 22;
1808
1809 if (portnumber)
1810 cfg.port = portnumber;
1811
1812 /* SFTP uses SSH2 by default always */
1813 cfg.sshprot = 2;
1814
d27b4a18 1815 /*
1816 * Disable scary things which shouldn't be enabled for simple
1817 * things like SCP and SFTP: agent forwarding, port forwarding,
1818 * X forwarding.
1819 */
1820 cfg.x11_forward = 0;
1821 cfg.agentfwd = 0;
1822 cfg.portfwd[0] = cfg.portfwd[1] = '\0';
1823
bebf22d0 1824 /* Set up subsystem name. */
4a8fc3c4 1825 strcpy(cfg.remote_cmd, "sftp");
1826 cfg.ssh_subsys = TRUE;
1827 cfg.nopty = TRUE;
1828
bebf22d0 1829 /*
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
248c0c5a 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.
bebf22d0 1837 *
248c0c5a 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
1840 * exec sftp-server
bebf22d0 1841 *
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.
1845 */
1846 cfg.remote_cmd_ptr2 =
248c0c5a 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"
1849 "exec sftp-server";
bebf22d0 1850 cfg.ssh_subsys2 = FALSE;
1851
4a8fc3c4 1852 back = &ssh_backend;
1853
2184a5d9 1854 err = back->init(cfg.host, cfg.port, &realhost, 0);
4a8fc3c4 1855 if (err != NULL) {
fa3db767 1856 fprintf(stderr, "ssh_init: %s\n", err);
4a8fc3c4 1857 return 1;
1858 }
1859 ssh_sftp_init();
1860 if (verbose && realhost != NULL)
1861 printf("Connected to %s\n", realhost);
fa3db767 1862 return 0;
1863}
1864
1865/*
1866 * Main program. Parse arguments etc.
1867 */
1868int main(int argc, char *argv[])
1869{
1870 int i;
1871 int portnumber = 0;
1872 char *userhost, *user;
1873 int mode = 0;
1874 int modeflags = 0;
1875 char *batchfile = NULL;
1876
1877 flags = FLAG_STDERR | FLAG_INTERACTIVE;
1878 ssh_get_line = &get_line;
1879 init_winsock();
1880 sk_init();
1881
1882 userhost = user = NULL;
1883
1884 for (i = 1; i < argc; i++) {
1885 if (argv[i][0] != '-') {
1886 if (userhost)
1887 usage();
1888 else
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) {
1894 usage();
1895 } else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) {
1896 user = argv[++i];
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) {
1902 mode = 1;
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) {
1909 i++;
1910 break;
1911 } else {
1912 usage();
1913 }
1914 }
1915 argc -= i;
1916 argv += i;
1917 back = NULL;
1918
1919 /*
1920 * If a user@host string has already been provided, connect to
1921 * it now.
1922 */
1923 if (userhost) {
1924 if (psftp_connect(userhost, user, portnumber))
1925 return 1;
1926 do_sftp_init();
1927 } else {
1928 printf("psftp: no hostname specified; use \"open host.name\""
1929 " to connect\n");
1930 }
4c7f0d61 1931
9954aaa3 1932 do_sftp(mode, modeflags, batchfile);
4a8fc3c4 1933
1934 if (back != NULL && back->socket() != NULL) {
1935 char ch;
1936 back->special(TS_EOF);
1937 sftp_recvdata(&ch, 1);
1938 }
1939 WSACleanup();
1940 random_save_seed();
1941
4c7f0d61 1942 return 0;
1943}