2 /* $OpenBSD: sftp.c,v 1.107 2009/02/02 11:15:14 dtucker Exp $ */
4 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20 __RCSID("$NetBSD: sftp.c,v 1.24 2009/02/16 20:53:55 christos Exp $");
21 #include <sys/types.h>
22 #include <sys/ioctl.h>
25 #include <sys/socket.h>
26 #include <sys/param.h>
27 #include <sys/statvfs.h>
44 #include "pathnames.h"
49 #include "sftp-common.h"
50 #include "sftp-client.h"
51 #include "fmt_scaled.h"
53 /* File to read commands from */
56 /* Are we in batchfile mode? */
59 /* Size of buffer used when copying files */
60 size_t copy_buffer_len
= 32768;
62 /* Number of concurrent outstanding requests */
63 size_t num_requests
= 256;
65 /* PID of ssh transport process */
66 static pid_t sshpid
= -1;
68 /* This is set to 0 if the progressmeter is not desired. */
71 /* SIGINT received during command processing */
72 volatile sig_atomic_t interrupted
= 0;
74 /* I wish qsort() took a separate ctx for the comparison function...*/
77 int remote_glob(struct sftp_conn
*, const char *, int,
78 int (*)(const char *, int), glob_t
*); /* proto for sftp-glob.c */
80 /* Separators for interactive commands */
81 #define WHITESPACE " \t\r\n"
84 #define LS_LONG_VIEW 0x01 /* Full view ala ls -l */
85 #define LS_SHORT_VIEW 0x02 /* Single row view ala ls -1 */
86 #define LS_NUMERIC_VIEW 0x04 /* Long view with numeric uid/gid */
87 #define LS_NAME_SORT 0x08 /* Sort by name (default) */
88 #define LS_TIME_SORT 0x10 /* Sort by mtime */
89 #define LS_SIZE_SORT 0x20 /* Sort by file size */
90 #define LS_REVERSE_SORT 0x40 /* Reverse sort order */
91 #define LS_SHOW_ALL 0x80 /* Don't skip filenames starting with '.' */
93 #define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW)
94 #define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
96 /* Commands for interactive mode */
120 #define I_PROGRESS 23
127 static const struct CMD cmds
[] = {
130 { "chdir", I_CHDIR
},
131 { "chgrp", I_CHGRP
},
132 { "chmod", I_CHMOD
},
133 { "chown", I_CHOWN
},
141 { "lchdir", I_LCHDIR
},
143 { "lmkdir", I_LMKDIR
},
147 { "lumask", I_LUMASK
},
148 { "mkdir", I_MKDIR
},
149 { "progress", I_PROGRESS
},
154 { "rename", I_RENAME
},
156 { "rmdir", I_RMDIR
},
157 { "symlink", I_SYMLINK
},
158 { "version", I_VERSION
},
164 int interactive_loop(int fd_in
, int fd_out
, char *file1
, char *file2
);
171 kill(sshpid
, SIGTERM
);
172 waitpid(sshpid
, NULL
, 0);
180 cmd_interrupt(int signo
)
182 const char msg
[] = "\rInterrupt \n";
183 int olderrno
= errno
;
185 write(STDERR_FILENO
, msg
, sizeof(msg
) - 1);
193 printf("Available commands:\n"
195 "cd path Change remote directory to 'path'\n"
196 "chgrp grp path Change group of file 'path' to 'grp'\n"
197 "chmod mode path Change permissions of file 'path' to 'mode'\n"
198 "chown own path Change owner of file 'path' to 'own'\n"
199 "df [-hi] [path] Display statistics for current directory or\n"
200 " filesystem containing 'path'\n"
202 "get [-P] remote-path [local-path] Download file\n"
203 "help Display this help text\n"
204 "lcd path Change local directory to 'path'\n"
205 "lls [ls-options [path]] Display local directory listing\n"
206 "lmkdir path Create local directory\n"
207 "ln oldpath newpath Symlink remote file\n"
208 "lpwd Print local working directory\n"
209 "ls [-1aflnrSt] [path] Display remote directory listing\n"
210 "lumask umask Set local umask to 'umask'\n"
211 "mkdir path Create remote directory\n"
212 "progress Toggle display of progress meter\n"
213 "put [-P] local-path [remote-path] Upload file\n"
214 "pwd Display remote working directory\n"
216 "rename oldpath newpath Rename remote file\n"
217 "rm path Delete remote file\n"
218 "rmdir path Remove remote directory\n"
219 "symlink oldpath newpath Symlink remote file\n"
220 "version Show SFTP version\n"
221 "!command Execute 'command' in local shell\n"
222 "! Escape to local shell\n"
223 "? Synonym for help\n");
227 local_do_shell(const char *args
)
236 if ((shell
= getenv("SHELL")) == NULL
)
237 shell
= _PATH_BSHELL
;
239 if ((pid
= fork()) == -1)
240 fatal("Couldn't fork: %s", strerror(errno
));
243 /* XXX: child has pipe fds to ssh subproc open - issue? */
245 debug3("Executing %s -c \"%s\"", shell
, args
);
246 execl(shell
, shell
, "-c", args
, (char *)NULL
);
248 debug3("Executing %s", shell
);
249 execl(shell
, shell
, (char *)NULL
);
251 fprintf(stderr
, "Couldn't execute \"%s\": %s\n", shell
,
255 while (waitpid(pid
, &status
, 0) == -1)
257 fatal("Couldn't wait for child: %s", strerror(errno
));
258 if (!WIFEXITED(status
))
259 error("Shell exited abnormally");
260 else if (WEXITSTATUS(status
))
261 error("Shell exited with status %d", WEXITSTATUS(status
));
265 local_do_ls(const char *args
)
268 local_do_shell(_PATH_LS
);
270 int len
= strlen(_PATH_LS
" ") + strlen(args
) + 1;
271 char *buf
= xmalloc(len
);
273 /* XXX: quoting - rip quoting code from ftp? */
274 snprintf(buf
, len
, _PATH_LS
" %s", args
);
280 /* Strip one path (usually the pwd) from the start of another */
282 path_strip(char *path
, char *strip
)
287 return (xstrdup(path
));
290 if (strncmp(path
, strip
, len
) == 0) {
291 if (strip
[len
- 1] != '/' && path
[len
] == '/')
293 return (xstrdup(path
+ len
));
296 return (xstrdup(path
));
300 path_append(char *p1
, char *p2
)
303 size_t len
= strlen(p1
) + strlen(p2
) + 2;
306 strlcpy(ret
, p1
, len
);
307 if (p1
[0] != '\0' && p1
[strlen(p1
) - 1] != '/')
308 strlcat(ret
, "/", len
);
309 strlcat(ret
, p2
, len
);
315 make_absolute(char *p
, char *pwd
)
320 if (p
&& p
[0] != '/') {
321 abs_str
= path_append(pwd
, p
);
329 infer_path(const char *p
, char **ifp
)
333 cp
= strrchr(p
, '/');
340 error("Invalid path");
344 *ifp
= xstrdup(cp
+ 1);
349 parse_getput_flags(const char *cmd
, char **argv
, int argc
, int *pflag
)
351 extern int opterr
, optind
, optopt
, optreset
;
354 optind
= optreset
= 1;
358 while ((ch
= getopt(argc
, argv
, "Pp")) != -1) {
365 error("%s: Invalid flag -%c", cmd
, optopt
);
374 parse_ls_flags(char **argv
, int argc
, int *lflag
)
376 extern int opterr
, optind
, optopt
, optreset
;
379 optind
= optreset
= 1;
382 *lflag
= LS_NAME_SORT
;
383 while ((ch
= getopt(argc
, argv
, "1Saflnrt")) != -1) {
386 *lflag
&= ~VIEW_FLAGS
;
387 *lflag
|= LS_SHORT_VIEW
;
390 *lflag
&= ~SORT_FLAGS
;
391 *lflag
|= LS_SIZE_SORT
;
394 *lflag
|= LS_SHOW_ALL
;
397 *lflag
&= ~SORT_FLAGS
;
400 *lflag
&= ~VIEW_FLAGS
;
401 *lflag
|= LS_LONG_VIEW
;
404 *lflag
&= ~VIEW_FLAGS
;
405 *lflag
|= LS_NUMERIC_VIEW
|LS_LONG_VIEW
;
408 *lflag
|= LS_REVERSE_SORT
;
411 *lflag
&= ~SORT_FLAGS
;
412 *lflag
|= LS_TIME_SORT
;
415 error("ls: Invalid flag -%c", optopt
);
424 parse_df_flags(const char *cmd
, char **argv
, int argc
, int *hflag
, int *iflag
)
426 extern int opterr
, optind
, optopt
, optreset
;
429 optind
= optreset
= 1;
433 while ((ch
= getopt(argc
, argv
, "hi")) != -1) {
442 error("%s: Invalid flag -%c", cmd
, optopt
);
455 /* XXX: report errors? */
456 if (stat(path
, &sb
) == -1)
459 return(S_ISDIR(sb
.st_mode
));
463 remote_is_dir(struct sftp_conn
*conn
, char *path
)
467 /* XXX: report errors? */
468 if ((a
= do_stat(conn
, path
, 1)) == NULL
)
470 if (!(a
->flags
& SSH2_FILEXFER_ATTR_PERMISSIONS
))
472 return(S_ISDIR(a
->perm
));
476 process_get(struct sftp_conn
*conn
, char *src
, char *dst
, char *pwd
, int pflag
)
478 char *abs_src
= NULL
;
479 char *abs_dst
= NULL
;
485 abs_src
= xstrdup(src
);
486 abs_src
= make_absolute(abs_src
, pwd
);
488 memset(&g
, 0, sizeof(g
));
489 debug3("Looking up %s", abs_src
);
490 if (remote_glob(conn
, abs_src
, 0, NULL
, &g
)) {
491 error("File \"%s\" not found.", abs_src
);
496 /* If multiple matches, dst must be a directory or unspecified */
497 if (g
.gl_matchc
> 1 && dst
&& !is_dir(dst
)) {
498 error("Multiple files match, but \"%s\" is not a directory",
504 for (i
= 0; g
.gl_pathv
[i
] && !interrupted
; i
++) {
505 if (infer_path(g
.gl_pathv
[i
], &tmp
)) {
510 if (g
.gl_matchc
== 1 && dst
) {
511 /* If directory specified, append filename */
514 if (infer_path(g
.gl_pathv
[0], &tmp
)) {
518 abs_dst
= path_append(dst
, tmp
);
521 abs_dst
= xstrdup(dst
);
523 abs_dst
= path_append(dst
, tmp
);
528 printf("Fetching %s to %s\n", g
.gl_pathv
[i
], abs_dst
);
529 if (do_download(conn
, g
.gl_pathv
[i
], abs_dst
, pflag
) == -1)
542 process_put(struct sftp_conn
*conn
, char *src
, char *dst
, char *pwd
, int pflag
)
544 char *tmp_dst
= NULL
;
545 char *abs_dst
= NULL
;
553 tmp_dst
= xstrdup(dst
);
554 tmp_dst
= make_absolute(tmp_dst
, pwd
);
557 memset(&g
, 0, sizeof(g
));
558 debug3("Looking up %s", src
);
559 if (glob(src
, GLOB_NOCHECK
, NULL
, &g
)) {
560 error("File \"%s\" not found.", src
);
565 /* If multiple matches, dst may be directory or unspecified */
566 if (g
.gl_matchc
> 1 && tmp_dst
&& !remote_is_dir(conn
, tmp_dst
)) {
567 error("Multiple files match, but \"%s\" is not a directory",
573 for (i
= 0; g
.gl_pathv
[i
] && !interrupted
; i
++) {
574 if (stat(g
.gl_pathv
[i
], &sb
) == -1) {
576 error("stat %s: %s", g
.gl_pathv
[i
], strerror(errno
));
580 if (!S_ISREG(sb
.st_mode
)) {
581 error("skipping non-regular file %s",
585 if (infer_path(g
.gl_pathv
[i
], &tmp
)) {
590 if (g
.gl_matchc
== 1 && tmp_dst
) {
591 /* If directory specified, append filename */
592 if (remote_is_dir(conn
, tmp_dst
)) {
593 if (infer_path(g
.gl_pathv
[0], &tmp
)) {
597 abs_dst
= path_append(tmp_dst
, tmp
);
600 abs_dst
= xstrdup(tmp_dst
);
602 } else if (tmp_dst
) {
603 abs_dst
= path_append(tmp_dst
, tmp
);
606 abs_dst
= make_absolute(tmp
, pwd
);
608 printf("Uploading %s to %s\n", g
.gl_pathv
[i
], abs_dst
);
609 if (do_upload(conn
, g
.gl_pathv
[i
], abs_dst
, pflag
) == -1)
623 sdirent_comp(const void *aa
, const void *bb
)
625 SFTP_DIRENT
*a
= *(SFTP_DIRENT
**)aa
;
626 SFTP_DIRENT
*b
= *(SFTP_DIRENT
**)bb
;
627 int rmul
= sort_flag
& LS_REVERSE_SORT
? -1 : 1;
629 #define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
630 if (sort_flag
& LS_NAME_SORT
)
631 return (rmul
* strcmp(a
->filename
, b
->filename
));
632 else if (sort_flag
& LS_TIME_SORT
)
633 return (rmul
* NCMP(a
->a
.mtime
, b
->a
.mtime
));
634 else if (sort_flag
& LS_SIZE_SORT
)
635 return (rmul
* NCMP(a
->a
.size
, b
->a
.size
));
637 fatal("Unknown ls sort type");
642 /* sftp ls.1 replacement for directories */
644 do_ls_dir(struct sftp_conn
*conn
, char *path
, char *strip_path
, int lflag
)
647 u_int c
= 1, colspace
= 0, columns
= 1;
650 if ((n
= do_readdir(conn
, path
, &d
)) != 0)
653 if (!(lflag
& LS_SHORT_VIEW
)) {
654 u_int m
= 0, width
= 80;
658 /* Count entries for sort and find longest filename */
659 for (n
= 0; d
[n
] != NULL
; n
++) {
660 if (d
[n
]->filename
[0] != '.' || (lflag
& LS_SHOW_ALL
))
661 m
= MAX(m
, strlen(d
[n
]->filename
));
664 /* Add any subpath that also needs to be counted */
665 tmp
= path_strip(path
, strip_path
);
669 if (ioctl(fileno(stdin
), TIOCGWINSZ
, &ws
) != -1)
672 columns
= width
/ (m
+ 2);
673 columns
= MAX(columns
, 1);
674 colspace
= width
/ columns
;
675 colspace
= MIN(colspace
, width
);
678 if (lflag
& SORT_FLAGS
) {
679 for (n
= 0; d
[n
] != NULL
; n
++)
680 ; /* count entries */
681 sort_flag
= lflag
& (SORT_FLAGS
|LS_REVERSE_SORT
);
682 qsort(d
, n
, sizeof(*d
), sdirent_comp
);
685 for (n
= 0; d
[n
] != NULL
&& !interrupted
; n
++) {
688 if (d
[n
]->filename
[0] == '.' && !(lflag
& LS_SHOW_ALL
))
691 tmp
= path_append(path
, d
[n
]->filename
);
692 fname
= path_strip(tmp
, strip_path
);
695 if (lflag
& LS_LONG_VIEW
) {
696 if (lflag
& LS_NUMERIC_VIEW
) {
700 memset(&sb
, 0, sizeof(sb
));
701 attrib_to_stat(&d
[n
]->a
, &sb
);
702 lname
= ls_file(fname
, &sb
, 1);
703 printf("%s\n", lname
);
706 printf("%s\n", d
[n
]->longname
);
708 printf("%-*s", colspace
, fname
);
719 if (!(lflag
& LS_LONG_VIEW
) && (c
!= 1))
722 free_sftp_dirents(d
);
726 /* sftp ls.1 replacement which handles path globs */
728 do_globbed_ls(struct sftp_conn
*conn
, char *path
, char *strip_path
,
732 u_int i
, c
= 1, colspace
= 0, columns
= 1;
735 memset(&g
, 0, sizeof(g
));
737 if (remote_glob(conn
, path
, GLOB_MARK
|GLOB_NOCHECK
|GLOB_BRACE
,
738 NULL
, &g
) || (g
.gl_pathc
&& !g
.gl_matchc
)) {
741 error("Can't ls: \"%s\" not found", path
);
749 * If the glob returns a single match and it is a directory,
750 * then just list its contents.
752 if (g
.gl_matchc
== 1) {
753 if ((a
= do_lstat(conn
, g
.gl_pathv
[0], 1)) == NULL
) {
757 if ((a
->flags
& SSH2_FILEXFER_ATTR_PERMISSIONS
) &&
761 err
= do_ls_dir(conn
, g
.gl_pathv
[0], strip_path
, lflag
);
767 if (!(lflag
& LS_SHORT_VIEW
)) {
768 u_int m
= 0, width
= 80;
771 /* Count entries for sort and find longest filename */
772 for (i
= 0; g
.gl_pathv
[i
]; i
++)
773 m
= MAX(m
, strlen(g
.gl_pathv
[i
]));
775 if (ioctl(fileno(stdin
), TIOCGWINSZ
, &ws
) != -1)
778 columns
= width
/ (m
+ 2);
779 columns
= MAX(columns
, 1);
780 colspace
= width
/ columns
;
783 for (i
= 0; g
.gl_pathv
[i
] && !interrupted
; i
++, a
= NULL
) {
786 fname
= path_strip(g
.gl_pathv
[i
], strip_path
);
788 if (lflag
& LS_LONG_VIEW
) {
793 * XXX: this is slow - 1 roundtrip per path
794 * A solution to this is to fork glob() and
795 * build a sftp specific version which keeps the
796 * attribs (which currently get thrown away)
797 * that the server returns as well as the filenames.
799 memset(&sb
, 0, sizeof(sb
));
801 a
= do_lstat(conn
, g
.gl_pathv
[i
], 1);
803 attrib_to_stat(a
, &sb
);
804 lname
= ls_file(fname
, &sb
, 1);
805 printf("%s\n", lname
);
808 printf("%-*s", colspace
, fname
);
818 if (!(lflag
& LS_LONG_VIEW
) && (c
!= 1))
829 do_df(struct sftp_conn
*conn
, char *path
, int hflag
, int iflag
)
831 struct sftp_statvfs st
;
832 char s_used
[FMT_SCALED_STRSIZE
];
833 char s_avail
[FMT_SCALED_STRSIZE
];
834 char s_root
[FMT_SCALED_STRSIZE
];
835 char s_total
[FMT_SCALED_STRSIZE
];
837 if (do_statvfs(conn
, path
, &st
, 1) == -1)
840 printf(" Inodes Used Avail "
841 "(root) %%Capacity\n");
842 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
843 (unsigned long long)st
.f_files
,
844 (unsigned long long)(st
.f_files
- st
.f_ffree
),
845 (unsigned long long)st
.f_favail
,
846 (unsigned long long)st
.f_ffree
,
847 (unsigned long long)(100 * (st
.f_files
- st
.f_ffree
) /
850 strlcpy(s_used
, "error", sizeof(s_used
));
851 strlcpy(s_avail
, "error", sizeof(s_avail
));
852 strlcpy(s_root
, "error", sizeof(s_root
));
853 strlcpy(s_total
, "error", sizeof(s_total
));
854 fmt_scaled((st
.f_blocks
- st
.f_bfree
) * st
.f_frsize
, s_used
);
855 fmt_scaled(st
.f_bavail
* st
.f_frsize
, s_avail
);
856 fmt_scaled(st
.f_bfree
* st
.f_frsize
, s_root
);
857 fmt_scaled(st
.f_blocks
* st
.f_frsize
, s_total
);
858 printf(" Size Used Avail (root) %%Capacity\n");
859 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
860 s_total
, s_used
, s_avail
, s_root
,
861 (unsigned long long)(100 * (st
.f_blocks
- st
.f_bfree
) /
864 printf(" Size Used Avail "
865 "(root) %%Capacity\n");
866 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
867 (unsigned long long)(st
.f_frsize
* st
.f_blocks
/ 1024),
868 (unsigned long long)(st
.f_frsize
*
869 (st
.f_blocks
- st
.f_bfree
) / 1024),
870 (unsigned long long)(st
.f_frsize
* st
.f_bavail
/ 1024),
871 (unsigned long long)(st
.f_frsize
* st
.f_bfree
/ 1024),
872 (unsigned long long)(100 * (st
.f_blocks
- st
.f_bfree
) /
879 * Undo escaping of glob sequences in place. Used to undo extra escaping
880 * applied in makeargv() when the string is destined for a function that
884 undo_glob_escape(char *s
)
919 * Split a string into an argument vector using sh(1)-style quoting,
920 * comment and escaping rules, but with some tweaks to handle glob(3)
922 * Returns NULL on error or a NULL-terminated array of arguments.
925 #define MAXARGLEN 8192
927 makeargv(const char *arg
, int *argcp
)
931 static char argvs
[MAXARGLEN
];
932 static char *argv
[MAXARGS
+ 1];
933 enum { MA_START
, MA_SQUOTE
, MA_DQUOTE
, MA_UNQUOTED
} state
, q
;
936 if (strlen(arg
) > sizeof(argvs
) - 1) {
938 error("string too long");
944 if (isspace((unsigned char)arg
[i
])) {
945 if (state
== MA_UNQUOTED
) {
946 /* Terminate current argument */
950 } else if (state
!= MA_START
)
952 } else if (arg
[i
] == '"' || arg
[i
] == '\'') {
953 q
= arg
[i
] == '"' ? MA_DQUOTE
: MA_SQUOTE
;
954 if (state
== MA_START
) {
955 argv
[argc
] = argvs
+ j
;
957 } else if (state
== MA_UNQUOTED
)
963 } else if (arg
[i
] == '\\') {
964 if (state
== MA_SQUOTE
|| state
== MA_DQUOTE
) {
965 quot
= state
== MA_SQUOTE
? '\'' : '"';
966 /* Unescape quote we are in */
967 /* XXX support \n and friends? */
968 if (arg
[i
+ 1] == quot
) {
971 } else if (arg
[i
+ 1] == '?' ||
972 arg
[i
+ 1] == '[' || arg
[i
+ 1] == '*') {
974 * Special case for sftp: append
975 * double-escaped glob sequence -
976 * glob will undo one level of
977 * escaping. NB. string can grow here.
979 if (j
>= sizeof(argvs
) - 5)
982 argvs
[j
++] = arg
[i
++];
986 argvs
[j
++] = arg
[i
++];
990 if (state
== MA_START
) {
991 argv
[argc
] = argvs
+ j
;
994 if (arg
[i
+ 1] == '?' || arg
[i
+ 1] == '[' ||
995 arg
[i
+ 1] == '*' || arg
[i
+ 1] == '\\') {
997 * Special case for sftp: append
998 * escaped glob sequence -
999 * glob will undo one level of
1002 argvs
[j
++] = arg
[i
++];
1003 argvs
[j
++] = arg
[i
];
1005 /* Unescape everything */
1006 /* XXX support \n and friends? */
1008 argvs
[j
++] = arg
[i
];
1011 } else if (arg
[i
] == '#') {
1012 if (state
== MA_SQUOTE
|| state
== MA_DQUOTE
)
1013 argvs
[j
++] = arg
[i
];
1016 } else if (arg
[i
] == '\0') {
1017 if (state
== MA_SQUOTE
|| state
== MA_DQUOTE
) {
1018 error("Unterminated quoted argument");
1022 if (state
== MA_UNQUOTED
) {
1028 if (state
== MA_START
) {
1029 argv
[argc
] = argvs
+ j
;
1030 state
= MA_UNQUOTED
;
1032 if ((state
== MA_SQUOTE
|| state
== MA_DQUOTE
) &&
1033 (arg
[i
] == '?' || arg
[i
] == '[' || arg
[i
] == '*')) {
1035 * Special case for sftp: escape quoted
1036 * glob(3) wildcards. NB. string can grow
1039 if (j
>= sizeof(argvs
) - 3)
1040 goto args_too_longs
;
1042 argvs
[j
++] = arg
[i
];
1044 argvs
[j
++] = arg
[i
];
1053 parse_args(const char **cpp
, int *pflag
, int *lflag
, int *iflag
, int *hflag
,
1054 unsigned long *n_arg
, char **path1
, char **path2
)
1056 const char *cmd
, *cp
= *cpp
;
1060 int i
, cmdnum
, optidx
, argc
;
1062 /* Skip leading whitespace */
1063 cp
= cp
+ strspn(cp
, WHITESPACE
);
1065 /* Ignore blank lines and lines which begin with comment '#' char */
1066 if (*cp
== '\0' || *cp
== '#')
1069 /* Check for leading '-' (disable error processing) */
1076 if ((argv
= makeargv(cp
, &argc
)) == NULL
)
1079 /* Figure out which command we have */
1080 for (i
= 0; cmds
[i
].c
!= NULL
; i
++) {
1081 if (strcasecmp(cmds
[i
].c
, argv
[0]) == 0)
1091 } else if (cmdnum
== -1) {
1092 error("Invalid command.");
1096 /* Get arguments and parse flags */
1097 *lflag
= *pflag
= *hflag
= *n_arg
= 0;
1098 *path1
= *path2
= NULL
;
1103 if ((optidx
= parse_getput_flags(cmd
, argv
, argc
, pflag
)) == -1)
1105 /* Get first pathname (mandatory) */
1106 if (argc
- optidx
< 1) {
1107 error("You must specify at least one path after a "
1108 "%s command.", cmd
);
1111 *path1
= xstrdup(argv
[optidx
]);
1112 /* Get second pathname (optional) */
1113 if (argc
- optidx
> 1) {
1114 *path2
= xstrdup(argv
[optidx
+ 1]);
1115 /* Destination is not globbed */
1116 undo_glob_escape(*path2
);
1121 if (argc
- optidx
< 2) {
1122 error("You must specify two paths after a %s "
1126 *path1
= xstrdup(argv
[optidx
]);
1127 *path2
= xstrdup(argv
[optidx
+ 1]);
1128 /* Paths are not globbed */
1129 undo_glob_escape(*path1
);
1130 undo_glob_escape(*path2
);
1138 /* Get pathname (mandatory) */
1139 if (argc
- optidx
< 1) {
1140 error("You must specify a path after a %s command.",
1144 *path1
= xstrdup(argv
[optidx
]);
1145 /* Only "rm" globs */
1147 undo_glob_escape(*path1
);
1150 if ((optidx
= parse_df_flags(cmd
, argv
, argc
, hflag
,
1153 /* Default to current directory if no path specified */
1154 if (argc
- optidx
< 1)
1157 *path1
= xstrdup(argv
[optidx
]);
1158 undo_glob_escape(*path1
);
1162 if ((optidx
= parse_ls_flags(argv
, argc
, lflag
)) == -1)
1164 /* Path is optional */
1165 if (argc
- optidx
> 0)
1166 *path1
= xstrdup(argv
[optidx
]);
1169 /* Skip ls command and following whitespace */
1170 cp
= cp
+ strlen(cmd
) + strspn(cp
, WHITESPACE
);
1172 /* Uses the rest of the line */
1179 /* Get numeric arg (mandatory) */
1180 if (argc
- optidx
< 1)
1183 l
= strtol(argv
[optidx
], &cp2
, base
);
1184 if (cp2
== argv
[optidx
] || *cp2
!= '\0' ||
1185 ((l
== LONG_MIN
|| l
== LONG_MAX
) && errno
== ERANGE
) ||
1188 error("You must supply a numeric argument "
1189 "to the %s command.", cmd
);
1193 if (cmdnum
== I_LUMASK
)
1195 /* Get pathname (mandatory) */
1196 if (argc
- optidx
< 2) {
1197 error("You must specify a path after a %s command.",
1201 *path1
= xstrdup(argv
[optidx
+ 1]);
1211 fatal("Command not implemented");
1219 parse_dispatch_command(struct sftp_conn
*conn
, const char *cmd
, char **pwd
,
1222 char *path1
, *path2
, *tmp
;
1223 int pflag
= 0, lflag
= 0, iflag
= 0, hflag
= 0, cmdnum
, i
;
1224 unsigned long n_arg
= 0;
1226 char path_buf
[MAXPATHLEN
];
1230 pflag
= 0; /* XXX gcc */
1231 lflag
= 0; /* XXX gcc */
1232 iflag
= 0; /* XXX gcc */
1233 hflag
= 0; /* XXX gcc */
1234 n_arg
= 0; /* XXX gcc */
1236 path1
= path2
= NULL
;
1237 cmdnum
= parse_args(&cmd
, &pflag
, &lflag
, &iflag
, &hflag
, &n_arg
,
1243 memset(&g
, 0, sizeof(g
));
1245 /* Perform command */
1251 /* Unrecognized command */
1255 err
= process_get(conn
, path1
, path2
, *pwd
, pflag
);
1258 err
= process_put(conn
, path1
, path2
, *pwd
, pflag
);
1261 path1
= make_absolute(path1
, *pwd
);
1262 path2
= make_absolute(path2
, *pwd
);
1263 err
= do_rename(conn
, path1
, path2
);
1266 path2
= make_absolute(path2
, *pwd
);
1267 err
= do_symlink(conn
, path1
, path2
);
1270 path1
= make_absolute(path1
, *pwd
);
1271 remote_glob(conn
, path1
, GLOB_NOCHECK
, NULL
, &g
);
1272 for (i
= 0; g
.gl_pathv
[i
] && !interrupted
; i
++) {
1273 printf("Removing %s\n", g
.gl_pathv
[i
]);
1274 err
= do_rm(conn
, g
.gl_pathv
[i
]);
1275 if (err
!= 0 && err_abort
)
1280 path1
= make_absolute(path1
, *pwd
);
1282 a
.flags
|= SSH2_FILEXFER_ATTR_PERMISSIONS
;
1284 err
= do_mkdir(conn
, path1
, &a
);
1287 path1
= make_absolute(path1
, *pwd
);
1288 err
= do_rmdir(conn
, path1
);
1291 path1
= make_absolute(path1
, *pwd
);
1292 if ((tmp
= do_realpath(conn
, path1
)) == NULL
) {
1296 if ((aa
= do_stat(conn
, tmp
, 0)) == NULL
) {
1301 if (!(aa
->flags
& SSH2_FILEXFER_ATTR_PERMISSIONS
)) {
1302 error("Can't change directory: Can't check target");
1307 if (!S_ISDIR(aa
->perm
)) {
1308 error("Can't change directory: \"%s\" is not "
1309 "a directory", tmp
);
1319 do_globbed_ls(conn
, *pwd
, *pwd
, lflag
);
1323 /* Strip pwd off beginning of non-absolute paths */
1328 path1
= make_absolute(path1
, *pwd
);
1329 err
= do_globbed_ls(conn
, path1
, tmp
, lflag
);
1332 /* Default to current directory if no path specified */
1334 path1
= xstrdup(*pwd
);
1335 path1
= make_absolute(path1
, *pwd
);
1336 err
= do_df(conn
, path1
, hflag
, iflag
);
1339 if (chdir(path1
) == -1) {
1340 error("Couldn't change local directory to "
1341 "\"%s\": %s", path1
, strerror(errno
));
1346 if (mkdir(path1
, 0777) == -1) {
1347 error("Couldn't create local directory "
1348 "\"%s\": %s", path1
, strerror(errno
));
1356 local_do_shell(cmd
);
1360 printf("Local umask: %03lo\n", n_arg
);
1363 path1
= make_absolute(path1
, *pwd
);
1365 a
.flags
|= SSH2_FILEXFER_ATTR_PERMISSIONS
;
1367 remote_glob(conn
, path1
, GLOB_NOCHECK
, NULL
, &g
);
1368 for (i
= 0; g
.gl_pathv
[i
] && !interrupted
; i
++) {
1369 printf("Changing mode on %s\n", g
.gl_pathv
[i
]);
1370 err
= do_setstat(conn
, g
.gl_pathv
[i
], &a
);
1371 if (err
!= 0 && err_abort
)
1377 path1
= make_absolute(path1
, *pwd
);
1378 remote_glob(conn
, path1
, GLOB_NOCHECK
, NULL
, &g
);
1379 for (i
= 0; g
.gl_pathv
[i
] && !interrupted
; i
++) {
1380 if (!(aa
= do_stat(conn
, g
.gl_pathv
[i
], 0))) {
1387 if (!(aa
->flags
& SSH2_FILEXFER_ATTR_UIDGID
)) {
1388 error("Can't get current ownership of "
1389 "remote file \"%s\"", g
.gl_pathv
[i
]);
1396 aa
->flags
&= SSH2_FILEXFER_ATTR_UIDGID
;
1397 if (cmdnum
== I_CHOWN
) {
1398 printf("Changing owner on %s\n", g
.gl_pathv
[i
]);
1401 printf("Changing group on %s\n", g
.gl_pathv
[i
]);
1404 err
= do_setstat(conn
, g
.gl_pathv
[i
], aa
);
1405 if (err
!= 0 && err_abort
)
1410 printf("Remote working directory: %s\n", *pwd
);
1413 if (!getcwd(path_buf
, sizeof(path_buf
))) {
1414 error("Couldn't get local cwd: %s", strerror(errno
));
1418 printf("Local working directory: %s\n", path_buf
);
1421 /* Processed below */
1427 printf("SFTP protocol version %u\n", sftp_proto_version(conn
));
1430 showprogress
= !showprogress
;
1432 printf("Progress meter enabled\n");
1434 printf("Progress meter disabled\n");
1437 fatal("%d is not implemented", cmdnum
);
1447 /* If an unignored error occurs in batch mode we should abort. */
1448 if (err_abort
&& err
!= 0)
1450 else if (cmdnum
== I_QUIT
)
1457 prompt(EditLine
*el
)
1463 interactive_loop(int fd_in
, int fd_out
, char *file1
, char *file2
)
1468 struct sftp_conn
*conn
;
1469 int err
, interactive
;
1470 EditLine
*el
= NULL
;
1473 extern char *__progname
;
1475 if (!batchmode
&& isatty(STDIN_FILENO
)) {
1476 if ((el
= el_init(__progname
, stdin
, stdout
, stderr
)) == NULL
)
1477 fatal("Couldn't initialise editline");
1478 if ((hl
= history_init()) == NULL
)
1479 fatal("Couldn't initialise editline history");
1480 history(hl
, &hev
, H_SETSIZE
, 100);
1481 el_set(el
, EL_HIST
, history
, hl
);
1483 el_set(el
, EL_PROMPT
, prompt
);
1484 el_set(el
, EL_EDITOR
, "emacs");
1485 el_set(el
, EL_TERMINAL
, NULL
);
1486 el_set(el
, EL_SIGNAL
, 1);
1487 el_source(el
, NULL
);
1490 conn
= do_init(fd_in
, fd_out
, copy_buffer_len
, num_requests
);
1492 fatal("Couldn't initialise connection to server");
1494 pwd
= do_realpath(conn
, ".");
1498 if (file1
!= NULL
) {
1499 dir
= xstrdup(file1
);
1500 dir
= make_absolute(dir
, pwd
);
1502 if (remote_is_dir(conn
, dir
) && file2
== NULL
) {
1503 printf("Changing to: %s\n", dir
);
1504 snprintf(cmd
, sizeof cmd
, "cd \"%s\"", dir
);
1505 if (parse_dispatch_command(conn
, cmd
, &pwd
, 1) != 0) {
1513 snprintf(cmd
, sizeof cmd
, "get %s", dir
);
1515 snprintf(cmd
, sizeof cmd
, "get %s %s", dir
,
1518 err
= parse_dispatch_command(conn
, cmd
, &pwd
, 1);
1527 setvbuf(stdout
, NULL
, _IOLBF
, 0);
1528 setvbuf(infile
, NULL
, _IOLBF
, 0);
1530 interactive
= !batchmode
&& isatty(STDIN_FILENO
);
1537 signal(SIGINT
, SIG_IGN
);
1542 if (fgets(cmd
, sizeof(cmd
), infile
) == NULL
) {
1547 if (!interactive
) { /* Echo command */
1548 printf("sftp> %s", cmd
);
1549 if (strlen(cmd
) > 0 &&
1550 cmd
[strlen(cmd
) - 1] != '\n')
1554 if ((line
= el_gets(el
, &count
)) == NULL
|| count
<= 0) {
1558 history(hl
, &hev
, H_ENTER
, line
);
1559 if (strlcpy(cmd
, line
, sizeof(cmd
)) >= sizeof(cmd
)) {
1560 fprintf(stderr
, "Error: input line too long\n");
1565 cp
= strrchr(cmd
, '\n');
1569 /* Handle user interrupts gracefully during commands */
1571 signal(SIGINT
, cmd_interrupt
);
1573 err
= parse_dispatch_command(conn
, cmd
, &pwd
, batchmode
);
1583 /* err == 1 signifies normal "quit" exit */
1584 return (err
>= 0 ? 0 : -1);
1588 connect_to_server(char *path
, char **args
, int *in
, int *out
)
1594 if (socketpair(AF_UNIX
, SOCK_STREAM
, 0, inout
) == -1)
1595 fatal("socketpair: %s", strerror(errno
));
1596 *in
= *out
= inout
[0];
1597 c_in
= c_out
= inout
[1];
1599 if ((sshpid
= fork()) == -1)
1600 fatal("fork: %s", strerror(errno
));
1601 else if (sshpid
== 0) {
1602 if ((dup2(c_in
, STDIN_FILENO
) == -1) ||
1603 (dup2(c_out
, STDOUT_FILENO
) == -1)) {
1604 fprintf(stderr
, "dup2: %s\n", strerror(errno
));
1613 * The underlying ssh is in the same process group, so we must
1614 * ignore SIGINT if we want to gracefully abort commands,
1615 * otherwise the signal will make it to the ssh process and
1618 signal(SIGINT
, SIG_IGN
);
1620 fprintf(stderr
, "exec: %s: %s\n", path
, strerror(errno
));
1624 signal(SIGTERM
, killchild
);
1625 signal(SIGINT
, killchild
);
1626 signal(SIGHUP
, killchild
);
1634 extern char *__progname
;
1637 "usage: %s [-1Cv] [-B buffer_size] [-b batchfile] [-F ssh_config]\n"
1638 " [-o ssh_option] [-P sftp_server_path] [-R num_requests]\n"
1639 " [-S program] [-s subsystem | sftp_server] host\n"
1640 " %s [user@]host[:file ...]\n"
1641 " %s [user@]host[:dir[/]]\n"
1642 " %s -b batchfile [user@]host\n", __progname
, __progname
, __progname
, __progname
);
1647 main(int argc
, char **argv
)
1649 int in
, out
, ch
, err
;
1650 char *host
, *userhost
, *cp
, *file2
= NULL
;
1651 int debug_level
= 0, sshver
= 2;
1652 char *file1
= NULL
, *sftp_server
= NULL
;
1653 char *ssh_program
= _PATH_SSH_PROGRAM
, *sftp_direct
= NULL
;
1654 LogLevel ll
= SYSLOG_LEVEL_INFO
;
1657 extern char *optarg
;
1659 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
1662 memset(&args
, '\0', sizeof(args
));
1664 addargs(&args
, "%s", ssh_program
);
1665 addargs(&args
, "-oForwardX11 no");
1666 addargs(&args
, "-oForwardAgent no");
1667 addargs(&args
, "-oPermitLocalCommand no");
1668 addargs(&args
, "-oClearAllForwardings yes");
1670 ll
= SYSLOG_LEVEL_INFO
;
1673 while ((ch
= getopt(argc
, argv
, "1hvCo:s:S:b:B:F:P:R:")) != -1) {
1676 addargs(&args
, "-C");
1679 if (debug_level
< 3) {
1680 addargs(&args
, "-v");
1681 ll
= SYSLOG_LEVEL_DEBUG1
+ debug_level
;
1687 addargs(&args
, "-%c%s", ch
, optarg
);
1691 if (sftp_server
== NULL
)
1692 sftp_server
= _PATH_SFTP_SERVER
;
1695 sftp_server
= optarg
;
1698 ssh_program
= optarg
;
1699 replacearg(&args
, 0, "%s", ssh_program
);
1703 fatal("Batch file already specified.");
1705 /* Allow "-" as stdin */
1706 if (strcmp(optarg
, "-") != 0 &&
1707 (infile
= fopen(optarg
, "r")) == NULL
)
1708 fatal("%s (%s).", strerror(errno
), optarg
);
1711 addargs(&args
, "-obatchmode yes");
1714 sftp_direct
= optarg
;
1717 copy_buffer_len
= strtol(optarg
, &cp
, 10);
1718 if (copy_buffer_len
== 0 || *cp
!= '\0')
1719 fatal("Invalid buffer size \"%s\"", optarg
);
1722 num_requests
= strtol(optarg
, &cp
, 10);
1723 if (num_requests
== 0 || *cp
!= '\0')
1724 fatal("Invalid number of requests \"%s\"",
1733 if (!isatty(STDERR_FILENO
))
1736 log_init(argv
[0], ll
, SYSLOG_FACILITY_USER
, 1);
1738 if (sftp_direct
== NULL
) {
1739 if (optind
== argc
|| argc
> (optind
+ 2))
1742 userhost
= xstrdup(argv
[optind
]);
1743 file2
= argv
[optind
+1];
1745 if ((host
= strrchr(userhost
, '@')) == NULL
)
1750 fprintf(stderr
, "Missing username\n");
1753 addargs(&args
, "-l%s", userhost
);
1756 if ((cp
= colon(host
)) != NULL
) {
1761 host
= cleanhostname(host
);
1763 fprintf(stderr
, "Missing hostname\n");
1767 addargs(&args
, "-oProtocol %d", sshver
);
1769 /* no subsystem if the server-spec contains a '/' */
1770 if (sftp_server
== NULL
|| strchr(sftp_server
, '/') == NULL
)
1771 addargs(&args
, "-s");
1773 addargs(&args
, "%s", host
);
1774 addargs(&args
, "%s", (sftp_server
!= NULL
?
1775 sftp_server
: "sftp"));
1778 fprintf(stderr
, "Connecting to %s...\n", host
);
1779 connect_to_server(ssh_program
, args
.list
, &in
, &out
);
1782 addargs(&args
, "sftp-server");
1785 fprintf(stderr
, "Attaching to %s...\n", sftp_direct
);
1786 connect_to_server(sftp_direct
, args
.list
, &in
, &out
);
1790 err
= interactive_loop(in
, out
, file1
, file2
);
1797 while (waitpid(sshpid
, NULL
, 0) == -1)
1799 fatal("Couldn't wait for ssh process: %s",
1802 exit(err
== 0 ? 0 : 1);