1 /* $OpenBSD: sftp.c,v 1.115 2009/12/20 07:28:36 guenther Exp $ */
3 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
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.
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.
20 #include <sys/types.h>
21 #include <sys/ioctl.h>
22 #ifdef HAVE_SYS_STAT_H
23 # include <sys/stat.h>
25 #include <sys/param.h>
26 #include <sys/socket.h>
28 #ifdef HAVE_SYS_STATVFS_H
29 #include <sys/statvfs.h>
44 typedef void EditLine
;
63 #include "pathnames.h"
68 #include "sftp-common.h"
69 #include "sftp-client.h"
71 #define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */
72 #define DEFAULT_NUM_REQUESTS 64 /* # concurrent outstanding requests */
74 /* File to read commands from */
77 /* Are we in batchfile mode? */
80 /* PID of ssh transport process */
81 static pid_t sshpid
= -1;
83 /* This is set to 0 if the progressmeter is not desired. */
86 /* When this option is set, we always recursively download/upload directories */
89 /* When this option is set, the file transfers will always preserve times */
92 /* SIGINT received during command processing */
93 volatile sig_atomic_t interrupted
= 0;
95 /* I wish qsort() took a separate ctx for the comparison function...*/
98 int remote_glob(struct sftp_conn
*, const char *, int,
99 int (*)(const char *, int), glob_t
*); /* proto for sftp-glob.c */
101 extern char *__progname
;
103 /* Separators for interactive commands */
104 #define WHITESPACE " \t\r\n"
107 #define LS_LONG_VIEW 0x01 /* Full view ala ls -l */
108 #define LS_SHORT_VIEW 0x02 /* Single row view ala ls -1 */
109 #define LS_NUMERIC_VIEW 0x04 /* Long view with numeric uid/gid */
110 #define LS_NAME_SORT 0x08 /* Sort by name (default) */
111 #define LS_TIME_SORT 0x10 /* Sort by mtime */
112 #define LS_SIZE_SORT 0x20 /* Sort by file size */
113 #define LS_REVERSE_SORT 0x40 /* Reverse sort order */
114 #define LS_SHOW_ALL 0x80 /* Don't skip filenames starting with '.' */
116 #define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW)
117 #define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
119 /* Commands for interactive mode */
143 #define I_PROGRESS 23
150 static const struct CMD cmds
[] = {
153 { "chdir", I_CHDIR
},
154 { "chgrp", I_CHGRP
},
155 { "chmod", I_CHMOD
},
156 { "chown", I_CHOWN
},
164 { "lchdir", I_LCHDIR
},
166 { "lmkdir", I_LMKDIR
},
170 { "lumask", I_LUMASK
},
171 { "mkdir", I_MKDIR
},
172 { "progress", I_PROGRESS
},
177 { "rename", I_RENAME
},
179 { "rmdir", I_RMDIR
},
180 { "symlink", I_SYMLINK
},
181 { "version", I_VERSION
},
187 int interactive_loop(struct sftp_conn
*, char *file1
, char *file2
);
194 kill(sshpid
, SIGTERM
);
195 waitpid(sshpid
, NULL
, 0);
203 cmd_interrupt(int signo
)
205 const char msg
[] = "\rInterrupt \n";
206 int olderrno
= errno
;
208 write(STDERR_FILENO
, msg
, sizeof(msg
) - 1);
216 printf("Available commands:\n"
218 "cd path Change remote directory to 'path'\n"
219 "chgrp grp path Change group of file 'path' to 'grp'\n"
220 "chmod mode path Change permissions of file 'path' to 'mode'\n"
221 "chown own path Change owner of file 'path' to 'own'\n"
222 "df [-hi] [path] Display statistics for current directory or\n"
223 " filesystem containing 'path'\n"
225 "get [-Pr] remote-path [local-path] Download file\n"
226 "help Display this help text\n"
227 "lcd path Change local directory to 'path'\n"
228 "lls [ls-options [path]] Display local directory listing\n"
229 "lmkdir path Create local directory\n"
230 "ln oldpath newpath Symlink remote file\n"
231 "lpwd Print local working directory\n"
232 "ls [-1aflnrSt] [path] Display remote directory listing\n"
233 "lumask umask Set local umask to 'umask'\n"
234 "mkdir path Create remote directory\n"
235 "progress Toggle display of progress meter\n"
236 "put [-Pr] local-path [remote-path] Upload file\n"
237 "pwd Display remote working directory\n"
239 "rename oldpath newpath Rename remote file\n"
240 "rm path Delete remote file\n"
241 "rmdir path Remove remote directory\n"
242 "symlink oldpath newpath Symlink remote file\n"
243 "version Show SFTP version\n"
244 "!command Execute 'command' in local shell\n"
245 "! Escape to local shell\n"
246 "? Synonym for help\n");
250 local_do_shell(const char *args
)
259 if ((shell
= getenv("SHELL")) == NULL
)
260 shell
= _PATH_BSHELL
;
262 if ((pid
= fork()) == -1)
263 fatal("Couldn't fork: %s", strerror(errno
));
266 /* XXX: child has pipe fds to ssh subproc open - issue? */
268 debug3("Executing %s -c \"%s\"", shell
, args
);
269 execl(shell
, shell
, "-c", args
, (char *)NULL
);
271 debug3("Executing %s", shell
);
272 execl(shell
, shell
, (char *)NULL
);
274 fprintf(stderr
, "Couldn't execute \"%s\": %s\n", shell
,
278 while (waitpid(pid
, &status
, 0) == -1)
280 fatal("Couldn't wait for child: %s", strerror(errno
));
281 if (!WIFEXITED(status
))
282 error("Shell exited abnormally");
283 else if (WEXITSTATUS(status
))
284 error("Shell exited with status %d", WEXITSTATUS(status
));
288 local_do_ls(const char *args
)
291 local_do_shell(_PATH_LS
);
293 int len
= strlen(_PATH_LS
" ") + strlen(args
) + 1;
294 char *buf
= xmalloc(len
);
296 /* XXX: quoting - rip quoting code from ftp? */
297 snprintf(buf
, len
, _PATH_LS
" %s", args
);
303 /* Strip one path (usually the pwd) from the start of another */
305 path_strip(char *path
, char *strip
)
310 return (xstrdup(path
));
313 if (strncmp(path
, strip
, len
) == 0) {
314 if (strip
[len
- 1] != '/' && path
[len
] == '/')
316 return (xstrdup(path
+ len
));
319 return (xstrdup(path
));
323 make_absolute(char *p
, char *pwd
)
328 if (p
&& p
[0] != '/') {
329 abs_str
= path_append(pwd
, p
);
337 parse_getput_flags(const char *cmd
, char **argv
, int argc
, int *pflag
,
340 extern int opterr
, optind
, optopt
, optreset
;
343 optind
= optreset
= 1;
347 while ((ch
= getopt(argc
, argv
, "PpRr")) != -1) {
358 error("%s: Invalid flag -%c", cmd
, optopt
);
367 parse_ls_flags(char **argv
, int argc
, int *lflag
)
369 extern int opterr
, optind
, optopt
, optreset
;
372 optind
= optreset
= 1;
375 *lflag
= LS_NAME_SORT
;
376 while ((ch
= getopt(argc
, argv
, "1Saflnrt")) != -1) {
379 *lflag
&= ~VIEW_FLAGS
;
380 *lflag
|= LS_SHORT_VIEW
;
383 *lflag
&= ~SORT_FLAGS
;
384 *lflag
|= LS_SIZE_SORT
;
387 *lflag
|= LS_SHOW_ALL
;
390 *lflag
&= ~SORT_FLAGS
;
393 *lflag
&= ~VIEW_FLAGS
;
394 *lflag
|= LS_LONG_VIEW
;
397 *lflag
&= ~VIEW_FLAGS
;
398 *lflag
|= LS_NUMERIC_VIEW
|LS_LONG_VIEW
;
401 *lflag
|= LS_REVERSE_SORT
;
404 *lflag
&= ~SORT_FLAGS
;
405 *lflag
|= LS_TIME_SORT
;
408 error("ls: Invalid flag -%c", optopt
);
417 parse_df_flags(const char *cmd
, char **argv
, int argc
, int *hflag
, int *iflag
)
419 extern int opterr
, optind
, optopt
, optreset
;
422 optind
= optreset
= 1;
426 while ((ch
= getopt(argc
, argv
, "hi")) != -1) {
435 error("%s: Invalid flag -%c", cmd
, optopt
);
448 /* XXX: report errors? */
449 if (stat(path
, &sb
) == -1)
452 return(S_ISDIR(sb
.st_mode
));
456 remote_is_dir(struct sftp_conn
*conn
, char *path
)
460 /* XXX: report errors? */
461 if ((a
= do_stat(conn
, path
, 1)) == NULL
)
463 if (!(a
->flags
& SSH2_FILEXFER_ATTR_PERMISSIONS
))
465 return(S_ISDIR(a
->perm
));
468 /* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
470 pathname_is_dir(char *pathname
)
472 size_t l
= strlen(pathname
);
474 return l
> 0 && pathname
[l
- 1] == '/';
478 process_get(struct sftp_conn
*conn
, char *src
, char *dst
, char *pwd
,
479 int pflag
, int rflag
)
481 char *abs_src
= NULL
;
482 char *abs_dst
= NULL
;
484 char *filename
, *tmp
=NULL
;
487 abs_src
= xstrdup(src
);
488 abs_src
= make_absolute(abs_src
, pwd
);
489 memset(&g
, 0, sizeof(g
));
491 debug3("Looking up %s", abs_src
);
492 if (remote_glob(conn
, abs_src
, GLOB_MARK
, NULL
, &g
)) {
493 error("File \"%s\" not found.", abs_src
);
499 * If multiple matches then dst must be a directory or
502 if (g
.gl_matchc
> 1 && dst
!= NULL
&& !is_dir(dst
)) {
503 error("Multiple source paths, but destination "
504 "\"%s\" is not a directory", dst
);
509 for (i
= 0; g
.gl_pathv
[i
] && !interrupted
; i
++) {
510 tmp
= xstrdup(g
.gl_pathv
[i
]);
511 if ((filename
= basename(tmp
)) == NULL
) {
512 error("basename %s: %s", tmp
, strerror(errno
));
518 if (g
.gl_matchc
== 1 && dst
) {
520 abs_dst
= path_append(dst
, filename
);
522 abs_dst
= xstrdup(dst
);
525 abs_dst
= path_append(dst
, filename
);
527 abs_dst
= xstrdup(filename
);
531 printf("Fetching %s to %s\n", g
.gl_pathv
[i
], abs_dst
);
532 if (pathname_is_dir(g
.gl_pathv
[i
]) && (rflag
|| global_rflag
)) {
533 if (download_dir(conn
, g
.gl_pathv
[i
], abs_dst
, NULL
,
534 pflag
|| global_pflag
, 1) == -1)
537 if (do_download(conn
, g
.gl_pathv
[i
], abs_dst
, NULL
,
538 pflag
|| global_pflag
) == -1)
552 process_put(struct sftp_conn
*conn
, char *src
, char *dst
, char *pwd
,
553 int pflag
, int rflag
)
555 char *tmp_dst
= NULL
;
556 char *abs_dst
= NULL
;
557 char *tmp
= NULL
, *filename
= NULL
;
560 int i
, dst_is_dir
= 1;
564 tmp_dst
= xstrdup(dst
);
565 tmp_dst
= make_absolute(tmp_dst
, pwd
);
568 memset(&g
, 0, sizeof(g
));
569 debug3("Looking up %s", src
);
570 if (glob(src
, GLOB_NOCHECK
| GLOB_MARK
, NULL
, &g
)) {
571 error("File \"%s\" not found.", src
);
576 /* If we aren't fetching to pwd then stash this status for later */
578 dst_is_dir
= remote_is_dir(conn
, tmp_dst
);
580 /* If multiple matches, dst may be directory or unspecified */
581 if (g
.gl_matchc
> 1 && tmp_dst
&& !dst_is_dir
) {
582 error("Multiple paths match, but destination "
583 "\"%s\" is not a directory", tmp_dst
);
588 for (i
= 0; g
.gl_pathv
[i
] && !interrupted
; i
++) {
589 if (stat(g
.gl_pathv
[i
], &sb
) == -1) {
591 error("stat %s: %s", g
.gl_pathv
[i
], strerror(errno
));
595 tmp
= xstrdup(g
.gl_pathv
[i
]);
596 if ((filename
= basename(tmp
)) == NULL
) {
597 error("basename %s: %s", tmp
, strerror(errno
));
603 if (g
.gl_matchc
== 1 && tmp_dst
) {
604 /* If directory specified, append filename */
606 abs_dst
= path_append(tmp_dst
, filename
);
608 abs_dst
= xstrdup(tmp_dst
);
609 } else if (tmp_dst
) {
610 abs_dst
= path_append(tmp_dst
, filename
);
612 abs_dst
= make_absolute(xstrdup(filename
), pwd
);
616 printf("Uploading %s to %s\n", g
.gl_pathv
[i
], abs_dst
);
617 if (pathname_is_dir(g
.gl_pathv
[i
]) && (rflag
|| global_rflag
)) {
618 if (upload_dir(conn
, g
.gl_pathv
[i
], abs_dst
,
619 pflag
|| global_pflag
, 1) == -1)
622 if (do_upload(conn
, g
.gl_pathv
[i
], abs_dst
,
623 pflag
|| global_pflag
) == -1)
638 sdirent_comp(const void *aa
, const void *bb
)
640 SFTP_DIRENT
*a
= *(SFTP_DIRENT
**)aa
;
641 SFTP_DIRENT
*b
= *(SFTP_DIRENT
**)bb
;
642 int rmul
= sort_flag
& LS_REVERSE_SORT
? -1 : 1;
644 #define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
645 if (sort_flag
& LS_NAME_SORT
)
646 return (rmul
* strcmp(a
->filename
, b
->filename
));
647 else if (sort_flag
& LS_TIME_SORT
)
648 return (rmul
* NCMP(a
->a
.mtime
, b
->a
.mtime
));
649 else if (sort_flag
& LS_SIZE_SORT
)
650 return (rmul
* NCMP(a
->a
.size
, b
->a
.size
));
652 fatal("Unknown ls sort type");
655 /* sftp ls.1 replacement for directories */
657 do_ls_dir(struct sftp_conn
*conn
, char *path
, char *strip_path
, int lflag
)
660 u_int c
= 1, colspace
= 0, columns
= 1;
663 if ((n
= do_readdir(conn
, path
, &d
)) != 0)
666 if (!(lflag
& LS_SHORT_VIEW
)) {
667 u_int m
= 0, width
= 80;
671 /* Count entries for sort and find longest filename */
672 for (n
= 0; d
[n
] != NULL
; n
++) {
673 if (d
[n
]->filename
[0] != '.' || (lflag
& LS_SHOW_ALL
))
674 m
= MAX(m
, strlen(d
[n
]->filename
));
677 /* Add any subpath that also needs to be counted */
678 tmp
= path_strip(path
, strip_path
);
682 if (ioctl(fileno(stdin
), TIOCGWINSZ
, &ws
) != -1)
685 columns
= width
/ (m
+ 2);
686 columns
= MAX(columns
, 1);
687 colspace
= width
/ columns
;
688 colspace
= MIN(colspace
, width
);
691 if (lflag
& SORT_FLAGS
) {
692 for (n
= 0; d
[n
] != NULL
; n
++)
693 ; /* count entries */
694 sort_flag
= lflag
& (SORT_FLAGS
|LS_REVERSE_SORT
);
695 qsort(d
, n
, sizeof(*d
), sdirent_comp
);
698 for (n
= 0; d
[n
] != NULL
&& !interrupted
; n
++) {
701 if (d
[n
]->filename
[0] == '.' && !(lflag
& LS_SHOW_ALL
))
704 tmp
= path_append(path
, d
[n
]->filename
);
705 fname
= path_strip(tmp
, strip_path
);
708 if (lflag
& LS_LONG_VIEW
) {
709 if (lflag
& LS_NUMERIC_VIEW
) {
713 memset(&sb
, 0, sizeof(sb
));
714 attrib_to_stat(&d
[n
]->a
, &sb
);
715 lname
= ls_file(fname
, &sb
, 1);
716 printf("%s\n", lname
);
719 printf("%s\n", d
[n
]->longname
);
721 printf("%-*s", colspace
, fname
);
732 if (!(lflag
& LS_LONG_VIEW
) && (c
!= 1))
735 free_sftp_dirents(d
);
739 /* sftp ls.1 replacement which handles path globs */
741 do_globbed_ls(struct sftp_conn
*conn
, char *path
, char *strip_path
,
745 u_int i
, c
= 1, colspace
= 0, columns
= 1;
748 memset(&g
, 0, sizeof(g
));
750 if (remote_glob(conn
, path
, GLOB_MARK
|GLOB_NOCHECK
|GLOB_BRACE
,
751 NULL
, &g
) || (g
.gl_pathc
&& !g
.gl_matchc
)) {
754 error("Can't ls: \"%s\" not found", path
);
762 * If the glob returns a single match and it is a directory,
763 * then just list its contents.
765 if (g
.gl_matchc
== 1) {
766 if ((a
= do_lstat(conn
, g
.gl_pathv
[0], 1)) == NULL
) {
770 if ((a
->flags
& SSH2_FILEXFER_ATTR_PERMISSIONS
) &&
774 err
= do_ls_dir(conn
, g
.gl_pathv
[0], strip_path
, lflag
);
780 if (!(lflag
& LS_SHORT_VIEW
)) {
781 u_int m
= 0, width
= 80;
784 /* Count entries for sort and find longest filename */
785 for (i
= 0; g
.gl_pathv
[i
]; i
++)
786 m
= MAX(m
, strlen(g
.gl_pathv
[i
]));
788 if (ioctl(fileno(stdin
), TIOCGWINSZ
, &ws
) != -1)
791 columns
= width
/ (m
+ 2);
792 columns
= MAX(columns
, 1);
793 colspace
= width
/ columns
;
796 for (i
= 0; g
.gl_pathv
[i
] && !interrupted
; i
++, a
= NULL
) {
799 fname
= path_strip(g
.gl_pathv
[i
], strip_path
);
801 if (lflag
& LS_LONG_VIEW
) {
806 * XXX: this is slow - 1 roundtrip per path
807 * A solution to this is to fork glob() and
808 * build a sftp specific version which keeps the
809 * attribs (which currently get thrown away)
810 * that the server returns as well as the filenames.
812 memset(&sb
, 0, sizeof(sb
));
814 a
= do_lstat(conn
, g
.gl_pathv
[i
], 1);
816 attrib_to_stat(a
, &sb
);
817 lname
= ls_file(fname
, &sb
, 1);
818 printf("%s\n", lname
);
821 printf("%-*s", colspace
, fname
);
831 if (!(lflag
& LS_LONG_VIEW
) && (c
!= 1))
842 do_df(struct sftp_conn
*conn
, char *path
, int hflag
, int iflag
)
844 struct sftp_statvfs st
;
845 char s_used
[FMT_SCALED_STRSIZE
];
846 char s_avail
[FMT_SCALED_STRSIZE
];
847 char s_root
[FMT_SCALED_STRSIZE
];
848 char s_total
[FMT_SCALED_STRSIZE
];
849 unsigned long long ffree
;
851 if (do_statvfs(conn
, path
, &st
, 1) == -1)
854 ffree
= st
.f_files
? (100 * (st
.f_files
- st
.f_ffree
) / st
.f_files
) : 0;
855 printf(" Inodes Used Avail "
856 "(root) %%Capacity\n");
857 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
858 (unsigned long long)st
.f_files
,
859 (unsigned long long)(st
.f_files
- st
.f_ffree
),
860 (unsigned long long)st
.f_favail
,
861 (unsigned long long)st
.f_ffree
, ffree
);
863 strlcpy(s_used
, "error", sizeof(s_used
));
864 strlcpy(s_avail
, "error", sizeof(s_avail
));
865 strlcpy(s_root
, "error", sizeof(s_root
));
866 strlcpy(s_total
, "error", sizeof(s_total
));
867 fmt_scaled((st
.f_blocks
- st
.f_bfree
) * st
.f_frsize
, s_used
);
868 fmt_scaled(st
.f_bavail
* st
.f_frsize
, s_avail
);
869 fmt_scaled(st
.f_bfree
* st
.f_frsize
, s_root
);
870 fmt_scaled(st
.f_blocks
* st
.f_frsize
, s_total
);
871 printf(" Size Used Avail (root) %%Capacity\n");
872 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
873 s_total
, s_used
, s_avail
, s_root
,
874 (unsigned long long)(100 * (st
.f_blocks
- st
.f_bfree
) /
877 printf(" Size Used Avail "
878 "(root) %%Capacity\n");
879 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
880 (unsigned long long)(st
.f_frsize
* st
.f_blocks
/ 1024),
881 (unsigned long long)(st
.f_frsize
*
882 (st
.f_blocks
- st
.f_bfree
) / 1024),
883 (unsigned long long)(st
.f_frsize
* st
.f_bavail
/ 1024),
884 (unsigned long long)(st
.f_frsize
* st
.f_bfree
/ 1024),
885 (unsigned long long)(100 * (st
.f_blocks
- st
.f_bfree
) /
892 * Undo escaping of glob sequences in place. Used to undo extra escaping
893 * applied in makeargv() when the string is destined for a function that
897 undo_glob_escape(char *s
)
932 * Split a string into an argument vector using sh(1)-style quoting,
933 * comment and escaping rules, but with some tweaks to handle glob(3)
935 * Returns NULL on error or a NULL-terminated array of arguments.
938 #define MAXARGLEN 8192
940 makeargv(const char *arg
, int *argcp
)
944 static char argvs
[MAXARGLEN
];
945 static char *argv
[MAXARGS
+ 1];
946 enum { MA_START
, MA_SQUOTE
, MA_DQUOTE
, MA_UNQUOTED
} state
, q
;
949 if (strlen(arg
) > sizeof(argvs
) - 1) {
951 error("string too long");
957 if (isspace(arg
[i
])) {
958 if (state
== MA_UNQUOTED
) {
959 /* Terminate current argument */
963 } else if (state
!= MA_START
)
965 } else if (arg
[i
] == '"' || arg
[i
] == '\'') {
966 q
= arg
[i
] == '"' ? MA_DQUOTE
: MA_SQUOTE
;
967 if (state
== MA_START
) {
968 argv
[argc
] = argvs
+ j
;
970 } else if (state
== MA_UNQUOTED
)
976 } else if (arg
[i
] == '\\') {
977 if (state
== MA_SQUOTE
|| state
== MA_DQUOTE
) {
978 quot
= state
== MA_SQUOTE
? '\'' : '"';
979 /* Unescape quote we are in */
980 /* XXX support \n and friends? */
981 if (arg
[i
+ 1] == quot
) {
984 } else if (arg
[i
+ 1] == '?' ||
985 arg
[i
+ 1] == '[' || arg
[i
+ 1] == '*') {
987 * Special case for sftp: append
988 * double-escaped glob sequence -
989 * glob will undo one level of
990 * escaping. NB. string can grow here.
992 if (j
>= sizeof(argvs
) - 5)
995 argvs
[j
++] = arg
[i
++];
999 argvs
[j
++] = arg
[i
++];
1000 argvs
[j
++] = arg
[i
];
1003 if (state
== MA_START
) {
1004 argv
[argc
] = argvs
+ j
;
1005 state
= MA_UNQUOTED
;
1007 if (arg
[i
+ 1] == '?' || arg
[i
+ 1] == '[' ||
1008 arg
[i
+ 1] == '*' || arg
[i
+ 1] == '\\') {
1010 * Special case for sftp: append
1011 * escaped glob sequence -
1012 * glob will undo one level of
1015 argvs
[j
++] = arg
[i
++];
1016 argvs
[j
++] = arg
[i
];
1018 /* Unescape everything */
1019 /* XXX support \n and friends? */
1021 argvs
[j
++] = arg
[i
];
1024 } else if (arg
[i
] == '#') {
1025 if (state
== MA_SQUOTE
|| state
== MA_DQUOTE
)
1026 argvs
[j
++] = arg
[i
];
1029 } else if (arg
[i
] == '\0') {
1030 if (state
== MA_SQUOTE
|| state
== MA_DQUOTE
) {
1031 error("Unterminated quoted argument");
1035 if (state
== MA_UNQUOTED
) {
1041 if (state
== MA_START
) {
1042 argv
[argc
] = argvs
+ j
;
1043 state
= MA_UNQUOTED
;
1045 if ((state
== MA_SQUOTE
|| state
== MA_DQUOTE
) &&
1046 (arg
[i
] == '?' || arg
[i
] == '[' || arg
[i
] == '*')) {
1048 * Special case for sftp: escape quoted
1049 * glob(3) wildcards. NB. string can grow
1052 if (j
>= sizeof(argvs
) - 3)
1053 goto args_too_longs
;
1055 argvs
[j
++] = arg
[i
];
1057 argvs
[j
++] = arg
[i
];
1066 parse_args(const char **cpp
, int *pflag
, int *rflag
, int *lflag
, int *iflag
, int *hflag
,
1067 unsigned long *n_arg
, char **path1
, char **path2
)
1069 const char *cmd
, *cp
= *cpp
;
1073 int i
, cmdnum
, optidx
, argc
;
1075 /* Skip leading whitespace */
1076 cp
= cp
+ strspn(cp
, WHITESPACE
);
1078 /* Ignore blank lines and lines which begin with comment '#' char */
1079 if (*cp
== '\0' || *cp
== '#')
1082 /* Check for leading '-' (disable error processing) */
1089 if ((argv
= makeargv(cp
, &argc
)) == NULL
)
1092 /* Figure out which command we have */
1093 for (i
= 0; cmds
[i
].c
!= NULL
; i
++) {
1094 if (strcasecmp(cmds
[i
].c
, argv
[0]) == 0)
1104 } else if (cmdnum
== -1) {
1105 error("Invalid command.");
1109 /* Get arguments and parse flags */
1110 *lflag
= *pflag
= *rflag
= *hflag
= *n_arg
= 0;
1111 *path1
= *path2
= NULL
;
1116 if ((optidx
= parse_getput_flags(cmd
, argv
, argc
, pflag
, rflag
)) == -1)
1118 /* Get first pathname (mandatory) */
1119 if (argc
- optidx
< 1) {
1120 error("You must specify at least one path after a "
1121 "%s command.", cmd
);
1124 *path1
= xstrdup(argv
[optidx
]);
1125 /* Get second pathname (optional) */
1126 if (argc
- optidx
> 1) {
1127 *path2
= xstrdup(argv
[optidx
+ 1]);
1128 /* Destination is not globbed */
1129 undo_glob_escape(*path2
);
1134 if (argc
- optidx
< 2) {
1135 error("You must specify two paths after a %s "
1139 *path1
= xstrdup(argv
[optidx
]);
1140 *path2
= xstrdup(argv
[optidx
+ 1]);
1141 /* Paths are not globbed */
1142 undo_glob_escape(*path1
);
1143 undo_glob_escape(*path2
);
1151 /* Get pathname (mandatory) */
1152 if (argc
- optidx
< 1) {
1153 error("You must specify a path after a %s command.",
1157 *path1
= xstrdup(argv
[optidx
]);
1158 /* Only "rm" globs */
1160 undo_glob_escape(*path1
);
1163 if ((optidx
= parse_df_flags(cmd
, argv
, argc
, hflag
,
1166 /* Default to current directory if no path specified */
1167 if (argc
- optidx
< 1)
1170 *path1
= xstrdup(argv
[optidx
]);
1171 undo_glob_escape(*path1
);
1175 if ((optidx
= parse_ls_flags(argv
, argc
, lflag
)) == -1)
1177 /* Path is optional */
1178 if (argc
- optidx
> 0)
1179 *path1
= xstrdup(argv
[optidx
]);
1182 /* Skip ls command and following whitespace */
1183 cp
= cp
+ strlen(cmd
) + strspn(cp
, WHITESPACE
);
1185 /* Uses the rest of the line */
1192 /* Get numeric arg (mandatory) */
1193 if (argc
- optidx
< 1)
1196 l
= strtol(argv
[optidx
], &cp2
, base
);
1197 if (cp2
== argv
[optidx
] || *cp2
!= '\0' ||
1198 ((l
== LONG_MIN
|| l
== LONG_MAX
) && errno
== ERANGE
) ||
1201 error("You must supply a numeric argument "
1202 "to the %s command.", cmd
);
1206 if (cmdnum
== I_LUMASK
)
1208 /* Get pathname (mandatory) */
1209 if (argc
- optidx
< 2) {
1210 error("You must specify a path after a %s command.",
1214 *path1
= xstrdup(argv
[optidx
+ 1]);
1224 fatal("Command not implemented");
1232 parse_dispatch_command(struct sftp_conn
*conn
, const char *cmd
, char **pwd
,
1235 char *path1
, *path2
, *tmp
;
1236 int pflag
= 0, rflag
= 0, lflag
= 0, iflag
= 0, hflag
= 0, cmdnum
, i
;
1237 unsigned long n_arg
= 0;
1239 char path_buf
[MAXPATHLEN
];
1243 path1
= path2
= NULL
;
1244 cmdnum
= parse_args(&cmd
, &pflag
, &rflag
, &lflag
, &iflag
, &hflag
, &n_arg
,
1250 memset(&g
, 0, sizeof(g
));
1252 /* Perform command */
1258 /* Unrecognized command */
1262 err
= process_get(conn
, path1
, path2
, *pwd
, pflag
, rflag
);
1265 err
= process_put(conn
, path1
, path2
, *pwd
, pflag
, rflag
);
1268 path1
= make_absolute(path1
, *pwd
);
1269 path2
= make_absolute(path2
, *pwd
);
1270 err
= do_rename(conn
, path1
, path2
);
1273 path2
= make_absolute(path2
, *pwd
);
1274 err
= do_symlink(conn
, path1
, path2
);
1277 path1
= make_absolute(path1
, *pwd
);
1278 remote_glob(conn
, path1
, GLOB_NOCHECK
, NULL
, &g
);
1279 for (i
= 0; g
.gl_pathv
[i
] && !interrupted
; i
++) {
1280 printf("Removing %s\n", g
.gl_pathv
[i
]);
1281 err
= do_rm(conn
, g
.gl_pathv
[i
]);
1282 if (err
!= 0 && err_abort
)
1287 path1
= make_absolute(path1
, *pwd
);
1289 a
.flags
|= SSH2_FILEXFER_ATTR_PERMISSIONS
;
1291 err
= do_mkdir(conn
, path1
, &a
, 1);
1294 path1
= make_absolute(path1
, *pwd
);
1295 err
= do_rmdir(conn
, path1
);
1298 path1
= make_absolute(path1
, *pwd
);
1299 if ((tmp
= do_realpath(conn
, path1
)) == NULL
) {
1303 if ((aa
= do_stat(conn
, tmp
, 0)) == NULL
) {
1308 if (!(aa
->flags
& SSH2_FILEXFER_ATTR_PERMISSIONS
)) {
1309 error("Can't change directory: Can't check target");
1314 if (!S_ISDIR(aa
->perm
)) {
1315 error("Can't change directory: \"%s\" is not "
1316 "a directory", tmp
);
1326 do_globbed_ls(conn
, *pwd
, *pwd
, lflag
);
1330 /* Strip pwd off beginning of non-absolute paths */
1335 path1
= make_absolute(path1
, *pwd
);
1336 err
= do_globbed_ls(conn
, path1
, tmp
, lflag
);
1339 /* Default to current directory if no path specified */
1341 path1
= xstrdup(*pwd
);
1342 path1
= make_absolute(path1
, *pwd
);
1343 err
= do_df(conn
, path1
, hflag
, iflag
);
1346 if (chdir(path1
) == -1) {
1347 error("Couldn't change local directory to "
1348 "\"%s\": %s", path1
, strerror(errno
));
1353 if (mkdir(path1
, 0777) == -1) {
1354 error("Couldn't create local directory "
1355 "\"%s\": %s", path1
, strerror(errno
));
1363 local_do_shell(cmd
);
1367 printf("Local umask: %03lo\n", n_arg
);
1370 path1
= make_absolute(path1
, *pwd
);
1372 a
.flags
|= SSH2_FILEXFER_ATTR_PERMISSIONS
;
1374 remote_glob(conn
, path1
, GLOB_NOCHECK
, NULL
, &g
);
1375 for (i
= 0; g
.gl_pathv
[i
] && !interrupted
; i
++) {
1376 printf("Changing mode on %s\n", g
.gl_pathv
[i
]);
1377 err
= do_setstat(conn
, g
.gl_pathv
[i
], &a
);
1378 if (err
!= 0 && err_abort
)
1384 path1
= make_absolute(path1
, *pwd
);
1385 remote_glob(conn
, path1
, GLOB_NOCHECK
, NULL
, &g
);
1386 for (i
= 0; g
.gl_pathv
[i
] && !interrupted
; i
++) {
1387 if (!(aa
= do_stat(conn
, g
.gl_pathv
[i
], 0))) {
1394 if (!(aa
->flags
& SSH2_FILEXFER_ATTR_UIDGID
)) {
1395 error("Can't get current ownership of "
1396 "remote file \"%s\"", g
.gl_pathv
[i
]);
1403 aa
->flags
&= SSH2_FILEXFER_ATTR_UIDGID
;
1404 if (cmdnum
== I_CHOWN
) {
1405 printf("Changing owner on %s\n", g
.gl_pathv
[i
]);
1408 printf("Changing group on %s\n", g
.gl_pathv
[i
]);
1411 err
= do_setstat(conn
, g
.gl_pathv
[i
], aa
);
1412 if (err
!= 0 && err_abort
)
1417 printf("Remote working directory: %s\n", *pwd
);
1420 if (!getcwd(path_buf
, sizeof(path_buf
))) {
1421 error("Couldn't get local cwd: %s", strerror(errno
));
1425 printf("Local working directory: %s\n", path_buf
);
1428 /* Processed below */
1434 printf("SFTP protocol version %u\n", sftp_proto_version(conn
));
1437 showprogress
= !showprogress
;
1439 printf("Progress meter enabled\n");
1441 printf("Progress meter disabled\n");
1444 fatal("%d is not implemented", cmdnum
);
1454 /* If an unignored error occurs in batch mode we should abort. */
1455 if (err_abort
&& err
!= 0)
1457 else if (cmdnum
== I_QUIT
)
1465 prompt(EditLine
*el
)
1472 interactive_loop(struct sftp_conn
*conn
, char *file1
, char *file2
)
1477 int err
, interactive
;
1478 EditLine
*el
= NULL
;
1482 extern char *__progname
;
1484 if (!batchmode
&& isatty(STDIN_FILENO
)) {
1485 if ((el
= el_init(__progname
, stdin
, stdout
, stderr
)) == NULL
)
1486 fatal("Couldn't initialise editline");
1487 if ((hl
= history_init()) == NULL
)
1488 fatal("Couldn't initialise editline history");
1489 history(hl
, &hev
, H_SETSIZE
, 100);
1490 el_set(el
, EL_HIST
, history
, hl
);
1492 el_set(el
, EL_PROMPT
, prompt
);
1493 el_set(el
, EL_EDITOR
, "emacs");
1494 el_set(el
, EL_TERMINAL
, NULL
);
1495 el_set(el
, EL_SIGNAL
, 1);
1496 el_source(el
, NULL
);
1498 #endif /* USE_LIBEDIT */
1500 pwd
= do_realpath(conn
, ".");
1504 if (file1
!= NULL
) {
1505 dir
= xstrdup(file1
);
1506 dir
= make_absolute(dir
, pwd
);
1508 if (remote_is_dir(conn
, dir
) && file2
== NULL
) {
1509 printf("Changing to: %s\n", dir
);
1510 snprintf(cmd
, sizeof cmd
, "cd \"%s\"", dir
);
1511 if (parse_dispatch_command(conn
, cmd
, &pwd
, 1) != 0) {
1519 snprintf(cmd
, sizeof cmd
, "get %s", dir
);
1521 snprintf(cmd
, sizeof cmd
, "get %s %s", dir
,
1524 err
= parse_dispatch_command(conn
, cmd
, &pwd
, 1);
1533 #if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF)
1534 setvbuf(stdout
, NULL
, _IOLBF
, 0);
1535 setvbuf(infile
, NULL
, _IOLBF
, 0);
1541 interactive
= !batchmode
&& isatty(STDIN_FILENO
);
1546 signal(SIGINT
, SIG_IGN
);
1551 if (fgets(cmd
, sizeof(cmd
), infile
) == NULL
) {
1556 if (!interactive
) { /* Echo command */
1557 printf("sftp> %s", cmd
);
1558 if (strlen(cmd
) > 0 &&
1559 cmd
[strlen(cmd
) - 1] != '\n')
1567 if ((line
= el_gets(el
, &count
)) == NULL
|| count
<= 0) {
1571 history(hl
, &hev
, H_ENTER
, line
);
1572 if (strlcpy(cmd
, line
, sizeof(cmd
)) >= sizeof(cmd
)) {
1573 fprintf(stderr
, "Error: input line too long\n");
1576 #endif /* USE_LIBEDIT */
1579 cp
= strrchr(cmd
, '\n');
1583 /* Handle user interrupts gracefully during commands */
1585 signal(SIGINT
, cmd_interrupt
);
1587 err
= parse_dispatch_command(conn
, cmd
, &pwd
, batchmode
);
1597 #endif /* USE_LIBEDIT */
1599 /* err == 1 signifies normal "quit" exit */
1600 return (err
>= 0 ? 0 : -1);
1604 connect_to_server(char *path
, char **args
, int *in
, int *out
)
1609 int pin
[2], pout
[2];
1611 if ((pipe(pin
) == -1) || (pipe(pout
) == -1))
1612 fatal("pipe: %s", strerror(errno
));
1617 #else /* USE_PIPES */
1620 if (socketpair(AF_UNIX
, SOCK_STREAM
, 0, inout
) == -1)
1621 fatal("socketpair: %s", strerror(errno
));
1622 *in
= *out
= inout
[0];
1623 c_in
= c_out
= inout
[1];
1624 #endif /* USE_PIPES */
1626 if ((sshpid
= fork()) == -1)
1627 fatal("fork: %s", strerror(errno
));
1628 else if (sshpid
== 0) {
1629 if ((dup2(c_in
, STDIN_FILENO
) == -1) ||
1630 (dup2(c_out
, STDOUT_FILENO
) == -1)) {
1631 fprintf(stderr
, "dup2: %s\n", strerror(errno
));
1640 * The underlying ssh is in the same process group, so we must
1641 * ignore SIGINT if we want to gracefully abort commands,
1642 * otherwise the signal will make it to the ssh process and
1645 signal(SIGINT
, SIG_IGN
);
1647 fprintf(stderr
, "exec: %s: %s\n", path
, strerror(errno
));
1651 signal(SIGTERM
, killchild
);
1652 signal(SIGINT
, killchild
);
1653 signal(SIGHUP
, killchild
);
1661 extern char *__progname
;
1664 "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
1665 " [-D sftp_server_path] [-F ssh_config] "
1666 "[-i identity_file]\n"
1667 " [-o ssh_option] [-P port] [-R num_requests] "
1669 " [-s subsystem | sftp_server] host\n"
1670 " %s [user@]host[:file ...]\n"
1671 " %s [user@]host[:dir[/]]\n"
1672 " %s -b batchfile [user@]host\n",
1673 __progname
, __progname
, __progname
, __progname
);
1678 main(int argc
, char **argv
)
1680 int in
, out
, ch
, err
;
1681 char *host
, *userhost
, *cp
, *file2
= NULL
;
1682 int debug_level
= 0, sshver
= 2;
1683 char *file1
= NULL
, *sftp_server
= NULL
;
1684 char *ssh_program
= _PATH_SSH_PROGRAM
, *sftp_direct
= NULL
;
1685 LogLevel ll
= SYSLOG_LEVEL_INFO
;
1688 extern char *optarg
;
1689 struct sftp_conn
*conn
;
1690 size_t copy_buffer_len
= DEFAULT_COPY_BUFLEN
;
1691 size_t num_requests
= DEFAULT_NUM_REQUESTS
;
1693 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
1696 __progname
= ssh_get_progname(argv
[0]);
1697 memset(&args
, '\0', sizeof(args
));
1699 addargs(&args
, "%s", ssh_program
);
1700 addargs(&args
, "-oForwardX11 no");
1701 addargs(&args
, "-oForwardAgent no");
1702 addargs(&args
, "-oPermitLocalCommand no");
1703 addargs(&args
, "-oClearAllForwardings yes");
1705 ll
= SYSLOG_LEVEL_INFO
;
1708 while ((ch
= getopt(argc
, argv
,
1709 "1246hqrvCc:D:i:o:s:S:b:B:F:P:R:")) != -1) {
1711 /* Passed through to ssh(1) */
1715 addargs(&args
, "-%c", ch
);
1717 /* Passed through to ssh(1) with argument */
1722 addargs(&args
, "-%c", ch
);
1723 addargs(&args
, "%s", optarg
);
1727 addargs(&args
, "-%c", ch
);
1730 addargs(&args
, "-oPort %s", optarg
);
1733 if (debug_level
< 3) {
1734 addargs(&args
, "-v");
1735 ll
= SYSLOG_LEVEL_DEBUG1
+ debug_level
;
1741 if (sftp_server
== NULL
)
1742 sftp_server
= _PATH_SFTP_SERVER
;
1748 copy_buffer_len
= strtol(optarg
, &cp
, 10);
1749 if (copy_buffer_len
== 0 || *cp
!= '\0')
1750 fatal("Invalid buffer size \"%s\"", optarg
);
1754 fatal("Batch file already specified.");
1756 /* Allow "-" as stdin */
1757 if (strcmp(optarg
, "-") != 0 &&
1758 (infile
= fopen(optarg
, "r")) == NULL
)
1759 fatal("%s (%s).", strerror(errno
), optarg
);
1762 addargs(&args
, "-obatchmode yes");
1768 sftp_direct
= optarg
;
1774 num_requests
= strtol(optarg
, &cp
, 10);
1775 if (num_requests
== 0 || *cp
!= '\0')
1776 fatal("Invalid number of requests \"%s\"",
1780 sftp_server
= optarg
;
1783 ssh_program
= optarg
;
1784 replacearg(&args
, 0, "%s", ssh_program
);
1792 if (!isatty(STDERR_FILENO
))
1795 log_init(argv
[0], ll
, SYSLOG_FACILITY_USER
, 1);
1797 if (sftp_direct
== NULL
) {
1798 if (optind
== argc
|| argc
> (optind
+ 2))
1801 userhost
= xstrdup(argv
[optind
]);
1802 file2
= argv
[optind
+1];
1804 if ((host
= strrchr(userhost
, '@')) == NULL
)
1809 fprintf(stderr
, "Missing username\n");
1812 addargs(&args
, "-l");
1813 addargs(&args
, "%s", userhost
);
1816 if ((cp
= colon(host
)) != NULL
) {
1821 host
= cleanhostname(host
);
1823 fprintf(stderr
, "Missing hostname\n");
1827 addargs(&args
, "-oProtocol %d", sshver
);
1829 /* no subsystem if the server-spec contains a '/' */
1830 if (sftp_server
== NULL
|| strchr(sftp_server
, '/') == NULL
)
1831 addargs(&args
, "-s");
1833 addargs(&args
, "--");
1834 addargs(&args
, "%s", host
);
1835 addargs(&args
, "%s", (sftp_server
!= NULL
?
1836 sftp_server
: "sftp"));
1838 connect_to_server(ssh_program
, args
.list
, &in
, &out
);
1841 addargs(&args
, "sftp-server");
1843 connect_to_server(sftp_direct
, args
.list
, &in
, &out
);
1847 conn
= do_init(in
, out
, copy_buffer_len
, num_requests
);
1849 fatal("Couldn't initialise connection to server");
1852 if (sftp_direct
== NULL
)
1853 fprintf(stderr
, "Connected to %s.\n", host
);
1855 fprintf(stderr
, "Attached to %s.\n", sftp_direct
);
1858 err
= interactive_loop(conn
, file1
, file2
);
1860 #if !defined(USE_PIPES)
1861 shutdown(in
, SHUT_RDWR
);
1862 shutdown(out
, SHUT_RDWR
);
1870 while (waitpid(sshpid
, NULL
, 0) == -1)
1872 fatal("Couldn't wait for ssh process: %s",
1875 exit(err
== 0 ? 0 : 1);