Add online help in PSFTP.
[sgt/putty] / psftp.c
... / ...
CommitLineData
1/*
2 * psftp.c: front end for PSFTP.
3 */
4
5#include <windows.h>
6
7#include <stdio.h>
8#include <stdlib.h>
9#include <stdarg.h>
10#include <assert.h>
11#include <limits.h>
12
13#define PUTTY_DO_GLOBALS
14#include "putty.h"
15#include "storage.h"
16#include "ssh.h"
17#include "sftp.h"
18#include "int64.h"
19
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
27/* ----------------------------------------------------------------------
28 * sftp client state.
29 */
30
31char *pwd, *homedir;
32
33/* ----------------------------------------------------------------------
34 * Higher-level helper functions used in commands.
35 */
36
37/*
38 * Attempt to canonify a pathname starting from the pwd. If
39 * canonification fails, at least fall back to returning a _valid_
40 * pathname (though it may be ugly, eg /home/simon/../foobar).
41 */
42char *canonify(char *name)
43{
44 char *fullname, *canonname;
45
46 if (name[0] == '/') {
47 fullname = dupstr(name);
48 } else {
49 char *slash;
50 if (pwd[strlen(pwd) - 1] == '/')
51 slash = "";
52 else
53 slash = "/";
54 fullname = dupcat(pwd, slash, name, NULL);
55 }
56
57 canonname = fxp_realpath(fullname);
58
59 if (canonname) {
60 sfree(fullname);
61 return canonname;
62 } else {
63 /*
64 * Attempt number 2. Some FXP_REALPATH implementations
65 * (glibc-based ones, in particular) require the _whole_
66 * path to point to something that exists, whereas others
67 * (BSD-based) only require all but the last component to
68 * exist. So if the first call failed, we should strip off
69 * everything from the last slash onwards and try again,
70 * then put the final component back on.
71 *
72 * Special cases:
73 *
74 * - if the last component is "/." or "/..", then we don't
75 * bother trying this because there's no way it can work.
76 *
77 * - if the thing actually ends with a "/", we remove it
78 * before we start. Except if the string is "/" itself
79 * (although I can't see why we'd have got here if so,
80 * because surely "/" would have worked the first
81 * time?), in which case we don't bother.
82 *
83 * - if there's no slash in the string at all, give up in
84 * confusion (we expect at least one because of the way
85 * we constructed the string).
86 */
87
88 int i;
89 char *returnname;
90
91 i = strlen(fullname);
92 if (i > 2 && fullname[i - 1] == '/')
93 fullname[--i] = '\0'; /* strip trailing / unless at pos 0 */
94 while (i > 0 && fullname[--i] != '/');
95
96 /*
97 * Give up on special cases.
98 */
99 if (fullname[i] != '/' || /* no slash at all */
100 !strcmp(fullname + i, "/.") || /* ends in /. */
101 !strcmp(fullname + i, "/..") || /* ends in /.. */
102 !strcmp(fullname, "/")) {
103 return fullname;
104 }
105
106 /*
107 * Now i points at the slash. Deal with the final special
108 * case i==0 (ie the whole path was "/nonexistentfile").
109 */
110 fullname[i] = '\0'; /* separate the string */
111 if (i == 0) {
112 canonname = fxp_realpath("/");
113 } else {
114 canonname = fxp_realpath(fullname);
115 }
116
117 if (!canonname)
118 return fullname; /* even that failed; give up */
119
120 /*
121 * We have a canonical name for all but the last path
122 * component. Concatenate the last component and return.
123 */
124 returnname = dupcat(canonname,
125 canonname[strlen(canonname) - 1] ==
126 '/' ? "" : "/", fullname + i + 1, NULL);
127 sfree(fullname);
128 sfree(canonname);
129 return returnname;
130 }
131}
132
133/* ----------------------------------------------------------------------
134 * Actual sftp commands.
135 */
136struct sftp_command {
137 char **words;
138 int nwords, wordssize;
139 int (*obey) (struct sftp_command *); /* returns <0 to quit */
140};
141
142int sftp_cmd_null(struct sftp_command *cmd)
143{
144 return 0;
145}
146
147int sftp_cmd_unknown(struct sftp_command *cmd)
148{
149 printf("psftp: unknown command \"%s\"\n", cmd->words[0]);
150 return 0;
151}
152
153int sftp_cmd_quit(struct sftp_command *cmd)
154{
155 return -1;
156}
157
158/*
159 * List a directory. If no arguments are given, list pwd; otherwise
160 * list the directory given in words[1].
161 */
162static int sftp_ls_compare(const void *av, const void *bv)
163{
164 const struct fxp_name *a = (const struct fxp_name *) av;
165 const struct fxp_name *b = (const struct fxp_name *) bv;
166 return strcmp(a->filename, b->filename);
167}
168int sftp_cmd_ls(struct sftp_command *cmd)
169{
170 struct fxp_handle *dirh;
171 struct fxp_names *names;
172 struct fxp_name *ournames;
173 int nnames, namesize;
174 char *dir, *cdir;
175 int i;
176
177 if (cmd->nwords < 2)
178 dir = ".";
179 else
180 dir = cmd->words[1];
181
182 cdir = canonify(dir);
183 if (!cdir) {
184 printf("%s: %s\n", dir, fxp_error());
185 return 0;
186 }
187
188 printf("Listing directory %s\n", cdir);
189
190 dirh = fxp_opendir(cdir);
191 if (dirh == NULL) {
192 printf("Unable to open %s: %s\n", dir, fxp_error());
193 } else {
194 nnames = namesize = 0;
195 ournames = NULL;
196
197 while (1) {
198
199 names = fxp_readdir(dirh);
200 if (names == NULL) {
201 if (fxp_error_type() == SSH_FX_EOF)
202 break;
203 printf("Reading directory %s: %s\n", dir, fxp_error());
204 break;
205 }
206 if (names->nnames == 0) {
207 fxp_free_names(names);
208 break;
209 }
210
211 if (nnames + names->nnames >= namesize) {
212 namesize += names->nnames + 128;
213 ournames =
214 srealloc(ournames, namesize * sizeof(*ournames));
215 }
216
217 for (i = 0; i < names->nnames; i++)
218 ournames[nnames++] = names->names[i];
219
220 names->nnames = 0; /* prevent free_names */
221 fxp_free_names(names);
222 }
223 fxp_close(dirh);
224
225 /*
226 * Now we have our filenames. Sort them by actual file
227 * name, and then output the longname parts.
228 */
229 qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare);
230
231 /*
232 * And print them.
233 */
234 for (i = 0; i < nnames; i++)
235 printf("%s\n", ournames[i].longname);
236 }
237
238 sfree(cdir);
239
240 return 0;
241}
242
243/*
244 * Change directories. We do this by canonifying the new name, then
245 * trying to OPENDIR it. Only if that succeeds do we set the new pwd.
246 */
247int sftp_cmd_cd(struct sftp_command *cmd)
248{
249 struct fxp_handle *dirh;
250 char *dir;
251
252 if (cmd->nwords < 2)
253 dir = dupstr(homedir);
254 else
255 dir = canonify(cmd->words[1]);
256
257 if (!dir) {
258 printf("%s: %s\n", dir, fxp_error());
259 return 0;
260 }
261
262 dirh = fxp_opendir(dir);
263 if (!dirh) {
264 printf("Directory %s: %s\n", dir, fxp_error());
265 sfree(dir);
266 return 0;
267 }
268
269 fxp_close(dirh);
270
271 sfree(pwd);
272 pwd = dir;
273 printf("Remote directory is now %s\n", pwd);
274
275 return 0;
276}
277
278/*
279 * Get a file and save it at the local end. We have two very
280 * similar commands here: `get' and `reget', which differ in that
281 * `reget' checks for the existence of the destination file and
282 * starts from where a previous aborted transfer left off.
283 */
284int sftp_general_get(struct sftp_command *cmd, int restart)
285{
286 struct fxp_handle *fh;
287 char *fname, *outfname;
288 uint64 offset;
289 FILE *fp;
290
291 if (cmd->nwords < 2) {
292 printf("get: expects a filename\n");
293 return 0;
294 }
295
296 fname = canonify(cmd->words[1]);
297 if (!fname) {
298 printf("%s: %s\n", cmd->words[1], fxp_error());
299 return 0;
300 }
301 outfname = (cmd->nwords == 2 ? cmd->words[1] : cmd->words[2]);
302
303 fh = fxp_open(fname, SSH_FXF_READ);
304 if (!fh) {
305 printf("%s: %s\n", fname, fxp_error());
306 sfree(fname);
307 return 0;
308 }
309
310 if (restart) {
311 fp = fopen(outfname, "rb+");
312 } else {
313 fp = fopen(outfname, "wb");
314 }
315
316 if (!fp) {
317 printf("local: unable to open %s\n", outfname);
318 fxp_close(fh);
319 sfree(fname);
320 return 0;
321 }
322
323 if (restart) {
324 long posn;
325 fseek(fp, 0L, SEEK_END);
326 posn = ftell(fp);
327 printf("reget: restarting at file position %ld\n", posn);
328 offset = uint64_make(0, posn);
329 } else {
330 offset = uint64_make(0, 0);
331 }
332
333 printf("remote:%s => local:%s\n", fname, outfname);
334
335 /*
336 * FIXME: we can use FXP_FSTAT here to get the file size, and
337 * thus put up a progress bar.
338 */
339 while (1) {
340 char buffer[4096];
341 int len;
342 int wpos, wlen;
343
344 len = fxp_read(fh, buffer, offset, sizeof(buffer));
345 if ((len == -1 && fxp_error_type() == SSH_FX_EOF) || len == 0)
346 break;
347 if (len == -1) {
348 printf("error while reading: %s\n", fxp_error());
349 break;
350 }
351
352 wpos = 0;
353 while (wpos < len) {
354 wlen = fwrite(buffer, 1, len - wpos, fp);
355 if (wlen <= 0) {
356 printf("error while writing local file\n");
357 break;
358 }
359 wpos += wlen;
360 }
361 if (wpos < len) /* we had an error */
362 break;
363 offset = uint64_add32(offset, len);
364 }
365
366 fclose(fp);
367 fxp_close(fh);
368 sfree(fname);
369
370 return 0;
371}
372int sftp_cmd_get(struct sftp_command *cmd)
373{
374 return sftp_general_get(cmd, 0);
375}
376int sftp_cmd_reget(struct sftp_command *cmd)
377{
378 return sftp_general_get(cmd, 1);
379}
380
381/*
382 * Send a file and store it at the remote end. We have two very
383 * similar commands here: `put' and `reput', which differ in that
384 * `reput' checks for the existence of the destination file and
385 * starts from where a previous aborted transfer left off.
386 */
387int sftp_general_put(struct sftp_command *cmd, int restart)
388{
389 struct fxp_handle *fh;
390 char *fname, *origoutfname, *outfname;
391 uint64 offset;
392 FILE *fp;
393
394 if (cmd->nwords < 2) {
395 printf("put: expects a filename\n");
396 return 0;
397 }
398
399 fname = cmd->words[1];
400 origoutfname = (cmd->nwords == 2 ? cmd->words[1] : cmd->words[2]);
401 outfname = canonify(origoutfname);
402 if (!outfname) {
403 printf("%s: %s\n", origoutfname, fxp_error());
404 return 0;
405 }
406
407 fp = fopen(fname, "rb");
408 if (!fp) {
409 printf("local: unable to open %s\n", fname);
410 sfree(outfname);
411 return 0;
412 }
413 if (restart) {
414 fh = fxp_open(outfname,
415 SSH_FXF_WRITE);
416 } else {
417 fh = fxp_open(outfname,
418 SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC);
419 }
420 if (!fh) {
421 printf("%s: %s\n", outfname, fxp_error());
422 sfree(outfname);
423 return 0;
424 }
425
426 if (restart) {
427 char decbuf[30];
428 struct fxp_attrs attrs;
429 if (!fxp_fstat(fh, &attrs)) {
430 printf("read size of %s: %s\n", outfname, fxp_error());
431 sfree(outfname);
432 return 0;
433 }
434 if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) {
435 printf("read size of %s: size was not given\n", outfname);
436 sfree(outfname);
437 return 0;
438 }
439 offset = attrs.size;
440 uint64_decimal(offset, decbuf);
441 printf("reput: restarting at file position %s\n", decbuf);
442 if (uint64_compare(offset, uint64_make(0, LONG_MAX)) > 0) {
443 printf("reput: remote file is larger than we can deal with\n");
444 sfree(outfname);
445 return 0;
446 }
447 if (fseek(fp, offset.lo, SEEK_SET) != 0)
448 fseek(fp, 0, SEEK_END); /* *shrug* */
449 } else {
450 offset = uint64_make(0, 0);
451 }
452
453 printf("local:%s => remote:%s\n", fname, outfname);
454
455 /*
456 * FIXME: we can use FXP_FSTAT here to get the file size, and
457 * thus put up a progress bar.
458 */
459 while (1) {
460 char buffer[4096];
461 int len;
462
463 len = fread(buffer, 1, sizeof(buffer), fp);
464 if (len == -1) {
465 printf("error while reading local file\n");
466 break;
467 } else if (len == 0) {
468 break;
469 }
470 if (!fxp_write(fh, buffer, offset, len)) {
471 printf("error while writing: %s\n", fxp_error());
472 break;
473 }
474 offset = uint64_add32(offset, len);
475 }
476
477 fxp_close(fh);
478 fclose(fp);
479 sfree(outfname);
480
481 return 0;
482}
483int sftp_cmd_put(struct sftp_command *cmd)
484{
485 return sftp_general_put(cmd, 0);
486}
487int sftp_cmd_reput(struct sftp_command *cmd)
488{
489 return sftp_general_put(cmd, 1);
490}
491
492int sftp_cmd_mkdir(struct sftp_command *cmd)
493{
494 char *dir;
495 int result;
496
497
498 if (cmd->nwords < 2) {
499 printf("mkdir: expects a directory\n");
500 return 0;
501 }
502
503 dir = canonify(cmd->words[1]);
504 if (!dir) {
505 printf("%s: %s\n", dir, fxp_error());
506 return 0;
507 }
508
509 result = fxp_mkdir(dir);
510 if (!result) {
511 printf("mkdir %s: %s\n", dir, fxp_error());
512 sfree(dir);
513 return 0;
514 }
515
516 sfree(dir);
517 return 0;
518}
519
520int sftp_cmd_rmdir(struct sftp_command *cmd)
521{
522 char *dir;
523 int result;
524
525
526 if (cmd->nwords < 2) {
527 printf("rmdir: expects a directory\n");
528 return 0;
529 }
530
531 dir = canonify(cmd->words[1]);
532 if (!dir) {
533 printf("%s: %s\n", dir, fxp_error());
534 return 0;
535 }
536
537 result = fxp_rmdir(dir);
538 if (!result) {
539 printf("rmdir %s: %s\n", dir, fxp_error());
540 sfree(dir);
541 return 0;
542 }
543
544 sfree(dir);
545 return 0;
546}
547
548int sftp_cmd_rm(struct sftp_command *cmd)
549{
550 char *fname;
551 int result;
552
553 if (cmd->nwords < 2) {
554 printf("rm: expects a filename\n");
555 return 0;
556 }
557
558 fname = canonify(cmd->words[1]);
559 if (!fname) {
560 printf("%s: %s\n", fname, fxp_error());
561 return 0;
562 }
563
564 result = fxp_remove(fname);
565 if (!result) {
566 printf("rm %s: %s\n", fname, fxp_error());
567 sfree(fname);
568 return 0;
569 }
570
571 sfree(fname);
572 return 0;
573
574}
575
576int sftp_cmd_mv(struct sftp_command *cmd)
577{
578 char *srcfname, *dstfname;
579 int result;
580
581 if (cmd->nwords < 3) {
582 printf("mv: expects two filenames\n");
583 return 0;
584 }
585 srcfname = canonify(cmd->words[1]);
586 if (!srcfname) {
587 printf("%s: %s\n", srcfname, fxp_error());
588 return 0;
589 }
590
591 dstfname = canonify(cmd->words[2]);
592 if (!dstfname) {
593 printf("%s: %s\n", dstfname, fxp_error());
594 return 0;
595 }
596
597 result = fxp_rename(srcfname, dstfname);
598 if (!result) {
599 char const *error = fxp_error();
600 struct fxp_attrs attrs;
601
602 /*
603 * The move might have failed because dstfname pointed at a
604 * directory. We check this possibility now: if dstfname
605 * _is_ a directory, we re-attempt the move by appending
606 * the basename of srcfname to dstfname.
607 */
608 result = fxp_stat(dstfname, &attrs);
609 if (result &&
610 (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) &&
611 (attrs.permissions & 0040000)) {
612 char *p;
613 char *newname, *newcanon;
614 printf("(destination %s is a directory)\n", dstfname);
615 p = srcfname + strlen(srcfname);
616 while (p > srcfname && p[-1] != '/') p--;
617 newname = dupcat(dstfname, "/", p, NULL);
618 newcanon = canonify(newname);
619 sfree(newname);
620 if (newcanon) {
621 sfree(dstfname);
622 dstfname = newcanon;
623 result = fxp_rename(srcfname, dstfname);
624 error = result ? NULL : fxp_error();
625 }
626 }
627 if (error) {
628 printf("mv %s %s: %s\n", srcfname, dstfname, error);
629 sfree(srcfname);
630 sfree(dstfname);
631 return 0;
632 }
633 }
634 printf("%s -> %s\n", srcfname, dstfname);
635
636 sfree(srcfname);
637 sfree(dstfname);
638 return 0;
639}
640
641int sftp_cmd_chmod(struct sftp_command *cmd)
642{
643 char *fname, *mode;
644 int result;
645 struct fxp_attrs attrs;
646 unsigned attrs_clr, attrs_xor, oldperms, newperms;
647
648 if (cmd->nwords < 3) {
649 printf("chmod: expects a mode specifier and a filename\n");
650 return 0;
651 }
652
653 /*
654 * Attempt to parse the mode specifier in cmd->words[1]. We
655 * don't support the full horror of Unix chmod; instead we
656 * support a much simpler syntax in which the user can either
657 * specify an octal number, or a comma-separated sequence of
658 * [ugoa]*[-+=][rwxst]+. (The initial [ugoa] sequence may
659 * _only_ be omitted if the only attribute mentioned is t,
660 * since all others require a user/group/other specification.
661 * Additionally, the s attribute may not be specified for any
662 * [ugoa] specifications other than exactly u or exactly g.
663 */
664 attrs_clr = attrs_xor = 0;
665 mode = cmd->words[1];
666 if (mode[0] >= '0' && mode[0] <= '9') {
667 if (mode[strspn(mode, "01234567")]) {
668 printf("chmod: numeric file modes should"
669 " contain digits 0-7 only\n");
670 return 0;
671 }
672 attrs_clr = 07777;
673 sscanf(mode, "%o", &attrs_xor);
674 attrs_xor &= attrs_clr;
675 } else {
676 while (*mode) {
677 char *modebegin = mode;
678 unsigned subset, perms;
679 int action;
680
681 subset = 0;
682 while (*mode && *mode != ',' &&
683 *mode != '+' && *mode != '-' && *mode != '=') {
684 switch (*mode) {
685 case 'u': subset |= 04700; break; /* setuid, user perms */
686 case 'g': subset |= 02070; break; /* setgid, group perms */
687 case 'o': subset |= 00007; break; /* just other perms */
688 case 'a': subset |= 06777; break; /* all of the above */
689 default:
690 printf("chmod: file mode '%.*s' contains unrecognised"
691 " user/group/other specifier '%c'\n",
692 strcspn(modebegin, ","), modebegin, *mode);
693 return 0;
694 }
695 mode++;
696 }
697 if (!*mode || *mode == ',') {
698 printf("chmod: file mode '%.*s' is incomplete\n",
699 strcspn(modebegin, ","), modebegin);
700 return 0;
701 }
702 action = *mode++;
703 if (!*mode || *mode == ',') {
704 printf("chmod: file mode '%.*s' is incomplete\n",
705 strcspn(modebegin, ","), modebegin);
706 return 0;
707 }
708 perms = 0;
709 while (*mode && *mode != ',') {
710 switch (*mode) {
711 case 'r': perms |= 00444; break;
712 case 'w': perms |= 00222; break;
713 case 'x': perms |= 00111; break;
714 case 't': perms |= 01000; subset |= 01000; break;
715 case 's':
716 if ((subset & 06777) != 04700 &&
717 (subset & 06777) != 02070) {
718 printf("chmod: file mode '%.*s': set[ug]id bit should"
719 " be used with exactly one of u or g only\n",
720 strcspn(modebegin, ","), modebegin);
721 return 0;
722 }
723 perms |= 06000;
724 break;
725 default:
726 printf("chmod: file mode '%.*s' contains unrecognised"
727 " permission specifier '%c'\n",
728 strcspn(modebegin, ","), modebegin, *mode);
729 return 0;
730 }
731 mode++;
732 }
733 if (!(subset & 06777) && (perms &~ subset)) {
734 printf("chmod: file mode '%.*s' contains no user/group/other"
735 " specifier and permissions other than 't' \n",
736 strcspn(modebegin, ","), modebegin, *mode);
737 return 0;
738 }
739 perms &= subset;
740 switch (action) {
741 case '+':
742 attrs_clr |= perms;
743 attrs_xor |= perms;
744 break;
745 case '-':
746 attrs_clr |= perms;
747 attrs_xor &= ~perms;
748 break;
749 case '=':
750 attrs_clr |= subset;
751 attrs_xor |= perms;
752 break;
753 }
754 if (*mode) mode++; /* eat comma */
755 }
756 }
757
758 fname = canonify(cmd->words[2]);
759 if (!fname) {
760 printf("%s: %s\n", fname, fxp_error());
761 return 0;
762 }
763
764 result = fxp_stat(fname, &attrs);
765 if (!result || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) {
766 printf("get attrs for %s: %s\n", fname,
767 result ? "file permissions not provided" : fxp_error());
768 sfree(fname);
769 return 0;
770 }
771
772 attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS; /* perms _only_ */
773 oldperms = attrs.permissions & 07777;
774 attrs.permissions &= ~attrs_clr;
775 attrs.permissions ^= attrs_xor;
776 newperms = attrs.permissions & 07777;
777
778 result = fxp_setstat(fname, attrs);
779
780 if (!result) {
781 printf("set attrs for %s: %s\n", fname, fxp_error());
782 sfree(fname);
783 return 0;
784 }
785
786 printf("%s: %04o -> %04o\n", fname, oldperms, newperms);
787
788 sfree(fname);
789 return 0;
790}
791
792static int sftp_cmd_help(struct sftp_command *cmd);
793
794static struct sftp_cmd_lookup {
795 char *name;
796 /*
797 * For help purposes, there are two kinds of command:
798 *
799 * - primary commands, in which `longhelp' is non-NULL. In
800 * this case `shorthelp' is descriptive text, and `longhelp'
801 * is longer descriptive text intended to be printed after
802 * the command name.
803 *
804 * - alias commands, in which `longhelp' is NULL. In this case
805 * `shorthelp' is the name of a primary command, which
806 * contains the help that should double up for this command.
807 */
808 char *shorthelp;
809 char *longhelp;
810 int (*obey) (struct sftp_command *);
811} sftp_lookup[] = {
812 /*
813 * List of sftp commands. This is binary-searched so it MUST be
814 * in ASCII order.
815 */
816 {
817 "bye", "finish your SFTP session",
818 "\n"
819 " Terminates your SFTP session and quits the PSFTP program.\n",
820 sftp_cmd_quit
821 },
822 {
823 "cd", "change your remote working directory",
824 " [ <New working directory> ]\n"
825 " Change the remote working directory for your SFTP session.\n"
826 " If a new working directory is not supplied, you will be\n"
827 " returned to your home directory.\n",
828 sftp_cmd_cd
829 },
830 {
831 "chmod", "change file permissions and modes",
832 " ( <octal-digits> | <modifiers> ) <filename>\n"
833 " Change the file permissions on a file or directory.\n"
834 " <octal-digits> can be any octal Unix permission specifier.\n"
835 " Alternatively, <modifiers> can include:\n"
836 " u+r make file readable by owning user\n"
837 " u+w make file writable by owning user\n"
838 " u+x make file executable by owning user\n"
839 " u-r make file not readable by owning user\n"
840 " [also u-w, u-x]\n"
841 " g+r make file readable by members of owning group\n"
842 " [also g+w, g+x, g-r, g-w, g-x]\n"
843 " o+r make file readable by all other users\n"
844 " [also o+w, o+x, o-r, o-w, o-x]\n"
845 " a+r make file readable by absolutely everybody\n"
846 " [also a+w, a+x, a-r, a-w, a-x]\n"
847 " u+s enable the Unix set-user-ID bit\n"
848 " u-s disable the Unix set-user-ID bit\n"
849 " g+s enable the Unix set-group-ID bit\n"
850 " g-s disable the Unix set-group-ID bit\n"
851 " +t enable the Unix \"sticky bit\"\n"
852 " You can give more than one modifier for the same user (\"g-rwx\"), and\n"
853 " more than one user for the same modifier (\"ug+w\"). You can\n"
854 " use commas to separate different modifiers (\"u+rwx,g+s\").\n",
855 sftp_cmd_chmod
856 },
857 {
858 "del", "delete a file",
859 " <filename>\n"
860 " Delete a file.\n",
861 sftp_cmd_rm
862 },
863 {
864 "delete", "delete a file",
865 "\n"
866 " Delete a file.\n",
867 sftp_cmd_rm
868 },
869 {
870 "dir", "list contents of a remote directory",
871 " [ <directory-name> ]\n"
872 " List the contents of a specified directory on the server.\n"
873 " If <directory-name> is not given, the current working directory\n"
874 " will be listed.\n",
875 sftp_cmd_ls
876 },
877 {
878 "exit", "bye", NULL, sftp_cmd_quit
879 },
880 {
881 "get", "download a file from the server to your local machine",
882 " <filename> [ <local-filename> ]\n"
883 " Downloads a file on the server and stores it locally under\n"
884 " the same name, or under a different one if you supply the\n"
885 " argument <local-filename>.\n",
886 sftp_cmd_get
887 },
888 {
889 "help", "give help",
890 " [ <command> [ <command> ... ] ]\n"
891 " Give general help if no commands are specified.\n"
892 " If one or more commands are specified, give specific help on\n"
893 " those particular commands.\n",
894 sftp_cmd_help
895 },
896 {
897 "ls", "dir", NULL,
898 sftp_cmd_ls
899 },
900 {
901 "mkdir", "create a directory on the remote server",
902 " <directory-name>\n"
903 " Creates a directory with the given name on the server.\n",
904 sftp_cmd_mkdir
905 },
906 {
907 "mv", "move or rename a file on the remote server",
908 " <source-filename> <destination-filename>\n"
909 " Moves or renames the file <source-filename> on the server,\n"
910 " so that it is accessible under the name <destination-filename>.\n",
911 sftp_cmd_mv
912 },
913 {
914 "put", "upload a file from your local machine to the server",
915 " <filename> [ <remote-filename> ]\n"
916 " Uploads a file to the server and stores it there under\n"
917 " the same name, or under a different one if you supply the\n"
918 " argument <remote-filename>.\n",
919 sftp_cmd_put
920 },
921 {
922 "quit", "bye", NULL,
923 sftp_cmd_quit
924 },
925 {
926 "reget", "continue downloading a file",
927 " <filename> [ <local-filename> ]\n"
928 " Works exactly like the \"get\" command, but the local file\n"
929 " must already exist. The download will begin at the end of the\n"
930 " file. This is for resuming a download that was interrupted.\n",
931 sftp_cmd_reget
932 },
933 {
934 "ren", "mv", NULL,
935 sftp_cmd_mv
936 },
937 {
938 "rename", "mv", NULL,
939 sftp_cmd_mv
940 },
941 {
942 "reput", "continue uploading a file",
943 " <filename> [ <remote-filename> ]\n"
944 " Works exactly like the \"put\" command, but the remote file\n"
945 " must already exist. The upload will begin at the end of the\n"
946 " file. This is for resuming an upload that was interrupted.\n",
947 sftp_cmd_reput
948 },
949 {
950 "rm", "del", NULL,
951 sftp_cmd_rm
952 },
953 {
954 "rmdir", "remove a directory on the remote server",
955 " <directory-name>\n"
956 " Removes the directory with the given name on the server.\n"
957 " The directory will not be removed unless it is empty.\n",
958 sftp_cmd_rmdir
959 }
960};
961
962const struct sftp_cmd_lookup *lookup_command(char *name)
963{
964 int i, j, k, cmp;
965
966 i = -1;
967 j = sizeof(sftp_lookup) / sizeof(*sftp_lookup);
968 while (j - i > 1) {
969 k = (j + i) / 2;
970 cmp = strcmp(name, sftp_lookup[k].name);
971 if (cmp < 0)
972 j = k;
973 else if (cmp > 0)
974 i = k;
975 else {
976 return &sftp_lookup[k];
977 }
978 }
979 return NULL;
980}
981
982static int sftp_cmd_help(struct sftp_command *cmd)
983{
984 int i;
985 if (cmd->nwords == 1) {
986 /*
987 * Give short help on each command.
988 */
989 int maxlen;
990 maxlen = 0;
991 for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) {
992 int len = strlen(sftp_lookup[i].name);
993 if (maxlen < len)
994 maxlen = len;
995 }
996 for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) {
997 const struct sftp_cmd_lookup *lookup;
998 lookup = &sftp_lookup[i];
999 printf("%-*s", maxlen+2, lookup->name);
1000 if (lookup->longhelp == NULL)
1001 lookup = lookup_command(lookup->shorthelp);
1002 printf("%s\n", lookup->shorthelp);
1003 }
1004 } else {
1005 /*
1006 * Give long help on specific commands.
1007 */
1008 for (i = 1; i < cmd->nwords; i++) {
1009 const struct sftp_cmd_lookup *lookup;
1010 lookup = lookup_command(cmd->words[i]);
1011 if (!lookup) {
1012 printf("help: %s: command not found\n", cmd->words[i]);
1013 } else {
1014 printf("%s", lookup->name);
1015 if (lookup->longhelp == NULL)
1016 lookup = lookup_command(lookup->shorthelp);
1017 printf("%s", lookup->longhelp);
1018 }
1019 }
1020 }
1021 return 0;
1022}
1023
1024/* ----------------------------------------------------------------------
1025 * Command line reading and parsing.
1026 */
1027struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags)
1028{
1029 char *line;
1030 int linelen, linesize;
1031 struct sftp_command *cmd;
1032 char *p, *q, *r;
1033 int quoting;
1034
1035 if ((mode == 0) || (modeflags & 1)) {
1036 printf("psftp> ");
1037 }
1038 fflush(stdout);
1039
1040 cmd = smalloc(sizeof(struct sftp_command));
1041 cmd->words = NULL;
1042 cmd->nwords = 0;
1043 cmd->wordssize = 0;
1044
1045 line = NULL;
1046 linesize = linelen = 0;
1047 while (1) {
1048 int len;
1049 char *ret;
1050
1051 linesize += 512;
1052 line = srealloc(line, linesize);
1053 ret = fgets(line + linelen, linesize - linelen, fp);
1054 if (modeflags & 1) {
1055 printf("%s", ret);
1056 }
1057
1058 if (!ret || (linelen == 0 && line[0] == '\0')) {
1059 cmd->obey = sftp_cmd_quit;
1060 printf("quit\n");
1061 return cmd; /* eof */
1062 }
1063 len = linelen + strlen(line + linelen);
1064 linelen += len;
1065 if (line[linelen - 1] == '\n') {
1066 linelen--;
1067 line[linelen] = '\0';
1068 break;
1069 }
1070 }
1071
1072 /*
1073 * Parse the command line into words. The syntax is:
1074 * - double quotes are removed, but cause spaces within to be
1075 * treated as non-separating.
1076 * - a double-doublequote pair is a literal double quote, inside
1077 * _or_ outside quotes. Like this:
1078 *
1079 * firstword "second word" "this has ""quotes"" in" sodoes""this""
1080 *
1081 * becomes
1082 *
1083 * >firstword<
1084 * >second word<
1085 * >this has "quotes" in<
1086 * >sodoes"this"<
1087 */
1088 p = line;
1089 while (*p) {
1090 /* skip whitespace */
1091 while (*p && (*p == ' ' || *p == '\t'))
1092 p++;
1093 /* mark start of word */
1094 q = r = p; /* q sits at start, r writes word */
1095 quoting = 0;
1096 while (*p) {
1097 if (!quoting && (*p == ' ' || *p == '\t'))
1098 break; /* reached end of word */
1099 else if (*p == '"' && p[1] == '"')
1100 p += 2, *r++ = '"'; /* a literal quote */
1101 else if (*p == '"')
1102 p++, quoting = !quoting;
1103 else
1104 *r++ = *p++;
1105 }
1106 if (*p)
1107 p++; /* skip over the whitespace */
1108 *r = '\0';
1109 if (cmd->nwords >= cmd->wordssize) {
1110 cmd->wordssize = cmd->nwords + 16;
1111 cmd->words =
1112 srealloc(cmd->words, cmd->wordssize * sizeof(char *));
1113 }
1114 cmd->words[cmd->nwords++] = q;
1115 }
1116
1117 /*
1118 * Now parse the first word and assign a function.
1119 */
1120
1121 if (cmd->nwords == 0)
1122 cmd->obey = sftp_cmd_null;
1123 else {
1124 const struct sftp_cmd_lookup *lookup;
1125 lookup = lookup_command(cmd->words[0]);
1126 if (!lookup)
1127 cmd->obey = sftp_cmd_unknown;
1128 else
1129 cmd->obey = lookup->obey;
1130 }
1131
1132 return cmd;
1133}
1134
1135void do_sftp(int mode, int modeflags, char *batchfile)
1136{
1137 FILE *fp;
1138
1139 /*
1140 * Do protocol initialisation.
1141 */
1142 if (!fxp_init()) {
1143 fprintf(stderr,
1144 "Fatal: unable to initialise SFTP: %s\n", fxp_error());
1145 return;
1146 }
1147
1148 /*
1149 * Find out where our home directory is.
1150 */
1151 homedir = fxp_realpath(".");
1152 if (!homedir) {
1153 fprintf(stderr,
1154 "Warning: failed to resolve home directory: %s\n",
1155 fxp_error());
1156 homedir = dupstr(".");
1157 } else {
1158 printf("Remote working directory is %s\n", homedir);
1159 }
1160 pwd = dupstr(homedir);
1161
1162 /*
1163 * Batch mode?
1164 */
1165 if (mode == 0) {
1166
1167 /* ------------------------------------------------------------------
1168 * Now we're ready to do Real Stuff.
1169 */
1170 while (1) {
1171 struct sftp_command *cmd;
1172 cmd = sftp_getcmd(stdin, 0, 0);
1173 if (!cmd)
1174 break;
1175 if (cmd->obey(cmd) < 0)
1176 break;
1177 }
1178 } else {
1179 fp = fopen(batchfile, "r");
1180 if (!fp) {
1181 printf("Fatal: unable to open %s\n", batchfile);
1182 return;
1183 }
1184 while (1) {
1185 struct sftp_command *cmd;
1186 cmd = sftp_getcmd(fp, mode, modeflags);
1187 if (!cmd)
1188 break;
1189 if (cmd->obey(cmd) < 0)
1190 break;
1191 if (fxp_error() != NULL) {
1192 if (!(modeflags & 2))
1193 break;
1194 }
1195 }
1196 fclose(fp);
1197
1198 }
1199}
1200
1201/* ----------------------------------------------------------------------
1202 * Dirty bits: integration with PuTTY.
1203 */
1204
1205static int verbose = 0;
1206
1207void verify_ssh_host_key(char *host, int port, char *keytype,
1208 char *keystr, char *fingerprint)
1209{
1210 int ret;
1211 HANDLE hin;
1212 DWORD savemode, i;
1213
1214 static const char absentmsg[] =
1215 "The server's host key is not cached in the registry. You\n"
1216 "have no guarantee that the server is the computer you\n"
1217 "think it is.\n"
1218 "The server's key fingerprint is:\n"
1219 "%s\n"
1220 "If you trust this host, enter \"y\" to add the key to\n"
1221 "PuTTY's cache and carry on connecting.\n"
1222 "If you want to carry on connecting just once, without\n"
1223 "adding the key to the cache, enter \"n\".\n"
1224 "If you do not trust this host, press Return to abandon the\n"
1225 "connection.\n"
1226 "Store key in cache? (y/n) ";
1227
1228 static const char wrongmsg[] =
1229 "WARNING - POTENTIAL SECURITY BREACH!\n"
1230 "The server's host key does not match the one PuTTY has\n"
1231 "cached in the registry. This means that either the\n"
1232 "server administrator has changed the host key, or you\n"
1233 "have actually connected to another computer pretending\n"
1234 "to be the server.\n"
1235 "The new key fingerprint is:\n"
1236 "%s\n"
1237 "If you were expecting this change and trust the new key,\n"
1238 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
1239 "If you want to carry on connecting but without updating\n"
1240 "the cache, enter \"n\".\n"
1241 "If you want to abandon the connection completely, press\n"
1242 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
1243 "safe choice.\n"
1244 "Update cached key? (y/n, Return cancels connection) ";
1245
1246 static const char abandoned[] = "Connection abandoned.\n";
1247
1248 char line[32];
1249
1250 /*
1251 * Verify the key against the registry.
1252 */
1253 ret = verify_host_key(host, port, keytype, keystr);
1254
1255 if (ret == 0) /* success - key matched OK */
1256 return;
1257
1258 if (ret == 2) { /* key was different */
1259 fprintf(stderr, wrongmsg, fingerprint);
1260 fflush(stderr);
1261 }
1262 if (ret == 1) { /* key was absent */
1263 fprintf(stderr, absentmsg, fingerprint);
1264 fflush(stderr);
1265 }
1266
1267 hin = GetStdHandle(STD_INPUT_HANDLE);
1268 GetConsoleMode(hin, &savemode);
1269 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
1270 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
1271 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
1272 SetConsoleMode(hin, savemode);
1273
1274 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
1275 if (line[0] == 'y' || line[0] == 'Y')
1276 store_host_key(host, port, keytype, keystr);
1277 } else {
1278 fprintf(stderr, abandoned);
1279 exit(0);
1280 }
1281}
1282
1283/*
1284 * Ask whether the selected cipher is acceptable (since it was
1285 * below the configured 'warn' threshold).
1286 * cs: 0 = both ways, 1 = client->server, 2 = server->client
1287 */
1288void askcipher(char *ciphername, int cs)
1289{
1290 HANDLE hin;
1291 DWORD savemode, i;
1292
1293 static const char msg[] =
1294 "The first %scipher supported by the server is\n"
1295 "%s, which is below the configured warning threshold.\n"
1296 "Continue with connection? (y/n) ";
1297 static const char abandoned[] = "Connection abandoned.\n";
1298
1299 char line[32];
1300
1301 fprintf(stderr, msg,
1302 (cs == 0) ? "" :
1303 (cs == 1) ? "client-to-server " :
1304 "server-to-client ",
1305 ciphername);
1306 fflush(stderr);
1307
1308 hin = GetStdHandle(STD_INPUT_HANDLE);
1309 GetConsoleMode(hin, &savemode);
1310 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
1311 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
1312 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
1313 SetConsoleMode(hin, savemode);
1314
1315 if (line[0] == 'y' || line[0] == 'Y') {
1316 return;
1317 } else {
1318 fprintf(stderr, abandoned);
1319 exit(0);
1320 }
1321}
1322
1323/*
1324 * Print an error message and perform a fatal exit.
1325 */
1326void fatalbox(char *fmt, ...)
1327{
1328 char str[0x100]; /* Make the size big enough */
1329 va_list ap;
1330 va_start(ap, fmt);
1331 strcpy(str, "Fatal:");
1332 vsprintf(str + strlen(str), fmt, ap);
1333 va_end(ap);
1334 strcat(str, "\n");
1335 fprintf(stderr, str);
1336
1337 exit(1);
1338}
1339void connection_fatal(char *fmt, ...)
1340{
1341 char str[0x100]; /* Make the size big enough */
1342 va_list ap;
1343 va_start(ap, fmt);
1344 strcpy(str, "Fatal:");
1345 vsprintf(str + strlen(str), fmt, ap);
1346 va_end(ap);
1347 strcat(str, "\n");
1348 fprintf(stderr, str);
1349
1350 exit(1);
1351}
1352
1353void logevent(char *string)
1354{
1355}
1356
1357void ldisc_send(char *buf, int len)
1358{
1359 /*
1360 * This is only here because of the calls to ldisc_send(NULL,
1361 * 0) in ssh.c. Nothing in PSFTP actually needs to use the
1362 * ldisc as an ldisc. So if we get called with any real data, I
1363 * want to know about it.
1364 */
1365 assert(len == 0);
1366}
1367
1368/*
1369 * Be told what socket we're supposed to be using.
1370 */
1371static SOCKET sftp_ssh_socket;
1372char *do_select(SOCKET skt, int startup)
1373{
1374 if (startup)
1375 sftp_ssh_socket = skt;
1376 else
1377 sftp_ssh_socket = INVALID_SOCKET;
1378 return NULL;
1379}
1380extern int select_result(WPARAM, LPARAM);
1381
1382/*
1383 * Receive a block of data from the SSH link. Block until all data
1384 * is available.
1385 *
1386 * To do this, we repeatedly call the SSH protocol module, with our
1387 * own trap in from_backend() to catch the data that comes back. We
1388 * do this until we have enough data.
1389 */
1390
1391static unsigned char *outptr; /* where to put the data */
1392static unsigned outlen; /* how much data required */
1393static unsigned char *pending = NULL; /* any spare data */
1394static unsigned pendlen = 0, pendsize = 0; /* length and phys. size of buffer */
1395int from_backend(int is_stderr, char *data, int datalen)
1396{
1397 unsigned char *p = (unsigned char *) data;
1398 unsigned len = (unsigned) datalen;
1399
1400 /*
1401 * stderr data is just spouted to local stderr and otherwise
1402 * ignored.
1403 */
1404 if (is_stderr) {
1405 fwrite(data, 1, len, stderr);
1406 return 0;
1407 }
1408
1409 /*
1410 * If this is before the real session begins, just return.
1411 */
1412 if (!outptr)
1413 return 0;
1414
1415 if (outlen > 0) {
1416 unsigned used = outlen;
1417 if (used > len)
1418 used = len;
1419 memcpy(outptr, p, used);
1420 outptr += used;
1421 outlen -= used;
1422 p += used;
1423 len -= used;
1424 }
1425
1426 if (len > 0) {
1427 if (pendsize < pendlen + len) {
1428 pendsize = pendlen + len + 4096;
1429 pending = (pending ? srealloc(pending, pendsize) :
1430 smalloc(pendsize));
1431 if (!pending)
1432 fatalbox("Out of memory");
1433 }
1434 memcpy(pending + pendlen, p, len);
1435 pendlen += len;
1436 }
1437
1438 return 0;
1439}
1440int sftp_recvdata(char *buf, int len)
1441{
1442 outptr = (unsigned char *) buf;
1443 outlen = len;
1444
1445 /*
1446 * See if the pending-input block contains some of what we
1447 * need.
1448 */
1449 if (pendlen > 0) {
1450 unsigned pendused = pendlen;
1451 if (pendused > outlen)
1452 pendused = outlen;
1453 memcpy(outptr, pending, pendused);
1454 memmove(pending, pending + pendused, pendlen - pendused);
1455 outptr += pendused;
1456 outlen -= pendused;
1457 pendlen -= pendused;
1458 if (pendlen == 0) {
1459 pendsize = 0;
1460 sfree(pending);
1461 pending = NULL;
1462 }
1463 if (outlen == 0)
1464 return 1;
1465 }
1466
1467 while (outlen > 0) {
1468 fd_set readfds;
1469
1470 FD_ZERO(&readfds);
1471 FD_SET(sftp_ssh_socket, &readfds);
1472 if (select(1, &readfds, NULL, NULL, NULL) < 0)
1473 return 0; /* doom */
1474 select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
1475 }
1476
1477 return 1;
1478}
1479int sftp_senddata(char *buf, int len)
1480{
1481 back->send((unsigned char *) buf, len);
1482 return 1;
1483}
1484
1485/*
1486 * Loop through the ssh connection and authentication process.
1487 */
1488static void ssh_sftp_init(void)
1489{
1490 if (sftp_ssh_socket == INVALID_SOCKET)
1491 return;
1492 while (!back->sendok()) {
1493 fd_set readfds;
1494 FD_ZERO(&readfds);
1495 FD_SET(sftp_ssh_socket, &readfds);
1496 if (select(1, &readfds, NULL, NULL, NULL) < 0)
1497 return; /* doom */
1498 select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
1499 }
1500}
1501
1502static char *password = NULL;
1503static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
1504{
1505 HANDLE hin, hout;
1506 DWORD savemode, newmode, i;
1507
1508 if (password) {
1509 static int tried_once = 0;
1510
1511 if (tried_once) {
1512 return 0;
1513 } else {
1514 strncpy(str, password, maxlen);
1515 str[maxlen - 1] = '\0';
1516 tried_once = 1;
1517 return 1;
1518 }
1519 }
1520
1521 hin = GetStdHandle(STD_INPUT_HANDLE);
1522 hout = GetStdHandle(STD_OUTPUT_HANDLE);
1523 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
1524 fprintf(stderr, "Cannot get standard input/output handles\n");
1525 exit(1);
1526 }
1527
1528 GetConsoleMode(hin, &savemode);
1529 newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
1530 if (is_pw)
1531 newmode &= ~ENABLE_ECHO_INPUT;
1532 else
1533 newmode |= ENABLE_ECHO_INPUT;
1534 SetConsoleMode(hin, newmode);
1535
1536 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
1537 ReadFile(hin, str, maxlen - 1, &i, NULL);
1538
1539 SetConsoleMode(hin, savemode);
1540
1541 if ((int) i > maxlen)
1542 i = maxlen - 1;
1543 else
1544 i = i - 2;
1545 str[i] = '\0';
1546
1547 if (is_pw)
1548 WriteFile(hout, "\r\n", 2, &i, NULL);
1549
1550 return 1;
1551}
1552
1553/*
1554 * Initialize the Win$ock driver.
1555 */
1556static void init_winsock(void)
1557{
1558 WORD winsock_ver;
1559 WSADATA wsadata;
1560
1561 winsock_ver = MAKEWORD(1, 1);
1562 if (WSAStartup(winsock_ver, &wsadata)) {
1563 fprintf(stderr, "Unable to initialise WinSock");
1564 exit(1);
1565 }
1566 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
1567 fprintf(stderr, "WinSock version is incompatible with 1.1");
1568 exit(1);
1569 }
1570}
1571
1572/*
1573 * Short description of parameters.
1574 */
1575static void usage(void)
1576{
1577 printf("PuTTY Secure File Transfer (SFTP) client\n");
1578 printf("%s\n", ver);
1579 printf("Usage: psftp [options] user@host\n");
1580 printf("Options:\n");
1581 printf(" -b file use specified batchfile\n");
1582 printf(" -bc output batchfile commands\n");
1583 printf(" -be don't stop batchfile processing if errors\n");
1584 printf(" -v show verbose messages\n");
1585 printf(" -P port connect to specified port\n");
1586 printf(" -pw passw login with specified password\n");
1587 exit(1);
1588}
1589
1590/*
1591 * Main program. Parse arguments etc.
1592 */
1593int main(int argc, char *argv[])
1594{
1595 int i;
1596 int portnumber = 0;
1597 char *user, *host, *userhost, *realhost;
1598 char *err;
1599 int mode = 0;
1600 int modeflags = 0;
1601 char *batchfile = NULL;
1602
1603 flags = FLAG_STDERR | FLAG_INTERACTIVE;
1604 ssh_get_line = &get_line;
1605 init_winsock();
1606 sk_init();
1607
1608 userhost = user = NULL;
1609
1610 for (i = 1; i < argc; i++) {
1611 if (argv[i][0] != '-') {
1612 if (userhost)
1613 usage();
1614 else
1615 userhost = dupstr(argv[i]);
1616 } else if (strcmp(argv[i], "-v") == 0) {
1617 verbose = 1, flags |= FLAG_VERBOSE;
1618 } else if (strcmp(argv[i], "-h") == 0 ||
1619 strcmp(argv[i], "-?") == 0) {
1620 usage();
1621 } else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) {
1622 user = argv[++i];
1623 } else if (strcmp(argv[i], "-P") == 0 && i + 1 < argc) {
1624 portnumber = atoi(argv[++i]);
1625 } else if (strcmp(argv[i], "-pw") == 0 && i + 1 < argc) {
1626 password = argv[++i];
1627 } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) {
1628 mode = 1;
1629 batchfile = argv[++i];
1630 } else if (strcmp(argv[i], "-bc") == 0 && i + 1 < argc) {
1631 modeflags = modeflags | 1;
1632 } else if (strcmp(argv[i], "-be") == 0 && i + 1 < argc) {
1633 modeflags = modeflags | 2;
1634 } else if (strcmp(argv[i], "--") == 0) {
1635 i++;
1636 break;
1637 } else {
1638 usage();
1639 }
1640 }
1641 argc -= i;
1642 argv += i;
1643 back = NULL;
1644
1645 if (argc > 0 || !userhost)
1646 usage();
1647
1648 /* Separate host and username */
1649 host = userhost;
1650 host = strrchr(host, '@');
1651 if (host == NULL) {
1652 host = userhost;
1653 } else {
1654 *host++ = '\0';
1655 if (user) {
1656 printf("psftp: multiple usernames specified; using \"%s\"\n",
1657 user);
1658 } else
1659 user = userhost;
1660 }
1661
1662 /* Try to load settings for this host */
1663 do_defaults(host, &cfg);
1664 if (cfg.host[0] == '\0') {
1665 /* No settings for this host; use defaults */
1666 do_defaults(NULL, &cfg);
1667 strncpy(cfg.host, host, sizeof(cfg.host) - 1);
1668 cfg.host[sizeof(cfg.host) - 1] = '\0';
1669 cfg.port = 22;
1670 }
1671
1672 /* Set username */
1673 if (user != NULL && user[0] != '\0') {
1674 strncpy(cfg.username, user, sizeof(cfg.username) - 1);
1675 cfg.username[sizeof(cfg.username) - 1] = '\0';
1676 }
1677 if (!cfg.username[0]) {
1678 printf("login as: ");
1679 if (!fgets(cfg.username, sizeof(cfg.username), stdin)) {
1680 fprintf(stderr, "psftp: aborting\n");
1681 exit(1);
1682 } else {
1683 int len = strlen(cfg.username);
1684 if (cfg.username[len - 1] == '\n')
1685 cfg.username[len - 1] = '\0';
1686 }
1687 }
1688
1689 if (cfg.protocol != PROT_SSH)
1690 cfg.port = 22;
1691
1692 if (portnumber)
1693 cfg.port = portnumber;
1694
1695 /* SFTP uses SSH2 by default always */
1696 cfg.sshprot = 2;
1697
1698 /* Set up subsystem name. */
1699 strcpy(cfg.remote_cmd, "sftp");
1700 cfg.ssh_subsys = TRUE;
1701 cfg.nopty = TRUE;
1702
1703 /*
1704 * Set up fallback option, for SSH1 servers or servers with the
1705 * sftp subsystem not enabled but the server binary installed
1706 * in the usual place. We only support fallback on Unix
1707 * systems, and we use a kludgy piece of shellery which should
1708 * try to find sftp-server in various places (the obvious
1709 * systemwide spots /usr/lib and /usr/local/lib, and then the
1710 * user's PATH) and finally give up.
1711 *
1712 * test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server
1713 * test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server
1714 * exec sftp-server
1715 *
1716 * the idea being that this will attempt to use either of the
1717 * obvious pathnames and then give up, and when it does give up
1718 * it will print the preferred pathname in the error messages.
1719 */
1720 cfg.remote_cmd_ptr2 =
1721 "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n"
1722 "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n"
1723 "exec sftp-server";
1724 cfg.ssh_subsys2 = FALSE;
1725
1726 back = &ssh_backend;
1727
1728 err = back->init(cfg.host, cfg.port, &realhost);
1729 if (err != NULL) {
1730 fprintf(stderr, "ssh_init: %s", err);
1731 return 1;
1732 }
1733 ssh_sftp_init();
1734 if (verbose && realhost != NULL)
1735 printf("Connected to %s\n", realhost);
1736
1737 do_sftp(mode, modeflags, batchfile);
1738
1739 if (back != NULL && back->socket() != NULL) {
1740 char ch;
1741 back->special(TS_EOF);
1742 sftp_recvdata(&ch, 1);
1743 }
1744 WSACleanup();
1745 random_save_seed();
1746
1747 return 0;
1748}