Modify the FIXME into a calculated risk. I don't like this, but I
[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
4c7f0d61 27/* ----------------------------------------------------------------------
4c7f0d61 28 * sftp client state.
29 */
30
31char *pwd, *homedir;
32
33/* ----------------------------------------------------------------------
34 * Higher-level helper functions used in commands.
35 */
36
37/*
f9e162aa 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).
4c7f0d61 41 */
32874aea 42char *canonify(char *name)
43{
f9e162aa 44 char *fullname, *canonname;
4a8fc3c4 45
f9e162aa 46 if (name[0] == '/') {
47 fullname = dupstr(name);
48 } else {
4a8fc3c4 49 char *slash;
32874aea 50 if (pwd[strlen(pwd) - 1] == '/')
4a8fc3c4 51 slash = "";
52 else
53 slash = "/";
54 fullname = dupcat(pwd, slash, name, NULL);
f9e162aa 55 }
4a8fc3c4 56
57 canonname = fxp_realpath(fullname);
58
f9e162aa 59 if (canonname) {
60 sfree(fullname);
61 return canonname;
50d7e054 62 } else {
32874aea 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;
50d7e054 130 }
4c7f0d61 131}
132
133/* ----------------------------------------------------------------------
134 * Actual sftp commands.
135 */
136struct sftp_command {
137 char **words;
138 int nwords, wordssize;
32874aea 139 int (*obey) (struct sftp_command *); /* returns <0 to quit */
4c7f0d61 140};
141
32874aea 142int sftp_cmd_null(struct sftp_command *cmd)
143{
4c7f0d61 144 return 0;
145}
146
32874aea 147int sftp_cmd_unknown(struct sftp_command *cmd)
148{
4c7f0d61 149 printf("psftp: unknown command \"%s\"\n", cmd->words[0]);
150 return 0;
151}
152
32874aea 153int sftp_cmd_quit(struct sftp_command *cmd)
154{
4c7f0d61 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 */
32874aea 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;
4c7f0d61 166 return strcmp(a->filename, b->filename);
167}
32874aea 168int sftp_cmd_ls(struct sftp_command *cmd)
169{
4c7f0d61 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;
32874aea 213 ournames =
214 srealloc(ournames, namesize * sizeof(*ournames));
4c7f0d61 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 */
32874aea 247int sftp_cmd_cd(struct sftp_command *cmd)
248{
4c7f0d61 249 struct fxp_handle *dirh;
250 char *dir;
251
252 if (cmd->nwords < 2)
f9e162aa 253 dir = dupstr(homedir);
4c7f0d61 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/*
d92624dc 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.
4c7f0d61 283 */
d92624dc 284int sftp_general_get(struct sftp_command *cmd, int restart)
32874aea 285{
4c7f0d61 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 }
d92624dc 309
310 if (restart) {
311 fp = fopen(outfname, "rb+");
312 } else {
313 fp = fopen(outfname, "wb");
314 }
315
4c7f0d61 316 if (!fp) {
317 printf("local: unable to open %s\n", outfname);
32874aea 318 fxp_close(fh);
4c7f0d61 319 sfree(fname);
320 return 0;
321 }
322
d92624dc 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 }
4c7f0d61 332
d92624dc 333 printf("remote:%s => local:%s\n", fname, outfname);
4c7f0d61 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));
32874aea 345 if ((len == -1 && fxp_error_type() == SSH_FX_EOF) || len == 0)
4c7f0d61 346 break;
347 if (len == -1) {
348 printf("error while reading: %s\n", fxp_error());
349 break;
350 }
32874aea 351
4c7f0d61 352 wpos = 0;
353 while (wpos < len) {
32874aea 354 wlen = fwrite(buffer, 1, len - wpos, fp);
4c7f0d61 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}
d92624dc 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}
4c7f0d61 380
381/*
d92624dc 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.
4c7f0d61 386 */
d92624dc 387int sftp_general_put(struct sftp_command *cmd, int restart)
32874aea 388{
4c7f0d61 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);
f9e162aa 402 if (!outfname) {
4c7f0d61 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);
4c7f0d61 410 sfree(outfname);
411 return 0;
412 }
d92624dc 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 }
4c7f0d61 420 if (!fh) {
421 printf("%s: %s\n", outfname, fxp_error());
422 sfree(outfname);
423 return 0;
424 }
425
d92624dc 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 }
4c7f0d61 452
d92624dc 453 printf("local:%s => remote:%s\n", fname, outfname);
4c7f0d61 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
f9e162aa 463 len = fread(buffer, 1, sizeof(buffer), fp);
4c7f0d61 464 if (len == -1) {
465 printf("error while reading local file\n");
466 break;
467 } else if (len == 0) {
468 break;
469 }
f9e162aa 470 if (!fxp_write(fh, buffer, offset, len)) {
4c7f0d61 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}
d92624dc 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}
4c7f0d61 491
9954aaa3 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
d92624dc 516 sfree(dir);
517 return 0;
9954aaa3 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
d92624dc 544 sfree(dir);
545 return 0;
9954aaa3 546}
547
548int sftp_cmd_rm(struct sftp_command *cmd)
549{
550 char *fname;
551 int result;
552
9954aaa3 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
d92624dc 564 result = fxp_remove(fname);
9954aaa3 565 if (!result) {
566 printf("rm %s: %s\n", fname, fxp_error());
567 sfree(fname);
568 return 0;
569 }
570
d92624dc 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");
9954aaa3 583 return 0;
d92624dc 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 }
9954aaa3 596
d92624dc 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;
9954aaa3 639}
640
d92624dc 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}
9954aaa3 791
4c7f0d61 792static struct sftp_cmd_lookup {
793 char *name;
32874aea 794 int (*obey) (struct sftp_command *);
4c7f0d61 795} sftp_lookup[] = {
796 /*
797 * List of sftp commands. This is binary-searched so it MUST be
798 * in ASCII order.
799 */
32874aea 800 {
801 "bye", sftp_cmd_quit}, {
802 "cd", sftp_cmd_cd}, {
d92624dc 803 "chmod", sftp_cmd_chmod}, {
804 "del", sftp_cmd_rm}, {
805 "delete", sftp_cmd_rm}, {
32874aea 806 "dir", sftp_cmd_ls}, {
807 "exit", sftp_cmd_quit}, {
808 "get", sftp_cmd_get}, {
809 "ls", sftp_cmd_ls}, {
9954aaa3 810 "mkdir", sftp_cmd_mkdir}, {
d92624dc 811 "mv", sftp_cmd_mv}, {
32874aea 812 "put", sftp_cmd_put}, {
d92624dc 813 "quit", sftp_cmd_quit}, {
814 "reget", sftp_cmd_reget}, {
815 "ren", sftp_cmd_mv}, {
816 "rename", sftp_cmd_mv}, {
817 "reput", sftp_cmd_reput}, {
818 "rm", sftp_cmd_rm}, {
819 "rmdir", sftp_cmd_rmdir},};
4c7f0d61 820
821/* ----------------------------------------------------------------------
822 * Command line reading and parsing.
823 */
9954aaa3 824struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags)
32874aea 825{
4c7f0d61 826 char *line;
827 int linelen, linesize;
828 struct sftp_command *cmd;
829 char *p, *q, *r;
830 int quoting;
831
9954aaa3 832 if ((mode == 0) || (modeflags & 1)) {
833 printf("psftp> ");
834 }
4c7f0d61 835 fflush(stdout);
836
837 cmd = smalloc(sizeof(struct sftp_command));
838 cmd->words = NULL;
839 cmd->nwords = 0;
840 cmd->wordssize = 0;
841
842 line = NULL;
843 linesize = linelen = 0;
844 while (1) {
845 int len;
846 char *ret;
847
848 linesize += 512;
849 line = srealloc(line, linesize);
9954aaa3 850 ret = fgets(line + linelen, linesize - linelen, fp);
851 if (modeflags & 1) {
852 printf("%s", ret);
853 }
4c7f0d61 854
855 if (!ret || (linelen == 0 && line[0] == '\0')) {
856 cmd->obey = sftp_cmd_quit;
857 printf("quit\n");
858 return cmd; /* eof */
859 }
32874aea 860 len = linelen + strlen(line + linelen);
4c7f0d61 861 linelen += len;
32874aea 862 if (line[linelen - 1] == '\n') {
4c7f0d61 863 linelen--;
864 line[linelen] = '\0';
865 break;
866 }
867 }
868
869 /*
870 * Parse the command line into words. The syntax is:
871 * - double quotes are removed, but cause spaces within to be
872 * treated as non-separating.
873 * - a double-doublequote pair is a literal double quote, inside
874 * _or_ outside quotes. Like this:
875 *
876 * firstword "second word" "this has ""quotes"" in" sodoes""this""
877 *
878 * becomes
879 *
880 * >firstword<
881 * >second word<
882 * >this has "quotes" in<
883 * >sodoes"this"<
884 */
885 p = line;
886 while (*p) {
887 /* skip whitespace */
32874aea 888 while (*p && (*p == ' ' || *p == '\t'))
889 p++;
4c7f0d61 890 /* mark start of word */
891 q = r = p; /* q sits at start, r writes word */
892 quoting = 0;
893 while (*p) {
894 if (!quoting && (*p == ' ' || *p == '\t'))
895 break; /* reached end of word */
896 else if (*p == '"' && p[1] == '"')
32874aea 897 p += 2, *r++ = '"'; /* a literal quote */
4c7f0d61 898 else if (*p == '"')
899 p++, quoting = !quoting;
900 else
901 *r++ = *p++;
902 }
32874aea 903 if (*p)
904 p++; /* skip over the whitespace */
4c7f0d61 905 *r = '\0';
906 if (cmd->nwords >= cmd->wordssize) {
907 cmd->wordssize = cmd->nwords + 16;
32874aea 908 cmd->words =
909 srealloc(cmd->words, cmd->wordssize * sizeof(char *));
4c7f0d61 910 }
911 cmd->words[cmd->nwords++] = q;
912 }
913
914 /*
915 * Now parse the first word and assign a function.
916 */
917
918 if (cmd->nwords == 0)
919 cmd->obey = sftp_cmd_null;
920 else {
921 int i, j, k, cmp;
922
923 cmd->obey = sftp_cmd_unknown;
924
925 i = -1;
926 j = sizeof(sftp_lookup) / sizeof(*sftp_lookup);
927 while (j - i > 1) {
928 k = (j + i) / 2;
929 cmp = strcmp(cmd->words[0], sftp_lookup[k].name);
930 if (cmp < 0)
931 j = k;
932 else if (cmp > 0)
933 i = k;
934 else {
935 cmd->obey = sftp_lookup[k].obey;
936 break;
937 }
938 }
939 }
940
941 return cmd;
942}
943
9954aaa3 944void do_sftp(int mode, int modeflags, char *batchfile)
32874aea 945{
9954aaa3 946 FILE *fp;
947
4c7f0d61 948 /*
949 * Do protocol initialisation.
950 */
951 if (!fxp_init()) {
952 fprintf(stderr,
32874aea 953 "Fatal: unable to initialise SFTP: %s\n", fxp_error());
0d694692 954 return;
4c7f0d61 955 }
956
957 /*
958 * Find out where our home directory is.
959 */
f9e162aa 960 homedir = fxp_realpath(".");
4c7f0d61 961 if (!homedir) {
962 fprintf(stderr,
963 "Warning: failed to resolve home directory: %s\n",
964 fxp_error());
965 homedir = dupstr(".");
966 } else {
967 printf("Remote working directory is %s\n", homedir);
968 }
969 pwd = dupstr(homedir);
970
9954aaa3 971 /*
972 * Batch mode?
4c7f0d61 973 */
9954aaa3 974 if (mode == 0) {
975
976 /* ------------------------------------------------------------------
977 * Now we're ready to do Real Stuff.
978 */
979 while (1) {
980 struct sftp_command *cmd;
981 cmd = sftp_getcmd(stdin, 0, 0);
982 if (!cmd)
983 break;
984 if (cmd->obey(cmd) < 0)
985 break;
986 }
987 } else {
988 fp = fopen(batchfile, "r");
989 if (!fp) {
990 printf("Fatal: unable to open %s\n", batchfile);
991 return;
992 }
993 while (1) {
994 struct sftp_command *cmd;
995 cmd = sftp_getcmd(fp, mode, modeflags);
996 if (!cmd)
997 break;
998 if (cmd->obey(cmd) < 0)
999 break;
1000 if (fxp_error() != NULL) {
1001 if (!(modeflags & 2))
1002 break;
1003 }
1004 }
1005 fclose(fp);
1006
4c7f0d61 1007 }
4a8fc3c4 1008}
4c7f0d61 1009
4a8fc3c4 1010/* ----------------------------------------------------------------------
1011 * Dirty bits: integration with PuTTY.
1012 */
1013
1014static int verbose = 0;
1015
1016void verify_ssh_host_key(char *host, int port, char *keytype,
32874aea 1017 char *keystr, char *fingerprint)
1018{
4a8fc3c4 1019 int ret;
d0718310 1020 HANDLE hin;
1021 DWORD savemode, i;
4a8fc3c4 1022
1023 static const char absentmsg[] =
32874aea 1024 "The server's host key is not cached in the registry. You\n"
1025 "have no guarantee that the server is the computer you\n"
1026 "think it is.\n"
1027 "The server's key fingerprint is:\n"
1028 "%s\n"
1029 "If you trust this host, enter \"y\" to add the key to\n"
1030 "PuTTY's cache and carry on connecting.\n"
d0718310 1031 "If you want to carry on connecting just once, without\n"
1032 "adding the key to the cache, enter \"n\".\n"
1033 "If you do not trust this host, press Return to abandon the\n"
1034 "connection.\n"
1035 "Store key in cache? (y/n) ";
4a8fc3c4 1036
1037 static const char wrongmsg[] =
32874aea 1038 "WARNING - POTENTIAL SECURITY BREACH!\n"
1039 "The server's host key does not match the one PuTTY has\n"
1040 "cached in the registry. This means that either the\n"
1041 "server administrator has changed the host key, or you\n"
1042 "have actually connected to another computer pretending\n"
1043 "to be the server.\n"
1044 "The new key fingerprint is:\n"
1045 "%s\n"
1046 "If you were expecting this change and trust the new key,\n"
d0718310 1047 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
32874aea 1048 "If you want to carry on connecting but without updating\n"
d0718310 1049 "the cache, enter \"n\".\n"
32874aea 1050 "If you want to abandon the connection completely, press\n"
1051 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
1052 "safe choice.\n"
1053 "Update cached key? (y/n, Return cancels connection) ";
4a8fc3c4 1054
1055 static const char abandoned[] = "Connection abandoned.\n";
1056
1057 char line[32];
1058
1059 /*
1060 * Verify the key against the registry.
1061 */
1062 ret = verify_host_key(host, port, keytype, keystr);
1063
32874aea 1064 if (ret == 0) /* success - key matched OK */
1065 return;
d0718310 1066
32874aea 1067 if (ret == 2) { /* key was different */
1068 fprintf(stderr, wrongmsg, fingerprint);
d0718310 1069 fflush(stderr);
4a8fc3c4 1070 }
32874aea 1071 if (ret == 1) { /* key was absent */
1072 fprintf(stderr, absentmsg, fingerprint);
d0718310 1073 fflush(stderr);
1074 }
1075
1076 hin = GetStdHandle(STD_INPUT_HANDLE);
1077 GetConsoleMode(hin, &savemode);
1078 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
1079 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
1080 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
1081 SetConsoleMode(hin, savemode);
1082
1083 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
1084 if (line[0] == 'y' || line[0] == 'Y')
32874aea 1085 store_host_key(host, port, keytype, keystr);
d0718310 1086 } else {
1087 fprintf(stderr, abandoned);
1088 exit(0);
4a8fc3c4 1089 }
1090}
1091
1092/*
ca20bfcf 1093 * Ask whether the selected cipher is acceptable (since it was
1094 * below the configured 'warn' threshold).
1095 * cs: 0 = both ways, 1 = client->server, 2 = server->client
1096 */
1097void askcipher(char *ciphername, int cs)
1098{
1099 HANDLE hin;
1100 DWORD savemode, i;
1101
1102 static const char msg[] =
1103 "The first %scipher supported by the server is\n"
1104 "%s, which is below the configured warning threshold.\n"
1105 "Continue with connection? (y/n) ";
1106 static const char abandoned[] = "Connection abandoned.\n";
1107
1108 char line[32];
1109
1110 fprintf(stderr, msg,
1111 (cs == 0) ? "" :
1112 (cs == 1) ? "client-to-server " :
1113 "server-to-client ",
1114 ciphername);
1115 fflush(stderr);
1116
1117 hin = GetStdHandle(STD_INPUT_HANDLE);
1118 GetConsoleMode(hin, &savemode);
1119 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
1120 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
1121 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
1122 SetConsoleMode(hin, savemode);
1123
1124 if (line[0] == 'y' || line[0] == 'Y') {
1125 return;
1126 } else {
1127 fprintf(stderr, abandoned);
1128 exit(0);
1129 }
1130}
1131
1132/*
4a8fc3c4 1133 * Print an error message and perform a fatal exit.
1134 */
1135void fatalbox(char *fmt, ...)
1136{
32874aea 1137 char str[0x100]; /* Make the size big enough */
4a8fc3c4 1138 va_list ap;
1139 va_start(ap, fmt);
1140 strcpy(str, "Fatal:");
32874aea 1141 vsprintf(str + strlen(str), fmt, ap);
4a8fc3c4 1142 va_end(ap);
1143 strcat(str, "\n");
1144 fprintf(stderr, str);
1145
1146 exit(1);
1147}
1148void connection_fatal(char *fmt, ...)
1149{
32874aea 1150 char str[0x100]; /* Make the size big enough */
4a8fc3c4 1151 va_list ap;
1152 va_start(ap, fmt);
1153 strcpy(str, "Fatal:");
32874aea 1154 vsprintf(str + strlen(str), fmt, ap);
4a8fc3c4 1155 va_end(ap);
1156 strcat(str, "\n");
1157 fprintf(stderr, str);
1158
1159 exit(1);
1160}
1161
32874aea 1162void logevent(char *string)
1163{
1164}
4a8fc3c4 1165
32874aea 1166void ldisc_send(char *buf, int len)
1167{
4a8fc3c4 1168 /*
1169 * This is only here because of the calls to ldisc_send(NULL,
1170 * 0) in ssh.c. Nothing in PSFTP actually needs to use the
1171 * ldisc as an ldisc. So if we get called with any real data, I
1172 * want to know about it.
4c7f0d61 1173 */
4a8fc3c4 1174 assert(len == 0);
1175}
1176
1177/*
1178 * Be told what socket we're supposed to be using.
1179 */
1180static SOCKET sftp_ssh_socket;
32874aea 1181char *do_select(SOCKET skt, int startup)
1182{
4a8fc3c4 1183 if (startup)
1184 sftp_ssh_socket = skt;
1185 else
1186 sftp_ssh_socket = INVALID_SOCKET;
1187 return NULL;
4c7f0d61 1188}
4a8fc3c4 1189extern int select_result(WPARAM, LPARAM);
1190
1191/*
1192 * Receive a block of data from the SSH link. Block until all data
1193 * is available.
1194 *
1195 * To do this, we repeatedly call the SSH protocol module, with our
1196 * own trap in from_backend() to catch the data that comes back. We
1197 * do this until we have enough data.
1198 */
1199
32874aea 1200static unsigned char *outptr; /* where to put the data */
1201static unsigned outlen; /* how much data required */
4a8fc3c4 1202static unsigned char *pending = NULL; /* any spare data */
32874aea 1203static unsigned pendlen = 0, pendsize = 0; /* length and phys. size of buffer */
5471d09a 1204int from_backend(int is_stderr, char *data, int datalen)
32874aea 1205{
1206 unsigned char *p = (unsigned char *) data;
1207 unsigned len = (unsigned) datalen;
4a8fc3c4 1208
1209 /*
1210 * stderr data is just spouted to local stderr and otherwise
1211 * ignored.
1212 */
1213 if (is_stderr) {
1214 fwrite(data, 1, len, stderr);
5471d09a 1215 return 0;
4a8fc3c4 1216 }
1217
1218 /*
1219 * If this is before the real session begins, just return.
1220 */
1221 if (!outptr)
5471d09a 1222 return 0;
4a8fc3c4 1223
1224 if (outlen > 0) {
32874aea 1225 unsigned used = outlen;
1226 if (used > len)
1227 used = len;
1228 memcpy(outptr, p, used);
1229 outptr += used;
1230 outlen -= used;
1231 p += used;
1232 len -= used;
4a8fc3c4 1233 }
1234
1235 if (len > 0) {
32874aea 1236 if (pendsize < pendlen + len) {
1237 pendsize = pendlen + len + 4096;
1238 pending = (pending ? srealloc(pending, pendsize) :
1239 smalloc(pendsize));
1240 if (!pending)
1241 fatalbox("Out of memory");
1242 }
1243 memcpy(pending + pendlen, p, len);
1244 pendlen += len;
4a8fc3c4 1245 }
5471d09a 1246
1247 return 0;
4a8fc3c4 1248}
32874aea 1249int sftp_recvdata(char *buf, int len)
1250{
1251 outptr = (unsigned char *) buf;
4a8fc3c4 1252 outlen = len;
1253
1254 /*
1255 * See if the pending-input block contains some of what we
1256 * need.
1257 */
1258 if (pendlen > 0) {
32874aea 1259 unsigned pendused = pendlen;
1260 if (pendused > outlen)
1261 pendused = outlen;
4a8fc3c4 1262 memcpy(outptr, pending, pendused);
32874aea 1263 memmove(pending, pending + pendused, pendlen - pendused);
4a8fc3c4 1264 outptr += pendused;
1265 outlen -= pendused;
32874aea 1266 pendlen -= pendused;
1267 if (pendlen == 0) {
1268 pendsize = 0;
1269 sfree(pending);
1270 pending = NULL;
1271 }
1272 if (outlen == 0)
1273 return 1;
4a8fc3c4 1274 }
1275
1276 while (outlen > 0) {
32874aea 1277 fd_set readfds;
4a8fc3c4 1278
32874aea 1279 FD_ZERO(&readfds);
1280 FD_SET(sftp_ssh_socket, &readfds);
1281 if (select(1, &readfds, NULL, NULL, NULL) < 0)
1282 return 0; /* doom */
1283 select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
4a8fc3c4 1284 }
1285
1286 return 1;
1287}
32874aea 1288int sftp_senddata(char *buf, int len)
1289{
1290 back->send((unsigned char *) buf, len);
4a8fc3c4 1291 return 1;
1292}
1293
1294/*
1295 * Loop through the ssh connection and authentication process.
1296 */
32874aea 1297static void ssh_sftp_init(void)
1298{
4a8fc3c4 1299 if (sftp_ssh_socket == INVALID_SOCKET)
1300 return;
1301 while (!back->sendok()) {
32874aea 1302 fd_set readfds;
1303 FD_ZERO(&readfds);
1304 FD_SET(sftp_ssh_socket, &readfds);
1305 if (select(1, &readfds, NULL, NULL, NULL) < 0)
1306 return; /* doom */
1307 select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
4a8fc3c4 1308 }
1309}
1310
1311static char *password = NULL;
fa17a66e 1312static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
4a8fc3c4 1313{
1314 HANDLE hin, hout;
fa17a66e 1315 DWORD savemode, newmode, i;
4a8fc3c4 1316
1317 if (password) {
32874aea 1318 static int tried_once = 0;
1319
1320 if (tried_once) {
1321 return 0;
1322 } else {
1323 strncpy(str, password, maxlen);
1324 str[maxlen - 1] = '\0';
1325 tried_once = 1;
1326 return 1;
1327 }
4a8fc3c4 1328 }
1329
1330 hin = GetStdHandle(STD_INPUT_HANDLE);
1331 hout = GetStdHandle(STD_OUTPUT_HANDLE);
1332 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
1333 fprintf(stderr, "Cannot get standard input/output handles\n");
1334 exit(1);
1335 }
1336
1337 GetConsoleMode(hin, &savemode);
fa17a66e 1338 newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
1339 if (is_pw)
32874aea 1340 newmode &= ~ENABLE_ECHO_INPUT;
fa17a66e 1341 else
32874aea 1342 newmode |= ENABLE_ECHO_INPUT;
fa17a66e 1343 SetConsoleMode(hin, newmode);
4a8fc3c4 1344
1345 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
32874aea 1346 ReadFile(hin, str, maxlen - 1, &i, NULL);
4a8fc3c4 1347
1348 SetConsoleMode(hin, savemode);
1349
32874aea 1350 if ((int) i > maxlen)
1351 i = maxlen - 1;
1352 else
1353 i = i - 2;
4a8fc3c4 1354 str[i] = '\0';
1355
fa17a66e 1356 if (is_pw)
32874aea 1357 WriteFile(hout, "\r\n", 2, &i, NULL);
4a8fc3c4 1358
1359 return 1;
1360}
1361
1362/*
1363 * Initialize the Win$ock driver.
1364 */
1365static void init_winsock(void)
1366{
1367 WORD winsock_ver;
1368 WSADATA wsadata;
1369
1370 winsock_ver = MAKEWORD(1, 1);
1371 if (WSAStartup(winsock_ver, &wsadata)) {
1372 fprintf(stderr, "Unable to initialise WinSock");
1373 exit(1);
1374 }
32874aea 1375 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
4a8fc3c4 1376 fprintf(stderr, "WinSock version is incompatible with 1.1");
1377 exit(1);
1378 }
1379}
1380
1381/*
1382 * Short description of parameters.
1383 */
1384static void usage(void)
1385{
1386 printf("PuTTY Secure File Transfer (SFTP) client\n");
1387 printf("%s\n", ver);
1388 printf("Usage: psftp [options] user@host\n");
1389 printf("Options:\n");
9954aaa3 1390 printf(" -b file use specified batchfile\n");
1391 printf(" -bc output batchfile commands\n");
1392 printf(" -be don't stop batchfile processing if errors\n");
4a8fc3c4 1393 printf(" -v show verbose messages\n");
1394 printf(" -P port connect to specified port\n");
1395 printf(" -pw passw login with specified password\n");
1396 exit(1);
1397}
1398
1399/*
1400 * Main program. Parse arguments etc.
1401 */
1402int main(int argc, char *argv[])
1403{
1404 int i;
1405 int portnumber = 0;
1406 char *user, *host, *userhost, *realhost;
1407 char *err;
9954aaa3 1408 int mode = 0;
1409 int modeflags = 0;
1410 char *batchfile = NULL;
4a8fc3c4 1411
2c23c16a 1412 flags = FLAG_STDERR | FLAG_INTERACTIVE;
fa17a66e 1413 ssh_get_line = &get_line;
4a8fc3c4 1414 init_winsock();
1415 sk_init();
1416
1417 userhost = user = NULL;
1418
1419 for (i = 1; i < argc; i++) {
1420 if (argv[i][0] != '-') {
1421 if (userhost)
1422 usage();
1423 else
1424 userhost = dupstr(argv[i]);
1425 } else if (strcmp(argv[i], "-v") == 0) {
1426 verbose = 1, flags |= FLAG_VERBOSE;
1427 } else if (strcmp(argv[i], "-h") == 0 ||
1428 strcmp(argv[i], "-?") == 0) {
1429 usage();
32874aea 1430 } else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) {
4a8fc3c4 1431 user = argv[++i];
32874aea 1432 } else if (strcmp(argv[i], "-P") == 0 && i + 1 < argc) {
4a8fc3c4 1433 portnumber = atoi(argv[++i]);
32874aea 1434 } else if (strcmp(argv[i], "-pw") == 0 && i + 1 < argc) {
4a8fc3c4 1435 password = argv[++i];
9954aaa3 1436 } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) {
1437 mode = 1;
1438 batchfile = argv[++i];
1439 } else if (strcmp(argv[i], "-bc") == 0 && i + 1 < argc) {
1440 modeflags = modeflags | 1;
1441 } else if (strcmp(argv[i], "-be") == 0 && i + 1 < argc) {
1442 modeflags = modeflags | 2;
4a8fc3c4 1443 } else if (strcmp(argv[i], "--") == 0) {
1444 i++;
1445 break;
1446 } else {
1447 usage();
1448 }
1449 }
1450 argc -= i;
1451 argv += i;
1452 back = NULL;
1453
1454 if (argc > 0 || !userhost)
1455 usage();
1456
1457 /* Separate host and username */
1458 host = userhost;
1459 host = strrchr(host, '@');
1460 if (host == NULL) {
1461 host = userhost;
1462 } else {
1463 *host++ = '\0';
1464 if (user) {
32874aea 1465 printf("psftp: multiple usernames specified; using \"%s\"\n",
1466 user);
4a8fc3c4 1467 } else
1468 user = userhost;
1469 }
1470
1471 /* Try to load settings for this host */
1472 do_defaults(host, &cfg);
1473 if (cfg.host[0] == '\0') {
1474 /* No settings for this host; use defaults */
32874aea 1475 do_defaults(NULL, &cfg);
1476 strncpy(cfg.host, host, sizeof(cfg.host) - 1);
1477 cfg.host[sizeof(cfg.host) - 1] = '\0';
4a8fc3c4 1478 cfg.port = 22;
1479 }
1480
1481 /* Set username */
1482 if (user != NULL && user[0] != '\0') {
32874aea 1483 strncpy(cfg.username, user, sizeof(cfg.username) - 1);
1484 cfg.username[sizeof(cfg.username) - 1] = '\0';
4a8fc3c4 1485 }
1486 if (!cfg.username[0]) {
1487 printf("login as: ");
1488 if (!fgets(cfg.username, sizeof(cfg.username), stdin)) {
1489 fprintf(stderr, "psftp: aborting\n");
1490 exit(1);
1491 } else {
1492 int len = strlen(cfg.username);
32874aea 1493 if (cfg.username[len - 1] == '\n')
1494 cfg.username[len - 1] = '\0';
4a8fc3c4 1495 }
1496 }
1497
1498 if (cfg.protocol != PROT_SSH)
1499 cfg.port = 22;
1500
1501 if (portnumber)
1502 cfg.port = portnumber;
1503
1504 /* SFTP uses SSH2 by default always */
1505 cfg.sshprot = 2;
1506
bebf22d0 1507 /* Set up subsystem name. */
4a8fc3c4 1508 strcpy(cfg.remote_cmd, "sftp");
1509 cfg.ssh_subsys = TRUE;
1510 cfg.nopty = TRUE;
1511
bebf22d0 1512 /*
1513 * Set up fallback option, for SSH1 servers or servers with the
1514 * sftp subsystem not enabled but the server binary installed
1515 * in the usual place. We only support fallback on Unix
248c0c5a 1516 * systems, and we use a kludgy piece of shellery which should
1517 * try to find sftp-server in various places (the obvious
1518 * systemwide spots /usr/lib and /usr/local/lib, and then the
1519 * user's PATH) and finally give up.
bebf22d0 1520 *
248c0c5a 1521 * test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server
1522 * test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server
1523 * exec sftp-server
bebf22d0 1524 *
1525 * the idea being that this will attempt to use either of the
1526 * obvious pathnames and then give up, and when it does give up
1527 * it will print the preferred pathname in the error messages.
1528 */
1529 cfg.remote_cmd_ptr2 =
248c0c5a 1530 "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n"
1531 "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n"
1532 "exec sftp-server";
bebf22d0 1533 cfg.ssh_subsys2 = FALSE;
1534
4a8fc3c4 1535 back = &ssh_backend;
1536
1537 err = back->init(cfg.host, cfg.port, &realhost);
1538 if (err != NULL) {
1539 fprintf(stderr, "ssh_init: %s", err);
1540 return 1;
1541 }
1542 ssh_sftp_init();
1543 if (verbose && realhost != NULL)
1544 printf("Connected to %s\n", realhost);
4c7f0d61 1545
9954aaa3 1546 do_sftp(mode, modeflags, batchfile);
4a8fc3c4 1547
1548 if (back != NULL && back->socket() != NULL) {
1549 char ch;
1550 back->special(TS_EOF);
1551 sftp_recvdata(&ch, 1);
1552 }
1553 WSACleanup();
1554 random_save_seed();
1555
4c7f0d61 1556 return 0;
1557}