1 /*
2 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 /* $OpenBSD: sftp.c,v 1.96 2007/01/03 04:09:15 stevesk Exp $ */
18
19 #include "includes.h"
20
21 #include <sys/types.h>
22 #include <sys/ioctl.h>
23 #ifdef HAVE_SYS_STAT_H
24 # include <sys/stat.h>
25 #endif
26 #include <sys/param.h>
27 #include <sys/socket.h>
28 #include <sys/wait.h>
29
30 #include <errno.h>
31
32 #ifdef HAVE_PATHS_H
33 # include <paths.h>
34 #endif
35
36 #ifdef USE_LIBEDIT
37 #include <histedit.h>
38 #else
39 #ifdef USE_LIBTECLA
40 #include <libtecla.h>
41 #define MAX_LINE_LEN 2048
42 #define MAX_CMD_HIST 10000
43 #endif /* USE_LIBTECLA */
44 #endif /* USE_LIBEDIT */
45
46 #include <signal.h>
47 #include <stdlib.h>
48 #include <stdio.h>
49 #include <string.h>
50 #include <unistd.h>
51 #include <stdarg.h>
52
53 #include "xmalloc.h"
54 #include "log.h"
55 #include "pathnames.h"
56 #include "misc.h"
57
58 #include "sftp.h"
59 #include "buffer.h"
60 #include "sftp-common.h"
61 #include "sftp-client.h"
62
63 #ifdef HAVE___PROGNAME
64 extern char *__progname;
65 #else
66 char *__progname;
67 #endif
68
69
70 /* File to read commands from */
71 FILE* infile;
72
73 /* Are we in batchfile mode? */
74 int batchmode = 0;
75
76 /* Size of buffer used when copying files */
77 size_t copy_buffer_len = 32768;
78
79 /* Number of concurrent outstanding requests */
80 size_t num_requests = 16;
81
82 /* PID of ssh transport process */
83 static pid_t sshpid = -1;
84
85 /* This is set to 0 if the progressmeter is not desired. */
86 int showprogress = 1;
87
88 /* SIGINT received during command processing */
89 volatile sig_atomic_t interrupted = 0;
90
91 /* I wish qsort() took a separate ctx for the comparison function...*/
92 int sort_flag;
93
94 int 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 0x01 /* Full view ala ls -l */
102 #define LS_SHORT_VIEW 0x02 /* Single row view ala ls -1 */
103 #define LS_NUMERIC_VIEW 0x04 /* Long view with numeric uid/gid */
104 #define LS_NAME_SORT 0x08 /* Sort by name (default) */
105 #define LS_TIME_SORT 0x10 /* Sort by mtime */
106 #define LS_SIZE_SORT 0x20 /* Sort by file size */
107 #define LS_REVERSE_SORT 0x40 /* Reverse sort order */
108 #define LS_SHOW_ALL 0x80 /* Don't skip filenames starting with '.' */
109
110 #define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW)
111 #define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
112
113 /* Commands for interactive mode */
114 #define I_CHDIR 1
115 #define I_CHGRP 2
116 #define I_CHMOD 3
117 #define I_CHOWN 4
118 #define I_GET 5
119 #define I_HELP 6
120 #define I_LCHDIR 7
121 #define I_LLS 8
122 #define I_LMKDIR 9
123 #define I_LPWD 10
124 #define I_LS 11
125 #define I_LUMASK 12
126 #define I_MKDIR 13
127 #define I_PUT 14
128 #define I_PWD 15
129 #define I_QUIT 16
130 #define I_RENAME 17
131 #define I_RM 18
132 #define I_RMDIR 19
133 #define I_SHELL 20
134 #define I_SYMLINK 21
135 #define I_VERSION 22
136 #define I_PROGRESS 23
137
138 struct CMD {
139 const char *c;
140 const int n;
141 };
142
143 static const struct CMD cmds[] = {
144 { "bye", I_QUIT },
145 { "cd", I_CHDIR },
146 { "chdir", I_CHDIR },
147 { "chgrp", I_CHGRP },
148 { "chmod", I_CHMOD },
149 { "chown", I_CHOWN },
150 { "dir", I_LS },
151 { "exit", I_QUIT },
152 { "get", I_GET },
153 { "mget", I_GET },
154 { "help", I_HELP },
155 { "lcd", I_LCHDIR },
156 { "lchdir", I_LCHDIR },
157 { "lls", I_LLS },
158 { "lmkdir", I_LMKDIR },
159 { "ln", I_SYMLINK },
160 { "lpwd", I_LPWD },
161 { "ls", I_LS },
162 { "lumask", I_LUMASK },
163 { "mkdir", I_MKDIR },
164 { "progress", I_PROGRESS },
165 { "put", I_PUT },
166 { "mput", I_PUT },
167 { "pwd", I_PWD },
168 { "quit", I_QUIT },
169 { "rename", I_RENAME },
170 { "rm", I_RM },
171 { "rmdir", I_RMDIR },
172 { "symlink", I_SYMLINK },
173 { "version", I_VERSION },
174 { "!", I_SHELL },
175 { "?", I_HELP },
176 { NULL, -1}
177 };
178
179 int interactive_loop(int fd_in, int fd_out, char *file1, char *file2);
180
181 /* ARGSUSED */
182 static void
killchild(int signo)183 killchild(int signo)
184 {
185 if (sshpid > 1) {
186 kill(sshpid, SIGTERM);
187 waitpid(sshpid, NULL, 0);
188 }
189
190 _exit(1);
191 }
192
193 /* ARGSUSED */
194 static void
cmd_interrupt(int signo)195 cmd_interrupt(int signo)
196 {
197 const char msg[] = "\rInterrupt \n";
198 int olderrno = errno;
199
200 write(STDERR_FILENO, msg, sizeof(msg) - 1);
201 interrupted = 1;
202 errno = olderrno;
203 }
204
205 static void
help(void)206 help(void)
207 {
208 printf(gettext("Available commands:\n"
209 "cd path Change remote directory to 'path'\n"
210 "lcd path Change local directory to 'path'\n"
211 "chgrp grp path Change group of file 'path' to 'grp'\n"
212 "chmod mode path Change permissions of file 'path' to 'mode'\n"
213 "chown own path Change owner of file 'path' to 'own'\n"
214 "help Display this help text\n"
215 "get remote-path [local-path] Download file\n"
216 "lls [ls-options [path]] Display local directory listing\n"
217 "ln oldpath newpath Symlink remote file\n"
218 "lmkdir path Create local directory\n"
219 "lpwd Print local working directory\n"
220 "ls [path] Display remote directory listing\n"
221 "lumask umask Set local umask to 'umask'\n"
222 "mkdir path Create remote directory\n"
223 "progress Toggle display of progress meter\n"
224 "put local-path [remote-path] Upload file\n"
225 "pwd Display remote working directory\n"
226 "exit Quit sftp\n"
227 "quit Quit sftp\n"
228 "rename oldpath newpath Rename remote file\n"
229 "rmdir path Remove remote directory\n"
230 "rm path Delete remote file\n"
231 "symlink oldpath newpath Symlink remote file\n"
232 "version Show SFTP version\n"
233 "!command Execute 'command' in local shell\n"
234 "! Escape to local shell\n"
235 "? Synonym for help\n"));
236 }
237
238 static void
local_do_shell(const char * args)239 local_do_shell(const char *args)
240 {
241 int status;
242 char *shell;
243 pid_t pid;
244
245 if (!*args)
246 args = NULL;
247
248 if ((shell = getenv("SHELL")) == NULL)
249 shell = _PATH_BSHELL;
250
251 if ((pid = fork()) == -1)
252 fatal("Couldn't fork: %s", strerror(errno));
253
254 if (pid == 0) {
255 /* XXX: child has pipe fds to ssh subproc open - issue? */
256 if (args) {
257 debug3("Executing %s -c \"%s\"", shell, args);
258 execl(shell, shell, "-c", args, (char *)NULL);
259 } else {
260 debug3("Executing %s", shell);
261 execl(shell, shell, (char *)NULL);
262 }
263 fprintf(stderr, gettext("Couldn't execute \"%s\": %s\n"), shell,
264 strerror(errno));
265 _exit(1);
266 }
267 while (waitpid(pid, &status, 0) == -1)
268 if (errno != EINTR)
269 fatal("Couldn't wait for child: %s", strerror(errno));
270 if (!WIFEXITED(status))
271 error("Shell exited abnormally");
272 else if (WEXITSTATUS(status))
273 error("Shell exited with status %d", WEXITSTATUS(status));
274 }
275
276 static void
local_do_ls(const char * args)277 local_do_ls(const char *args)
278 {
279 if (!args || !*args)
280 local_do_shell(_PATH_LS);
281 else {
282 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
283 char *buf = xmalloc(len);
284
285 /* XXX: quoting - rip quoting code from ftp? */
286 snprintf(buf, len, _PATH_LS " %s", args);
287 local_do_shell(buf);
288 xfree(buf);
289 }
290 }
291
292 /* Strip one path (usually the pwd) from the start of another */
293 static char *
path_strip(char * path,char * strip)294 path_strip(char *path, char *strip)
295 {
296 size_t len;
297
298 if (strip == NULL)
299 return (xstrdup(path));
300
301 len = strlen(strip);
302 if (strncmp(path, strip, len) == 0) {
303 if (strip[len - 1] != '/' && path[len] == '/')
304 len++;
305 return (xstrdup(path + len));
306 }
307
308 return (xstrdup(path));
309 }
310
311 static char *
path_append(char * p1,char * p2)312 path_append(char *p1, char *p2)
313 {
314 char *ret;
315 size_t len = strlen(p1) + strlen(p2) + 2;
316
317 ret = xmalloc(len);
318 strlcpy(ret, p1, len);
319 if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/')
320 strlcat(ret, "/", len);
321 strlcat(ret, p2, len);
322
323 return(ret);
324 }
325
326 static char *
make_absolute(char * p,char * pwd)327 make_absolute(char *p, char *pwd)
328 {
329 char *abs_str;
330
331 /* Derelativise */
332 if (p && p[0] != '/') {
333 abs_str = path_append(pwd, p);
334 xfree(p);
335 return(abs_str);
336 } else
337 return(p);
338 }
339
340 static int
infer_path(const char * p,char ** ifp)341 infer_path(const char *p, char **ifp)
342 {
343 char *cp;
344
345 cp = strrchr(p, '/');
346 if (cp == NULL) {
347 *ifp = xstrdup(p);
348 return(0);
349 }
350
351 if (!cp[1]) {
352 error("Invalid path");
353 return(-1);
354 }
355
356 *ifp = xstrdup(cp + 1);
357 return(0);
358 }
359
360 static int
parse_getput_flags(const char ** cpp,int * pflag)361 parse_getput_flags(const char **cpp, int *pflag)
362 {
363 const char *cp = *cpp;
364
365 /* Check for flags */
366 if (cp[0] == '-' && cp[1] && strchr(WHITESPACE, cp[2])) {
367 switch (cp[1]) {
368 case 'p':
369 case 'P':
370 *pflag = 1;
371 break;
372 default:
373 error("Invalid flag -%c", cp[1]);
374 return(-1);
375 }
376 cp += 2;
377 *cpp = cp + strspn(cp, WHITESPACE);
378 }
379
380 return(0);
381 }
382
383 static int
parse_ls_flags(const char ** cpp,int * lflag)384 parse_ls_flags(const char **cpp, int *lflag)
385 {
386 const char *cp = *cpp;
387
388 /* Defaults */
389 *lflag = LS_NAME_SORT;
390
391 /* Check for flags */
392 if (cp++[0] == '-') {
393 for (; strchr(WHITESPACE, *cp) == NULL; cp++) {
394 switch (*cp) {
395 case 'l':
396 *lflag &= ~VIEW_FLAGS;
397 *lflag |= LS_LONG_VIEW;
398 break;
399 case '1':
400 *lflag &= ~VIEW_FLAGS;
401 *lflag |= LS_SHORT_VIEW;
402 break;
403 case 'n':
404 *lflag &= ~VIEW_FLAGS;
405 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
406 break;
407 case 'S':
408 *lflag &= ~SORT_FLAGS;
409 *lflag |= LS_SIZE_SORT;
410 break;
411 case 't':
412 *lflag &= ~SORT_FLAGS;
413 *lflag |= LS_TIME_SORT;
414 break;
415 case 'r':
416 *lflag |= LS_REVERSE_SORT;
417 break;
418 case 'f':
419 *lflag &= ~SORT_FLAGS;
420 break;
421 case 'a':
422 *lflag |= LS_SHOW_ALL;
423 break;
424 default:
425 error("Invalid flag -%c", *cp);
426 return(-1);
427 }
428 }
429 *cpp = cp + strspn(cp, WHITESPACE);
430 }
431
432 return(0);
433 }
434
435 static int
get_pathname(const char ** cpp,char ** path)436 get_pathname(const char **cpp, char **path)
437 {
438 const char *cp = *cpp, *end;
439 char quot;
440 u_int i, j;
441
442 cp += strspn(cp, WHITESPACE);
443 if (!*cp) {
444 *cpp = cp;
445 *path = NULL;
446 return (0);
447 }
448
449 *path = xmalloc(strlen(cp) + 1);
450
451 /* Check for quoted filenames */
452 if (*cp == '\"' || *cp == '\'') {
453 quot = *cp++;
454
455 /* Search for terminating quote, unescape some chars */
456 for (i = j = 0; i <= strlen(cp); i++) {
457 if (cp[i] == quot) { /* Found quote */
458 i++;
459 (*path)[j] = '\0';
460 break;
461 }
462 if (cp[i] == '\0') { /* End of string */
463 error("Unterminated quote");
464 goto fail;
465 }
466 if (cp[i] == '\\') { /* Escaped characters */
467 i++;
468 if (cp[i] != '\'' && cp[i] != '\"' &&
469 cp[i] != '\\') {
470 error("Bad escaped character '\\%c'",
471 cp[i]);
472 goto fail;
473 }
474 }
475 (*path)[j++] = cp[i];
476 }
477
478 if (j == 0) {
479 error("Empty quotes");
480 goto fail;
481 }
482 *cpp = cp + i + strspn(cp + i, WHITESPACE);
483 } else {
484 /* Read to end of filename */
485 end = strpbrk(cp, WHITESPACE);
486 if (end == NULL)
487 end = strchr(cp, '\0');
488 *cpp = end + strspn(end, WHITESPACE);
489
490 memcpy(*path, cp, end - cp);
491 (*path)[end - cp] = '\0';
492 }
493 return (0);
494
495 fail:
496 xfree(*path);
497 *path = NULL;
498 return (-1);
499 }
500
501 static int
is_dir(char * path)502 is_dir(char *path)
503 {
504 struct stat sb;
505
506 /* XXX: report errors? */
507 if (stat(path, &sb) == -1)
508 return(0);
509
510 return(S_ISDIR(sb.st_mode));
511 }
512
513 static int
is_reg(char * path)514 is_reg(char *path)
515 {
516 struct stat sb;
517
518 if (stat(path, &sb) == -1)
519 fatal("stat %s: %s", path, strerror(errno));
520
521 return(S_ISREG(sb.st_mode));
522 }
523
524 static int
remote_is_dir(struct sftp_conn * conn,char * path)525 remote_is_dir(struct sftp_conn *conn, char *path)
526 {
527 Attrib *a;
528
529 /* XXX: report errors? */
530 if ((a = do_stat(conn, path, 1)) == NULL)
531 return(0);
532 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
533 return(0);
534 return(S_ISDIR(a->perm));
535 }
536
537 static int
process_get(struct sftp_conn * conn,char * src,char * dst,char * pwd,int pflag)538 process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
539 {
540 char *abs_src = NULL;
541 char *abs_dst = NULL;
542 char *tmp;
543 glob_t g;
544 int err = 0;
545 int i;
546
547 abs_src = xstrdup(src);
548 abs_src = make_absolute(abs_src, pwd);
549
550 memset(&g, 0, sizeof(g));
551 debug3("Looking up %s", abs_src);
552 if (remote_glob(conn, abs_src, 0, NULL, &g)) {
553 error("File \"%s\" not found.", abs_src);
554 err = -1;
555 goto out;
556 }
557
558 /* If multiple matches, dst must be a directory or unspecified */
559 if (g.gl_matchc > 1 && dst && !is_dir(dst)) {
560 error("Multiple files match, but \"%s\" is not a directory",
561 dst);
562 err = -1;
563 goto out;
564 }
565
566 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
567 if (infer_path(g.gl_pathv[i], &tmp)) {
568 err = -1;
569 goto out;
570 }
571
572 if (g.gl_matchc == 1 && dst) {
573 /* If directory specified, append filename */
574 xfree(tmp);
575 if (is_dir(dst)) {
576 if (infer_path(g.gl_pathv[0], &tmp)) {
577 err = 1;
578 goto out;
579 }
580 abs_dst = path_append(dst, tmp);
581 xfree(tmp);
582 } else
583 abs_dst = xstrdup(dst);
584 } else if (dst) {
585 abs_dst = path_append(dst, tmp);
586 xfree(tmp);
587 } else
588 abs_dst = tmp;
589
590 printf(gettext("Fetching %s to %s\n"), g.gl_pathv[i], abs_dst);
591 if (do_download(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
592 err = -1;
593 xfree(abs_dst);
594 abs_dst = NULL;
595 }
596
597 out:
598 xfree(abs_src);
599 globfree(&g);
600 return(err);
601 }
602
603 static int
process_put(struct sftp_conn * conn,char * src,char * dst,char * pwd,int pflag)604 process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
605 {
606 char *tmp_dst = NULL;
607 char *abs_dst = NULL;
608 char *tmp;
609 glob_t g;
610 int err = 0;
611 int i;
612
613 if (dst) {
614 tmp_dst = xstrdup(dst);
615 tmp_dst = make_absolute(tmp_dst, pwd);
616 }
617
618 memset(&g, 0, sizeof(g));
619 debug3("Looking up %s", src);
620 if (glob(src, GLOB_LIMIT, NULL, &g)) {
621 error("File \"%s\" not found.", src);
622 err = -1;
623 goto out;
624 }
625
626 /* If multiple matches, dst may be directory or unspecified */
627 if (g.gl_matchc > 1 && tmp_dst && !remote_is_dir(conn, tmp_dst)) {
628 error("Multiple files match, but \"%s\" is not a directory",
629 tmp_dst);
630 err = -1;
631 goto out;
632 }
633
634 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
635 if (!is_reg(g.gl_pathv[i])) {
636 error("skipping non-regular file %s",
637 g.gl_pathv[i]);
638 continue;
639 }
640 if (infer_path(g.gl_pathv[i], &tmp)) {
641 err = -1;
642 goto out;
643 }
644
645 if (g.gl_matchc == 1 && tmp_dst) {
646 /* If directory specified, append filename */
647 if (remote_is_dir(conn, tmp_dst)) {
648 if (infer_path(g.gl_pathv[0], &tmp)) {
649 err = 1;
650 goto out;
651 }
652 abs_dst = path_append(tmp_dst, tmp);
653 xfree(tmp);
654 } else
655 abs_dst = xstrdup(tmp_dst);
656
657 } else if (tmp_dst) {
658 abs_dst = path_append(tmp_dst, tmp);
659 xfree(tmp);
660 } else
661 abs_dst = make_absolute(tmp, pwd);
662
663 printf(gettext("Uploading %s to %s\n"), g.gl_pathv[i], abs_dst);
664 if (do_upload(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
665 err = -1;
666 }
667
668 out:
669 if (abs_dst)
670 xfree(abs_dst);
671 if (tmp_dst)
672 xfree(tmp_dst);
673 globfree(&g);
674 return(err);
675 }
676
677 static int
sdirent_comp(const void * aa,const void * bb)678 sdirent_comp(const void *aa, const void *bb)
679 {
680 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
681 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
682 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
683
684 #define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
685 if (sort_flag & LS_NAME_SORT)
686 return (rmul * strcmp(a->filename, b->filename));
687 else if (sort_flag & LS_TIME_SORT)
688 return (rmul * NCMP(a->a.mtime, b->a.mtime));
689 else if (sort_flag & LS_SIZE_SORT)
690 return (rmul * NCMP(a->a.size, b->a.size));
691
692 fatal("Unknown ls sort type");
693
694 /* NOTREACHED */
695 return (0);
696 }
697
698 /* sftp ls.1 replacement for directories */
699 static int
do_ls_dir(struct sftp_conn * conn,char * path,char * strip_path,int lflag)700 do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
701 {
702 int n;
703 u_int c = 1, colspace = 0, columns = 1;
704 SFTP_DIRENT **d;
705
706 if ((n = do_readdir(conn, path, &d)) != 0)
707 return (n);
708
709 if (!(lflag & LS_SHORT_VIEW)) {
710 u_int m = 0, width = 80;
711 struct winsize ws;
712 char *tmp;
713
714 /* Count entries for sort and find longest filename */
715 for (n = 0; d[n] != NULL; n++) {
716 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
717 m = MAX(m, strlen(d[n]->filename));
718 }
719
720 /* Add any subpath that also needs to be counted */
721 tmp = path_strip(path, strip_path);
722 m += strlen(tmp);
723 xfree(tmp);
724
725 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
726 width = ws.ws_col;
727
728 columns = width / (m + 2);
729 columns = MAX(columns, 1);
730 colspace = width / columns;
731 colspace = MIN(colspace, width);
732 }
733
734 if (lflag & SORT_FLAGS) {
735 for (n = 0; d[n] != NULL; n++)
736 ; /* count entries */
737 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
738 qsort(d, n, sizeof(*d), sdirent_comp);
739 }
740
741 for (n = 0; d[n] != NULL && !interrupted; n++) {
742 char *tmp, *fname;
743
744 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
745 continue;
746
747 tmp = path_append(path, d[n]->filename);
748 fname = path_strip(tmp, strip_path);
749 xfree(tmp);
750
751 if (lflag & LS_LONG_VIEW) {
752 if (lflag & LS_NUMERIC_VIEW) {
753 char *lname;
754 struct stat sb;
755
756 memset(&sb, 0, sizeof(sb));
757 attrib_to_stat(&d[n]->a, &sb);
758 lname = ls_file(fname, &sb, 1);
759 printf("%s\n", lname);
760 xfree(lname);
761 } else
762 printf("%s\n", d[n]->longname);
763 } else {
764 printf("%-*s", colspace, fname);
765 if (c >= columns) {
766 printf("\n");
767 c = 1;
768 } else
769 c++;
770 }
771
772 xfree(fname);
773 }
774
775 if (!(lflag & LS_LONG_VIEW) && (c != 1))
776 printf("\n");
777
778 free_sftp_dirents(d);
779 return (0);
780 }
781
782 /* sftp ls.1 replacement which handles path globs */
783 static int
do_globbed_ls(struct sftp_conn * conn,char * path,char * strip_path,int lflag)784 do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
785 int lflag)
786 {
787 glob_t g;
788 u_int i, c = 1, colspace = 0, columns = 1;
789 Attrib *a = NULL;
790
791 memset(&g, 0, sizeof(g));
792
793 if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE,
794 NULL, &g) || (g.gl_pathc && !g.gl_matchc)) {
795 if (g.gl_pathc)
796 globfree(&g);
797 error("Can't ls: \"%s\" not found", path);
798 return (-1);
799 }
800
801 if (interrupted)
802 goto out;
803
804 /*
805 * If the glob returns a single match and it is a directory,
806 * then just list its contents.
807 */
808 if (g.gl_matchc == 1) {
809 if ((a = do_lstat(conn, g.gl_pathv[0], 1)) == NULL) {
810 globfree(&g);
811 return (-1);
812 }
813 if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
814 S_ISDIR(a->perm)) {
815 int err;
816
817 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
818 globfree(&g);
819 return (err);
820 }
821 }
822
823 if (!(lflag & LS_SHORT_VIEW)) {
824 u_int m = 0, width = 80;
825 struct winsize ws;
826
827 /* Count entries for sort and find longest filename */
828 for (i = 0; g.gl_pathv[i]; i++)
829 m = MAX(m, strlen(g.gl_pathv[i]));
830
831 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
832 width = ws.ws_col;
833
834 columns = width / (m + 2);
835 columns = MAX(columns, 1);
836 colspace = width / columns;
837 }
838
839 for (i = 0; g.gl_pathv[i] && !interrupted; i++, a = NULL) {
840 char *fname;
841
842 fname = path_strip(g.gl_pathv[i], strip_path);
843
844 if (lflag & LS_LONG_VIEW) {
845 char *lname;
846 struct stat sb;
847
848 /*
849 * XXX: this is slow - 1 roundtrip per path
850 * A solution to this is to fork glob() and
851 * build a sftp specific version which keeps the
852 * attribs (which currently get thrown away)
853 * that the server returns as well as the filenames.
854 */
855 memset(&sb, 0, sizeof(sb));
856 if (a == NULL)
857 a = do_lstat(conn, g.gl_pathv[i], 1);
858 if (a != NULL)
859 attrib_to_stat(a, &sb);
860 lname = ls_file(fname, &sb, 1);
861 printf("%s\n", lname);
862 xfree(lname);
863 } else {
864 printf("%-*s", colspace, fname);
865 if (c >= columns) {
866 printf("\n");
867 c = 1;
868 } else
869 c++;
870 }
871 xfree(fname);
872 }
873
874 if (!(lflag & LS_LONG_VIEW) && (c != 1))
875 printf("\n");
876
877 out:
878 if (g.gl_pathc)
879 globfree(&g);
880
881 return (0);
882 }
883
884 static int
parse_args(const char ** cpp,int * pflag,int * lflag,int * iflag,unsigned long * n_arg,char ** path1,char ** path2)885 parse_args(const char **cpp, int *pflag, int *lflag, int *iflag,
886 unsigned long *n_arg, char **path1, char **path2)
887 {
888 const char *cmd, *cp = *cpp;
889 char *cp2;
890 int base = 0;
891 long l;
892 int i, cmdnum;
893
894 /* Skip leading whitespace */
895 cp = cp + strspn(cp, WHITESPACE);
896
897 /* Ignore blank lines and lines which begin with comment '#' char */
898 if (*cp == '\0' || *cp == '#')
899 return (0);
900
901 /* Check for leading '-' (disable error processing) */
902 *iflag = 0;
903 if (*cp == '-') {
904 *iflag = 1;
905 cp++;
906 }
907
908 /* Figure out which command we have */
909 for (i = 0; cmds[i].c; i++) {
910 int cmdlen = strlen(cmds[i].c);
911
912 /* Check for command followed by whitespace */
913 if (!strncasecmp(cp, cmds[i].c, cmdlen) &&
914 strchr(WHITESPACE, cp[cmdlen])) {
915 cp += cmdlen;
916 cp = cp + strspn(cp, WHITESPACE);
917 break;
918 }
919 }
920 cmdnum = cmds[i].n;
921 cmd = cmds[i].c;
922
923 /* Special case */
924 if (*cp == '!') {
925 cp++;
926 cmdnum = I_SHELL;
927 } else if (cmdnum == -1) {
928 error("Invalid command.");
929 return (-1);
930 }
931
932 /* Get arguments and parse flags */
933 *lflag = *pflag = *n_arg = 0;
934 *path1 = *path2 = NULL;
935 switch (cmdnum) {
936 case I_GET:
937 case I_PUT:
938 if (parse_getput_flags(&cp, pflag))
939 return(-1);
940 /* Get first pathname (mandatory) */
941 if (get_pathname(&cp, path1))
942 return(-1);
943 if (*path1 == NULL) {
944 error("You must specify at least one path after a "
945 "%s command.", cmd);
946 return(-1);
947 }
948 /* Try to get second pathname (optional) */
949 if (get_pathname(&cp, path2))
950 return(-1);
951 break;
952 case I_RENAME:
953 case I_SYMLINK:
954 if (get_pathname(&cp, path1))
955 return(-1);
956 if (get_pathname(&cp, path2))
957 return(-1);
958 if (!*path1 || !*path2) {
959 error("You must specify two paths after a %s "
960 "command.", cmd);
961 return(-1);
962 }
963 break;
964 case I_RM:
965 case I_MKDIR:
966 case I_RMDIR:
967 case I_CHDIR:
968 case I_LCHDIR:
969 case I_LMKDIR:
970 /* Get pathname (mandatory) */
971 if (get_pathname(&cp, path1))
972 return(-1);
973 if (*path1 == NULL) {
974 error("You must specify a path after a %s command.",
975 cmd);
976 return(-1);
977 }
978 break;
979 case I_LS:
980 if (parse_ls_flags(&cp, lflag))
981 return(-1);
982 /* Path is optional */
983 if (get_pathname(&cp, path1))
984 return(-1);
985 break;
986 case I_LLS:
987 case I_SHELL:
988 /* Uses the rest of the line */
989 break;
990 case I_LUMASK:
991 base = 8;
992 /* FALLTHRU */
993 case I_CHMOD:
994 base = 8;
995 /* FALLTHRU */
996 case I_CHOWN:
997 case I_CHGRP:
998 /* Get numeric arg (mandatory) */
999 errno = 0;
1000 l = strtol(cp, &cp2, base);
1001 if (cp2 == cp || ((l == LONG_MIN || l == LONG_MAX) &&
1002 errno == ERANGE) || l < 0) {
1003 error("You must supply a numeric argument "
1004 "to the %s command.", cmd);
1005 return(-1);
1006 }
1007 cp = cp2;
1008 *n_arg = l;
1009 if (cmdnum == I_LUMASK && strchr(WHITESPACE, *cp))
1010 break;
1011 if (cmdnum == I_LUMASK || !strchr(WHITESPACE, *cp)) {
1012 error("You must supply a numeric argument "
1013 "to the %s command.", cmd);
1014 return(-1);
1015 }
1016 cp += strspn(cp, WHITESPACE);
1017
1018 /* Get pathname (mandatory) */
1019 if (get_pathname(&cp, path1))
1020 return(-1);
1021 if (*path1 == NULL) {
1022 error("You must specify a path after a %s command.",
1023 cmd);
1024 return(-1);
1025 }
1026 break;
1027 case I_QUIT:
1028 case I_PWD:
1029 case I_LPWD:
1030 case I_HELP:
1031 case I_VERSION:
1032 case I_PROGRESS:
1033 break;
1034 default:
1035 fatal("Command not implemented");
1036 }
1037
1038 *cpp = cp;
1039 return(cmdnum);
1040 }
1041
1042 static int
parse_dispatch_command(struct sftp_conn * conn,const char * cmd,char ** pwd,int err_abort)1043 parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1044 int err_abort)
1045 {
1046 char *path1, *path2, *tmp;
1047 int pflag, lflag, iflag, cmdnum, i;
1048 unsigned long n_arg;
1049 Attrib a, *aa;
1050 char path_buf[MAXPATHLEN];
1051 int err = 0;
1052 glob_t g;
1053
1054 path1 = path2 = NULL;
1055 cmdnum = parse_args(&cmd, &pflag, &lflag, &iflag, &n_arg,
1056 &path1, &path2);
1057
1058 if (iflag != 0)
1059 err_abort = 0;
1060
1061 memset(&g, 0, sizeof(g));
1062
1063 /* Perform command */
1064 switch (cmdnum) {
1065 case 0:
1066 /* Blank line */
1067 break;
1068 case -1:
1069 /* Unrecognized command */
1070 err = -1;
1071 break;
1072 case I_GET:
1073 err = process_get(conn, path1, path2, *pwd, pflag);
1074 break;
1075 case I_PUT:
1076 err = process_put(conn, path1, path2, *pwd, pflag);
1077 break;
1078 case I_RENAME:
1079 path1 = make_absolute(path1, *pwd);
1080 path2 = make_absolute(path2, *pwd);
1081 err = do_rename(conn, path1, path2);
1082 break;
1083 case I_SYMLINK:
1084 path2 = make_absolute(path2, *pwd);
1085 err = do_symlink(conn, path1, path2);
1086 break;
1087 case I_RM:
1088 path1 = make_absolute(path1, *pwd);
1089 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1090 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1091 printf(gettext("Removing %s\n"), g.gl_pathv[i]);
1092 err = do_rm(conn, g.gl_pathv[i]);
1093 if (err != 0 && err_abort)
1094 break;
1095 }
1096 break;
1097 case I_MKDIR:
1098 path1 = make_absolute(path1, *pwd);
1099 attrib_clear(&a);
1100 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1101 a.perm = 0777;
1102 err = do_mkdir(conn, path1, &a);
1103 break;
1104 case I_RMDIR:
1105 path1 = make_absolute(path1, *pwd);
1106 err = do_rmdir(conn, path1);
1107 break;
1108 case I_CHDIR:
1109 path1 = make_absolute(path1, *pwd);
1110 if ((tmp = do_realpath(conn, path1)) == NULL) {
1111 err = 1;
1112 break;
1113 }
1114 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1115 xfree(tmp);
1116 err = 1;
1117 break;
1118 }
1119 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1120 error("Can't change directory: Can't check target");
1121 xfree(tmp);
1122 err = 1;
1123 break;
1124 }
1125 if (!S_ISDIR(aa->perm)) {
1126 error("Can't change directory: \"%s\" is not "
1127 "a directory", tmp);
1128 xfree(tmp);
1129 err = 1;
1130 break;
1131 }
1132 xfree(*pwd);
1133 *pwd = tmp;
1134 break;
1135 case I_LS:
1136 if (!path1) {
1137 do_globbed_ls(conn, *pwd, *pwd, lflag);
1138 break;
1139 }
1140
1141 /* Strip pwd off beginning of non-absolute paths */
1142 tmp = NULL;
1143 if (*path1 != '/')
1144 tmp = *pwd;
1145
1146 path1 = make_absolute(path1, *pwd);
1147 err = do_globbed_ls(conn, path1, tmp, lflag);
1148 break;
1149 case I_LCHDIR:
1150 if (chdir(path1) == -1) {
1151 error("Couldn't change local directory to "
1152 "\"%s\": %s", path1, strerror(errno));
1153 err = 1;
1154 }
1155 break;
1156 case I_LMKDIR:
1157 if (mkdir(path1, 0777) == -1) {
1158 error("Couldn't create local directory "
1159 "\"%s\": %s", path1, strerror(errno));
1160 err = 1;
1161 }
1162 break;
1163 case I_LLS:
1164 local_do_ls(cmd);
1165 break;
1166 case I_SHELL:
1167 local_do_shell(cmd);
1168 break;
1169 case I_LUMASK:
1170 umask(n_arg);
1171 printf(gettext("Local umask: %03lo\n"), n_arg);
1172 break;
1173 case I_CHMOD:
1174 path1 = make_absolute(path1, *pwd);
1175 attrib_clear(&a);
1176 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1177 a.perm = n_arg;
1178 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1179 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1180 printf(gettext("Changing mode on %s\n"), g.gl_pathv[i]);
1181 err = do_setstat(conn, g.gl_pathv[i], &a);
1182 if (err != 0 && err_abort)
1183 break;
1184 }
1185 break;
1186 case I_CHOWN:
1187 case I_CHGRP:
1188 path1 = make_absolute(path1, *pwd);
1189 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
1190 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
1191 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
1192 if (err != 0 && err_abort)
1193 break;
1194 else
1195 continue;
1196 }
1197 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1198 error("Can't get current ownership of "
1199 "remote file \"%s\"", g.gl_pathv[i]);
1200 if (err != 0 && err_abort)
1201 break;
1202 else
1203 continue;
1204 }
1205 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1206 if (cmdnum == I_CHOWN) {
1207 printf(gettext("Changing owner on %s\n"), g.gl_pathv[i]);
1208 aa->uid = n_arg;
1209 } else {
1210 printf(gettext("Changing group on %s\n"), g.gl_pathv[i]);
1211 aa->gid = n_arg;
1212 }
1213 err = do_setstat(conn, g.gl_pathv[i], aa);
1214 if (err != 0 && err_abort)
1215 break;
1216 }
1217 break;
1218 case I_PWD:
1219 printf(gettext("Remote working directory: %s\n"), *pwd);
1220 break;
1221 case I_LPWD:
1222 if (!getcwd(path_buf, sizeof(path_buf))) {
1223 error("Couldn't get local cwd: %s", strerror(errno));
1224 err = -1;
1225 break;
1226 }
1227 printf(gettext("Local working directory: %s\n"), path_buf);
1228 break;
1229 case I_QUIT:
1230 /* Processed below */
1231 break;
1232 case I_HELP:
1233 help();
1234 break;
1235 case I_VERSION:
1236 printf(gettext("SFTP protocol version %u\n"), sftp_proto_version(conn));
1237 break;
1238 case I_PROGRESS:
1239 showprogress = !showprogress;
1240 if (showprogress)
1241 printf("Progress meter enabled\n");
1242 else
1243 printf("Progress meter disabled\n");
1244 break;
1245 default:
1246 fatal("%d is not implemented", cmdnum);
1247 }
1248
1249 if (g.gl_pathc)
1250 globfree(&g);
1251 if (path1)
1252 xfree(path1);
1253 if (path2)
1254 xfree(path2);
1255
1256 /* If an unignored error occurs in batch mode we should abort. */
1257 if (err_abort && err != 0)
1258 return (-1);
1259 else if (cmdnum == I_QUIT)
1260 return (1);
1261
1262 return (0);
1263 }
1264
1265 #ifdef USE_LIBEDIT
1266 static char *
prompt(EditLine * el)1267 prompt(EditLine *el)
1268 {
1269 return ("sftp> ");
1270 }
1271 #else
1272 #ifdef USE_LIBTECLA
1273 /*
1274 * Disable default TAB completion for filenames, because it displays local
1275 * files for every commands, which is not desirable.
1276 */
1277 static
CPL_MATCH_FN(nomatch)1278 CPL_MATCH_FN(nomatch)
1279 {
1280 return (0);
1281 }
1282 #endif /* USE_LIBTECLA */
1283 #endif /* USE_LIBEDIT */
1284
1285 int
interactive_loop(int fd_in,int fd_out,char * file1,char * file2)1286 interactive_loop(int fd_in, int fd_out, char *file1, char *file2)
1287 {
1288 char *pwd;
1289 char *dir = NULL;
1290 char cmd[2048];
1291 struct sftp_conn *conn;
1292 int err, interactive;
1293 void *il = NULL;
1294
1295 #ifdef USE_LIBEDIT
1296 EditLine *el = NULL;
1297 History *hl = NULL;
1298 HistEvent hev;
1299
1300 if (!batchmode && isatty(STDIN_FILENO)) {
1301 if ((il = el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1302 fatal("Couldn't initialise editline");
1303 if ((hl = history_init()) == NULL)
1304 fatal("Couldn't initialise editline history");
1305 history(hl, &hev, H_SETSIZE, 100);
1306 el_set(el, EL_HIST, history, hl);
1307
1308 el_set(el, EL_PROMPT, prompt);
1309 el_set(el, EL_EDITOR, "emacs");
1310 el_set(el, EL_TERMINAL, NULL);
1311 el_set(el, EL_SIGNAL, 1);
1312 el_source(el, NULL);
1313 }
1314 #else
1315 #ifdef USE_LIBTECLA
1316 GetLine *gl = NULL;
1317
1318 if (!batchmode && isatty(STDIN_FILENO)) {
1319 if ((il = gl = new_GetLine(MAX_LINE_LEN, MAX_CMD_HIST)) == NULL)
1320 fatal("Couldn't initialize GetLine");
1321 if (gl_customize_completion(gl, NULL, nomatch) != 0) {
1322 (void) del_GetLine(gl);
1323 fatal("Couldn't register completion function");
1324 }
1325 }
1326 #endif /* USE_LIBTECLA */
1327 #endif /* USE_LIBEDIT */
1328
1329 conn = do_init(fd_in, fd_out, copy_buffer_len, num_requests);
1330 if (conn == NULL)
1331 fatal("Couldn't initialise connection to server");
1332
1333 pwd = do_realpath(conn, ".");
1334 if (pwd == NULL)
1335 fatal("Need cwd");
1336
1337 if (file1 != NULL) {
1338 dir = xstrdup(file1);
1339 dir = make_absolute(dir, pwd);
1340
1341 if (remote_is_dir(conn, dir) && file2 == NULL) {
1342 printf(gettext("Changing to: %s\n"), dir);
1343 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
1344 if (parse_dispatch_command(conn, cmd, &pwd, 1) != 0) {
1345 xfree(dir);
1346 xfree(pwd);
1347 xfree(conn);
1348 return (-1);
1349 }
1350 } else {
1351 if (file2 == NULL)
1352 snprintf(cmd, sizeof cmd, "get %s", dir);
1353 else
1354 snprintf(cmd, sizeof cmd, "get %s %s", dir,
1355 file2);
1356
1357 err = parse_dispatch_command(conn, cmd, &pwd, 1);
1358 xfree(dir);
1359 xfree(pwd);
1360 xfree(conn);
1361 return (err);
1362 }
1363 xfree(dir);
1364 }
1365
1366 #if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF)
1367 setvbuf(stdout, NULL, _IOLBF, 0);
1368 setvbuf(infile, NULL, _IOLBF, 0);
1369 #else
1370 setlinebuf(stdout);
1371 setlinebuf(infile);
1372 #endif
1373
1374 interactive = !batchmode && isatty(STDIN_FILENO);
1375 err = 0;
1376 for (;;) {
1377 char *cp;
1378
1379 signal(SIGINT, SIG_IGN);
1380
1381 if (il == NULL) {
1382 if (interactive)
1383 printf("sftp> ");
1384 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
1385 if (interactive)
1386 printf("\n");
1387 break;
1388 }
1389 if (!interactive) { /* Echo command */
1390 printf("sftp> %s", cmd);
1391 if (strlen(cmd) > 0 &&
1392 cmd[strlen(cmd) - 1] != '\n')
1393 printf("\n");
1394 }
1395 }
1396 #ifdef USE_LIBEDIT
1397 else {
1398 const char *line;
1399 int count = 0;
1400
1401 if ((line = el_gets(el, &count)) == NULL || count <= 0) {
1402 printf("\n");
1403 break;
1404 }
1405 history(hl, &hev, H_ENTER, line);
1406 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
1407 fprintf(stderr, gettext("Error: input line too long\n"));
1408 continue;
1409 }
1410 }
1411 #else
1412 #ifdef USE_LIBTECLA
1413 else {
1414 const char *line;
1415
1416 line = gl_get_line(gl, "sftp> ", NULL, -1);
1417 if (line != NULL) {
1418 if (strlcpy(cmd, line, sizeof(cmd)) >=
1419 sizeof(cmd)) {
1420 fprintf(stderr, gettext(
1421 "Error: input line too long\n"));
1422 continue;
1423 }
1424 } else {
1425 GlReturnStatus rtn;
1426
1427 rtn = gl_return_status(gl);
1428 if (rtn == GLR_SIGNAL) {
1429 gl_abandon_line(gl);
1430 continue;
1431 } else if (rtn == GLR_ERROR) {
1432 fprintf(stderr, gettext(
1433 "Error reading terminal: %s/\n"),
1434 gl_error_message(gl, NULL, 0));
1435 }
1436 break;
1437 }
1438 }
1439 #endif /* USE_LIBTECLA */
1440 #endif /* USE_LIBEDIT */
1441
1442 cp = strrchr(cmd, '\n');
1443 if (cp)
1444 *cp = '\0';
1445
1446 /* Handle user interrupts gracefully during commands */
1447 interrupted = 0;
1448 signal(SIGINT, cmd_interrupt);
1449
1450 err = parse_dispatch_command(conn, cmd, &pwd, batchmode);
1451 if (err != 0)
1452 break;
1453 }
1454 xfree(pwd);
1455 xfree(conn);
1456
1457 #ifdef USE_LIBEDIT
1458 if (el != NULL)
1459 el_end(el);
1460 #else
1461 #ifdef USE_LIBTECLA
1462 if (gl != NULL)
1463 (void) del_GetLine(gl);
1464 #endif /* USE_LIBTECLA */
1465 #endif /* USE_LIBEDIT */
1466
1467 /* err == 1 signifies normal "quit" exit */
1468 return (err >= 0 ? 0 : -1);
1469 }
1470
1471 static void
connect_to_server(char * path,char ** args,int * in,int * out)1472 connect_to_server(char *path, char **args, int *in, int *out)
1473 {
1474 int c_in, c_out;
1475
1476 int inout[2];
1477
1478 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
1479 fatal("socketpair: %s", strerror(errno));
1480 *in = *out = inout[0];
1481 c_in = c_out = inout[1];
1482
1483 if ((sshpid = fork()) == -1)
1484 fatal("fork: %s", strerror(errno));
1485 else if (sshpid == 0) {
1486 if ((dup2(c_in, STDIN_FILENO) == -1) ||
1487 (dup2(c_out, STDOUT_FILENO) == -1)) {
1488 fprintf(stderr, "dup2: %s\n", strerror(errno));
1489 _exit(1);
1490 }
1491 close(*in);
1492 close(*out);
1493 close(c_in);
1494 close(c_out);
1495
1496 /*
1497 * The underlying ssh is in the same process group, so we must
1498 * ignore SIGINT if we want to gracefully abort commands,
1499 * otherwise the signal will make it to the ssh process and
1500 * kill it too
1501 */
1502 signal(SIGINT, SIG_IGN);
1503 execvp(path, args);
1504 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
1505 _exit(1);
1506 }
1507
1508 signal(SIGTERM, killchild);
1509 signal(SIGINT, killchild);
1510 signal(SIGHUP, killchild);
1511 close(c_in);
1512 close(c_out);
1513 }
1514
1515 static void
usage(void)1516 usage(void)
1517 {
1518 fprintf(stderr,
1519 gettext("Usage: %s [-1Cv] [-b batchfile] [-B buffer_size]\n"
1520 " [-F ssh_config] [-o ssh_option] [-P sftp_server_path]\n"
1521 " [-R num_requests] [-s subsystem | sftp_server]\n"
1522 " [-S program] [user@]host[:dir[/] | :file [file]]\n"),
1523 __progname, __progname, __progname, __progname);
1524 exit(1);
1525 }
1526
1527 int
main(int argc,char ** argv)1528 main(int argc, char **argv)
1529 {
1530 int in, out, ch, err;
1531 char *host, *userhost, *cp, *file2 = NULL;
1532 int debug_level = 0, sshver = 2;
1533 char *file1 = NULL, *sftp_server = NULL;
1534 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
1535 LogLevel ll = SYSLOG_LEVEL_INFO;
1536 arglist args;
1537 extern int optind;
1538 extern char *optarg;
1539
1540 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
1541 sanitise_stdfd();
1542
1543 __progname = get_progname(argv[0]);
1544
1545 (void) g11n_setlocale(LC_ALL, "");
1546
1547 memset(&args, '\0', sizeof(args));
1548 args.list = NULL;
1549 addargs(&args, "%s", ssh_program);
1550 addargs(&args, "-oForwardX11 no");
1551 addargs(&args, "-oForwardAgent no");
1552 addargs(&args, "-oClearAllForwardings yes");
1553
1554 ll = SYSLOG_LEVEL_INFO;
1555 infile = stdin;
1556
1557 while ((ch = getopt(argc, argv, "1hvCo:s:S:b:B:F:P:R:")) != -1) {
1558 switch (ch) {
1559 case 'C':
1560 addargs(&args, "-C");
1561 break;
1562 case 'v':
1563 if (debug_level < 3) {
1564 addargs(&args, "-v");
1565 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
1566 }
1567 debug_level++;
1568 break;
1569 case 'F':
1570 case 'o':
1571 addargs(&args, "-%c%s", ch, optarg);
1572 break;
1573 case '1':
1574 sshver = 1;
1575 if (sftp_server == NULL)
1576 sftp_server = _PATH_SFTP_SERVER;
1577 break;
1578 case 's':
1579 sftp_server = optarg;
1580 break;
1581 case 'S':
1582 ssh_program = optarg;
1583 replacearg(&args, 0, "%s", ssh_program);
1584 break;
1585 case 'b':
1586 if (batchmode)
1587 fatal("Batch file already specified.");
1588
1589 /* Allow "-" as stdin */
1590 if (strcmp(optarg, "-") != 0 &&
1591 (infile = fopen(optarg, "r")) == NULL)
1592 fatal("%s (%s).", strerror(errno), optarg);
1593 showprogress = 0;
1594 batchmode = 1;
1595 addargs(&args, "-obatchmode yes");
1596 break;
1597 case 'P':
1598 sftp_direct = optarg;
1599 break;
1600 case 'B':
1601 copy_buffer_len = strtol(optarg, &cp, 10);
1602 if (copy_buffer_len == 0 || *cp != '\0')
1603 fatal("Invalid buffer size \"%s\"", optarg);
1604 break;
1605 case 'R':
1606 num_requests = strtol(optarg, &cp, 10);
1607 if (num_requests == 0 || *cp != '\0')
1608 fatal("Invalid number of requests \"%s\"",
1609 optarg);
1610 break;
1611 case 'h':
1612 default:
1613 usage();
1614 }
1615 }
1616
1617 if (!isatty(STDERR_FILENO))
1618 showprogress = 0;
1619
1620 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
1621
1622 if (sftp_direct == NULL) {
1623 if (optind == argc || argc > (optind + 2))
1624 usage();
1625
1626 userhost = xstrdup(argv[optind]);
1627 file2 = argv[optind+1];
1628
1629 if ((host = strrchr(userhost, '@')) == NULL)
1630 host = userhost;
1631 else {
1632 *host++ = '\0';
1633 if (!userhost[0]) {
1634 fprintf(stderr, gettext("Missing username\n"));
1635 usage();
1636 }
1637 addargs(&args, "-l%s", userhost);
1638 }
1639
1640 if ((cp = colon(host)) != NULL) {
1641 *cp++ = '\0';
1642 file1 = cp;
1643 }
1644
1645 host = cleanhostname(host);
1646 if (!*host) {
1647 fprintf(stderr, gettext("Missing hostname\n"));
1648 usage();
1649 }
1650
1651 addargs(&args, "-oProtocol %d", sshver);
1652
1653 /* no subsystem if the server-spec contains a '/' */
1654 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
1655 addargs(&args, "-s");
1656
1657 addargs(&args, "%s", host);
1658 addargs(&args, "%s", (sftp_server != NULL ?
1659 sftp_server : "sftp"));
1660
1661 if (!batchmode)
1662 fprintf(stderr, gettext("Connecting to %s...\n"), host);
1663 connect_to_server(ssh_program, args.list, &in, &out);
1664 } else {
1665 args.list = NULL;
1666 addargs(&args, "sftp-server");
1667
1668 if (!batchmode)
1669 fprintf(stderr, gettext("Attaching to %s...\n"), sftp_direct);
1670 connect_to_server(sftp_direct, args.list, &in, &out);
1671 }
1672 freeargs(&args);
1673
1674 err = interactive_loop(in, out, file1, file2);
1675
1676 shutdown(in, SHUT_RDWR);
1677 shutdown(out, SHUT_RDWR);
1678
1679 close(in);
1680 close(out);
1681 if (batchmode)
1682 fclose(infile);
1683
1684 while (waitpid(sshpid, NULL, 0) == -1)
1685 if (errno != EINTR)
1686 fatal("Couldn't wait for ssh process: %s",
1687 strerror(errno));
1688
1689 return (err == 0 ? 0 : 1);
1690 }
1691