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