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