Deleted Added
sdiff udiff text old ( 1.231 ) new ( 1.232 )
full compact
1/* $OpenBSD: sftp.c,v 1.232 2023/03/31 04:45:08 dtucker Exp $ */
2/*
3 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18#include <sys/types.h>
19#include <sys/ioctl.h>
20#include <sys/wait.h>
21#include <sys/stat.h>
22#include <sys/socket.h>
23#include <sys/statvfs.h>
24
25#include <ctype.h>
26#include <errno.h>
27#include <glob.h>
28#include <histedit.h>
29#include <paths.h>
30#include <libgen.h>
31#include <locale.h>
32#include <signal.h>
33#include <stdarg.h>
34#include <stdlib.h>
35#include <stdio.h>
36#include <string.h>
37#include <unistd.h>
38#include <limits.h>
39#include <util.h>
40
41#include "xmalloc.h"
42#include "log.h"
43#include "pathnames.h"
44#include "misc.h"
45#include "utf8.h"
46
47#include "sftp.h"
48#include "ssherr.h"
49#include "sshbuf.h"
50#include "sftp-common.h"
51#include "sftp-client.h"
52#include "sftp-usergroup.h"
53
54/* File to read commands from */
55FILE* infile;
56
57/* Are we in batchfile mode? */
58int batchmode = 0;
59
60/* PID of ssh transport process */
61static volatile pid_t sshpid = -1;
62
63/* Suppress diagnostic messages */
64int quiet = 0;
65
66/* This is set to 0 if the progressmeter is not desired. */
67int showprogress = 1;
68
69/* When this option is set, we always recursively download/upload directories */
70int global_rflag = 0;
71
72/* When this option is set, we resume download or upload if possible */
73int global_aflag = 0;
74
75/* When this option is set, the file transfers will always preserve times */
76int global_pflag = 0;
77
78/* When this option is set, transfers will have fsync() called on each file */
79int global_fflag = 0;
80
81/* SIGINT received during command processing */
82volatile sig_atomic_t interrupted = 0;
83
84/* I wish qsort() took a separate ctx for the comparison function...*/
85int sort_flag;
86glob_t *sort_glob;
87
88/* Context used for commandline completion */
89struct complete_ctx {
90 struct sftp_conn *conn;
91 char **remote_pathp;
92};
93
94int remote_glob(struct sftp_conn *, const char *, int,
95 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
96
97/* Separators for interactive commands */
98#define WHITESPACE " \t\r\n"
99
100/* ls flags */
101#define LS_LONG_VIEW 0x0001 /* Full view ala ls -l */
102#define LS_SHORT_VIEW 0x0002 /* Single row view ala ls -1 */
103#define LS_NUMERIC_VIEW 0x0004 /* Long view with numeric uid/gid */
104#define LS_NAME_SORT 0x0008 /* Sort by name (default) */
105#define LS_TIME_SORT 0x0010 /* Sort by mtime */
106#define LS_SIZE_SORT 0x0020 /* Sort by file size */
107#define LS_REVERSE_SORT 0x0040 /* Reverse sort order */
108#define LS_SHOW_ALL 0x0080 /* Don't skip filenames starting with '.' */
109#define LS_SI_UNITS 0x0100 /* Display sizes as K, M, G, etc. */
110
111#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS)
112#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
113
114/* Commands for interactive mode */
115enum sftp_command {
116 I_CHDIR = 1,
117 I_CHGRP,
118 I_CHMOD,
119 I_CHOWN,
120 I_COPY,
121 I_DF,
122 I_GET,
123 I_HELP,
124 I_LCHDIR,
125 I_LINK,
126 I_LLS,
127 I_LMKDIR,
128 I_LPWD,
129 I_LS,
130 I_LUMASK,
131 I_MKDIR,
132 I_PUT,
133 I_PWD,
134 I_QUIT,
135 I_REGET,
136 I_RENAME,
137 I_REPUT,
138 I_RM,
139 I_RMDIR,
140 I_SHELL,
141 I_SYMLINK,
142 I_VERSION,
143 I_PROGRESS,
144};
145
146struct CMD {
147 const char *c;
148 const int n;
149 const int t; /* Completion type for the first argument */
150 const int t2; /* completion type for the optional second argument */
151};
152
153/* Type of completion */
154#define NOARGS 0
155#define REMOTE 1
156#define LOCAL 2
157
158static const struct CMD cmds[] = {
159 { "bye", I_QUIT, NOARGS, NOARGS },
160 { "cd", I_CHDIR, REMOTE, NOARGS },
161 { "chdir", I_CHDIR, REMOTE, NOARGS },
162 { "chgrp", I_CHGRP, REMOTE, NOARGS },
163 { "chmod", I_CHMOD, REMOTE, NOARGS },
164 { "chown", I_CHOWN, REMOTE, NOARGS },
165 { "copy", I_COPY, REMOTE, LOCAL },
166 { "cp", I_COPY, REMOTE, LOCAL },
167 { "df", I_DF, REMOTE, NOARGS },
168 { "dir", I_LS, REMOTE, NOARGS },
169 { "exit", I_QUIT, NOARGS, NOARGS },
170 { "get", I_GET, REMOTE, LOCAL },
171 { "help", I_HELP, NOARGS, NOARGS },
172 { "lcd", I_LCHDIR, LOCAL, NOARGS },
173 { "lchdir", I_LCHDIR, LOCAL, NOARGS },
174 { "lls", I_LLS, LOCAL, NOARGS },
175 { "lmkdir", I_LMKDIR, LOCAL, NOARGS },
176 { "ln", I_LINK, REMOTE, REMOTE },
177 { "lpwd", I_LPWD, LOCAL, NOARGS },
178 { "ls", I_LS, REMOTE, NOARGS },
179 { "lumask", I_LUMASK, NOARGS, NOARGS },
180 { "mkdir", I_MKDIR, REMOTE, NOARGS },
181 { "mget", I_GET, REMOTE, LOCAL },
182 { "mput", I_PUT, LOCAL, REMOTE },
183 { "progress", I_PROGRESS, NOARGS, NOARGS },
184 { "put", I_PUT, LOCAL, REMOTE },
185 { "pwd", I_PWD, REMOTE, NOARGS },
186 { "quit", I_QUIT, NOARGS, NOARGS },
187 { "reget", I_REGET, REMOTE, LOCAL },
188 { "rename", I_RENAME, REMOTE, REMOTE },
189 { "reput", I_REPUT, LOCAL, REMOTE },
190 { "rm", I_RM, REMOTE, NOARGS },
191 { "rmdir", I_RMDIR, REMOTE, NOARGS },
192 { "symlink", I_SYMLINK, REMOTE, REMOTE },
193 { "version", I_VERSION, NOARGS, NOARGS },
194 { "!", I_SHELL, NOARGS, NOARGS },
195 { "?", I_HELP, NOARGS, NOARGS },
196 { NULL, -1, -1, -1 }
197};
198
199static void
200killchild(int signo)
201{
202 pid_t pid;
203
204 pid = sshpid;
205 if (pid > 1) {
206 kill(pid, SIGTERM);
207 (void)waitpid(pid, NULL, 0);
208 }
209
210 _exit(1);
211}
212
213static void
214suspchild(int signo)
215{
216 if (sshpid > 1) {
217 kill(sshpid, signo);
218 while (waitpid(sshpid, NULL, WUNTRACED) == -1 && errno == EINTR)
219 continue;
220 }
221 kill(getpid(), SIGSTOP);
222}
223
224static void
225cmd_interrupt(int signo)
226{
227 const char msg[] = "\rInterrupt \n";
228 int olderrno = errno;
229
230 (void)write(STDERR_FILENO, msg, sizeof(msg) - 1);
231 interrupted = 1;
232 errno = olderrno;
233}
234
235static void
236read_interrupt(int signo)
237{
238 interrupted = 1;
239}
240
241static void
242sigchld_handler(int sig)
243{
244 int save_errno = errno;
245 pid_t pid;
246 const char msg[] = "\rConnection closed. \n";
247
248 /* Report if ssh transport process dies. */
249 while ((pid = waitpid(sshpid, NULL, WNOHANG)) == -1 && errno == EINTR)
250 continue;
251 if (pid == sshpid) {
252 if (!quiet)
253 (void)write(STDERR_FILENO, msg, sizeof(msg) - 1);
254 sshpid = -1;
255 }
256
257 errno = save_errno;
258}
259
260static void
261help(void)
262{
263 printf("Available commands:\n"
264 "bye Quit sftp\n"
265 "cd path Change remote directory to 'path'\n"
266 "chgrp [-h] grp path Change group of file 'path' to 'grp'\n"
267 "chmod [-h] mode path Change permissions of file 'path' to 'mode'\n"
268 "chown [-h] own path Change owner of file 'path' to 'own'\n"
269 "copy oldpath newpath Copy remote file\n"
270 "cp oldpath newpath Copy remote file\n"
271 "df [-hi] [path] Display statistics for current directory or\n"
272 " filesystem containing 'path'\n"
273 "exit Quit sftp\n"
274 "get [-afpR] remote [local] Download file\n"
275 "help Display this help text\n"
276 "lcd path Change local directory to 'path'\n"
277 "lls [ls-options [path]] Display local directory listing\n"
278 "lmkdir path Create local directory\n"
279 "ln [-s] oldpath newpath Link remote file (-s for symlink)\n"
280 "lpwd Print local working directory\n"
281 "ls [-1afhlnrSt] [path] Display remote directory listing\n"
282 "lumask umask Set local umask to 'umask'\n"
283 "mkdir path Create remote directory\n"
284 "progress Toggle display of progress meter\n"
285 "put [-afpR] local [remote] Upload file\n"
286 "pwd Display remote working directory\n"
287 "quit Quit sftp\n"
288 "reget [-fpR] remote [local] Resume download file\n"
289 "rename oldpath newpath Rename remote file\n"
290 "reput [-fpR] local [remote] Resume upload file\n"
291 "rm path Delete remote file\n"
292 "rmdir path Remove remote directory\n"
293 "symlink oldpath newpath Symlink remote file\n"
294 "version Show SFTP version\n"
295 "!command Execute 'command' in local shell\n"
296 "! Escape to local shell\n"
297 "? Synonym for help\n");
298}
299
300static void
301local_do_shell(const char *args)
302{
303 int status;
304 char *shell;
305 pid_t pid;
306
307 if (!*args)
308 args = NULL;
309
310 if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
311 shell = _PATH_BSHELL;
312
313 if ((pid = fork()) == -1)
314 fatal("Couldn't fork: %s", strerror(errno));
315
316 if (pid == 0) {
317 /* XXX: child has pipe fds to ssh subproc open - issue? */
318 if (args) {
319 debug3("Executing %s -c \"%s\"", shell, args);
320 execl(shell, shell, "-c", args, (char *)NULL);
321 } else {
322 debug3("Executing %s", shell);
323 execl(shell, shell, (char *)NULL);
324 }
325 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
326 strerror(errno));
327 _exit(1);
328 }
329 while (waitpid(pid, &status, 0) == -1)
330 if (errno != EINTR)
331 fatal("Couldn't wait for child: %s", strerror(errno));
332 if (!WIFEXITED(status))
333 error("Shell exited abnormally");
334 else if (WEXITSTATUS(status))
335 error("Shell exited with status %d", WEXITSTATUS(status));
336}
337
338static void
339local_do_ls(const char *args)
340{
341 if (!args || !*args)
342 local_do_shell(_PATH_LS);
343 else {
344 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
345 char *buf = xmalloc(len);
346
347 /* XXX: quoting - rip quoting code from ftp? */
348 snprintf(buf, len, _PATH_LS " %s", args);
349 local_do_shell(buf);
350 free(buf);
351 }
352}
353
354/* Strip one path (usually the pwd) from the start of another */
355static char *
356path_strip(const char *path, const char *strip)
357{
358 size_t len;
359
360 if (strip == NULL)
361 return (xstrdup(path));
362
363 len = strlen(strip);
364 if (strncmp(path, strip, len) == 0) {
365 if (strip[len - 1] != '/' && path[len] == '/')
366 len++;
367 return (xstrdup(path + len));
368 }
369
370 return (xstrdup(path));
371}
372
373static int
374parse_getput_flags(const char *cmd, char **argv, int argc,
375 int *aflag, int *fflag, int *pflag, int *rflag)
376{
377 extern int opterr, optind, optopt, optreset;
378 int ch;
379
380 optind = optreset = 1;
381 opterr = 0;
382
383 *aflag = *fflag = *rflag = *pflag = 0;
384 while ((ch = getopt(argc, argv, "afPpRr")) != -1) {
385 switch (ch) {
386 case 'a':
387 *aflag = 1;
388 break;
389 case 'f':
390 *fflag = 1;
391 break;
392 case 'p':
393 case 'P':
394 *pflag = 1;
395 break;
396 case 'r':
397 case 'R':
398 *rflag = 1;
399 break;
400 default:
401 error("%s: Invalid flag -%c", cmd, optopt);
402 return -1;
403 }
404 }
405
406 return optind;
407}
408
409static int
410parse_link_flags(const char *cmd, char **argv, int argc, int *sflag)
411{
412 extern int opterr, optind, optopt, optreset;
413 int ch;
414
415 optind = optreset = 1;
416 opterr = 0;
417
418 *sflag = 0;
419 while ((ch = getopt(argc, argv, "s")) != -1) {
420 switch (ch) {
421 case 's':
422 *sflag = 1;
423 break;
424 default:
425 error("%s: Invalid flag -%c", cmd, optopt);
426 return -1;
427 }
428 }
429
430 return optind;
431}
432
433static int
434parse_rename_flags(const char *cmd, char **argv, int argc, int *lflag)
435{
436 extern int opterr, optind, optopt, optreset;
437 int ch;
438
439 optind = optreset = 1;
440 opterr = 0;
441
442 *lflag = 0;
443 while ((ch = getopt(argc, argv, "l")) != -1) {
444 switch (ch) {
445 case 'l':
446 *lflag = 1;
447 break;
448 default:
449 error("%s: Invalid flag -%c", cmd, optopt);
450 return -1;
451 }
452 }
453
454 return optind;
455}
456
457static int
458parse_ls_flags(char **argv, int argc, int *lflag)
459{
460 extern int opterr, optind, optopt, optreset;
461 int ch;
462
463 optind = optreset = 1;
464 opterr = 0;
465
466 *lflag = LS_NAME_SORT;
467 while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
468 switch (ch) {
469 case '1':
470 *lflag &= ~VIEW_FLAGS;
471 *lflag |= LS_SHORT_VIEW;
472 break;
473 case 'S':
474 *lflag &= ~SORT_FLAGS;
475 *lflag |= LS_SIZE_SORT;
476 break;
477 case 'a':
478 *lflag |= LS_SHOW_ALL;
479 break;
480 case 'f':
481 *lflag &= ~SORT_FLAGS;
482 break;
483 case 'h':
484 *lflag |= LS_SI_UNITS;
485 break;
486 case 'l':
487 *lflag &= ~LS_SHORT_VIEW;
488 *lflag |= LS_LONG_VIEW;
489 break;
490 case 'n':
491 *lflag &= ~LS_SHORT_VIEW;
492 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
493 break;
494 case 'r':
495 *lflag |= LS_REVERSE_SORT;
496 break;
497 case 't':
498 *lflag &= ~SORT_FLAGS;
499 *lflag |= LS_TIME_SORT;
500 break;
501 default:
502 error("ls: Invalid flag -%c", optopt);
503 return -1;
504 }
505 }
506
507 return optind;
508}
509
510static int
511parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
512{
513 extern int opterr, optind, optopt, optreset;
514 int ch;
515
516 optind = optreset = 1;
517 opterr = 0;
518
519 *hflag = *iflag = 0;
520 while ((ch = getopt(argc, argv, "hi")) != -1) {
521 switch (ch) {
522 case 'h':
523 *hflag = 1;
524 break;
525 case 'i':
526 *iflag = 1;
527 break;
528 default:
529 error("%s: Invalid flag -%c", cmd, optopt);
530 return -1;
531 }
532 }
533
534 return optind;
535}
536
537static int
538parse_ch_flags(const char *cmd, char **argv, int argc, int *hflag)
539{
540 extern int opterr, optind, optopt, optreset;
541 int ch;
542
543 optind = optreset = 1;
544 opterr = 0;
545
546 *hflag = 0;
547 while ((ch = getopt(argc, argv, "h")) != -1) {
548 switch (ch) {
549 case 'h':
550 *hflag = 1;
551 break;
552 default:
553 error("%s: Invalid flag -%c", cmd, optopt);
554 return -1;
555 }
556 }
557
558 return optind;
559}
560
561static int
562parse_no_flags(const char *cmd, char **argv, int argc)
563{
564 extern int opterr, optind, optopt, optreset;
565 int ch;
566
567 optind = optreset = 1;
568 opterr = 0;
569
570 while ((ch = getopt(argc, argv, "")) != -1) {
571 switch (ch) {
572 default:
573 error("%s: Invalid flag -%c", cmd, optopt);
574 return -1;
575 }
576 }
577
578 return optind;
579}
580
581static char *
582escape_glob(const char *s)
583{
584 size_t i, o, len;
585 char *ret;
586
587 len = strlen(s);
588 ret = xcalloc(2, len + 1);
589 for (i = o = 0; i < len; i++) {
590 if (strchr("[]?*\\", s[i]) != NULL)
591 ret[o++] = '\\';
592 ret[o++] = s[i];
593 }
594 ret[o++] = '\0';
595 return ret;
596}
597
598/*
599 * Arg p must be dynamically allocated. make_absolute will either return it
600 * or free it and and allocate a new one. Caller must free returned string.
601 */
602static char *
603make_absolute_pwd_glob(char *p, const char *pwd)
604{
605 char *ret, *escpwd;
606
607 escpwd = escape_glob(pwd);
608 if (p == NULL)
609 return escpwd;
610 ret = make_absolute(p, escpwd);
611 free(escpwd);
612 return ret;
613}
614
615static int
616process_get(struct sftp_conn *conn, const char *src, const char *dst,
617 const char *pwd, int pflag, int rflag, int resume, int fflag)
618{
619 char *filename, *abs_src = NULL, *abs_dst = NULL, *tmp = NULL;
620 glob_t g;
621 int i, r, err = 0;
622
623 abs_src = make_absolute_pwd_glob(xstrdup(src), pwd);
624 memset(&g, 0, sizeof(g));
625
626 debug3("Looking up %s", abs_src);
627 if ((r = remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) != 0) {
628 if (r == GLOB_NOSPACE) {
629 error("Too many matches for \"%s\".", abs_src);
630 } else {
631 error("File \"%s\" not found.", abs_src);
632 }
633 err = -1;
634 goto out;
635 }
636
637 /*
638 * If multiple matches then dst must be a directory or
639 * unspecified.
640 */
641 if (g.gl_matchc > 1 && dst != NULL && !local_is_dir(dst)) {
642 error("Multiple source paths, but destination "
643 "\"%s\" is not a directory", dst);
644 err = -1;
645 goto out;
646 }
647
648 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
649 tmp = xstrdup(g.gl_pathv[i]);
650 if ((filename = basename(tmp)) == NULL) {
651 error("basename %s: %s", tmp, strerror(errno));
652 free(tmp);
653 err = -1;
654 goto out;
655 }
656
657 if (g.gl_matchc == 1 && dst) {
658 if (local_is_dir(dst)) {
659 abs_dst = path_append(dst, filename);
660 } else {
661 abs_dst = xstrdup(dst);
662 }
663 } else if (dst) {
664 abs_dst = path_append(dst, filename);
665 } else {
666 abs_dst = xstrdup(filename);
667 }
668 free(tmp);
669
670 resume |= global_aflag;
671 if (!quiet && resume)
672 mprintf("Resuming %s to %s\n",
673 g.gl_pathv[i], abs_dst);
674 else if (!quiet && !resume)
675 mprintf("Fetching %s to %s\n",
676 g.gl_pathv[i], abs_dst);
677 /* XXX follow link flag */
678 if (globpath_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
679 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
680 pflag || global_pflag, 1, resume,
681 fflag || global_fflag, 0, 0) == -1)
682 err = -1;
683 } else {
684 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
685 pflag || global_pflag, resume,
686 fflag || global_fflag, 0) == -1)
687 err = -1;
688 }
689 free(abs_dst);
690 abs_dst = NULL;
691 }
692
693out:
694 free(abs_src);
695 globfree(&g);
696 return(err);
697}
698
699static int
700process_put(struct sftp_conn *conn, const char *src, const char *dst,
701 const char *pwd, int pflag, int rflag, int resume, int fflag)
702{
703 char *tmp_dst = NULL;
704 char *abs_dst = NULL;
705 char *tmp = NULL, *filename = NULL;
706 glob_t g;
707 int err = 0;
708 int i, dst_is_dir = 1;
709 struct stat sb;
710
711 if (dst) {
712 tmp_dst = xstrdup(dst);
713 tmp_dst = make_absolute(tmp_dst, pwd);
714 }
715
716 memset(&g, 0, sizeof(g));
717 debug3("Looking up %s", src);
718 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
719 error("File \"%s\" not found.", src);
720 err = -1;
721 goto out;
722 }
723
724 /* If we aren't fetching to pwd then stash this status for later */
725 if (tmp_dst != NULL)
726 dst_is_dir = remote_is_dir(conn, tmp_dst);
727
728 /* If multiple matches, dst may be directory or unspecified */
729 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
730 error("Multiple paths match, but destination "
731 "\"%s\" is not a directory", tmp_dst);
732 err = -1;
733 goto out;
734 }
735
736 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
737 if (stat(g.gl_pathv[i], &sb) == -1) {
738 err = -1;
739 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
740 continue;
741 }
742
743 tmp = xstrdup(g.gl_pathv[i]);
744 if ((filename = basename(tmp)) == NULL) {
745 error("basename %s: %s", tmp, strerror(errno));
746 free(tmp);
747 err = -1;
748 goto out;
749 }
750
751 free(abs_dst);
752 abs_dst = NULL;
753 if (g.gl_matchc == 1 && tmp_dst) {
754 /* If directory specified, append filename */
755 if (dst_is_dir)
756 abs_dst = path_append(tmp_dst, filename);
757 else
758 abs_dst = xstrdup(tmp_dst);
759 } else if (tmp_dst) {
760 abs_dst = path_append(tmp_dst, filename);
761 } else {
762 abs_dst = make_absolute(xstrdup(filename), pwd);
763 }
764 free(tmp);
765
766 resume |= global_aflag;
767 if (!quiet && resume)
768 mprintf("Resuming upload of %s to %s\n",
769 g.gl_pathv[i], abs_dst);
770 else if (!quiet && !resume)
771 mprintf("Uploading %s to %s\n",
772 g.gl_pathv[i], abs_dst);
773 /* XXX follow_link_flag */
774 if (globpath_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
775 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
776 pflag || global_pflag, 1, resume,
777 fflag || global_fflag, 0, 0) == -1)
778 err = -1;
779 } else {
780 if (do_upload(conn, g.gl_pathv[i], abs_dst,
781 pflag || global_pflag, resume,
782 fflag || global_fflag, 0) == -1)
783 err = -1;
784 }
785 }
786
787out:
788 free(abs_dst);
789 free(tmp_dst);
790 globfree(&g);
791 return(err);
792}
793
794static int
795sdirent_comp(const void *aa, const void *bb)
796{
797 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
798 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
799 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
800
801#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
802 if (sort_flag & LS_NAME_SORT)
803 return (rmul * strcmp(a->filename, b->filename));
804 else if (sort_flag & LS_TIME_SORT)
805 return (rmul * NCMP(a->a.mtime, b->a.mtime));
806 else if (sort_flag & LS_SIZE_SORT)
807 return (rmul * NCMP(a->a.size, b->a.size));
808
809 fatal("Unknown ls sort type");
810}
811
812/* sftp ls.1 replacement for directories */
813static int
814do_ls_dir(struct sftp_conn *conn, const char *path,
815 const char *strip_path, int lflag)
816{
817 int n;
818 u_int c = 1, colspace = 0, columns = 1;
819 SFTP_DIRENT **d;
820
821 if ((n = do_readdir(conn, path, &d)) != 0)
822 return (n);
823
824 if (!(lflag & LS_SHORT_VIEW)) {
825 u_int m = 0, width = 80;
826 struct winsize ws;
827 char *tmp;
828
829 /* Count entries for sort and find longest filename */
830 for (n = 0; d[n] != NULL; n++) {
831 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
832 m = MAXIMUM(m, strlen(d[n]->filename));
833 }
834
835 /* Add any subpath that also needs to be counted */
836 tmp = path_strip(path, strip_path);
837 m += strlen(tmp);
838 free(tmp);
839
840 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
841 width = ws.ws_col;
842
843 columns = width / (m + 2);
844 columns = MAXIMUM(columns, 1);
845 colspace = width / columns;
846 colspace = MINIMUM(colspace, width);
847 }
848
849 if (lflag & SORT_FLAGS) {
850 for (n = 0; d[n] != NULL; n++)
851 ; /* count entries */
852 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
853 qsort(d, n, sizeof(*d), sdirent_comp);
854 }
855
856 get_remote_user_groups_from_dirents(conn, d);
857 for (n = 0; d[n] != NULL && !interrupted; n++) {
858 char *tmp, *fname;
859
860 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
861 continue;
862
863 tmp = path_append(path, d[n]->filename);
864 fname = path_strip(tmp, strip_path);
865 free(tmp);
866
867 if (lflag & LS_LONG_VIEW) {
868 if ((lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) != 0 ||
869 can_get_users_groups_by_id(conn)) {
870 char *lname;
871 struct stat sb;
872
873 memset(&sb, 0, sizeof(sb));
874 attrib_to_stat(&d[n]->a, &sb);
875 lname = ls_file(fname, &sb, 1,
876 (lflag & LS_SI_UNITS),
877 ruser_name(sb.st_uid),
878 rgroup_name(sb.st_gid));
879 mprintf("%s\n", lname);
880 free(lname);
881 } else
882 mprintf("%s\n", d[n]->longname);
883 } else {
884 mprintf("%-*s", colspace, fname);
885 if (c >= columns) {
886 printf("\n");
887 c = 1;
888 } else
889 c++;
890 }
891
892 free(fname);
893 }
894
895 if (!(lflag & LS_LONG_VIEW) && (c != 1))
896 printf("\n");
897
898 free_sftp_dirents(d);
899 return (0);
900}
901
902static int
903sglob_comp(const void *aa, const void *bb)
904{
905 u_int a = *(const u_int *)aa;
906 u_int b = *(const u_int *)bb;
907 const char *ap = sort_glob->gl_pathv[a];
908 const char *bp = sort_glob->gl_pathv[b];
909 const struct stat *as = sort_glob->gl_statv[a];
910 const struct stat *bs = sort_glob->gl_statv[b];
911 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
912
913#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
914 if (sort_flag & LS_NAME_SORT)
915 return (rmul * strcmp(ap, bp));
916 else if (sort_flag & LS_TIME_SORT) {
917 if (timespeccmp(&as->st_mtim, &bs->st_mtim, ==))
918 return 0;
919 return timespeccmp(&as->st_mtim, &bs->st_mtim, <) ?
920 rmul : -rmul;
921 } else if (sort_flag & LS_SIZE_SORT)
922 return (rmul * NCMP(as->st_size, bs->st_size));
923
924 fatal("Unknown ls sort type");
925}
926
927/* sftp ls.1 replacement which handles path globs */
928static int
929do_globbed_ls(struct sftp_conn *conn, const char *path,
930 const char *strip_path, int lflag)
931{
932 char *fname, *lname;
933 glob_t g;
934 int err, r;
935 struct winsize ws;
936 u_int i, j, nentries, *indices = NULL, c = 1;
937 u_int colspace = 0, columns = 1, m = 0, width = 80;
938
939 memset(&g, 0, sizeof(g));
940
941 if ((r = remote_glob(conn, path,
942 GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
943 NULL, &g)) != 0 ||
944 (g.gl_pathc && !g.gl_matchc)) {
945 if (g.gl_pathc)
946 globfree(&g);
947 if (r == GLOB_NOSPACE) {
948 error("Can't ls: Too many matches for \"%s\"", path);
949 } else {
950 error("Can't ls: \"%s\" not found", path);
951 }
952 return -1;
953 }
954
955 if (interrupted)
956 goto out;
957
958 /*
959 * If the glob returns a single match and it is a directory,
960 * then just list its contents.
961 */
962 if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
963 S_ISDIR(g.gl_statv[0]->st_mode)) {
964 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
965 globfree(&g);
966 return err;
967 }
968
969 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
970 width = ws.ws_col;
971
972 if (!(lflag & LS_SHORT_VIEW)) {
973 /* Count entries for sort and find longest filename */
974 for (i = 0; g.gl_pathv[i]; i++)
975 m = MAXIMUM(m, strlen(g.gl_pathv[i]));
976
977 columns = width / (m + 2);
978 columns = MAXIMUM(columns, 1);
979 colspace = width / columns;
980 }
981
982 /*
983 * Sorting: rather than mess with the contents of glob_t, prepare
984 * an array of indices into it and sort that. For the usual
985 * unsorted case, the indices are just the identity 1=1, 2=2, etc.
986 */
987 for (nentries = 0; g.gl_pathv[nentries] != NULL; nentries++)
988 ; /* count entries */
989 indices = xcalloc(nentries, sizeof(*indices));
990 for (i = 0; i < nentries; i++)
991 indices[i] = i;
992
993 if (lflag & SORT_FLAGS) {
994 sort_glob = &g;
995 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
996 qsort(indices, nentries, sizeof(*indices), sglob_comp);
997 sort_glob = NULL;
998 }
999
1000 get_remote_user_groups_from_glob(conn, &g);
1001 for (j = 0; j < nentries && !interrupted; j++) {
1002 i = indices[j];
1003 fname = path_strip(g.gl_pathv[i], strip_path);
1004 if (lflag & LS_LONG_VIEW) {
1005 if (g.gl_statv[i] == NULL) {
1006 error("no stat information for %s", fname);
1007 free(fname);
1008 continue;
1009 }
1010 lname = ls_file(fname, g.gl_statv[i], 1,
1011 (lflag & LS_SI_UNITS),
1012 ruser_name(g.gl_statv[i]->st_uid),
1013 rgroup_name(g.gl_statv[i]->st_gid));
1014 mprintf("%s\n", lname);
1015 free(lname);
1016 } else {
1017 mprintf("%-*s", colspace, fname);
1018 if (c >= columns) {
1019 printf("\n");
1020 c = 1;
1021 } else
1022 c++;
1023 }
1024 free(fname);
1025 }
1026
1027 if (!(lflag & LS_LONG_VIEW) && (c != 1))
1028 printf("\n");
1029
1030 out:
1031 if (g.gl_pathc)
1032 globfree(&g);
1033 free(indices);
1034
1035 return 0;
1036}
1037
1038static int
1039do_df(struct sftp_conn *conn, const char *path, int hflag, int iflag)
1040{
1041 struct sftp_statvfs st;
1042 char s_used[FMT_SCALED_STRSIZE], s_avail[FMT_SCALED_STRSIZE];
1043 char s_root[FMT_SCALED_STRSIZE], s_total[FMT_SCALED_STRSIZE];
1044 char s_icapacity[16], s_dcapacity[16];
1045
1046 if (do_statvfs(conn, path, &st, 1) == -1)
1047 return -1;
1048 if (st.f_files == 0)
1049 strlcpy(s_icapacity, "ERR", sizeof(s_icapacity));
1050 else {
1051 snprintf(s_icapacity, sizeof(s_icapacity), "%3llu%%",
1052 (unsigned long long)(100 * (st.f_files - st.f_ffree) /
1053 st.f_files));
1054 }
1055 if (st.f_blocks == 0)
1056 strlcpy(s_dcapacity, "ERR", sizeof(s_dcapacity));
1057 else {
1058 snprintf(s_dcapacity, sizeof(s_dcapacity), "%3llu%%",
1059 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
1060 st.f_blocks));
1061 }
1062 if (iflag) {
1063 printf(" Inodes Used Avail "
1064 "(root) %%Capacity\n");
1065 printf("%11llu %11llu %11llu %11llu %s\n",
1066 (unsigned long long)st.f_files,
1067 (unsigned long long)(st.f_files - st.f_ffree),
1068 (unsigned long long)st.f_favail,
1069 (unsigned long long)st.f_ffree, s_icapacity);
1070 } else if (hflag) {
1071 strlcpy(s_used, "error", sizeof(s_used));
1072 strlcpy(s_avail, "error", sizeof(s_avail));
1073 strlcpy(s_root, "error", sizeof(s_root));
1074 strlcpy(s_total, "error", sizeof(s_total));
1075 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
1076 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
1077 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
1078 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
1079 printf(" Size Used Avail (root) %%Capacity\n");
1080 printf("%7sB %7sB %7sB %7sB %s\n",
1081 s_total, s_used, s_avail, s_root, s_dcapacity);
1082 } else {
1083 printf(" Size Used Avail "
1084 "(root) %%Capacity\n");
1085 printf("%12llu %12llu %12llu %12llu %s\n",
1086 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
1087 (unsigned long long)(st.f_frsize *
1088 (st.f_blocks - st.f_bfree) / 1024),
1089 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
1090 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
1091 s_dcapacity);
1092 }
1093 return 0;
1094}
1095
1096/*
1097 * Undo escaping of glob sequences in place. Used to undo extra escaping
1098 * applied in makeargv() when the string is destined for a function that
1099 * does not glob it.
1100 */
1101static void
1102undo_glob_escape(char *s)
1103{
1104 size_t i, j;
1105
1106 for (i = j = 0;;) {
1107 if (s[i] == '\0') {
1108 s[j] = '\0';
1109 return;
1110 }
1111 if (s[i] != '\\') {
1112 s[j++] = s[i++];
1113 continue;
1114 }
1115 /* s[i] == '\\' */
1116 ++i;
1117 switch (s[i]) {
1118 case '?':
1119 case '[':
1120 case '*':
1121 case '\\':
1122 s[j++] = s[i++];
1123 break;
1124 case '\0':
1125 s[j++] = '\\';
1126 s[j] = '\0';
1127 return;
1128 default:
1129 s[j++] = '\\';
1130 s[j++] = s[i++];
1131 break;
1132 }
1133 }
1134}
1135
1136/*
1137 * Split a string into an argument vector using sh(1)-style quoting,
1138 * comment and escaping rules, but with some tweaks to handle glob(3)
1139 * wildcards.
1140 * The "sloppy" flag allows for recovery from missing terminating quote, for
1141 * use in parsing incomplete commandlines during tab autocompletion.
1142 *
1143 * Returns NULL on error or a NULL-terminated array of arguments.
1144 *
1145 * If "lastquote" is not NULL, the quoting character used for the last
1146 * argument is placed in *lastquote ("\0", "'" or "\"").
1147 *
1148 * If "terminated" is not NULL, *terminated will be set to 1 when the
1149 * last argument's quote has been properly terminated or 0 otherwise.
1150 * This parameter is only of use if "sloppy" is set.
1151 */
1152#define MAXARGS 128
1153#define MAXARGLEN 8192
1154static char **
1155makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
1156 u_int *terminated)
1157{
1158 int argc, quot;
1159 size_t i, j;
1160 static char argvs[MAXARGLEN];
1161 static char *argv[MAXARGS + 1];
1162 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
1163
1164 *argcp = argc = 0;
1165 if (strlen(arg) > sizeof(argvs) - 1) {
1166 args_too_longs:
1167 error("string too long");
1168 return NULL;
1169 }
1170 if (terminated != NULL)
1171 *terminated = 1;
1172 if (lastquote != NULL)
1173 *lastquote = '\0';
1174 state = MA_START;
1175 i = j = 0;
1176 for (;;) {
1177 if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){
1178 error("Too many arguments.");
1179 return NULL;
1180 }
1181 if (isspace((unsigned char)arg[i])) {
1182 if (state == MA_UNQUOTED) {
1183 /* Terminate current argument */
1184 argvs[j++] = '\0';
1185 argc++;
1186 state = MA_START;
1187 } else if (state != MA_START)
1188 argvs[j++] = arg[i];
1189 } else if (arg[i] == '"' || arg[i] == '\'') {
1190 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1191 if (state == MA_START) {
1192 argv[argc] = argvs + j;
1193 state = q;
1194 if (lastquote != NULL)
1195 *lastquote = arg[i];
1196 } else if (state == MA_UNQUOTED)
1197 state = q;
1198 else if (state == q)
1199 state = MA_UNQUOTED;
1200 else
1201 argvs[j++] = arg[i];
1202 } else if (arg[i] == '\\') {
1203 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1204 quot = state == MA_SQUOTE ? '\'' : '"';
1205 /* Unescape quote we are in */
1206 /* XXX support \n and friends? */
1207 if (arg[i + 1] == quot) {
1208 i++;
1209 argvs[j++] = arg[i];
1210 } else if (arg[i + 1] == '?' ||
1211 arg[i + 1] == '[' || arg[i + 1] == '*') {
1212 /*
1213 * Special case for sftp: append
1214 * double-escaped glob sequence -
1215 * glob will undo one level of
1216 * escaping. NB. string can grow here.
1217 */
1218 if (j >= sizeof(argvs) - 5)
1219 goto args_too_longs;
1220 argvs[j++] = '\\';
1221 argvs[j++] = arg[i++];
1222 argvs[j++] = '\\';
1223 argvs[j++] = arg[i];
1224 } else {
1225 argvs[j++] = arg[i++];
1226 argvs[j++] = arg[i];
1227 }
1228 } else {
1229 if (state == MA_START) {
1230 argv[argc] = argvs + j;
1231 state = MA_UNQUOTED;
1232 if (lastquote != NULL)
1233 *lastquote = '\0';
1234 }
1235 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1236 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1237 /*
1238 * Special case for sftp: append
1239 * escaped glob sequence -
1240 * glob will undo one level of
1241 * escaping.
1242 */
1243 argvs[j++] = arg[i++];
1244 argvs[j++] = arg[i];
1245 } else {
1246 /* Unescape everything */
1247 /* XXX support \n and friends? */
1248 i++;
1249 argvs[j++] = arg[i];
1250 }
1251 }
1252 } else if (arg[i] == '#') {
1253 if (state == MA_SQUOTE || state == MA_DQUOTE)
1254 argvs[j++] = arg[i];
1255 else
1256 goto string_done;
1257 } else if (arg[i] == '\0') {
1258 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1259 if (sloppy) {
1260 state = MA_UNQUOTED;
1261 if (terminated != NULL)
1262 *terminated = 0;
1263 goto string_done;
1264 }
1265 error("Unterminated quoted argument");
1266 return NULL;
1267 }
1268 string_done:
1269 if (state == MA_UNQUOTED) {
1270 argvs[j++] = '\0';
1271 argc++;
1272 }
1273 break;
1274 } else {
1275 if (state == MA_START) {
1276 argv[argc] = argvs + j;
1277 state = MA_UNQUOTED;
1278 if (lastquote != NULL)
1279 *lastquote = '\0';
1280 }
1281 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1282 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1283 /*
1284 * Special case for sftp: escape quoted
1285 * glob(3) wildcards. NB. string can grow
1286 * here.
1287 */
1288 if (j >= sizeof(argvs) - 3)
1289 goto args_too_longs;
1290 argvs[j++] = '\\';
1291 argvs[j++] = arg[i];
1292 } else
1293 argvs[j++] = arg[i];
1294 }
1295 i++;
1296 }
1297 *argcp = argc;
1298 return argv;
1299}
1300
1301static int
1302parse_args(const char **cpp, int *ignore_errors, int *disable_echo, int *aflag,
1303 int *fflag, int *hflag, int *iflag, int *lflag, int *pflag,
1304 int *rflag, int *sflag,
1305 unsigned long *n_arg, char **path1, char **path2)
1306{
1307 const char *cmd, *cp = *cpp;
1308 char *cp2, **argv;
1309 int base = 0;
1310 long long ll;
1311 int path1_mandatory = 0, i, cmdnum, optidx, argc;
1312
1313 /* Skip leading whitespace */
1314 cp = cp + strspn(cp, WHITESPACE);
1315
1316 /*
1317 * Check for leading '-' (disable error processing) and '@' (suppress
1318 * command echo)
1319 */
1320 *ignore_errors = 0;
1321 *disable_echo = 0;
1322 for (;*cp != '\0'; cp++) {
1323 if (*cp == '-') {
1324 *ignore_errors = 1;
1325 } else if (*cp == '@') {
1326 *disable_echo = 1;
1327 } else {
1328 /* all other characters terminate prefix processing */
1329 break;
1330 }
1331 }
1332 cp = cp + strspn(cp, WHITESPACE);
1333
1334 /* Ignore blank lines and lines which begin with comment '#' char */
1335 if (*cp == '\0' || *cp == '#')
1336 return (0);
1337
1338 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
1339 return -1;
1340
1341 /* Figure out which command we have */
1342 for (i = 0; cmds[i].c != NULL; i++) {
1343 if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0)
1344 break;
1345 }
1346 cmdnum = cmds[i].n;
1347 cmd = cmds[i].c;
1348
1349 /* Special case */
1350 if (*cp == '!') {
1351 cp++;
1352 cmdnum = I_SHELL;
1353 } else if (cmdnum == -1) {
1354 error("Invalid command.");
1355 return -1;
1356 }
1357
1358 /* Get arguments and parse flags */
1359 *aflag = *fflag = *hflag = *iflag = *lflag = *pflag = 0;
1360 *rflag = *sflag = 0;
1361 *path1 = *path2 = NULL;
1362 optidx = 1;
1363 switch (cmdnum) {
1364 case I_GET:
1365 case I_REGET:
1366 case I_REPUT:
1367 case I_PUT:
1368 if ((optidx = parse_getput_flags(cmd, argv, argc,
1369 aflag, fflag, pflag, rflag)) == -1)
1370 return -1;
1371 /* Get first pathname (mandatory) */
1372 if (argc - optidx < 1) {
1373 error("You must specify at least one path after a "
1374 "%s command.", cmd);
1375 return -1;
1376 }
1377 *path1 = xstrdup(argv[optidx]);
1378 /* Get second pathname (optional) */
1379 if (argc - optidx > 1) {
1380 *path2 = xstrdup(argv[optidx + 1]);
1381 /* Destination is not globbed */
1382 undo_glob_escape(*path2);
1383 }
1384 break;
1385 case I_LINK:
1386 if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1387 return -1;
1388 goto parse_two_paths;
1389 case I_COPY:
1390 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1391 return -1;
1392 goto parse_two_paths;
1393 case I_RENAME:
1394 if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1)
1395 return -1;
1396 goto parse_two_paths;
1397 case I_SYMLINK:
1398 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1399 return -1;
1400 parse_two_paths:
1401 if (argc - optidx < 2) {
1402 error("You must specify two paths after a %s "
1403 "command.", cmd);
1404 return -1;
1405 }
1406 *path1 = xstrdup(argv[optidx]);
1407 *path2 = xstrdup(argv[optidx + 1]);
1408 /* Paths are not globbed */
1409 undo_glob_escape(*path1);
1410 undo_glob_escape(*path2);
1411 break;
1412 case I_RM:
1413 case I_MKDIR:
1414 case I_RMDIR:
1415 case I_LMKDIR:
1416 path1_mandatory = 1;
1417 /* FALLTHROUGH */
1418 case I_CHDIR:
1419 case I_LCHDIR:
1420 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1421 return -1;
1422 /* Get pathname (mandatory) */
1423 if (argc - optidx < 1) {
1424 if (!path1_mandatory)
1425 break; /* return a NULL path1 */
1426 error("You must specify a path after a %s command.",
1427 cmd);
1428 return -1;
1429 }
1430 *path1 = xstrdup(argv[optidx]);
1431 /* Only "rm" globs */
1432 if (cmdnum != I_RM)
1433 undo_glob_escape(*path1);
1434 break;
1435 case I_DF:
1436 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1437 iflag)) == -1)
1438 return -1;
1439 /* Default to current directory if no path specified */
1440 if (argc - optidx < 1)
1441 *path1 = NULL;
1442 else {
1443 *path1 = xstrdup(argv[optidx]);
1444 undo_glob_escape(*path1);
1445 }
1446 break;
1447 case I_LS:
1448 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
1449 return(-1);
1450 /* Path is optional */
1451 if (argc - optidx > 0)
1452 *path1 = xstrdup(argv[optidx]);
1453 break;
1454 case I_LLS:
1455 /* Skip ls command and following whitespace */
1456 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
1457 case I_SHELL:
1458 /* Uses the rest of the line */
1459 break;
1460 case I_LUMASK:
1461 case I_CHMOD:
1462 base = 8;
1463 /* FALLTHROUGH */
1464 case I_CHOWN:
1465 case I_CHGRP:
1466 if ((optidx = parse_ch_flags(cmd, argv, argc, hflag)) == -1)
1467 return -1;
1468 /* Get numeric arg (mandatory) */
1469 if (argc - optidx < 1)
1470 goto need_num_arg;
1471 errno = 0;
1472 ll = strtoll(argv[optidx], &cp2, base);
1473 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1474 ((ll == LLONG_MIN || ll == LLONG_MAX) && errno == ERANGE) ||
1475 ll < 0 || ll > UINT32_MAX) {
1476 need_num_arg:
1477 error("You must supply a numeric argument "
1478 "to the %s command.", cmd);
1479 return -1;
1480 }
1481 *n_arg = ll;
1482 if (cmdnum == I_LUMASK)
1483 break;
1484 /* Get pathname (mandatory) */
1485 if (argc - optidx < 2) {
1486 error("You must specify a path after a %s command.",
1487 cmd);
1488 return -1;
1489 }
1490 *path1 = xstrdup(argv[optidx + 1]);
1491 break;
1492 case I_QUIT:
1493 case I_PWD:
1494 case I_LPWD:
1495 case I_HELP:
1496 case I_VERSION:
1497 case I_PROGRESS:
1498 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1499 return -1;
1500 break;
1501 default:
1502 fatal("Command not implemented");
1503 }
1504
1505 *cpp = cp;
1506 return(cmdnum);
1507}
1508
1509static int
1510parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1511 const char *startdir, int err_abort, int echo_command)
1512{
1513 const char *ocmd = cmd;
1514 char *path1, *path2, *tmp;
1515 int ignore_errors = 0, disable_echo = 1;
1516 int aflag = 0, fflag = 0, hflag = 0, iflag = 0;
1517 int lflag = 0, pflag = 0, rflag = 0, sflag = 0;
1518 int cmdnum, i;
1519 unsigned long n_arg = 0;
1520 Attrib a, *aa;
1521 char path_buf[PATH_MAX];
1522 int err = 0;
1523 glob_t g;
1524
1525 path1 = path2 = NULL;
1526 cmdnum = parse_args(&cmd, &ignore_errors, &disable_echo, &aflag, &fflag,
1527 &hflag, &iflag, &lflag, &pflag, &rflag, &sflag, &n_arg,
1528 &path1, &path2);
1529 if (ignore_errors != 0)
1530 err_abort = 0;
1531
1532 if (echo_command && !disable_echo)
1533 mprintf("sftp> %s\n", ocmd);
1534
1535 memset(&g, 0, sizeof(g));
1536
1537 /* Perform command */
1538 switch (cmdnum) {
1539 case 0:
1540 /* Blank line */
1541 break;
1542 case -1:
1543 /* Unrecognized command */
1544 err = -1;
1545 break;
1546 case I_REGET:
1547 aflag = 1;
1548 /* FALLTHROUGH */
1549 case I_GET:
1550 err = process_get(conn, path1, path2, *pwd, pflag,
1551 rflag, aflag, fflag);
1552 break;
1553 case I_REPUT:
1554 aflag = 1;
1555 /* FALLTHROUGH */
1556 case I_PUT:
1557 err = process_put(conn, path1, path2, *pwd, pflag,
1558 rflag, aflag, fflag);
1559 break;
1560 case I_COPY:
1561 path1 = make_absolute(path1, *pwd);
1562 path2 = make_absolute(path2, *pwd);
1563 err = do_copy(conn, path1, path2);
1564 break;
1565 case I_RENAME:
1566 path1 = make_absolute(path1, *pwd);
1567 path2 = make_absolute(path2, *pwd);
1568 err = do_rename(conn, path1, path2, lflag);
1569 break;
1570 case I_SYMLINK:
1571 sflag = 1;
1572 /* FALLTHROUGH */
1573 case I_LINK:
1574 if (!sflag)
1575 path1 = make_absolute(path1, *pwd);
1576 path2 = make_absolute(path2, *pwd);
1577 err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
1578 break;
1579 case I_RM:
1580 path1 = make_absolute_pwd_glob(path1, *pwd);
1581 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1582 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1583 if (!quiet)
1584 mprintf("Removing %s\n", g.gl_pathv[i]);
1585 err = do_rm(conn, g.gl_pathv[i]);
1586 if (err != 0 && err_abort)
1587 break;
1588 }
1589 break;
1590 case I_MKDIR:
1591 path1 = make_absolute(path1, *pwd);
1592 attrib_clear(&a);
1593 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1594 a.perm = 0777;
1595 err = do_mkdir(conn, path1, &a, 1);
1596 break;
1597 case I_RMDIR:
1598 path1 = make_absolute(path1, *pwd);
1599 err = do_rmdir(conn, path1);
1600 break;
1601 case I_CHDIR:
1602 if (path1 == NULL || *path1 == '\0')
1603 path1 = xstrdup(startdir);
1604 path1 = make_absolute(path1, *pwd);
1605 if ((tmp = do_realpath(conn, path1)) == NULL) {
1606 err = 1;
1607 break;
1608 }
1609 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1610 free(tmp);
1611 err = 1;
1612 break;
1613 }
1614 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1615 error("Can't change directory: Can't check target");
1616 free(tmp);
1617 err = 1;
1618 break;
1619 }
1620 if (!S_ISDIR(aa->perm)) {
1621 error("Can't change directory: \"%s\" is not "
1622 "a directory", tmp);
1623 free(tmp);
1624 err = 1;
1625 break;
1626 }
1627 free(*pwd);
1628 *pwd = tmp;
1629 break;
1630 case I_LS:
1631 if (!path1) {
1632 do_ls_dir(conn, *pwd, *pwd, lflag);
1633 break;
1634 }
1635
1636 /* Strip pwd off beginning of non-absolute paths */
1637 tmp = NULL;
1638 if (!path_absolute(path1))
1639 tmp = *pwd;
1640
1641 path1 = make_absolute_pwd_glob(path1, *pwd);
1642 err = do_globbed_ls(conn, path1, tmp, lflag);
1643 break;
1644 case I_DF:
1645 /* Default to current directory if no path specified */
1646 if (path1 == NULL)
1647 path1 = xstrdup(*pwd);
1648 path1 = make_absolute(path1, *pwd);
1649 err = do_df(conn, path1, hflag, iflag);
1650 break;
1651 case I_LCHDIR:
1652 if (path1 == NULL || *path1 == '\0')
1653 path1 = xstrdup("~");
1654 tmp = tilde_expand_filename(path1, getuid());
1655 free(path1);
1656 path1 = tmp;
1657 if (chdir(path1) == -1) {
1658 error("Couldn't change local directory to "
1659 "\"%s\": %s", path1, strerror(errno));
1660 err = 1;
1661 }
1662 break;
1663 case I_LMKDIR:
1664 if (mkdir(path1, 0777) == -1) {
1665 error("Couldn't create local directory "
1666 "\"%s\": %s", path1, strerror(errno));
1667 err = 1;
1668 }
1669 break;
1670 case I_LLS:
1671 local_do_ls(cmd);
1672 break;
1673 case I_SHELL:
1674 local_do_shell(cmd);
1675 break;
1676 case I_LUMASK:
1677 umask(n_arg);
1678 printf("Local umask: %03lo\n", n_arg);
1679 break;
1680 case I_CHMOD:
1681 path1 = make_absolute_pwd_glob(path1, *pwd);
1682 attrib_clear(&a);
1683 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1684 a.perm = n_arg;
1685 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1686 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1687 if (!quiet)
1688 mprintf("Changing mode on %s\n",
1689 g.gl_pathv[i]);
1690 err = (hflag ? do_lsetstat : do_setstat)(conn,
1691 g.gl_pathv[i], &a);
1692 if (err != 0 && err_abort)
1693 break;
1694 }
1695 break;
1696 case I_CHOWN:
1697 case I_CHGRP:
1698 path1 = make_absolute_pwd_glob(path1, *pwd);
1699 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1700 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1701 if (!(aa = (hflag ? do_lstat : do_stat)(conn,
1702 g.gl_pathv[i], 0))) {
1703 if (err_abort) {
1704 err = -1;
1705 break;
1706 } else
1707 continue;
1708 }
1709 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1710 error("Can't get current ownership of "
1711 "remote file \"%s\"", g.gl_pathv[i]);
1712 if (err_abort) {
1713 err = -1;
1714 break;
1715 } else
1716 continue;
1717 }
1718 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1719 if (cmdnum == I_CHOWN) {
1720 if (!quiet)
1721 mprintf("Changing owner on %s\n",
1722 g.gl_pathv[i]);
1723 aa->uid = n_arg;
1724 } else {
1725 if (!quiet)
1726 mprintf("Changing group on %s\n",
1727 g.gl_pathv[i]);
1728 aa->gid = n_arg;
1729 }
1730 err = (hflag ? do_lsetstat : do_setstat)(conn,
1731 g.gl_pathv[i], aa);
1732 if (err != 0 && err_abort)
1733 break;
1734 }
1735 break;
1736 case I_PWD:
1737 mprintf("Remote working directory: %s\n", *pwd);
1738 break;
1739 case I_LPWD:
1740 if (!getcwd(path_buf, sizeof(path_buf))) {
1741 error("Couldn't get local cwd: %s", strerror(errno));
1742 err = -1;
1743 break;
1744 }
1745 mprintf("Local working directory: %s\n", path_buf);
1746 break;
1747 case I_QUIT:
1748 /* Processed below */
1749 break;
1750 case I_HELP:
1751 help();
1752 break;
1753 case I_VERSION:
1754 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1755 break;
1756 case I_PROGRESS:
1757 showprogress = !showprogress;
1758 if (showprogress)
1759 printf("Progress meter enabled\n");
1760 else
1761 printf("Progress meter disabled\n");
1762 break;
1763 default:
1764 fatal("%d is not implemented", cmdnum);
1765 }
1766
1767 if (g.gl_pathc)
1768 globfree(&g);
1769 free(path1);
1770 free(path2);
1771
1772 /* If an unignored error occurs in batch mode we should abort. */
1773 if (err_abort && err != 0)
1774 return (-1);
1775 else if (cmdnum == I_QUIT)
1776 return (1);
1777
1778 return (0);
1779}
1780
1781static char *
1782prompt(EditLine *el)
1783{
1784 return ("sftp> ");
1785}
1786
1787/* Display entries in 'list' after skipping the first 'len' chars */
1788static void
1789complete_display(char **list, u_int len)
1790{
1791 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1792 struct winsize ws;
1793 char *tmp;
1794
1795 /* Count entries for sort and find longest */
1796 for (y = 0; list[y]; y++)
1797 m = MAXIMUM(m, strlen(list[y]));
1798
1799 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1800 width = ws.ws_col;
1801
1802 m = m > len ? m - len : 0;
1803 columns = width / (m + 2);
1804 columns = MAXIMUM(columns, 1);
1805 colspace = width / columns;
1806 colspace = MINIMUM(colspace, width);
1807
1808 printf("\n");
1809 m = 1;
1810 for (y = 0; list[y]; y++) {
1811 llen = strlen(list[y]);
1812 tmp = llen > len ? list[y] + len : "";
1813 mprintf("%-*s", colspace, tmp);
1814 if (m >= columns) {
1815 printf("\n");
1816 m = 1;
1817 } else
1818 m++;
1819 }
1820 printf("\n");
1821}
1822
1823/*
1824 * Given a "list" of words that begin with a common prefix of "word",
1825 * attempt to find an autocompletion to extends "word" by the next
1826 * characters common to all entries in "list".
1827 */
1828static char *
1829complete_ambiguous(const char *word, char **list, size_t count)
1830{
1831 if (word == NULL)
1832 return NULL;
1833
1834 if (count > 0) {
1835 u_int y, matchlen = strlen(list[0]);
1836
1837 /* Find length of common stem */
1838 for (y = 1; list[y]; y++) {
1839 u_int x;
1840
1841 for (x = 0; x < matchlen; x++)
1842 if (list[0][x] != list[y][x])
1843 break;
1844
1845 matchlen = x;
1846 }
1847
1848 if (matchlen > strlen(word)) {
1849 char *tmp = xstrdup(list[0]);
1850
1851 tmp[matchlen] = '\0';
1852 return tmp;
1853 }
1854 }
1855
1856 return xstrdup(word);
1857}
1858
1859/* Autocomplete a sftp command */
1860static int
1861complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1862 int terminated)
1863{
1864 u_int y, count = 0, cmdlen, tmplen;
1865 char *tmp, **list, argterm[3];
1866 const LineInfo *lf;
1867
1868 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1869
1870 /* No command specified: display all available commands */
1871 if (cmd == NULL) {
1872 for (y = 0; cmds[y].c; y++)
1873 list[count++] = xstrdup(cmds[y].c);
1874
1875 list[count] = NULL;
1876 complete_display(list, 0);
1877
1878 for (y = 0; list[y] != NULL; y++)
1879 free(list[y]);
1880 free(list);
1881 return count;
1882 }
1883
1884 /* Prepare subset of commands that start with "cmd" */
1885 cmdlen = strlen(cmd);
1886 for (y = 0; cmds[y].c; y++) {
1887 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
1888 list[count++] = xstrdup(cmds[y].c);
1889 }
1890 list[count] = NULL;
1891
1892 if (count == 0) {
1893 free(list);
1894 return 0;
1895 }
1896
1897 /* Complete ambiguous command */
1898 tmp = complete_ambiguous(cmd, list, count);
1899 if (count > 1)
1900 complete_display(list, 0);
1901
1902 for (y = 0; list[y]; y++)
1903 free(list[y]);
1904 free(list);
1905
1906 if (tmp != NULL) {
1907 tmplen = strlen(tmp);
1908 cmdlen = strlen(cmd);
1909 /* If cmd may be extended then do so */
1910 if (tmplen > cmdlen)
1911 if (el_insertstr(el, tmp + cmdlen) == -1)
1912 fatal("el_insertstr failed.");
1913 lf = el_line(el);
1914 /* Terminate argument cleanly */
1915 if (count == 1) {
1916 y = 0;
1917 if (!terminated)
1918 argterm[y++] = quote;
1919 if (lastarg || *(lf->cursor) != ' ')
1920 argterm[y++] = ' ';
1921 argterm[y] = '\0';
1922 if (y > 0 && el_insertstr(el, argterm) == -1)
1923 fatal("el_insertstr failed.");
1924 }
1925 free(tmp);
1926 }
1927
1928 return count;
1929}
1930
1931/*
1932 * Determine whether a particular sftp command's arguments (if any) represent
1933 * local or remote files. The "cmdarg" argument specifies the actual argument
1934 * and accepts values 1 or 2.
1935 */
1936static int
1937complete_is_remote(char *cmd, int cmdarg) {
1938 int i;
1939
1940 if (cmd == NULL)
1941 return -1;
1942
1943 for (i = 0; cmds[i].c; i++) {
1944 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c))) {
1945 if (cmdarg == 1)
1946 return cmds[i].t;
1947 else if (cmdarg == 2)
1948 return cmds[i].t2;
1949 break;
1950 }
1951 }
1952
1953 return -1;
1954}
1955
1956/* Autocomplete a filename "file" */
1957static int
1958complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1959 char *file, int remote, int lastarg, char quote, int terminated)
1960{
1961 glob_t g;
1962 char *tmp, *tmp2, ins[8];
1963 u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
1964 int clen;
1965 const LineInfo *lf;
1966
1967 /* Glob from "file" location */
1968 if (file == NULL)
1969 tmp = xstrdup("*");
1970 else
1971 xasprintf(&tmp, "%s*", file);
1972
1973 /* Check if the path is absolute. */
1974 isabs = path_absolute(tmp);
1975
1976 memset(&g, 0, sizeof(g));
1977 if (remote != LOCAL) {
1978 tmp = make_absolute_pwd_glob(tmp, remote_path);
1979 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1980 } else
1981 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1982
1983 /* Determine length of pwd so we can trim completion display */
1984 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1985 /* Terminate counting on first unescaped glob metacharacter */
1986 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1987 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1988 hadglob = 1;
1989 break;
1990 }
1991 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1992 tmplen++;
1993 if (tmp[tmplen] == '/')
1994 pwdlen = tmplen + 1; /* track last seen '/' */
1995 }
1996 free(tmp);
1997 tmp = NULL;
1998
1999 if (g.gl_matchc == 0)
2000 goto out;
2001
2002 if (g.gl_matchc > 1)
2003 complete_display(g.gl_pathv, pwdlen);
2004
2005 /* Don't try to extend globs */
2006 if (file == NULL || hadglob)
2007 goto out;
2008
2009 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
2010 tmp = path_strip(tmp2, isabs ? NULL : remote_path);
2011 free(tmp2);
2012
2013 if (tmp == NULL)
2014 goto out;
2015
2016 tmplen = strlen(tmp);
2017 filelen = strlen(file);
2018
2019 /* Count the number of escaped characters in the input string. */
2020 cesc = isesc = 0;
2021 for (i = 0; i < filelen; i++) {
2022 if (!isesc && file[i] == '\\' && i + 1 < filelen){
2023 isesc = 1;
2024 cesc++;
2025 } else
2026 isesc = 0;
2027 }
2028
2029 if (tmplen > (filelen - cesc)) {
2030 tmp2 = tmp + filelen - cesc;
2031 len = strlen(tmp2);
2032 /* quote argument on way out */
2033 for (i = 0; i < len; i += clen) {
2034 if ((clen = mblen(tmp2 + i, len - i)) < 0 ||
2035 (size_t)clen > sizeof(ins) - 2)
2036 fatal("invalid multibyte character");
2037 ins[0] = '\\';
2038 memcpy(ins + 1, tmp2 + i, clen);
2039 ins[clen + 1] = '\0';
2040 switch (tmp2[i]) {
2041 case '\'':
2042 case '"':
2043 case '\\':
2044 case '\t':
2045 case '[':
2046 case ' ':
2047 case '#':
2048 case '*':
2049 if (quote == '\0' || tmp2[i] == quote) {
2050 if (el_insertstr(el, ins) == -1)
2051 fatal("el_insertstr "
2052 "failed.");
2053 break;
2054 }
2055 /* FALLTHROUGH */
2056 default:
2057 if (el_insertstr(el, ins + 1) == -1)
2058 fatal("el_insertstr failed.");
2059 break;
2060 }
2061 }
2062 }
2063
2064 lf = el_line(el);
2065 if (g.gl_matchc == 1) {
2066 i = 0;
2067 if (!terminated && quote != '\0')
2068 ins[i++] = quote;
2069 if (*(lf->cursor - 1) != '/' &&
2070 (lastarg || *(lf->cursor) != ' '))
2071 ins[i++] = ' ';
2072 ins[i] = '\0';
2073 if (i > 0 && el_insertstr(el, ins) == -1)
2074 fatal("el_insertstr failed.");
2075 }
2076 free(tmp);
2077
2078 out:
2079 globfree(&g);
2080 return g.gl_matchc;
2081}
2082
2083/* tab-completion hook function, called via libedit */
2084static unsigned char
2085complete(EditLine *el, int ch)
2086{
2087 char **argv, *line, quote;
2088 int argc, carg;
2089 u_int cursor, len, terminated, ret = CC_ERROR;
2090 const LineInfo *lf;
2091 struct complete_ctx *complete_ctx;
2092
2093 lf = el_line(el);
2094 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
2095 fatal_f("el_get failed");
2096
2097 /* Figure out which argument the cursor points to */
2098 cursor = lf->cursor - lf->buffer;
2099 line = xmalloc(cursor + 1);
2100 memcpy(line, lf->buffer, cursor);
2101 line[cursor] = '\0';
2102 argv = makeargv(line, &carg, 1, &quote, &terminated);
2103 free(line);
2104
2105 /* Get all the arguments on the line */
2106 len = lf->lastchar - lf->buffer;
2107 line = xmalloc(len + 1);
2108 memcpy(line, lf->buffer, len);
2109 line[len] = '\0';
2110 argv = makeargv(line, &argc, 1, NULL, NULL);
2111
2112 /* Ensure cursor is at EOL or a argument boundary */
2113 if (line[cursor] != ' ' && line[cursor] != '\0' &&
2114 line[cursor] != '\n') {
2115 free(line);
2116 return ret;
2117 }
2118
2119 if (carg == 0) {
2120 /* Show all available commands */
2121 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
2122 ret = CC_REDISPLAY;
2123 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
2124 /* Handle the command parsing */
2125 if (complete_cmd_parse(el, argv[0], argc == carg,
2126 quote, terminated) != 0)
2127 ret = CC_REDISPLAY;
2128 } else if (carg >= 1) {
2129 /* Handle file parsing */
2130 int remote = 0;
2131 int i = 0, cmdarg = 0;
2132 char *filematch = NULL;
2133
2134 if (carg > 1 && line[cursor-1] != ' ')
2135 filematch = argv[carg - 1];
2136
2137 for (i = 1; i < carg; i++) {
2138 /* Skip flags */
2139 if (argv[i][0] != '-')
2140 cmdarg++;
2141 }
2142
2143 /*
2144 * If previous argument is complete, then offer completion
2145 * on the next one.
2146 */
2147 if (line[cursor - 1] == ' ')
2148 cmdarg++;
2149
2150 remote = complete_is_remote(argv[0], cmdarg);
2151
2152 if ((remote == REMOTE || remote == LOCAL) &&
2153 complete_match(el, complete_ctx->conn,
2154 *complete_ctx->remote_pathp, filematch,
2155 remote, carg == argc, quote, terminated) != 0)
2156 ret = CC_REDISPLAY;
2157 }
2158
2159 free(line);
2160 return ret;
2161}
2162
2163static int
2164interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
2165{
2166 char *remote_path;
2167 char *dir = NULL, *startdir = NULL;
2168 char cmd[2048];
2169 int err, interactive;
2170 EditLine *el = NULL;
2171 History *hl = NULL;
2172 HistEvent hev;
2173 extern char *__progname;
2174 struct complete_ctx complete_ctx;
2175
2176 if (!batchmode && isatty(STDIN_FILENO)) {
2177 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
2178 fatal("Couldn't initialise editline");
2179 if ((hl = history_init()) == NULL)
2180 fatal("Couldn't initialise editline history");
2181 history(hl, &hev, H_SETSIZE, 100);
2182 el_set(el, EL_HIST, history, hl);
2183
2184 el_set(el, EL_PROMPT, prompt);
2185 el_set(el, EL_EDITOR, "emacs");
2186 el_set(el, EL_TERMINAL, NULL);
2187 el_set(el, EL_SIGNAL, 1);
2188 el_source(el, NULL);
2189
2190 /* Tab Completion */
2191 el_set(el, EL_ADDFN, "ftp-complete",
2192 "Context sensitive argument completion", complete);
2193 complete_ctx.conn = conn;
2194 complete_ctx.remote_pathp = &remote_path;
2195 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
2196 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
2197 /* enable ctrl-left-arrow and ctrl-right-arrow */
2198 el_set(el, EL_BIND, "\\e[1;5C", "em-next-word", NULL);
2199 el_set(el, EL_BIND, "\\e\\e[C", "em-next-word", NULL);
2200 el_set(el, EL_BIND, "\\e[1;5D", "ed-prev-word", NULL);
2201 el_set(el, EL_BIND, "\\e\\e[D", "ed-prev-word", NULL);
2202 /* make ^w match ksh behaviour */
2203 el_set(el, EL_BIND, "^w", "ed-delete-prev-word", NULL);
2204 }
2205
2206 remote_path = do_realpath(conn, ".");
2207 if (remote_path == NULL)
2208 fatal("Need cwd");
2209 startdir = xstrdup(remote_path);
2210
2211 if (file1 != NULL) {
2212 dir = xstrdup(file1);
2213 dir = make_absolute(dir, remote_path);
2214
2215 if (remote_is_dir(conn, dir) && file2 == NULL) {
2216 if (!quiet)
2217 mprintf("Changing to: %s\n", dir);
2218 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
2219 if (parse_dispatch_command(conn, cmd,
2220 &remote_path, startdir, 1, 0) != 0) {
2221 free(dir);
2222 free(startdir);
2223 free(remote_path);
2224 free(conn);
2225 return (-1);
2226 }
2227 } else {
2228 /* XXX this is wrong wrt quoting */
2229 snprintf(cmd, sizeof cmd, "get%s %s%s%s",
2230 global_aflag ? " -a" : "", dir,
2231 file2 == NULL ? "" : " ",
2232 file2 == NULL ? "" : file2);
2233 err = parse_dispatch_command(conn, cmd,
2234 &remote_path, startdir, 1, 0);
2235 free(dir);
2236 free(startdir);
2237 free(remote_path);
2238 free(conn);
2239 return (err);
2240 }
2241 free(dir);
2242 }
2243
2244 setvbuf(stdout, NULL, _IOLBF, 0);
2245 setvbuf(infile, NULL, _IOLBF, 0);
2246
2247 interactive = !batchmode && isatty(STDIN_FILENO);
2248 err = 0;
2249 for (;;) {
2250 struct sigaction sa;
2251 const char *line;
2252 int count = 0;
2253
2254 interrupted = 0;
2255 memset(&sa, 0, sizeof(sa));
2256 sa.sa_handler = interactive ? read_interrupt : killchild;
2257 if (sigaction(SIGINT, &sa, NULL) == -1) {
2258 debug3("sigaction(%s): %s", strsignal(SIGINT),
2259 strerror(errno));
2260 break;
2261 }
2262 if (el == NULL) {
2263 if (interactive)
2264 printf("sftp> ");
2265 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
2266 if (interactive)
2267 printf("\n");
2268 if (interrupted)
2269 continue;
2270 break;
2271 }
2272 } else {
2273 if ((line = el_gets(el, &count)) == NULL ||
2274 count <= 0) {
2275 printf("\n");
2276 if (interrupted)
2277 continue;
2278 break;
2279 }
2280 history(hl, &hev, H_ENTER, line);
2281 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
2282 fprintf(stderr, "Error: input line too long\n");
2283 continue;
2284 }
2285 }
2286
2287 cmd[strcspn(cmd, "\n")] = '\0';
2288
2289 /* Handle user interrupts gracefully during commands */
2290 interrupted = 0;
2291 ssh_signal(SIGINT, cmd_interrupt);
2292
2293 err = parse_dispatch_command(conn, cmd, &remote_path,
2294 startdir, batchmode, !interactive && el == NULL);
2295 if (err != 0)
2296 break;
2297 }
2298 ssh_signal(SIGCHLD, SIG_DFL);
2299 free(remote_path);
2300 free(startdir);
2301 free(conn);
2302
2303 if (el != NULL)
2304 el_end(el);
2305
2306 /* err == 1 signifies normal "quit" exit */
2307 return (err >= 0 ? 0 : -1);
2308}
2309
2310static void
2311connect_to_server(char *path, char **args, int *in, int *out)
2312{
2313 int c_in, c_out, inout[2];
2314
2315 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2316 fatal("socketpair: %s", strerror(errno));
2317 *in = *out = inout[0];
2318 c_in = c_out = inout[1];
2319
2320 if ((sshpid = fork()) == -1)
2321 fatal("fork: %s", strerror(errno));
2322 else if (sshpid == 0) {
2323 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2324 (dup2(c_out, STDOUT_FILENO) == -1)) {
2325 fprintf(stderr, "dup2: %s\n", strerror(errno));
2326 _exit(1);
2327 }
2328 close(*in);
2329 close(*out);
2330 close(c_in);
2331 close(c_out);
2332
2333 /*
2334 * The underlying ssh is in the same process group, so we must
2335 * ignore SIGINT if we want to gracefully abort commands,
2336 * otherwise the signal will make it to the ssh process and
2337 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2338 * underlying ssh, it must *not* ignore that signal.
2339 */
2340 ssh_signal(SIGINT, SIG_IGN);
2341 ssh_signal(SIGTERM, SIG_DFL);
2342 execvp(path, args);
2343 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
2344 _exit(1);
2345 }
2346
2347 ssh_signal(SIGTERM, killchild);
2348 ssh_signal(SIGINT, killchild);
2349 ssh_signal(SIGHUP, killchild);
2350 ssh_signal(SIGTSTP, suspchild);
2351 ssh_signal(SIGTTIN, suspchild);
2352 ssh_signal(SIGTTOU, suspchild);
2353 ssh_signal(SIGCHLD, sigchld_handler);
2354 close(c_in);
2355 close(c_out);
2356}
2357
2358static void
2359usage(void)
2360{
2361 extern char *__progname;
2362
2363 fprintf(stderr,
2364 "usage: %s [-46AaCfNpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
2365 " [-D sftp_server_command] [-F ssh_config] [-i identity_file]\n"
2366 " [-J destination] [-l limit] [-o ssh_option] [-P port]\n"
2367 " [-R num_requests] [-S program] [-s subsystem | sftp_server]\n"
2368 " [-X sftp_option] destination\n",
2369 __progname);
2370 exit(1);
2371}
2372
2373int
2374main(int argc, char **argv)
2375{
2376 int r, in, out, ch, err, tmp, port = -1, noisy = 0;
2377 char *host = NULL, *user, *cp, **cpp, *file2 = NULL;
2378 int debug_level = 0;
2379 char *file1 = NULL, *sftp_server = NULL;
2380 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
2381 const char *errstr;
2382 LogLevel ll = SYSLOG_LEVEL_INFO;
2383 arglist args;
2384 extern int optind;
2385 extern char *optarg;
2386 struct sftp_conn *conn;
2387 size_t copy_buffer_len = 0;
2388 size_t num_requests = 0;
2389 long long llv, limit_kbps = 0;
2390
2391 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2392 sanitise_stdfd();
2393 setlocale(LC_CTYPE, "");
2394
2395 memset(&args, '\0', sizeof(args));
2396 args.list = NULL;
2397 addargs(&args, "%s", ssh_program);
2398 addargs(&args, "-oForwardX11 no");
2399 addargs(&args, "-oPermitLocalCommand no");
2400 addargs(&args, "-oClearAllForwardings yes");
2401
2402 ll = SYSLOG_LEVEL_INFO;
2403 infile = stdin;
2404
2405 while ((ch = getopt(argc, argv,
2406 "1246AafhNpqrvCc:D:i:l:o:s:S:b:B:F:J:P:R:X:")) != -1) {
2407 switch (ch) {
2408 /* Passed through to ssh(1) */
2409 case 'A':
2410 case '4':
2411 case '6':
2412 case 'C':
2413 addargs(&args, "-%c", ch);
2414 break;
2415 /* Passed through to ssh(1) with argument */
2416 case 'F':
2417 case 'J':
2418 case 'c':
2419 case 'i':
2420 case 'o':
2421 addargs(&args, "-%c", ch);
2422 addargs(&args, "%s", optarg);
2423 break;
2424 case 'q':
2425 ll = SYSLOG_LEVEL_ERROR;
2426 quiet = 1;
2427 showprogress = 0;
2428 addargs(&args, "-%c", ch);
2429 break;
2430 case 'P':
2431 port = a2port(optarg);
2432 if (port <= 0)
2433 fatal("Bad port \"%s\"\n", optarg);
2434 break;
2435 case 'v':
2436 if (debug_level < 3) {
2437 addargs(&args, "-v");
2438 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2439 }
2440 debug_level++;
2441 break;
2442 case '1':
2443 fatal("SSH protocol v.1 is no longer supported");
2444 break;
2445 case '2':
2446 /* accept silently */
2447 break;
2448 case 'a':
2449 global_aflag = 1;
2450 break;
2451 case 'B':
2452 copy_buffer_len = strtol(optarg, &cp, 10);
2453 if (copy_buffer_len == 0 || *cp != '\0')
2454 fatal("Invalid buffer size \"%s\"", optarg);
2455 break;
2456 case 'b':
2457 if (batchmode)
2458 fatal("Batch file already specified.");
2459
2460 /* Allow "-" as stdin */
2461 if (strcmp(optarg, "-") != 0 &&
2462 (infile = fopen(optarg, "r")) == NULL)
2463 fatal("%s (%s).", strerror(errno), optarg);
2464 showprogress = 0;
2465 quiet = batchmode = 1;
2466 addargs(&args, "-obatchmode yes");
2467 break;
2468 case 'f':
2469 global_fflag = 1;
2470 break;
2471 case 'N':
2472 noisy = 1; /* Used to clear quiet mode after getopt */
2473 break;
2474 case 'p':
2475 global_pflag = 1;
2476 break;
2477 case 'D':
2478 sftp_direct = optarg;
2479 break;
2480 case 'l':
2481 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2482 &errstr);
2483 if (errstr != NULL)
2484 usage();
2485 limit_kbps *= 1024; /* kbps */
2486 break;
2487 case 'r':
2488 global_rflag = 1;
2489 break;
2490 case 'R':
2491 num_requests = strtol(optarg, &cp, 10);
2492 if (num_requests == 0 || *cp != '\0')
2493 fatal("Invalid number of requests \"%s\"",
2494 optarg);
2495 break;
2496 case 's':
2497 sftp_server = optarg;
2498 break;
2499 case 'S':
2500 ssh_program = optarg;
2501 replacearg(&args, 0, "%s", ssh_program);
2502 break;
2503 case 'X':
2504 /* Please keep in sync with ssh.c -X */
2505 if (strncmp(optarg, "buffer=", 7) == 0) {
2506 r = scan_scaled(optarg + 7, &llv);
2507 if (r == 0 && (llv <= 0 || llv > 256 * 1024)) {
2508 r = -1;
2509 errno = EINVAL;
2510 }
2511 if (r == -1) {
2512 fatal("Invalid buffer size \"%s\": %s",
2513 optarg + 7, strerror(errno));
2514 }
2515 copy_buffer_len = (size_t)llv;
2516 } else if (strncmp(optarg, "nrequests=", 10) == 0) {
2517 llv = strtonum(optarg + 10, 1, 256 * 1024,
2518 &errstr);
2519 if (errstr != NULL) {
2520 fatal("Invalid number of requests "
2521 "\"%s\": %s", optarg + 10, errstr);
2522 }
2523 num_requests = (size_t)llv;
2524 } else {
2525 fatal("Invalid -X option");
2526 }
2527 break;
2528 case 'h':
2529 default:
2530 usage();
2531 }
2532 }
2533
2534 /* Do this last because we want the user to be able to override it */
2535 addargs(&args, "-oForwardAgent no");
2536
2537 if (!isatty(STDERR_FILENO))
2538 showprogress = 0;
2539
2540 if (noisy)
2541 quiet = 0;
2542
2543 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2544
2545 if (sftp_direct == NULL) {
2546 if (optind == argc || argc > (optind + 2))
2547 usage();
2548 argv += optind;
2549
2550 switch (parse_uri("sftp", *argv, &user, &host, &tmp, &file1)) {
2551 case -1:
2552 usage();
2553 break;
2554 case 0:
2555 if (tmp != -1)
2556 port = tmp;
2557 break;
2558 default:
2559 /* Try with user, host and path. */
2560 if (parse_user_host_path(*argv, &user, &host,
2561 &file1) == 0)
2562 break;
2563 /* Try with user and host. */
2564 if (parse_user_host_port(*argv, &user, &host, NULL)
2565 == 0)
2566 break;
2567 /* Treat as a plain hostname. */
2568 host = xstrdup(*argv);
2569 host = cleanhostname(host);
2570 break;
2571 }
2572 file2 = *(argv + 1);
2573
2574 if (!*host) {
2575 fprintf(stderr, "Missing hostname\n");
2576 usage();
2577 }
2578
2579 if (port != -1)
2580 addargs(&args, "-oPort %d", port);
2581 if (user != NULL) {
2582 addargs(&args, "-l");
2583 addargs(&args, "%s", user);
2584 }
2585
2586 /* no subsystem if the server-spec contains a '/' */
2587 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2588 addargs(&args, "-s");
2589
2590 addargs(&args, "--");
2591 addargs(&args, "%s", host);
2592 addargs(&args, "%s", (sftp_server != NULL ?
2593 sftp_server : "sftp"));
2594
2595 connect_to_server(ssh_program, args.list, &in, &out);
2596 } else {
2597 if ((r = argv_split(sftp_direct, &tmp, &cpp, 1)) != 0)
2598 fatal_r(r, "Parse -D arguments");
2599 if (cpp[0] == 0)
2600 fatal("No sftp server specified via -D");
2601 connect_to_server(cpp[0], cpp, &in, &out);
2602 argv_free(cpp, tmp);
2603 }
2604 freeargs(&args);
2605
2606 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
2607 if (conn == NULL)
2608 fatal("Couldn't initialise connection to server");
2609
2610 if (!quiet) {
2611 if (sftp_direct == NULL)
2612 fprintf(stderr, "Connected to %s.\n", host);
2613 else
2614 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2615 }
2616
2617 err = interactive_loop(conn, file1, file2);
2618
2619 close(in);
2620 close(out);
2621 if (batchmode)
2622 fclose(infile);
2623
2624 while (waitpid(sshpid, NULL, 0) == -1 && sshpid > 1)
2625 if (errno != EINTR)
2626 fatal("Couldn't wait for ssh process: %s",
2627 strerror(errno));
2628
2629 exit(err == 0 ? 0 : 1);
2630}