blob: 76add3908ca74bbd0f74ba6a4705663767b7e94b [file] [log] [blame]
Greg Hartman9768ca42017-06-22 20:49:52 -07001/* $OpenBSD: sftp.c,v 1.178 2017/02/15 01:46:47 djm Exp $ */
Greg Hartmanbd77cf72015-02-25 13:21:06 -08002/*
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 "includes.h"
19
20#include <sys/types.h>
21#include <sys/ioctl.h>
22#ifdef HAVE_SYS_STAT_H
23# include <sys/stat.h>
24#endif
25#include <sys/param.h>
26#include <sys/socket.h>
27#include <sys/wait.h>
28#ifdef HAVE_SYS_STATVFS_H
29#include <sys/statvfs.h>
30#endif
31
32#include <ctype.h>
33#include <errno.h>
34
35#ifdef HAVE_PATHS_H
36# include <paths.h>
37#endif
38#ifdef HAVE_LIBGEN_H
39#include <libgen.h>
40#endif
Adam Langleyd0592972015-03-30 14:49:51 -070041#ifdef HAVE_LOCALE_H
42# include <locale.h>
43#endif
Greg Hartmanbd77cf72015-02-25 13:21:06 -080044#ifdef USE_LIBEDIT
45#include <histedit.h>
46#else
47typedef void EditLine;
48#endif
Adam Langleyd0592972015-03-30 14:49:51 -070049#include <limits.h>
Greg Hartmanbd77cf72015-02-25 13:21:06 -080050#include <signal.h>
Greg Hartman9768ca42017-06-22 20:49:52 -070051#include <stdarg.h>
Greg Hartmanbd77cf72015-02-25 13:21:06 -080052#include <stdlib.h>
53#include <stdio.h>
54#include <string.h>
55#include <unistd.h>
56#include <stdarg.h>
57
58#ifdef HAVE_UTIL_H
59# include <util.h>
60#endif
61
Greg Hartmanbd77cf72015-02-25 13:21:06 -080062#include "xmalloc.h"
63#include "log.h"
64#include "pathnames.h"
65#include "misc.h"
Greg Hartman9768ca42017-06-22 20:49:52 -070066#include "utf8.h"
Greg Hartmanbd77cf72015-02-25 13:21:06 -080067
68#include "sftp.h"
Adam Langleyd0592972015-03-30 14:49:51 -070069#include "ssherr.h"
70#include "sshbuf.h"
Greg Hartmanbd77cf72015-02-25 13:21:06 -080071#include "sftp-common.h"
72#include "sftp-client.h"
73
74#define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */
75#define DEFAULT_NUM_REQUESTS 64 /* # concurrent outstanding requests */
76
77/* File to read commands from */
78FILE* infile;
79
80/* Are we in batchfile mode? */
81int batchmode = 0;
82
83/* PID of ssh transport process */
84static pid_t sshpid = -1;
85
Adam Langleyd0592972015-03-30 14:49:51 -070086/* Suppress diagnositic messages */
87int quiet = 0;
88
Greg Hartmanbd77cf72015-02-25 13:21:06 -080089/* This is set to 0 if the progressmeter is not desired. */
90int showprogress = 1;
91
92/* When this option is set, we always recursively download/upload directories */
93int global_rflag = 0;
94
Adam Langleyd0592972015-03-30 14:49:51 -070095/* When this option is set, we resume download or upload if possible */
96int global_aflag = 0;
97
Greg Hartmanbd77cf72015-02-25 13:21:06 -080098/* When this option is set, the file transfers will always preserve times */
99int global_pflag = 0;
100
Adam Langleyd0592972015-03-30 14:49:51 -0700101/* When this option is set, transfers will have fsync() called on each file */
102int global_fflag = 0;
103
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800104/* SIGINT received during command processing */
105volatile sig_atomic_t interrupted = 0;
106
107/* I wish qsort() took a separate ctx for the comparison function...*/
108int sort_flag;
109
110/* Context used for commandline completion */
111struct complete_ctx {
112 struct sftp_conn *conn;
113 char **remote_pathp;
114};
115
116int remote_glob(struct sftp_conn *, const char *, int,
117 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
118
119extern char *__progname;
120
121/* Separators for interactive commands */
122#define WHITESPACE " \t\r\n"
123
124/* ls flags */
125#define LS_LONG_VIEW 0x0001 /* Full view ala ls -l */
126#define LS_SHORT_VIEW 0x0002 /* Single row view ala ls -1 */
127#define LS_NUMERIC_VIEW 0x0004 /* Long view with numeric uid/gid */
128#define LS_NAME_SORT 0x0008 /* Sort by name (default) */
129#define LS_TIME_SORT 0x0010 /* Sort by mtime */
130#define LS_SIZE_SORT 0x0020 /* Sort by file size */
131#define LS_REVERSE_SORT 0x0040 /* Reverse sort order */
132#define LS_SHOW_ALL 0x0080 /* Don't skip filenames starting with '.' */
133#define LS_SI_UNITS 0x0100 /* Display sizes as K, M, G, etc. */
134
135#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS)
136#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
137
138/* Commands for interactive mode */
Adam Langleyd0592972015-03-30 14:49:51 -0700139enum sftp_command {
140 I_CHDIR = 1,
141 I_CHGRP,
142 I_CHMOD,
143 I_CHOWN,
144 I_DF,
145 I_GET,
146 I_HELP,
147 I_LCHDIR,
148 I_LINK,
149 I_LLS,
150 I_LMKDIR,
151 I_LPWD,
152 I_LS,
153 I_LUMASK,
154 I_MKDIR,
155 I_PUT,
156 I_PWD,
157 I_QUIT,
158 I_REGET,
159 I_RENAME,
160 I_REPUT,
161 I_RM,
162 I_RMDIR,
163 I_SHELL,
164 I_SYMLINK,
165 I_VERSION,
166 I_PROGRESS,
167};
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800168
169struct CMD {
170 const char *c;
171 const int n;
172 const int t;
173};
174
175/* Type of completion */
176#define NOARGS 0
177#define REMOTE 1
178#define LOCAL 2
179
180static const struct CMD cmds[] = {
181 { "bye", I_QUIT, NOARGS },
182 { "cd", I_CHDIR, REMOTE },
183 { "chdir", I_CHDIR, REMOTE },
184 { "chgrp", I_CHGRP, REMOTE },
185 { "chmod", I_CHMOD, REMOTE },
186 { "chown", I_CHOWN, REMOTE },
187 { "df", I_DF, REMOTE },
188 { "dir", I_LS, REMOTE },
189 { "exit", I_QUIT, NOARGS },
190 { "get", I_GET, REMOTE },
191 { "help", I_HELP, NOARGS },
192 { "lcd", I_LCHDIR, LOCAL },
193 { "lchdir", I_LCHDIR, LOCAL },
194 { "lls", I_LLS, LOCAL },
195 { "lmkdir", I_LMKDIR, LOCAL },
196 { "ln", I_LINK, REMOTE },
197 { "lpwd", I_LPWD, LOCAL },
198 { "ls", I_LS, REMOTE },
199 { "lumask", I_LUMASK, NOARGS },
200 { "mkdir", I_MKDIR, REMOTE },
201 { "mget", I_GET, REMOTE },
202 { "mput", I_PUT, LOCAL },
203 { "progress", I_PROGRESS, NOARGS },
204 { "put", I_PUT, LOCAL },
205 { "pwd", I_PWD, REMOTE },
206 { "quit", I_QUIT, NOARGS },
Adam Langleyd0592972015-03-30 14:49:51 -0700207 { "reget", I_REGET, REMOTE },
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800208 { "rename", I_RENAME, REMOTE },
Adam Langleyd0592972015-03-30 14:49:51 -0700209 { "reput", I_REPUT, LOCAL },
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800210 { "rm", I_RM, REMOTE },
211 { "rmdir", I_RMDIR, REMOTE },
212 { "symlink", I_SYMLINK, REMOTE },
213 { "version", I_VERSION, NOARGS },
214 { "!", I_SHELL, NOARGS },
215 { "?", I_HELP, NOARGS },
216 { NULL, -1, -1 }
217};
218
219int interactive_loop(struct sftp_conn *, char *file1, char *file2);
220
221/* ARGSUSED */
222static void
223killchild(int signo)
224{
225 if (sshpid > 1) {
226 kill(sshpid, SIGTERM);
227 waitpid(sshpid, NULL, 0);
228 }
229
230 _exit(1);
231}
232
233/* ARGSUSED */
234static void
Greg Hartman9768ca42017-06-22 20:49:52 -0700235suspchild(int signo)
236{
237 if (sshpid > 1) {
238 kill(sshpid, signo);
239 while (waitpid(sshpid, NULL, WUNTRACED) == -1 && errno == EINTR)
240 continue;
241 }
242 kill(getpid(), SIGSTOP);
243}
244
245/* ARGSUSED */
246static void
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800247cmd_interrupt(int signo)
248{
249 const char msg[] = "\rInterrupt \n";
250 int olderrno = errno;
251
Adam Langleyd0592972015-03-30 14:49:51 -0700252 (void)write(STDERR_FILENO, msg, sizeof(msg) - 1);
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800253 interrupted = 1;
254 errno = olderrno;
255}
256
257static void
258help(void)
259{
260 printf("Available commands:\n"
261 "bye Quit sftp\n"
262 "cd path Change remote directory to 'path'\n"
263 "chgrp grp path Change group of file 'path' to 'grp'\n"
264 "chmod mode path Change permissions of file 'path' to 'mode'\n"
265 "chown own path Change owner of file 'path' to 'own'\n"
266 "df [-hi] [path] Display statistics for current directory or\n"
267 " filesystem containing 'path'\n"
268 "exit Quit sftp\n"
Adam Langleyd0592972015-03-30 14:49:51 -0700269 "get [-afPpRr] remote [local] Download file\n"
270 "reget [-fPpRr] remote [local] Resume download file\n"
271 "reput [-fPpRr] [local] remote Resume upload file\n"
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800272 "help Display this help text\n"
273 "lcd path Change local directory to 'path'\n"
274 "lls [ls-options [path]] Display local directory listing\n"
275 "lmkdir path Create local directory\n"
276 "ln [-s] oldpath newpath Link remote file (-s for symlink)\n"
277 "lpwd Print local working directory\n"
278 "ls [-1afhlnrSt] [path] Display remote directory listing\n"
279 "lumask umask Set local umask to 'umask'\n"
280 "mkdir path Create remote directory\n"
281 "progress Toggle display of progress meter\n"
Adam Langleyd0592972015-03-30 14:49:51 -0700282 "put [-afPpRr] local [remote] Upload file\n"
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800283 "pwd Display remote working directory\n"
284 "quit Quit sftp\n"
285 "rename oldpath newpath Rename remote file\n"
286 "rm path Delete remote file\n"
287 "rmdir path Remove remote directory\n"
288 "symlink oldpath newpath Symlink remote file\n"
289 "version Show SFTP version\n"
290 "!command Execute 'command' in local shell\n"
291 "! Escape to local shell\n"
292 "? Synonym for help\n");
293}
294
295static void
296local_do_shell(const char *args)
297{
298 int status;
299 char *shell;
300 pid_t pid;
301
302 if (!*args)
303 args = NULL;
304
305 if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
306 shell = _PATH_BSHELL;
307
308 if ((pid = fork()) == -1)
309 fatal("Couldn't fork: %s", strerror(errno));
310
311 if (pid == 0) {
312 /* XXX: child has pipe fds to ssh subproc open - issue? */
313 if (args) {
314 debug3("Executing %s -c \"%s\"", shell, args);
315 execl(shell, shell, "-c", args, (char *)NULL);
316 } else {
317 debug3("Executing %s", shell);
318 execl(shell, shell, (char *)NULL);
319 }
320 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
321 strerror(errno));
322 _exit(1);
323 }
324 while (waitpid(pid, &status, 0) == -1)
325 if (errno != EINTR)
326 fatal("Couldn't wait for child: %s", strerror(errno));
327 if (!WIFEXITED(status))
328 error("Shell exited abnormally");
329 else if (WEXITSTATUS(status))
330 error("Shell exited with status %d", WEXITSTATUS(status));
331}
332
333static void
334local_do_ls(const char *args)
335{
336 if (!args || !*args)
337 local_do_shell(_PATH_LS);
338 else {
339 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
340 char *buf = xmalloc(len);
341
342 /* XXX: quoting - rip quoting code from ftp? */
343 snprintf(buf, len, _PATH_LS " %s", args);
344 local_do_shell(buf);
Adam Langleyd0592972015-03-30 14:49:51 -0700345 free(buf);
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800346 }
347}
348
349/* Strip one path (usually the pwd) from the start of another */
350static char *
Greg Hartman9768ca42017-06-22 20:49:52 -0700351path_strip(const char *path, const char *strip)
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800352{
353 size_t len;
354
355 if (strip == NULL)
356 return (xstrdup(path));
357
358 len = strlen(strip);
359 if (strncmp(path, strip, len) == 0) {
360 if (strip[len - 1] != '/' && path[len] == '/')
361 len++;
362 return (xstrdup(path + len));
363 }
364
365 return (xstrdup(path));
366}
367
368static char *
Greg Hartman9768ca42017-06-22 20:49:52 -0700369make_absolute(char *p, const char *pwd)
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800370{
371 char *abs_str;
372
373 /* Derelativise */
374 if (p && p[0] != '/') {
375 abs_str = path_append(pwd, p);
Adam Langleyd0592972015-03-30 14:49:51 -0700376 free(p);
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800377 return(abs_str);
378 } else
379 return(p);
380}
381
382static int
Adam Langleyd0592972015-03-30 14:49:51 -0700383parse_getput_flags(const char *cmd, char **argv, int argc,
384 int *aflag, int *fflag, int *pflag, int *rflag)
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800385{
386 extern int opterr, optind, optopt, optreset;
387 int ch;
388
389 optind = optreset = 1;
390 opterr = 0;
391
Adam Langleyd0592972015-03-30 14:49:51 -0700392 *aflag = *fflag = *rflag = *pflag = 0;
393 while ((ch = getopt(argc, argv, "afPpRr")) != -1) {
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800394 switch (ch) {
Adam Langleyd0592972015-03-30 14:49:51 -0700395 case 'a':
396 *aflag = 1;
397 break;
398 case 'f':
399 *fflag = 1;
400 break;
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800401 case 'p':
402 case 'P':
403 *pflag = 1;
404 break;
405 case 'r':
406 case 'R':
407 *rflag = 1;
408 break;
409 default:
410 error("%s: Invalid flag -%c", cmd, optopt);
411 return -1;
412 }
413 }
414
415 return optind;
416}
417
418static int
419parse_link_flags(const char *cmd, char **argv, int argc, int *sflag)
420{
421 extern int opterr, optind, optopt, optreset;
422 int ch;
423
424 optind = optreset = 1;
425 opterr = 0;
426
427 *sflag = 0;
428 while ((ch = getopt(argc, argv, "s")) != -1) {
429 switch (ch) {
430 case 's':
431 *sflag = 1;
432 break;
433 default:
434 error("%s: Invalid flag -%c", cmd, optopt);
435 return -1;
436 }
437 }
438
439 return optind;
440}
441
442static int
Adam Langleyd0592972015-03-30 14:49:51 -0700443parse_rename_flags(const char *cmd, char **argv, int argc, int *lflag)
444{
445 extern int opterr, optind, optopt, optreset;
446 int ch;
447
448 optind = optreset = 1;
449 opterr = 0;
450
451 *lflag = 0;
452 while ((ch = getopt(argc, argv, "l")) != -1) {
453 switch (ch) {
454 case 'l':
455 *lflag = 1;
456 break;
457 default:
458 error("%s: Invalid flag -%c", cmd, optopt);
459 return -1;
460 }
461 }
462
463 return optind;
464}
465
466static int
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800467parse_ls_flags(char **argv, int argc, int *lflag)
468{
469 extern int opterr, optind, optopt, optreset;
470 int ch;
471
472 optind = optreset = 1;
473 opterr = 0;
474
475 *lflag = LS_NAME_SORT;
476 while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
477 switch (ch) {
478 case '1':
479 *lflag &= ~VIEW_FLAGS;
480 *lflag |= LS_SHORT_VIEW;
481 break;
482 case 'S':
483 *lflag &= ~SORT_FLAGS;
484 *lflag |= LS_SIZE_SORT;
485 break;
486 case 'a':
487 *lflag |= LS_SHOW_ALL;
488 break;
489 case 'f':
490 *lflag &= ~SORT_FLAGS;
491 break;
492 case 'h':
493 *lflag |= LS_SI_UNITS;
494 break;
495 case 'l':
496 *lflag &= ~LS_SHORT_VIEW;
497 *lflag |= LS_LONG_VIEW;
498 break;
499 case 'n':
500 *lflag &= ~LS_SHORT_VIEW;
501 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
502 break;
503 case 'r':
504 *lflag |= LS_REVERSE_SORT;
505 break;
506 case 't':
507 *lflag &= ~SORT_FLAGS;
508 *lflag |= LS_TIME_SORT;
509 break;
510 default:
511 error("ls: Invalid flag -%c", optopt);
512 return -1;
513 }
514 }
515
516 return optind;
517}
518
519static int
520parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
521{
522 extern int opterr, optind, optopt, optreset;
523 int ch;
524
525 optind = optreset = 1;
526 opterr = 0;
527
528 *hflag = *iflag = 0;
529 while ((ch = getopt(argc, argv, "hi")) != -1) {
530 switch (ch) {
531 case 'h':
532 *hflag = 1;
533 break;
534 case 'i':
535 *iflag = 1;
536 break;
537 default:
538 error("%s: Invalid flag -%c", cmd, optopt);
539 return -1;
540 }
541 }
542
543 return optind;
544}
545
546static int
Adam Langleyd0592972015-03-30 14:49:51 -0700547parse_no_flags(const char *cmd, char **argv, int argc)
548{
549 extern int opterr, optind, optopt, optreset;
550 int ch;
551
552 optind = optreset = 1;
553 opterr = 0;
554
555 while ((ch = getopt(argc, argv, "")) != -1) {
556 switch (ch) {
557 default:
558 error("%s: Invalid flag -%c", cmd, optopt);
559 return -1;
560 }
561 }
562
563 return optind;
564}
565
566static int
Greg Hartman9768ca42017-06-22 20:49:52 -0700567is_dir(const char *path)
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800568{
569 struct stat sb;
570
571 /* XXX: report errors? */
572 if (stat(path, &sb) == -1)
573 return(0);
574
575 return(S_ISDIR(sb.st_mode));
576}
577
578static int
Greg Hartman9768ca42017-06-22 20:49:52 -0700579remote_is_dir(struct sftp_conn *conn, const char *path)
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800580{
581 Attrib *a;
582
583 /* XXX: report errors? */
584 if ((a = do_stat(conn, path, 1)) == NULL)
585 return(0);
586 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
587 return(0);
588 return(S_ISDIR(a->perm));
589}
590
591/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
592static int
Greg Hartman9768ca42017-06-22 20:49:52 -0700593pathname_is_dir(const char *pathname)
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800594{
595 size_t l = strlen(pathname);
596
597 return l > 0 && pathname[l - 1] == '/';
598}
599
600static int
Greg Hartman9768ca42017-06-22 20:49:52 -0700601process_get(struct sftp_conn *conn, const char *src, const char *dst,
602 const char *pwd, int pflag, int rflag, int resume, int fflag)
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800603{
604 char *abs_src = NULL;
605 char *abs_dst = NULL;
606 glob_t g;
607 char *filename, *tmp=NULL;
Adam Langleyd0592972015-03-30 14:49:51 -0700608 int i, r, err = 0;
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800609
610 abs_src = xstrdup(src);
611 abs_src = make_absolute(abs_src, pwd);
612 memset(&g, 0, sizeof(g));
613
614 debug3("Looking up %s", abs_src);
Adam Langleyd0592972015-03-30 14:49:51 -0700615 if ((r = remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) != 0) {
616 if (r == GLOB_NOSPACE) {
617 error("Too many matches for \"%s\".", abs_src);
618 } else {
619 error("File \"%s\" not found.", abs_src);
620 }
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800621 err = -1;
622 goto out;
623 }
624
625 /*
626 * If multiple matches then dst must be a directory or
627 * unspecified.
628 */
629 if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
630 error("Multiple source paths, but destination "
631 "\"%s\" is not a directory", dst);
632 err = -1;
633 goto out;
634 }
635
636 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
637 tmp = xstrdup(g.gl_pathv[i]);
638 if ((filename = basename(tmp)) == NULL) {
639 error("basename %s: %s", tmp, strerror(errno));
Adam Langleyd0592972015-03-30 14:49:51 -0700640 free(tmp);
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800641 err = -1;
642 goto out;
643 }
644
645 if (g.gl_matchc == 1 && dst) {
646 if (is_dir(dst)) {
647 abs_dst = path_append(dst, filename);
648 } else {
649 abs_dst = xstrdup(dst);
650 }
651 } else if (dst) {
652 abs_dst = path_append(dst, filename);
653 } else {
654 abs_dst = xstrdup(filename);
655 }
Adam Langleyd0592972015-03-30 14:49:51 -0700656 free(tmp);
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800657
Adam Langleyd0592972015-03-30 14:49:51 -0700658 resume |= global_aflag;
659 if (!quiet && resume)
Greg Hartman9768ca42017-06-22 20:49:52 -0700660 mprintf("Resuming %s to %s\n",
661 g.gl_pathv[i], abs_dst);
Adam Langleyd0592972015-03-30 14:49:51 -0700662 else if (!quiet && !resume)
Greg Hartman9768ca42017-06-22 20:49:52 -0700663 mprintf("Fetching %s to %s\n",
664 g.gl_pathv[i], abs_dst);
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800665 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
Adam Langleyd0592972015-03-30 14:49:51 -0700666 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
667 pflag || global_pflag, 1, resume,
668 fflag || global_fflag) == -1)
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800669 err = -1;
670 } else {
671 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
Adam Langleyd0592972015-03-30 14:49:51 -0700672 pflag || global_pflag, resume,
673 fflag || global_fflag) == -1)
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800674 err = -1;
675 }
Adam Langleyd0592972015-03-30 14:49:51 -0700676 free(abs_dst);
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800677 abs_dst = NULL;
678 }
679
680out:
Adam Langleyd0592972015-03-30 14:49:51 -0700681 free(abs_src);
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800682 globfree(&g);
683 return(err);
684}
685
686static int
Greg Hartman9768ca42017-06-22 20:49:52 -0700687process_put(struct sftp_conn *conn, const char *src, const char *dst,
688 const char *pwd, int pflag, int rflag, int resume, int fflag)
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800689{
690 char *tmp_dst = NULL;
691 char *abs_dst = NULL;
692 char *tmp = NULL, *filename = NULL;
693 glob_t g;
694 int err = 0;
695 int i, dst_is_dir = 1;
696 struct stat sb;
697
698 if (dst) {
699 tmp_dst = xstrdup(dst);
700 tmp_dst = make_absolute(tmp_dst, pwd);
701 }
702
703 memset(&g, 0, sizeof(g));
704 debug3("Looking up %s", src);
705 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
706 error("File \"%s\" not found.", src);
707 err = -1;
708 goto out;
709 }
710
711 /* If we aren't fetching to pwd then stash this status for later */
712 if (tmp_dst != NULL)
713 dst_is_dir = remote_is_dir(conn, tmp_dst);
714
715 /* If multiple matches, dst may be directory or unspecified */
716 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
717 error("Multiple paths match, but destination "
718 "\"%s\" is not a directory", tmp_dst);
719 err = -1;
720 goto out;
721 }
722
723 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
724 if (stat(g.gl_pathv[i], &sb) == -1) {
725 err = -1;
726 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
727 continue;
728 }
Adam Langleyd0592972015-03-30 14:49:51 -0700729
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800730 tmp = xstrdup(g.gl_pathv[i]);
731 if ((filename = basename(tmp)) == NULL) {
732 error("basename %s: %s", tmp, strerror(errno));
Adam Langleyd0592972015-03-30 14:49:51 -0700733 free(tmp);
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800734 err = -1;
735 goto out;
736 }
737
738 if (g.gl_matchc == 1 && tmp_dst) {
739 /* If directory specified, append filename */
740 if (dst_is_dir)
741 abs_dst = path_append(tmp_dst, filename);
742 else
743 abs_dst = xstrdup(tmp_dst);
744 } else if (tmp_dst) {
745 abs_dst = path_append(tmp_dst, filename);
746 } else {
747 abs_dst = make_absolute(xstrdup(filename), pwd);
748 }
Adam Langleyd0592972015-03-30 14:49:51 -0700749 free(tmp);
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800750
Adam Langleyd0592972015-03-30 14:49:51 -0700751 resume |= global_aflag;
752 if (!quiet && resume)
Greg Hartman9768ca42017-06-22 20:49:52 -0700753 mprintf("Resuming upload of %s to %s\n",
754 g.gl_pathv[i], abs_dst);
Adam Langleyd0592972015-03-30 14:49:51 -0700755 else if (!quiet && !resume)
Greg Hartman9768ca42017-06-22 20:49:52 -0700756 mprintf("Uploading %s to %s\n",
757 g.gl_pathv[i], abs_dst);
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800758 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
759 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
Adam Langleyd0592972015-03-30 14:49:51 -0700760 pflag || global_pflag, 1, resume,
761 fflag || global_fflag) == -1)
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800762 err = -1;
763 } else {
764 if (do_upload(conn, g.gl_pathv[i], abs_dst,
Adam Langleyd0592972015-03-30 14:49:51 -0700765 pflag || global_pflag, resume,
766 fflag || global_fflag) == -1)
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800767 err = -1;
768 }
769 }
770
771out:
Adam Langleyd0592972015-03-30 14:49:51 -0700772 free(abs_dst);
773 free(tmp_dst);
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800774 globfree(&g);
775 return(err);
776}
777
778static int
779sdirent_comp(const void *aa, const void *bb)
780{
781 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
782 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
783 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
784
785#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
786 if (sort_flag & LS_NAME_SORT)
787 return (rmul * strcmp(a->filename, b->filename));
788 else if (sort_flag & LS_TIME_SORT)
789 return (rmul * NCMP(a->a.mtime, b->a.mtime));
790 else if (sort_flag & LS_SIZE_SORT)
791 return (rmul * NCMP(a->a.size, b->a.size));
792
793 fatal("Unknown ls sort type");
794}
795
796/* sftp ls.1 replacement for directories */
797static int
Greg Hartman9768ca42017-06-22 20:49:52 -0700798do_ls_dir(struct sftp_conn *conn, const char *path,
799 const char *strip_path, int lflag)
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800800{
801 int n;
802 u_int c = 1, colspace = 0, columns = 1;
803 SFTP_DIRENT **d;
804
805 if ((n = do_readdir(conn, path, &d)) != 0)
806 return (n);
807
808 if (!(lflag & LS_SHORT_VIEW)) {
809 u_int m = 0, width = 80;
810 struct winsize ws;
811 char *tmp;
812
813 /* Count entries for sort and find longest filename */
814 for (n = 0; d[n] != NULL; n++) {
815 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
Greg Hartman9768ca42017-06-22 20:49:52 -0700816 m = MAXIMUM(m, strlen(d[n]->filename));
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800817 }
818
819 /* Add any subpath that also needs to be counted */
820 tmp = path_strip(path, strip_path);
821 m += strlen(tmp);
Adam Langleyd0592972015-03-30 14:49:51 -0700822 free(tmp);
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800823
824 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
825 width = ws.ws_col;
826
827 columns = width / (m + 2);
Greg Hartman9768ca42017-06-22 20:49:52 -0700828 columns = MAXIMUM(columns, 1);
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800829 colspace = width / columns;
Greg Hartman9768ca42017-06-22 20:49:52 -0700830 colspace = MINIMUM(colspace, width);
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800831 }
832
833 if (lflag & SORT_FLAGS) {
834 for (n = 0; d[n] != NULL; n++)
835 ; /* count entries */
836 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
837 qsort(d, n, sizeof(*d), sdirent_comp);
838 }
839
840 for (n = 0; d[n] != NULL && !interrupted; n++) {
841 char *tmp, *fname;
842
843 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
844 continue;
845
846 tmp = path_append(path, d[n]->filename);
847 fname = path_strip(tmp, strip_path);
Adam Langleyd0592972015-03-30 14:49:51 -0700848 free(tmp);
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800849
850 if (lflag & LS_LONG_VIEW) {
851 if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
852 char *lname;
853 struct stat sb;
854
855 memset(&sb, 0, sizeof(sb));
856 attrib_to_stat(&d[n]->a, &sb);
857 lname = ls_file(fname, &sb, 1,
858 (lflag & LS_SI_UNITS));
Greg Hartman9768ca42017-06-22 20:49:52 -0700859 mprintf("%s\n", lname);
Adam Langleyd0592972015-03-30 14:49:51 -0700860 free(lname);
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800861 } else
Greg Hartman9768ca42017-06-22 20:49:52 -0700862 mprintf("%s\n", d[n]->longname);
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800863 } else {
Greg Hartman9768ca42017-06-22 20:49:52 -0700864 mprintf("%-*s", colspace, fname);
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800865 if (c >= columns) {
866 printf("\n");
867 c = 1;
868 } else
869 c++;
870 }
871
Adam Langleyd0592972015-03-30 14:49:51 -0700872 free(fname);
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800873 }
874
875 if (!(lflag & LS_LONG_VIEW) && (c != 1))
876 printf("\n");
877
878 free_sftp_dirents(d);
879 return (0);
880}
881
882/* sftp ls.1 replacement which handles path globs */
883static int
Greg Hartman9768ca42017-06-22 20:49:52 -0700884do_globbed_ls(struct sftp_conn *conn, const char *path,
885 const char *strip_path, int lflag)
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800886{
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800887 char *fname, *lname;
888 glob_t g;
Adam Langleyd0592972015-03-30 14:49:51 -0700889 int err, r;
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800890 struct winsize ws;
891 u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80;
892
893 memset(&g, 0, sizeof(g));
894
Adam Langleyd0592972015-03-30 14:49:51 -0700895 if ((r = remote_glob(conn, path,
896 GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
897 NULL, &g)) != 0 ||
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800898 (g.gl_pathc && !g.gl_matchc)) {
899 if (g.gl_pathc)
900 globfree(&g);
Adam Langleyd0592972015-03-30 14:49:51 -0700901 if (r == GLOB_NOSPACE) {
902 error("Can't ls: Too many matches for \"%s\"", path);
903 } else {
904 error("Can't ls: \"%s\" not found", path);
905 }
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800906 return -1;
907 }
908
909 if (interrupted)
910 goto out;
911
912 /*
913 * If the glob returns a single match and it is a directory,
914 * then just list its contents.
915 */
916 if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
917 S_ISDIR(g.gl_statv[0]->st_mode)) {
918 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
919 globfree(&g);
920 return err;
921 }
922
923 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
924 width = ws.ws_col;
925
926 if (!(lflag & LS_SHORT_VIEW)) {
927 /* Count entries for sort and find longest filename */
928 for (i = 0; g.gl_pathv[i]; i++)
Greg Hartman9768ca42017-06-22 20:49:52 -0700929 m = MAXIMUM(m, strlen(g.gl_pathv[i]));
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800930
931 columns = width / (m + 2);
Greg Hartman9768ca42017-06-22 20:49:52 -0700932 columns = MAXIMUM(columns, 1);
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800933 colspace = width / columns;
934 }
935
Adam Langleyd0592972015-03-30 14:49:51 -0700936 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800937 fname = path_strip(g.gl_pathv[i], strip_path);
938 if (lflag & LS_LONG_VIEW) {
939 if (g.gl_statv[i] == NULL) {
940 error("no stat information for %s", fname);
941 continue;
942 }
943 lname = ls_file(fname, g.gl_statv[i], 1,
944 (lflag & LS_SI_UNITS));
Greg Hartman9768ca42017-06-22 20:49:52 -0700945 mprintf("%s\n", lname);
Adam Langleyd0592972015-03-30 14:49:51 -0700946 free(lname);
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800947 } else {
Greg Hartman9768ca42017-06-22 20:49:52 -0700948 mprintf("%-*s", colspace, fname);
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800949 if (c >= columns) {
950 printf("\n");
951 c = 1;
952 } else
953 c++;
954 }
Adam Langleyd0592972015-03-30 14:49:51 -0700955 free(fname);
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800956 }
957
958 if (!(lflag & LS_LONG_VIEW) && (c != 1))
959 printf("\n");
960
961 out:
962 if (g.gl_pathc)
963 globfree(&g);
964
965 return 0;
966}
967
968static int
Greg Hartman9768ca42017-06-22 20:49:52 -0700969do_df(struct sftp_conn *conn, const char *path, int hflag, int iflag)
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800970{
971 struct sftp_statvfs st;
Greg Hartman9768ca42017-06-22 20:49:52 -0700972 char s_used[FMT_SCALED_STRSIZE], s_avail[FMT_SCALED_STRSIZE];
973 char s_root[FMT_SCALED_STRSIZE], s_total[FMT_SCALED_STRSIZE];
974 char s_icapacity[16], s_dcapacity[16];
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800975
976 if (do_statvfs(conn, path, &st, 1) == -1)
977 return -1;
Greg Hartman9768ca42017-06-22 20:49:52 -0700978 if (st.f_files == 0)
979 strlcpy(s_icapacity, "ERR", sizeof(s_icapacity));
980 else {
981 snprintf(s_icapacity, sizeof(s_icapacity), "%3llu%%",
982 (unsigned long long)(100 * (st.f_files - st.f_ffree) /
983 st.f_files));
984 }
985 if (st.f_blocks == 0)
986 strlcpy(s_dcapacity, "ERR", sizeof(s_dcapacity));
987 else {
988 snprintf(s_dcapacity, sizeof(s_dcapacity), "%3llu%%",
989 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
990 st.f_blocks));
991 }
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800992 if (iflag) {
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800993 printf(" Inodes Used Avail "
994 "(root) %%Capacity\n");
Greg Hartman9768ca42017-06-22 20:49:52 -0700995 printf("%11llu %11llu %11llu %11llu %s\n",
Greg Hartmanbd77cf72015-02-25 13:21:06 -0800996 (unsigned long long)st.f_files,
997 (unsigned long long)(st.f_files - st.f_ffree),
998 (unsigned long long)st.f_favail,
Greg Hartman9768ca42017-06-22 20:49:52 -0700999 (unsigned long long)st.f_ffree, s_icapacity);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001000 } else if (hflag) {
1001 strlcpy(s_used, "error", sizeof(s_used));
1002 strlcpy(s_avail, "error", sizeof(s_avail));
1003 strlcpy(s_root, "error", sizeof(s_root));
1004 strlcpy(s_total, "error", sizeof(s_total));
1005 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
1006 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
1007 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
1008 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
1009 printf(" Size Used Avail (root) %%Capacity\n");
Greg Hartman9768ca42017-06-22 20:49:52 -07001010 printf("%7sB %7sB %7sB %7sB %s\n",
1011 s_total, s_used, s_avail, s_root, s_dcapacity);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001012 } else {
1013 printf(" Size Used Avail "
1014 "(root) %%Capacity\n");
Greg Hartman9768ca42017-06-22 20:49:52 -07001015 printf("%12llu %12llu %12llu %12llu %s\n",
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001016 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
1017 (unsigned long long)(st.f_frsize *
1018 (st.f_blocks - st.f_bfree) / 1024),
1019 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
1020 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
Greg Hartman9768ca42017-06-22 20:49:52 -07001021 s_dcapacity);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001022 }
1023 return 0;
1024}
1025
1026/*
1027 * Undo escaping of glob sequences in place. Used to undo extra escaping
1028 * applied in makeargv() when the string is destined for a function that
1029 * does not glob it.
1030 */
1031static void
1032undo_glob_escape(char *s)
1033{
1034 size_t i, j;
1035
1036 for (i = j = 0;;) {
1037 if (s[i] == '\0') {
1038 s[j] = '\0';
1039 return;
1040 }
1041 if (s[i] != '\\') {
1042 s[j++] = s[i++];
1043 continue;
1044 }
1045 /* s[i] == '\\' */
1046 ++i;
1047 switch (s[i]) {
1048 case '?':
1049 case '[':
1050 case '*':
1051 case '\\':
1052 s[j++] = s[i++];
1053 break;
1054 case '\0':
1055 s[j++] = '\\';
1056 s[j] = '\0';
1057 return;
1058 default:
1059 s[j++] = '\\';
1060 s[j++] = s[i++];
1061 break;
1062 }
1063 }
1064}
1065
1066/*
1067 * Split a string into an argument vector using sh(1)-style quoting,
1068 * comment and escaping rules, but with some tweaks to handle glob(3)
1069 * wildcards.
1070 * The "sloppy" flag allows for recovery from missing terminating quote, for
1071 * use in parsing incomplete commandlines during tab autocompletion.
1072 *
1073 * Returns NULL on error or a NULL-terminated array of arguments.
1074 *
1075 * If "lastquote" is not NULL, the quoting character used for the last
1076 * argument is placed in *lastquote ("\0", "'" or "\"").
Adam Langleyd0592972015-03-30 14:49:51 -07001077 *
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001078 * If "terminated" is not NULL, *terminated will be set to 1 when the
1079 * last argument's quote has been properly terminated or 0 otherwise.
1080 * This parameter is only of use if "sloppy" is set.
1081 */
1082#define MAXARGS 128
1083#define MAXARGLEN 8192
1084static char **
1085makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
1086 u_int *terminated)
1087{
1088 int argc, quot;
1089 size_t i, j;
1090 static char argvs[MAXARGLEN];
1091 static char *argv[MAXARGS + 1];
1092 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
1093
1094 *argcp = argc = 0;
1095 if (strlen(arg) > sizeof(argvs) - 1) {
1096 args_too_longs:
1097 error("string too long");
1098 return NULL;
1099 }
1100 if (terminated != NULL)
1101 *terminated = 1;
1102 if (lastquote != NULL)
1103 *lastquote = '\0';
1104 state = MA_START;
1105 i = j = 0;
1106 for (;;) {
Adam Langleyd0592972015-03-30 14:49:51 -07001107 if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){
1108 error("Too many arguments.");
1109 return NULL;
1110 }
1111 if (isspace((unsigned char)arg[i])) {
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001112 if (state == MA_UNQUOTED) {
1113 /* Terminate current argument */
1114 argvs[j++] = '\0';
1115 argc++;
1116 state = MA_START;
1117 } else if (state != MA_START)
1118 argvs[j++] = arg[i];
1119 } else if (arg[i] == '"' || arg[i] == '\'') {
1120 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1121 if (state == MA_START) {
1122 argv[argc] = argvs + j;
1123 state = q;
1124 if (lastquote != NULL)
1125 *lastquote = arg[i];
Adam Langleyd0592972015-03-30 14:49:51 -07001126 } else if (state == MA_UNQUOTED)
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001127 state = q;
1128 else if (state == q)
1129 state = MA_UNQUOTED;
1130 else
1131 argvs[j++] = arg[i];
1132 } else if (arg[i] == '\\') {
1133 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1134 quot = state == MA_SQUOTE ? '\'' : '"';
1135 /* Unescape quote we are in */
1136 /* XXX support \n and friends? */
1137 if (arg[i + 1] == quot) {
1138 i++;
1139 argvs[j++] = arg[i];
1140 } else if (arg[i + 1] == '?' ||
1141 arg[i + 1] == '[' || arg[i + 1] == '*') {
1142 /*
1143 * Special case for sftp: append
1144 * double-escaped glob sequence -
1145 * glob will undo one level of
1146 * escaping. NB. string can grow here.
1147 */
1148 if (j >= sizeof(argvs) - 5)
1149 goto args_too_longs;
1150 argvs[j++] = '\\';
1151 argvs[j++] = arg[i++];
1152 argvs[j++] = '\\';
1153 argvs[j++] = arg[i];
1154 } else {
1155 argvs[j++] = arg[i++];
1156 argvs[j++] = arg[i];
1157 }
1158 } else {
1159 if (state == MA_START) {
1160 argv[argc] = argvs + j;
1161 state = MA_UNQUOTED;
1162 if (lastquote != NULL)
1163 *lastquote = '\0';
1164 }
1165 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1166 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1167 /*
1168 * Special case for sftp: append
1169 * escaped glob sequence -
1170 * glob will undo one level of
1171 * escaping.
1172 */
1173 argvs[j++] = arg[i++];
1174 argvs[j++] = arg[i];
1175 } else {
1176 /* Unescape everything */
1177 /* XXX support \n and friends? */
1178 i++;
1179 argvs[j++] = arg[i];
1180 }
1181 }
1182 } else if (arg[i] == '#') {
1183 if (state == MA_SQUOTE || state == MA_DQUOTE)
1184 argvs[j++] = arg[i];
1185 else
1186 goto string_done;
1187 } else if (arg[i] == '\0') {
1188 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1189 if (sloppy) {
1190 state = MA_UNQUOTED;
1191 if (terminated != NULL)
1192 *terminated = 0;
1193 goto string_done;
1194 }
1195 error("Unterminated quoted argument");
1196 return NULL;
1197 }
1198 string_done:
1199 if (state == MA_UNQUOTED) {
1200 argvs[j++] = '\0';
1201 argc++;
1202 }
1203 break;
1204 } else {
1205 if (state == MA_START) {
1206 argv[argc] = argvs + j;
1207 state = MA_UNQUOTED;
1208 if (lastquote != NULL)
1209 *lastquote = '\0';
1210 }
1211 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1212 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1213 /*
1214 * Special case for sftp: escape quoted
1215 * glob(3) wildcards. NB. string can grow
1216 * here.
1217 */
1218 if (j >= sizeof(argvs) - 3)
1219 goto args_too_longs;
1220 argvs[j++] = '\\';
1221 argvs[j++] = arg[i];
1222 } else
1223 argvs[j++] = arg[i];
1224 }
1225 i++;
1226 }
1227 *argcp = argc;
1228 return argv;
1229}
1230
1231static int
Adam Langleyd0592972015-03-30 14:49:51 -07001232parse_args(const char **cpp, int *ignore_errors, int *aflag,
Greg Hartman9768ca42017-06-22 20:49:52 -07001233 int *fflag, int *hflag, int *iflag, int *lflag, int *pflag,
Adam Langleyd0592972015-03-30 14:49:51 -07001234 int *rflag, int *sflag,
1235 unsigned long *n_arg, char **path1, char **path2)
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001236{
1237 const char *cmd, *cp = *cpp;
1238 char *cp2, **argv;
1239 int base = 0;
1240 long l;
1241 int i, cmdnum, optidx, argc;
1242
1243 /* Skip leading whitespace */
1244 cp = cp + strspn(cp, WHITESPACE);
1245
1246 /* Check for leading '-' (disable error processing) */
Adam Langleyd0592972015-03-30 14:49:51 -07001247 *ignore_errors = 0;
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001248 if (*cp == '-') {
Adam Langleyd0592972015-03-30 14:49:51 -07001249 *ignore_errors = 1;
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001250 cp++;
1251 cp = cp + strspn(cp, WHITESPACE);
1252 }
1253
1254 /* Ignore blank lines and lines which begin with comment '#' char */
1255 if (*cp == '\0' || *cp == '#')
1256 return (0);
1257
1258 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
1259 return -1;
1260
1261 /* Figure out which command we have */
1262 for (i = 0; cmds[i].c != NULL; i++) {
Adam Langleyd0592972015-03-30 14:49:51 -07001263 if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0)
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001264 break;
1265 }
1266 cmdnum = cmds[i].n;
1267 cmd = cmds[i].c;
1268
1269 /* Special case */
1270 if (*cp == '!') {
1271 cp++;
1272 cmdnum = I_SHELL;
1273 } else if (cmdnum == -1) {
1274 error("Invalid command.");
1275 return -1;
1276 }
1277
1278 /* Get arguments and parse flags */
Adam Langleyd0592972015-03-30 14:49:51 -07001279 *aflag = *fflag = *hflag = *iflag = *lflag = *pflag = 0;
1280 *rflag = *sflag = 0;
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001281 *path1 = *path2 = NULL;
1282 optidx = 1;
1283 switch (cmdnum) {
1284 case I_GET:
Adam Langleyd0592972015-03-30 14:49:51 -07001285 case I_REGET:
1286 case I_REPUT:
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001287 case I_PUT:
1288 if ((optidx = parse_getput_flags(cmd, argv, argc,
Adam Langleyd0592972015-03-30 14:49:51 -07001289 aflag, fflag, pflag, rflag)) == -1)
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001290 return -1;
1291 /* Get first pathname (mandatory) */
1292 if (argc - optidx < 1) {
1293 error("You must specify at least one path after a "
1294 "%s command.", cmd);
1295 return -1;
1296 }
1297 *path1 = xstrdup(argv[optidx]);
1298 /* Get second pathname (optional) */
1299 if (argc - optidx > 1) {
1300 *path2 = xstrdup(argv[optidx + 1]);
1301 /* Destination is not globbed */
1302 undo_glob_escape(*path2);
1303 }
1304 break;
1305 case I_LINK:
1306 if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1307 return -1;
Adam Langleyd0592972015-03-30 14:49:51 -07001308 goto parse_two_paths;
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001309 case I_RENAME:
Adam Langleyd0592972015-03-30 14:49:51 -07001310 if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1)
1311 return -1;
1312 goto parse_two_paths;
1313 case I_SYMLINK:
1314 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1315 return -1;
1316 parse_two_paths:
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001317 if (argc - optidx < 2) {
1318 error("You must specify two paths after a %s "
1319 "command.", cmd);
1320 return -1;
1321 }
1322 *path1 = xstrdup(argv[optidx]);
1323 *path2 = xstrdup(argv[optidx + 1]);
1324 /* Paths are not globbed */
1325 undo_glob_escape(*path1);
1326 undo_glob_escape(*path2);
1327 break;
1328 case I_RM:
1329 case I_MKDIR:
1330 case I_RMDIR:
1331 case I_CHDIR:
1332 case I_LCHDIR:
1333 case I_LMKDIR:
Adam Langleyd0592972015-03-30 14:49:51 -07001334 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1335 return -1;
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001336 /* Get pathname (mandatory) */
1337 if (argc - optidx < 1) {
1338 error("You must specify a path after a %s command.",
1339 cmd);
1340 return -1;
1341 }
1342 *path1 = xstrdup(argv[optidx]);
1343 /* Only "rm" globs */
1344 if (cmdnum != I_RM)
1345 undo_glob_escape(*path1);
1346 break;
1347 case I_DF:
1348 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1349 iflag)) == -1)
1350 return -1;
1351 /* Default to current directory if no path specified */
1352 if (argc - optidx < 1)
1353 *path1 = NULL;
1354 else {
1355 *path1 = xstrdup(argv[optidx]);
1356 undo_glob_escape(*path1);
1357 }
1358 break;
1359 case I_LS:
1360 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
1361 return(-1);
1362 /* Path is optional */
1363 if (argc - optidx > 0)
1364 *path1 = xstrdup(argv[optidx]);
1365 break;
1366 case I_LLS:
1367 /* Skip ls command and following whitespace */
1368 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
1369 case I_SHELL:
1370 /* Uses the rest of the line */
1371 break;
1372 case I_LUMASK:
1373 case I_CHMOD:
1374 base = 8;
1375 case I_CHOWN:
1376 case I_CHGRP:
Adam Langleyd0592972015-03-30 14:49:51 -07001377 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1378 return -1;
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001379 /* Get numeric arg (mandatory) */
1380 if (argc - optidx < 1)
1381 goto need_num_arg;
1382 errno = 0;
1383 l = strtol(argv[optidx], &cp2, base);
1384 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1385 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1386 l < 0) {
1387 need_num_arg:
1388 error("You must supply a numeric argument "
1389 "to the %s command.", cmd);
1390 return -1;
1391 }
1392 *n_arg = l;
1393 if (cmdnum == I_LUMASK)
1394 break;
1395 /* Get pathname (mandatory) */
1396 if (argc - optidx < 2) {
1397 error("You must specify a path after a %s command.",
1398 cmd);
1399 return -1;
1400 }
1401 *path1 = xstrdup(argv[optidx + 1]);
1402 break;
1403 case I_QUIT:
1404 case I_PWD:
1405 case I_LPWD:
1406 case I_HELP:
1407 case I_VERSION:
1408 case I_PROGRESS:
Adam Langleyd0592972015-03-30 14:49:51 -07001409 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1410 return -1;
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001411 break;
1412 default:
1413 fatal("Command not implemented");
1414 }
1415
1416 *cpp = cp;
1417 return(cmdnum);
1418}
1419
1420static int
1421parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1422 int err_abort)
1423{
1424 char *path1, *path2, *tmp;
Greg Hartman9768ca42017-06-22 20:49:52 -07001425 int ignore_errors = 0, aflag = 0, fflag = 0, hflag = 0,
Adam Langleyd0592972015-03-30 14:49:51 -07001426 iflag = 0;
1427 int lflag = 0, pflag = 0, rflag = 0, sflag = 0;
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001428 int cmdnum, i;
1429 unsigned long n_arg = 0;
1430 Attrib a, *aa;
Adam Langleyd0592972015-03-30 14:49:51 -07001431 char path_buf[PATH_MAX];
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001432 int err = 0;
1433 glob_t g;
1434
1435 path1 = path2 = NULL;
Adam Langleyd0592972015-03-30 14:49:51 -07001436 cmdnum = parse_args(&cmd, &ignore_errors, &aflag, &fflag, &hflag,
1437 &iflag, &lflag, &pflag, &rflag, &sflag, &n_arg, &path1, &path2);
1438 if (ignore_errors != 0)
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001439 err_abort = 0;
1440
1441 memset(&g, 0, sizeof(g));
1442
1443 /* Perform command */
1444 switch (cmdnum) {
1445 case 0:
1446 /* Blank line */
1447 break;
1448 case -1:
1449 /* Unrecognized command */
1450 err = -1;
1451 break;
Adam Langleyd0592972015-03-30 14:49:51 -07001452 case I_REGET:
1453 aflag = 1;
1454 /* FALLTHROUGH */
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001455 case I_GET:
Adam Langleyd0592972015-03-30 14:49:51 -07001456 err = process_get(conn, path1, path2, *pwd, pflag,
1457 rflag, aflag, fflag);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001458 break;
Adam Langleyd0592972015-03-30 14:49:51 -07001459 case I_REPUT:
1460 aflag = 1;
1461 /* FALLTHROUGH */
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001462 case I_PUT:
Adam Langleyd0592972015-03-30 14:49:51 -07001463 err = process_put(conn, path1, path2, *pwd, pflag,
1464 rflag, aflag, fflag);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001465 break;
1466 case I_RENAME:
1467 path1 = make_absolute(path1, *pwd);
1468 path2 = make_absolute(path2, *pwd);
Adam Langleyd0592972015-03-30 14:49:51 -07001469 err = do_rename(conn, path1, path2, lflag);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001470 break;
1471 case I_SYMLINK:
1472 sflag = 1;
1473 case I_LINK:
Adam Langleyd0592972015-03-30 14:49:51 -07001474 if (!sflag)
1475 path1 = make_absolute(path1, *pwd);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001476 path2 = make_absolute(path2, *pwd);
1477 err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
1478 break;
1479 case I_RM:
1480 path1 = make_absolute(path1, *pwd);
1481 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1482 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Adam Langleyd0592972015-03-30 14:49:51 -07001483 if (!quiet)
Greg Hartman9768ca42017-06-22 20:49:52 -07001484 mprintf("Removing %s\n", g.gl_pathv[i]);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001485 err = do_rm(conn, g.gl_pathv[i]);
1486 if (err != 0 && err_abort)
1487 break;
1488 }
1489 break;
1490 case I_MKDIR:
1491 path1 = make_absolute(path1, *pwd);
1492 attrib_clear(&a);
1493 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1494 a.perm = 0777;
1495 err = do_mkdir(conn, path1, &a, 1);
1496 break;
1497 case I_RMDIR:
1498 path1 = make_absolute(path1, *pwd);
1499 err = do_rmdir(conn, path1);
1500 break;
1501 case I_CHDIR:
1502 path1 = make_absolute(path1, *pwd);
1503 if ((tmp = do_realpath(conn, path1)) == NULL) {
1504 err = 1;
1505 break;
1506 }
1507 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
Adam Langleyd0592972015-03-30 14:49:51 -07001508 free(tmp);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001509 err = 1;
1510 break;
1511 }
1512 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1513 error("Can't change directory: Can't check target");
Adam Langleyd0592972015-03-30 14:49:51 -07001514 free(tmp);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001515 err = 1;
1516 break;
1517 }
1518 if (!S_ISDIR(aa->perm)) {
1519 error("Can't change directory: \"%s\" is not "
1520 "a directory", tmp);
Adam Langleyd0592972015-03-30 14:49:51 -07001521 free(tmp);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001522 err = 1;
1523 break;
1524 }
Adam Langleyd0592972015-03-30 14:49:51 -07001525 free(*pwd);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001526 *pwd = tmp;
1527 break;
1528 case I_LS:
1529 if (!path1) {
1530 do_ls_dir(conn, *pwd, *pwd, lflag);
1531 break;
1532 }
1533
1534 /* Strip pwd off beginning of non-absolute paths */
1535 tmp = NULL;
1536 if (*path1 != '/')
1537 tmp = *pwd;
1538
1539 path1 = make_absolute(path1, *pwd);
1540 err = do_globbed_ls(conn, path1, tmp, lflag);
1541 break;
1542 case I_DF:
1543 /* Default to current directory if no path specified */
1544 if (path1 == NULL)
1545 path1 = xstrdup(*pwd);
1546 path1 = make_absolute(path1, *pwd);
1547 err = do_df(conn, path1, hflag, iflag);
1548 break;
1549 case I_LCHDIR:
Adam Langleyd0592972015-03-30 14:49:51 -07001550 tmp = tilde_expand_filename(path1, getuid());
1551 free(path1);
1552 path1 = tmp;
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001553 if (chdir(path1) == -1) {
1554 error("Couldn't change local directory to "
1555 "\"%s\": %s", path1, strerror(errno));
1556 err = 1;
1557 }
1558 break;
1559 case I_LMKDIR:
1560 if (mkdir(path1, 0777) == -1) {
1561 error("Couldn't create local directory "
1562 "\"%s\": %s", path1, strerror(errno));
1563 err = 1;
1564 }
1565 break;
1566 case I_LLS:
1567 local_do_ls(cmd);
1568 break;
1569 case I_SHELL:
1570 local_do_shell(cmd);
1571 break;
1572 case I_LUMASK:
1573 umask(n_arg);
1574 printf("Local umask: %03lo\n", n_arg);
1575 break;
1576 case I_CHMOD:
1577 path1 = make_absolute(path1, *pwd);
1578 attrib_clear(&a);
1579 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1580 a.perm = n_arg;
1581 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1582 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Adam Langleyd0592972015-03-30 14:49:51 -07001583 if (!quiet)
Greg Hartman9768ca42017-06-22 20:49:52 -07001584 mprintf("Changing mode on %s\n",
1585 g.gl_pathv[i]);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001586 err = do_setstat(conn, g.gl_pathv[i], &a);
1587 if (err != 0 && err_abort)
1588 break;
1589 }
1590 break;
1591 case I_CHOWN:
1592 case I_CHGRP:
1593 path1 = make_absolute(path1, *pwd);
1594 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1595 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1596 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
1597 if (err_abort) {
1598 err = -1;
1599 break;
1600 } else
1601 continue;
1602 }
1603 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1604 error("Can't get current ownership of "
1605 "remote file \"%s\"", g.gl_pathv[i]);
1606 if (err_abort) {
1607 err = -1;
1608 break;
1609 } else
1610 continue;
1611 }
1612 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1613 if (cmdnum == I_CHOWN) {
Adam Langleyd0592972015-03-30 14:49:51 -07001614 if (!quiet)
Greg Hartman9768ca42017-06-22 20:49:52 -07001615 mprintf("Changing owner on %s\n",
Adam Langleyd0592972015-03-30 14:49:51 -07001616 g.gl_pathv[i]);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001617 aa->uid = n_arg;
1618 } else {
Adam Langleyd0592972015-03-30 14:49:51 -07001619 if (!quiet)
Greg Hartman9768ca42017-06-22 20:49:52 -07001620 mprintf("Changing group on %s\n",
Adam Langleyd0592972015-03-30 14:49:51 -07001621 g.gl_pathv[i]);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001622 aa->gid = n_arg;
1623 }
1624 err = do_setstat(conn, g.gl_pathv[i], aa);
1625 if (err != 0 && err_abort)
1626 break;
1627 }
1628 break;
1629 case I_PWD:
Greg Hartman9768ca42017-06-22 20:49:52 -07001630 mprintf("Remote working directory: %s\n", *pwd);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001631 break;
1632 case I_LPWD:
1633 if (!getcwd(path_buf, sizeof(path_buf))) {
1634 error("Couldn't get local cwd: %s", strerror(errno));
1635 err = -1;
1636 break;
1637 }
Greg Hartman9768ca42017-06-22 20:49:52 -07001638 mprintf("Local working directory: %s\n", path_buf);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001639 break;
1640 case I_QUIT:
1641 /* Processed below */
1642 break;
1643 case I_HELP:
1644 help();
1645 break;
1646 case I_VERSION:
1647 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1648 break;
1649 case I_PROGRESS:
1650 showprogress = !showprogress;
1651 if (showprogress)
1652 printf("Progress meter enabled\n");
1653 else
1654 printf("Progress meter disabled\n");
1655 break;
1656 default:
1657 fatal("%d is not implemented", cmdnum);
1658 }
1659
1660 if (g.gl_pathc)
1661 globfree(&g);
Adam Langleyd0592972015-03-30 14:49:51 -07001662 free(path1);
1663 free(path2);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001664
1665 /* If an unignored error occurs in batch mode we should abort. */
1666 if (err_abort && err != 0)
1667 return (-1);
1668 else if (cmdnum == I_QUIT)
1669 return (1);
1670
1671 return (0);
1672}
1673
1674#ifdef USE_LIBEDIT
1675static char *
1676prompt(EditLine *el)
1677{
1678 return ("sftp> ");
1679}
1680
1681/* Display entries in 'list' after skipping the first 'len' chars */
1682static void
1683complete_display(char **list, u_int len)
1684{
1685 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1686 struct winsize ws;
1687 char *tmp;
1688
1689 /* Count entries for sort and find longest */
Adam Langleyd0592972015-03-30 14:49:51 -07001690 for (y = 0; list[y]; y++)
Greg Hartman9768ca42017-06-22 20:49:52 -07001691 m = MAXIMUM(m, strlen(list[y]));
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001692
1693 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1694 width = ws.ws_col;
1695
1696 m = m > len ? m - len : 0;
1697 columns = width / (m + 2);
Greg Hartman9768ca42017-06-22 20:49:52 -07001698 columns = MAXIMUM(columns, 1);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001699 colspace = width / columns;
Greg Hartman9768ca42017-06-22 20:49:52 -07001700 colspace = MINIMUM(colspace, width);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001701
1702 printf("\n");
1703 m = 1;
1704 for (y = 0; list[y]; y++) {
1705 llen = strlen(list[y]);
1706 tmp = llen > len ? list[y] + len : "";
Greg Hartman9768ca42017-06-22 20:49:52 -07001707 mprintf("%-*s", colspace, tmp);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001708 if (m >= columns) {
1709 printf("\n");
1710 m = 1;
1711 } else
1712 m++;
1713 }
1714 printf("\n");
1715}
1716
1717/*
1718 * Given a "list" of words that begin with a common prefix of "word",
1719 * attempt to find an autocompletion to extends "word" by the next
1720 * characters common to all entries in "list".
1721 */
1722static char *
1723complete_ambiguous(const char *word, char **list, size_t count)
1724{
1725 if (word == NULL)
1726 return NULL;
1727
1728 if (count > 0) {
1729 u_int y, matchlen = strlen(list[0]);
1730
1731 /* Find length of common stem */
1732 for (y = 1; list[y]; y++) {
1733 u_int x;
1734
Adam Langleyd0592972015-03-30 14:49:51 -07001735 for (x = 0; x < matchlen; x++)
1736 if (list[0][x] != list[y][x])
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001737 break;
1738
1739 matchlen = x;
1740 }
1741
1742 if (matchlen > strlen(word)) {
1743 char *tmp = xstrdup(list[0]);
1744
1745 tmp[matchlen] = '\0';
1746 return tmp;
1747 }
Adam Langleyd0592972015-03-30 14:49:51 -07001748 }
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001749
1750 return xstrdup(word);
1751}
1752
1753/* Autocomplete a sftp command */
1754static int
1755complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1756 int terminated)
1757{
1758 u_int y, count = 0, cmdlen, tmplen;
1759 char *tmp, **list, argterm[3];
1760 const LineInfo *lf;
1761
1762 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1763
1764 /* No command specified: display all available commands */
1765 if (cmd == NULL) {
1766 for (y = 0; cmds[y].c; y++)
1767 list[count++] = xstrdup(cmds[y].c);
Adam Langleyd0592972015-03-30 14:49:51 -07001768
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001769 list[count] = NULL;
1770 complete_display(list, 0);
1771
Adam Langleyd0592972015-03-30 14:49:51 -07001772 for (y = 0; list[y] != NULL; y++)
1773 free(list[y]);
1774 free(list);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001775 return count;
1776 }
1777
1778 /* Prepare subset of commands that start with "cmd" */
1779 cmdlen = strlen(cmd);
1780 for (y = 0; cmds[y].c; y++) {
Adam Langleyd0592972015-03-30 14:49:51 -07001781 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001782 list[count++] = xstrdup(cmds[y].c);
1783 }
1784 list[count] = NULL;
1785
Adam Langleyd0592972015-03-30 14:49:51 -07001786 if (count == 0) {
1787 free(list);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001788 return 0;
Adam Langleyd0592972015-03-30 14:49:51 -07001789 }
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001790
1791 /* Complete ambigious command */
1792 tmp = complete_ambiguous(cmd, list, count);
1793 if (count > 1)
1794 complete_display(list, 0);
1795
Adam Langleyd0592972015-03-30 14:49:51 -07001796 for (y = 0; list[y]; y++)
1797 free(list[y]);
1798 free(list);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001799
1800 if (tmp != NULL) {
1801 tmplen = strlen(tmp);
1802 cmdlen = strlen(cmd);
1803 /* If cmd may be extended then do so */
1804 if (tmplen > cmdlen)
1805 if (el_insertstr(el, tmp + cmdlen) == -1)
1806 fatal("el_insertstr failed.");
1807 lf = el_line(el);
1808 /* Terminate argument cleanly */
1809 if (count == 1) {
1810 y = 0;
1811 if (!terminated)
1812 argterm[y++] = quote;
1813 if (lastarg || *(lf->cursor) != ' ')
1814 argterm[y++] = ' ';
1815 argterm[y] = '\0';
1816 if (y > 0 && el_insertstr(el, argterm) == -1)
1817 fatal("el_insertstr failed.");
1818 }
Adam Langleyd0592972015-03-30 14:49:51 -07001819 free(tmp);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001820 }
1821
1822 return count;
1823}
1824
1825/*
1826 * Determine whether a particular sftp command's arguments (if any)
1827 * represent local or remote files.
1828 */
1829static int
1830complete_is_remote(char *cmd) {
1831 int i;
1832
1833 if (cmd == NULL)
1834 return -1;
1835
1836 for (i = 0; cmds[i].c; i++) {
Adam Langleyd0592972015-03-30 14:49:51 -07001837 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001838 return cmds[i].t;
1839 }
1840
1841 return -1;
1842}
1843
1844/* Autocomplete a filename "file" */
1845static int
1846complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1847 char *file, int remote, int lastarg, char quote, int terminated)
1848{
1849 glob_t g;
Adam Langleyd0592972015-03-30 14:49:51 -07001850 char *tmp, *tmp2, ins[8];
1851 u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
1852 int clen;
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001853 const LineInfo *lf;
Adam Langleyd0592972015-03-30 14:49:51 -07001854
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001855 /* Glob from "file" location */
1856 if (file == NULL)
1857 tmp = xstrdup("*");
1858 else
1859 xasprintf(&tmp, "%s*", file);
1860
Adam Langleyd0592972015-03-30 14:49:51 -07001861 /* Check if the path is absolute. */
1862 isabs = tmp[0] == '/';
1863
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001864 memset(&g, 0, sizeof(g));
1865 if (remote != LOCAL) {
1866 tmp = make_absolute(tmp, remote_path);
1867 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Adam Langleyd0592972015-03-30 14:49:51 -07001868 } else
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001869 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Adam Langleyd0592972015-03-30 14:49:51 -07001870
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001871 /* Determine length of pwd so we can trim completion display */
1872 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1873 /* Terminate counting on first unescaped glob metacharacter */
1874 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1875 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1876 hadglob = 1;
1877 break;
1878 }
1879 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1880 tmplen++;
1881 if (tmp[tmplen] == '/')
1882 pwdlen = tmplen + 1; /* track last seen '/' */
1883 }
Adam Langleyd0592972015-03-30 14:49:51 -07001884 free(tmp);
1885 tmp = NULL;
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001886
Adam Langleyd0592972015-03-30 14:49:51 -07001887 if (g.gl_matchc == 0)
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001888 goto out;
1889
1890 if (g.gl_matchc > 1)
1891 complete_display(g.gl_pathv, pwdlen);
1892
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001893 /* Don't try to extend globs */
1894 if (file == NULL || hadglob)
1895 goto out;
1896
1897 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
Adam Langleyd0592972015-03-30 14:49:51 -07001898 tmp = path_strip(tmp2, isabs ? NULL : remote_path);
1899 free(tmp2);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001900
1901 if (tmp == NULL)
1902 goto out;
1903
1904 tmplen = strlen(tmp);
1905 filelen = strlen(file);
1906
Adam Langleyd0592972015-03-30 14:49:51 -07001907 /* Count the number of escaped characters in the input string. */
1908 cesc = isesc = 0;
1909 for (i = 0; i < filelen; i++) {
1910 if (!isesc && file[i] == '\\' && i + 1 < filelen){
1911 isesc = 1;
1912 cesc++;
1913 } else
1914 isesc = 0;
1915 }
1916
1917 if (tmplen > (filelen - cesc)) {
1918 tmp2 = tmp + filelen - cesc;
1919 len = strlen(tmp2);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001920 /* quote argument on way out */
Adam Langleyd0592972015-03-30 14:49:51 -07001921 for (i = 0; i < len; i += clen) {
1922 if ((clen = mblen(tmp2 + i, len - i)) < 0 ||
1923 (size_t)clen > sizeof(ins) - 2)
1924 fatal("invalid multibyte character");
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001925 ins[0] = '\\';
Adam Langleyd0592972015-03-30 14:49:51 -07001926 memcpy(ins + 1, tmp2 + i, clen);
1927 ins[clen + 1] = '\0';
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001928 switch (tmp2[i]) {
1929 case '\'':
1930 case '"':
1931 case '\\':
1932 case '\t':
1933 case '[':
1934 case ' ':
Adam Langleyd0592972015-03-30 14:49:51 -07001935 case '#':
1936 case '*':
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001937 if (quote == '\0' || tmp2[i] == quote) {
1938 if (el_insertstr(el, ins) == -1)
1939 fatal("el_insertstr "
1940 "failed.");
1941 break;
1942 }
1943 /* FALLTHROUGH */
1944 default:
1945 if (el_insertstr(el, ins + 1) == -1)
1946 fatal("el_insertstr failed.");
1947 break;
1948 }
1949 }
1950 }
1951
1952 lf = el_line(el);
1953 if (g.gl_matchc == 1) {
1954 i = 0;
Adam Langleyd0592972015-03-30 14:49:51 -07001955 if (!terminated && quote != '\0')
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001956 ins[i++] = quote;
1957 if (*(lf->cursor - 1) != '/' &&
1958 (lastarg || *(lf->cursor) != ' '))
1959 ins[i++] = ' ';
1960 ins[i] = '\0';
1961 if (i > 0 && el_insertstr(el, ins) == -1)
1962 fatal("el_insertstr failed.");
1963 }
Adam Langleyd0592972015-03-30 14:49:51 -07001964 free(tmp);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001965
1966 out:
1967 globfree(&g);
1968 return g.gl_matchc;
1969}
1970
1971/* tab-completion hook function, called via libedit */
1972static unsigned char
1973complete(EditLine *el, int ch)
1974{
Adam Langleyd0592972015-03-30 14:49:51 -07001975 char **argv, *line, quote;
1976 int argc, carg;
1977 u_int cursor, len, terminated, ret = CC_ERROR;
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001978 const LineInfo *lf;
1979 struct complete_ctx *complete_ctx;
1980
1981 lf = el_line(el);
1982 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1983 fatal("%s: el_get failed", __func__);
1984
1985 /* Figure out which argument the cursor points to */
1986 cursor = lf->cursor - lf->buffer;
Greg Hartmanccacbc92016-02-03 09:59:44 -08001987 line = xmalloc(cursor + 1);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001988 memcpy(line, lf->buffer, cursor);
1989 line[cursor] = '\0';
1990 argv = makeargv(line, &carg, 1, &quote, &terminated);
Adam Langleyd0592972015-03-30 14:49:51 -07001991 free(line);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001992
1993 /* Get all the arguments on the line */
1994 len = lf->lastchar - lf->buffer;
Greg Hartmanccacbc92016-02-03 09:59:44 -08001995 line = xmalloc(len + 1);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08001996 memcpy(line, lf->buffer, len);
1997 line[len] = '\0';
1998 argv = makeargv(line, &argc, 1, NULL, NULL);
1999
2000 /* Ensure cursor is at EOL or a argument boundary */
2001 if (line[cursor] != ' ' && line[cursor] != '\0' &&
2002 line[cursor] != '\n') {
Adam Langleyd0592972015-03-30 14:49:51 -07002003 free(line);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08002004 return ret;
2005 }
2006
2007 if (carg == 0) {
2008 /* Show all available commands */
2009 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
2010 ret = CC_REDISPLAY;
2011 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
2012 /* Handle the command parsing */
2013 if (complete_cmd_parse(el, argv[0], argc == carg,
Adam Langleyd0592972015-03-30 14:49:51 -07002014 quote, terminated) != 0)
Greg Hartmanbd77cf72015-02-25 13:21:06 -08002015 ret = CC_REDISPLAY;
2016 } else if (carg >= 1) {
2017 /* Handle file parsing */
2018 int remote = complete_is_remote(argv[0]);
2019 char *filematch = NULL;
2020
2021 if (carg > 1 && line[cursor-1] != ' ')
2022 filematch = argv[carg - 1];
2023
2024 if (remote != 0 &&
2025 complete_match(el, complete_ctx->conn,
2026 *complete_ctx->remote_pathp, filematch,
Adam Langleyd0592972015-03-30 14:49:51 -07002027 remote, carg == argc, quote, terminated) != 0)
Greg Hartmanbd77cf72015-02-25 13:21:06 -08002028 ret = CC_REDISPLAY;
2029 }
2030
Adam Langleyd0592972015-03-30 14:49:51 -07002031 free(line);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08002032 return ret;
2033}
2034#endif /* USE_LIBEDIT */
2035
2036int
2037interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
2038{
2039 char *remote_path;
2040 char *dir = NULL;
2041 char cmd[2048];
2042 int err, interactive;
2043 EditLine *el = NULL;
2044#ifdef USE_LIBEDIT
2045 History *hl = NULL;
2046 HistEvent hev;
2047 extern char *__progname;
2048 struct complete_ctx complete_ctx;
2049
2050 if (!batchmode && isatty(STDIN_FILENO)) {
2051 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
2052 fatal("Couldn't initialise editline");
2053 if ((hl = history_init()) == NULL)
2054 fatal("Couldn't initialise editline history");
2055 history(hl, &hev, H_SETSIZE, 100);
2056 el_set(el, EL_HIST, history, hl);
2057
2058 el_set(el, EL_PROMPT, prompt);
2059 el_set(el, EL_EDITOR, "emacs");
2060 el_set(el, EL_TERMINAL, NULL);
2061 el_set(el, EL_SIGNAL, 1);
2062 el_source(el, NULL);
2063
2064 /* Tab Completion */
Adam Langleyd0592972015-03-30 14:49:51 -07002065 el_set(el, EL_ADDFN, "ftp-complete",
Greg Hartmanbd77cf72015-02-25 13:21:06 -08002066 "Context sensitive argument completion", complete);
2067 complete_ctx.conn = conn;
2068 complete_ctx.remote_pathp = &remote_path;
2069 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
2070 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Adam Langleyd0592972015-03-30 14:49:51 -07002071 /* enable ctrl-left-arrow and ctrl-right-arrow */
2072 el_set(el, EL_BIND, "\\e[1;5C", "em-next-word", NULL);
2073 el_set(el, EL_BIND, "\\e[5C", "em-next-word", NULL);
2074 el_set(el, EL_BIND, "\\e[1;5D", "ed-prev-word", NULL);
2075 el_set(el, EL_BIND, "\\e\\e[D", "ed-prev-word", NULL);
2076 /* make ^w match ksh behaviour */
2077 el_set(el, EL_BIND, "^w", "ed-delete-prev-word", NULL);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08002078 }
2079#endif /* USE_LIBEDIT */
2080
2081 remote_path = do_realpath(conn, ".");
2082 if (remote_path == NULL)
2083 fatal("Need cwd");
2084
2085 if (file1 != NULL) {
2086 dir = xstrdup(file1);
2087 dir = make_absolute(dir, remote_path);
2088
2089 if (remote_is_dir(conn, dir) && file2 == NULL) {
Adam Langleyd0592972015-03-30 14:49:51 -07002090 if (!quiet)
Greg Hartman9768ca42017-06-22 20:49:52 -07002091 mprintf("Changing to: %s\n", dir);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08002092 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
2093 if (parse_dispatch_command(conn, cmd,
2094 &remote_path, 1) != 0) {
Adam Langleyd0592972015-03-30 14:49:51 -07002095 free(dir);
2096 free(remote_path);
2097 free(conn);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08002098 return (-1);
2099 }
2100 } else {
Adam Langleyd0592972015-03-30 14:49:51 -07002101 /* XXX this is wrong wrt quoting */
2102 snprintf(cmd, sizeof cmd, "get%s %s%s%s",
2103 global_aflag ? " -a" : "", dir,
2104 file2 == NULL ? "" : " ",
2105 file2 == NULL ? "" : file2);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08002106 err = parse_dispatch_command(conn, cmd,
2107 &remote_path, 1);
Adam Langleyd0592972015-03-30 14:49:51 -07002108 free(dir);
2109 free(remote_path);
2110 free(conn);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08002111 return (err);
2112 }
Adam Langleyd0592972015-03-30 14:49:51 -07002113 free(dir);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08002114 }
2115
Greg Hartmanbd77cf72015-02-25 13:21:06 -08002116 setvbuf(stdout, NULL, _IOLBF, 0);
2117 setvbuf(infile, NULL, _IOLBF, 0);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08002118
2119 interactive = !batchmode && isatty(STDIN_FILENO);
2120 err = 0;
2121 for (;;) {
2122 char *cp;
2123
2124 signal(SIGINT, SIG_IGN);
2125
2126 if (el == NULL) {
2127 if (interactive)
2128 printf("sftp> ");
2129 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
2130 if (interactive)
2131 printf("\n");
2132 break;
2133 }
2134 if (!interactive) { /* Echo command */
Greg Hartman9768ca42017-06-22 20:49:52 -07002135 mprintf("sftp> %s", cmd);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08002136 if (strlen(cmd) > 0 &&
2137 cmd[strlen(cmd) - 1] != '\n')
2138 printf("\n");
2139 }
2140 } else {
2141#ifdef USE_LIBEDIT
2142 const char *line;
2143 int count = 0;
2144
2145 if ((line = el_gets(el, &count)) == NULL ||
2146 count <= 0) {
2147 printf("\n");
2148 break;
2149 }
2150 history(hl, &hev, H_ENTER, line);
2151 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
2152 fprintf(stderr, "Error: input line too long\n");
2153 continue;
2154 }
2155#endif /* USE_LIBEDIT */
2156 }
2157
2158 cp = strrchr(cmd, '\n');
2159 if (cp)
2160 *cp = '\0';
2161
2162 /* Handle user interrupts gracefully during commands */
2163 interrupted = 0;
2164 signal(SIGINT, cmd_interrupt);
2165
2166 err = parse_dispatch_command(conn, cmd, &remote_path,
2167 batchmode);
2168 if (err != 0)
2169 break;
2170 }
Adam Langleyd0592972015-03-30 14:49:51 -07002171 free(remote_path);
2172 free(conn);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08002173
2174#ifdef USE_LIBEDIT
2175 if (el != NULL)
2176 el_end(el);
2177#endif /* USE_LIBEDIT */
2178
2179 /* err == 1 signifies normal "quit" exit */
2180 return (err >= 0 ? 0 : -1);
2181}
2182
2183static void
2184connect_to_server(char *path, char **args, int *in, int *out)
2185{
2186 int c_in, c_out;
2187
2188#ifdef USE_PIPES
2189 int pin[2], pout[2];
2190
2191 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2192 fatal("pipe: %s", strerror(errno));
2193 *in = pin[0];
2194 *out = pout[1];
2195 c_in = pout[0];
2196 c_out = pin[1];
2197#else /* USE_PIPES */
2198 int inout[2];
2199
2200 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2201 fatal("socketpair: %s", strerror(errno));
2202 *in = *out = inout[0];
2203 c_in = c_out = inout[1];
2204#endif /* USE_PIPES */
2205
2206 if ((sshpid = fork()) == -1)
2207 fatal("fork: %s", strerror(errno));
2208 else if (sshpid == 0) {
2209 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2210 (dup2(c_out, STDOUT_FILENO) == -1)) {
2211 fprintf(stderr, "dup2: %s\n", strerror(errno));
2212 _exit(1);
2213 }
2214 close(*in);
2215 close(*out);
2216 close(c_in);
2217 close(c_out);
2218
2219 /*
2220 * The underlying ssh is in the same process group, so we must
2221 * ignore SIGINT if we want to gracefully abort commands,
2222 * otherwise the signal will make it to the ssh process and
2223 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2224 * underlying ssh, it must *not* ignore that signal.
2225 */
2226 signal(SIGINT, SIG_IGN);
2227 signal(SIGTERM, SIG_DFL);
2228 execvp(path, args);
2229 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
2230 _exit(1);
2231 }
2232
2233 signal(SIGTERM, killchild);
2234 signal(SIGINT, killchild);
2235 signal(SIGHUP, killchild);
Greg Hartman9768ca42017-06-22 20:49:52 -07002236 signal(SIGTSTP, suspchild);
2237 signal(SIGTTIN, suspchild);
2238 signal(SIGTTOU, suspchild);
Greg Hartmanbd77cf72015-02-25 13:21:06 -08002239 close(c_in);
2240 close(c_out);
2241}
2242
2243static void
2244usage(void)
2245{
2246 extern char *__progname;
2247
2248 fprintf(stderr,
Adam Langleyd0592972015-03-30 14:49:51 -07002249 "usage: %s [-1246aCfpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Greg Hartmanbd77cf72015-02-25 13:21:06 -08002250 " [-D sftp_server_path] [-F ssh_config] "
2251 "[-i identity_file] [-l limit]\n"
2252 " [-o ssh_option] [-P port] [-R num_requests] "
2253 "[-S program]\n"
2254 " [-s subsystem | sftp_server] host\n"
2255 " %s [user@]host[:file ...]\n"
2256 " %s [user@]host[:dir[/]]\n"
2257 " %s -b batchfile [user@]host\n",
2258 __progname, __progname, __progname, __progname);
2259 exit(1);
2260}
2261
2262int
2263main(int argc, char **argv)
2264{
2265 int in, out, ch, err;
2266 char *host = NULL, *userhost, *cp, *file2 = NULL;
2267 int debug_level = 0, sshver = 2;
2268 char *file1 = NULL, *sftp_server = NULL;
2269 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
2270 const char *errstr;
2271 LogLevel ll = SYSLOG_LEVEL_INFO;
2272 arglist args;
2273 extern int optind;
2274 extern char *optarg;
2275 struct sftp_conn *conn;
2276 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2277 size_t num_requests = DEFAULT_NUM_REQUESTS;
2278 long long limit_kbps = 0;
2279
Greg Hartman9768ca42017-06-22 20:49:52 -07002280 ssh_malloc_init(); /* must be called before any mallocs */
Greg Hartmanbd77cf72015-02-25 13:21:06 -08002281 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2282 sanitise_stdfd();
Greg Hartman9768ca42017-06-22 20:49:52 -07002283 msetlocale();
Greg Hartmanbd77cf72015-02-25 13:21:06 -08002284
2285 __progname = ssh_get_progname(argv[0]);
2286 memset(&args, '\0', sizeof(args));
2287 args.list = NULL;
2288 addargs(&args, "%s", ssh_program);
2289 addargs(&args, "-oForwardX11 no");
2290 addargs(&args, "-oForwardAgent no");
2291 addargs(&args, "-oPermitLocalCommand no");
2292 addargs(&args, "-oClearAllForwardings yes");
2293
2294 ll = SYSLOG_LEVEL_INFO;
2295 infile = stdin;
2296
2297 while ((ch = getopt(argc, argv,
Adam Langleyd0592972015-03-30 14:49:51 -07002298 "1246afhpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Greg Hartmanbd77cf72015-02-25 13:21:06 -08002299 switch (ch) {
2300 /* Passed through to ssh(1) */
2301 case '4':
2302 case '6':
2303 case 'C':
2304 addargs(&args, "-%c", ch);
2305 break;
2306 /* Passed through to ssh(1) with argument */
2307 case 'F':
2308 case 'c':
2309 case 'i':
2310 case 'o':
2311 addargs(&args, "-%c", ch);
2312 addargs(&args, "%s", optarg);
2313 break;
2314 case 'q':
Adam Langleyd0592972015-03-30 14:49:51 -07002315 ll = SYSLOG_LEVEL_ERROR;
2316 quiet = 1;
Greg Hartmanbd77cf72015-02-25 13:21:06 -08002317 showprogress = 0;
2318 addargs(&args, "-%c", ch);
2319 break;
2320 case 'P':
2321 addargs(&args, "-oPort %s", optarg);
2322 break;
2323 case 'v':
2324 if (debug_level < 3) {
2325 addargs(&args, "-v");
2326 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2327 }
2328 debug_level++;
2329 break;
2330 case '1':
2331 sshver = 1;
2332 if (sftp_server == NULL)
2333 sftp_server = _PATH_SFTP_SERVER;
2334 break;
2335 case '2':
2336 sshver = 2;
2337 break;
Adam Langleyd0592972015-03-30 14:49:51 -07002338 case 'a':
2339 global_aflag = 1;
2340 break;
Greg Hartmanbd77cf72015-02-25 13:21:06 -08002341 case 'B':
2342 copy_buffer_len = strtol(optarg, &cp, 10);
2343 if (copy_buffer_len == 0 || *cp != '\0')
2344 fatal("Invalid buffer size \"%s\"", optarg);
2345 break;
2346 case 'b':
2347 if (batchmode)
2348 fatal("Batch file already specified.");
2349
2350 /* Allow "-" as stdin */
2351 if (strcmp(optarg, "-") != 0 &&
2352 (infile = fopen(optarg, "r")) == NULL)
2353 fatal("%s (%s).", strerror(errno), optarg);
2354 showprogress = 0;
Adam Langleyd0592972015-03-30 14:49:51 -07002355 quiet = batchmode = 1;
Greg Hartmanbd77cf72015-02-25 13:21:06 -08002356 addargs(&args, "-obatchmode yes");
2357 break;
Adam Langleyd0592972015-03-30 14:49:51 -07002358 case 'f':
2359 global_fflag = 1;
2360 break;
Greg Hartmanbd77cf72015-02-25 13:21:06 -08002361 case 'p':
2362 global_pflag = 1;
2363 break;
2364 case 'D':
2365 sftp_direct = optarg;
2366 break;
2367 case 'l':
2368 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2369 &errstr);
2370 if (errstr != NULL)
2371 usage();
2372 limit_kbps *= 1024; /* kbps */
2373 break;
2374 case 'r':
2375 global_rflag = 1;
2376 break;
2377 case 'R':
2378 num_requests = strtol(optarg, &cp, 10);
2379 if (num_requests == 0 || *cp != '\0')
2380 fatal("Invalid number of requests \"%s\"",
2381 optarg);
2382 break;
2383 case 's':
2384 sftp_server = optarg;
2385 break;
2386 case 'S':
2387 ssh_program = optarg;
2388 replacearg(&args, 0, "%s", ssh_program);
2389 break;
2390 case 'h':
2391 default:
2392 usage();
2393 }
2394 }
2395
2396 if (!isatty(STDERR_FILENO))
2397 showprogress = 0;
2398
2399 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2400
2401 if (sftp_direct == NULL) {
2402 if (optind == argc || argc > (optind + 2))
2403 usage();
2404
2405 userhost = xstrdup(argv[optind]);
2406 file2 = argv[optind+1];
2407
2408 if ((host = strrchr(userhost, '@')) == NULL)
2409 host = userhost;
2410 else {
2411 *host++ = '\0';
2412 if (!userhost[0]) {
2413 fprintf(stderr, "Missing username\n");
2414 usage();
2415 }
2416 addargs(&args, "-l");
2417 addargs(&args, "%s", userhost);
2418 }
2419
2420 if ((cp = colon(host)) != NULL) {
2421 *cp++ = '\0';
2422 file1 = cp;
2423 }
2424
2425 host = cleanhostname(host);
2426 if (!*host) {
2427 fprintf(stderr, "Missing hostname\n");
2428 usage();
2429 }
2430
2431 addargs(&args, "-oProtocol %d", sshver);
2432
2433 /* no subsystem if the server-spec contains a '/' */
2434 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2435 addargs(&args, "-s");
2436
2437 addargs(&args, "--");
2438 addargs(&args, "%s", host);
2439 addargs(&args, "%s", (sftp_server != NULL ?
2440 sftp_server : "sftp"));
2441
2442 connect_to_server(ssh_program, args.list, &in, &out);
2443 } else {
2444 args.list = NULL;
2445 addargs(&args, "sftp-server");
2446
2447 connect_to_server(sftp_direct, args.list, &in, &out);
2448 }
2449 freeargs(&args);
2450
2451 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
2452 if (conn == NULL)
2453 fatal("Couldn't initialise connection to server");
2454
Adam Langleyd0592972015-03-30 14:49:51 -07002455 if (!quiet) {
Greg Hartmanbd77cf72015-02-25 13:21:06 -08002456 if (sftp_direct == NULL)
2457 fprintf(stderr, "Connected to %s.\n", host);
2458 else
2459 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2460 }
2461
2462 err = interactive_loop(conn, file1, file2);
2463
2464#if !defined(USE_PIPES)
2465 shutdown(in, SHUT_RDWR);
2466 shutdown(out, SHUT_RDWR);
2467#endif
2468
2469 close(in);
2470 close(out);
2471 if (batchmode)
2472 fclose(infile);
2473
2474 while (waitpid(sshpid, NULL, 0) == -1)
2475 if (errno != EINTR)
2476 fatal("Couldn't wait for ssh process: %s",
2477 strerror(errno));
2478
2479 exit(err == 0 ? 0 : 1);
2480}