1 | /* |
---|
2 | * Copyright (c) 2001,2002 Damien Miller. All rights reserved. |
---|
3 | * |
---|
4 | * Redistribution and use in source and binary forms, with or without |
---|
5 | * modification, are permitted provided that the following conditions |
---|
6 | * are met: |
---|
7 | * 1. Redistributions of source code must retain the above copyright |
---|
8 | * notice, this list of conditions and the following disclaimer. |
---|
9 | * 2. Redistributions in binary form must reproduce the above copyright |
---|
10 | * notice, this list of conditions and the following disclaimer in the |
---|
11 | * documentation and/or other materials provided with the distribution. |
---|
12 | * |
---|
13 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
---|
14 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
---|
15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
---|
16 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
---|
17 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
---|
18 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
---|
19 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
---|
20 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
---|
21 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
---|
22 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
---|
23 | */ |
---|
24 | |
---|
25 | /* XXX: recursive operations */ |
---|
26 | |
---|
27 | #include "includes.h" |
---|
28 | RCSID("$OpenBSD: sftp-int.c,v 1.49 2002/09/12 00:13:06 djm Exp $"); |
---|
29 | |
---|
30 | #include "buffer.h" |
---|
31 | #include "xmalloc.h" |
---|
32 | #include "log.h" |
---|
33 | #include "pathnames.h" |
---|
34 | |
---|
35 | #include "sftp.h" |
---|
36 | #include "sftp-common.h" |
---|
37 | #include "sftp-glob.h" |
---|
38 | #include "sftp-client.h" |
---|
39 | #include "sftp-int.h" |
---|
40 | |
---|
41 | /* File to read commands from */ |
---|
42 | extern FILE *infile; |
---|
43 | |
---|
44 | /* Size of buffer used when copying files */ |
---|
45 | extern size_t copy_buffer_len; |
---|
46 | |
---|
47 | /* Number of concurrent outstanding requests */ |
---|
48 | extern int num_requests; |
---|
49 | |
---|
50 | /* Seperators for interactive commands */ |
---|
51 | #define WHITESPACE " \t\r\n" |
---|
52 | |
---|
53 | /* Commands for interactive mode */ |
---|
54 | #define I_CHDIR 1 |
---|
55 | #define I_CHGRP 2 |
---|
56 | #define I_CHMOD 3 |
---|
57 | #define I_CHOWN 4 |
---|
58 | #define I_GET 5 |
---|
59 | #define I_HELP 6 |
---|
60 | #define I_LCHDIR 7 |
---|
61 | #define I_LLS 8 |
---|
62 | #define I_LMKDIR 9 |
---|
63 | #define I_LPWD 10 |
---|
64 | #define I_LS 11 |
---|
65 | #define I_LUMASK 12 |
---|
66 | #define I_MKDIR 13 |
---|
67 | #define I_PUT 14 |
---|
68 | #define I_PWD 15 |
---|
69 | #define I_QUIT 16 |
---|
70 | #define I_RENAME 17 |
---|
71 | #define I_RM 18 |
---|
72 | #define I_RMDIR 19 |
---|
73 | #define I_SHELL 20 |
---|
74 | #define I_SYMLINK 21 |
---|
75 | #define I_VERSION 22 |
---|
76 | |
---|
77 | struct CMD { |
---|
78 | const char *c; |
---|
79 | const int n; |
---|
80 | }; |
---|
81 | |
---|
82 | const struct CMD cmds[] = { |
---|
83 | { "bye", I_QUIT }, |
---|
84 | { "cd", I_CHDIR }, |
---|
85 | { "chdir", I_CHDIR }, |
---|
86 | { "chgrp", I_CHGRP }, |
---|
87 | { "chmod", I_CHMOD }, |
---|
88 | { "chown", I_CHOWN }, |
---|
89 | { "dir", I_LS }, |
---|
90 | { "exit", I_QUIT }, |
---|
91 | { "get", I_GET }, |
---|
92 | { "mget", I_GET }, |
---|
93 | { "help", I_HELP }, |
---|
94 | { "lcd", I_LCHDIR }, |
---|
95 | { "lchdir", I_LCHDIR }, |
---|
96 | { "lls", I_LLS }, |
---|
97 | { "lmkdir", I_LMKDIR }, |
---|
98 | { "ln", I_SYMLINK }, |
---|
99 | { "lpwd", I_LPWD }, |
---|
100 | { "ls", I_LS }, |
---|
101 | { "lumask", I_LUMASK }, |
---|
102 | { "mkdir", I_MKDIR }, |
---|
103 | { "put", I_PUT }, |
---|
104 | { "mput", I_PUT }, |
---|
105 | { "pwd", I_PWD }, |
---|
106 | { "quit", I_QUIT }, |
---|
107 | { "rename", I_RENAME }, |
---|
108 | { "rm", I_RM }, |
---|
109 | { "rmdir", I_RMDIR }, |
---|
110 | { "symlink", I_SYMLINK }, |
---|
111 | { "version", I_VERSION }, |
---|
112 | { "!", I_SHELL }, |
---|
113 | { "?", I_HELP }, |
---|
114 | { NULL, -1} |
---|
115 | }; |
---|
116 | |
---|
117 | static void |
---|
118 | help(void) |
---|
119 | { |
---|
120 | printf("Available commands:\n"); |
---|
121 | printf("cd path Change remote directory to 'path'\n"); |
---|
122 | printf("lcd path Change local directory to 'path'\n"); |
---|
123 | printf("chgrp grp path Change group of file 'path' to 'grp'\n"); |
---|
124 | printf("chmod mode path Change permissions of file 'path' to 'mode'\n"); |
---|
125 | printf("chown own path Change owner of file 'path' to 'own'\n"); |
---|
126 | printf("help Display this help text\n"); |
---|
127 | printf("get remote-path [local-path] Download file\n"); |
---|
128 | printf("lls [ls-options [path]] Display local directory listing\n"); |
---|
129 | printf("ln oldpath newpath Symlink remote file\n"); |
---|
130 | printf("lmkdir path Create local directory\n"); |
---|
131 | printf("lpwd Print local working directory\n"); |
---|
132 | printf("ls [path] Display remote directory listing\n"); |
---|
133 | printf("lumask umask Set local umask to 'umask'\n"); |
---|
134 | printf("mkdir path Create remote directory\n"); |
---|
135 | printf("put local-path [remote-path] Upload file\n"); |
---|
136 | printf("pwd Display remote working directory\n"); |
---|
137 | printf("exit Quit sftp\n"); |
---|
138 | printf("quit Quit sftp\n"); |
---|
139 | printf("rename oldpath newpath Rename remote file\n"); |
---|
140 | printf("rmdir path Remove remote directory\n"); |
---|
141 | printf("rm path Delete remote file\n"); |
---|
142 | printf("symlink oldpath newpath Symlink remote file\n"); |
---|
143 | printf("version Show SFTP version\n"); |
---|
144 | printf("!command Execute 'command' in local shell\n"); |
---|
145 | printf("! Escape to local shell\n"); |
---|
146 | printf("? Synonym for help\n"); |
---|
147 | } |
---|
148 | |
---|
149 | static void |
---|
150 | local_do_shell(const char *args) |
---|
151 | { |
---|
152 | int status; |
---|
153 | char *shell; |
---|
154 | pid_t pid; |
---|
155 | |
---|
156 | if (!*args) |
---|
157 | args = NULL; |
---|
158 | |
---|
159 | if ((shell = getenv("SHELL")) == NULL) |
---|
160 | shell = _PATH_BSHELL; |
---|
161 | |
---|
162 | if ((pid = fork()) == -1) |
---|
163 | fatal("Couldn't fork: %s", strerror(errno)); |
---|
164 | |
---|
165 | if (pid == 0) { |
---|
166 | /* XXX: child has pipe fds to ssh subproc open - issue? */ |
---|
167 | if (args) { |
---|
168 | debug3("Executing %s -c \"%s\"", shell, args); |
---|
169 | execl(shell, shell, "-c", args, (char *)NULL); |
---|
170 | } else { |
---|
171 | debug3("Executing %s", shell); |
---|
172 | execl(shell, shell, (char *)NULL); |
---|
173 | } |
---|
174 | fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell, |
---|
175 | strerror(errno)); |
---|
176 | _exit(1); |
---|
177 | } |
---|
178 | while (waitpid(pid, &status, 0) == -1) |
---|
179 | if (errno != EINTR) |
---|
180 | fatal("Couldn't wait for child: %s", strerror(errno)); |
---|
181 | if (!WIFEXITED(status)) |
---|
182 | error("Shell exited abormally"); |
---|
183 | else if (WEXITSTATUS(status)) |
---|
184 | error("Shell exited with status %d", WEXITSTATUS(status)); |
---|
185 | } |
---|
186 | |
---|
187 | static void |
---|
188 | local_do_ls(const char *args) |
---|
189 | { |
---|
190 | if (!args || !*args) |
---|
191 | local_do_shell(_PATH_LS); |
---|
192 | else { |
---|
193 | int len = strlen(_PATH_LS " ") + strlen(args) + 1; |
---|
194 | char *buf = xmalloc(len); |
---|
195 | |
---|
196 | /* XXX: quoting - rip quoting code from ftp? */ |
---|
197 | snprintf(buf, len, _PATH_LS " %s", args); |
---|
198 | local_do_shell(buf); |
---|
199 | xfree(buf); |
---|
200 | } |
---|
201 | } |
---|
202 | |
---|
203 | /* Strip one path (usually the pwd) from the start of another */ |
---|
204 | static char * |
---|
205 | path_strip(char *path, char *strip) |
---|
206 | { |
---|
207 | size_t len; |
---|
208 | |
---|
209 | if (strip == NULL) |
---|
210 | return (xstrdup(path)); |
---|
211 | |
---|
212 | len = strlen(strip); |
---|
213 | if (strip != NULL && strncmp(path, strip, len) == 0) { |
---|
214 | if (strip[len - 1] != '/' && path[len] == '/') |
---|
215 | len++; |
---|
216 | return (xstrdup(path + len)); |
---|
217 | } |
---|
218 | |
---|
219 | return (xstrdup(path)); |
---|
220 | } |
---|
221 | |
---|
222 | static char * |
---|
223 | path_append(char *p1, char *p2) |
---|
224 | { |
---|
225 | char *ret; |
---|
226 | int len = strlen(p1) + strlen(p2) + 2; |
---|
227 | |
---|
228 | ret = xmalloc(len); |
---|
229 | strlcpy(ret, p1, len); |
---|
230 | if (p1[strlen(p1) - 1] != '/') |
---|
231 | strlcat(ret, "/", len); |
---|
232 | strlcat(ret, p2, len); |
---|
233 | |
---|
234 | return(ret); |
---|
235 | } |
---|
236 | |
---|
237 | static char * |
---|
238 | make_absolute(char *p, char *pwd) |
---|
239 | { |
---|
240 | char *abs; |
---|
241 | |
---|
242 | /* Derelativise */ |
---|
243 | if (p && p[0] != '/') { |
---|
244 | abs = path_append(pwd, p); |
---|
245 | xfree(p); |
---|
246 | return(abs); |
---|
247 | } else |
---|
248 | return(p); |
---|
249 | } |
---|
250 | |
---|
251 | static int |
---|
252 | infer_path(const char *p, char **ifp) |
---|
253 | { |
---|
254 | char *cp; |
---|
255 | |
---|
256 | cp = strrchr(p, '/'); |
---|
257 | if (cp == NULL) { |
---|
258 | *ifp = xstrdup(p); |
---|
259 | return(0); |
---|
260 | } |
---|
261 | |
---|
262 | if (!cp[1]) { |
---|
263 | error("Invalid path"); |
---|
264 | return(-1); |
---|
265 | } |
---|
266 | |
---|
267 | *ifp = xstrdup(cp + 1); |
---|
268 | return(0); |
---|
269 | } |
---|
270 | |
---|
271 | static int |
---|
272 | parse_getput_flags(const char **cpp, int *pflag) |
---|
273 | { |
---|
274 | const char *cp = *cpp; |
---|
275 | |
---|
276 | /* Check for flags */ |
---|
277 | if (cp[0] == '-' && cp[1] && strchr(WHITESPACE, cp[2])) { |
---|
278 | switch (cp[1]) { |
---|
279 | case 'p': |
---|
280 | case 'P': |
---|
281 | *pflag = 1; |
---|
282 | break; |
---|
283 | default: |
---|
284 | error("Invalid flag -%c", cp[1]); |
---|
285 | return(-1); |
---|
286 | } |
---|
287 | cp += 2; |
---|
288 | *cpp = cp + strspn(cp, WHITESPACE); |
---|
289 | } |
---|
290 | |
---|
291 | return(0); |
---|
292 | } |
---|
293 | |
---|
294 | static int |
---|
295 | parse_ls_flags(const char **cpp, int *lflag) |
---|
296 | { |
---|
297 | const char *cp = *cpp; |
---|
298 | |
---|
299 | /* Check for flags */ |
---|
300 | if (cp++[0] == '-') { |
---|
301 | for(; strchr(WHITESPACE, *cp) == NULL; cp++) { |
---|
302 | switch (*cp) { |
---|
303 | case 'l': |
---|
304 | *lflag = 1; |
---|
305 | break; |
---|
306 | default: |
---|
307 | error("Invalid flag -%c", *cp); |
---|
308 | return(-1); |
---|
309 | } |
---|
310 | } |
---|
311 | *cpp = cp + strspn(cp, WHITESPACE); |
---|
312 | } |
---|
313 | |
---|
314 | return(0); |
---|
315 | } |
---|
316 | |
---|
317 | static int |
---|
318 | get_pathname(const char **cpp, char **path) |
---|
319 | { |
---|
320 | const char *cp = *cpp, *end; |
---|
321 | char quot; |
---|
322 | int i; |
---|
323 | |
---|
324 | cp += strspn(cp, WHITESPACE); |
---|
325 | if (!*cp) { |
---|
326 | *cpp = cp; |
---|
327 | *path = NULL; |
---|
328 | return (0); |
---|
329 | } |
---|
330 | |
---|
331 | /* Check for quoted filenames */ |
---|
332 | if (*cp == '\"' || *cp == '\'') { |
---|
333 | quot = *cp++; |
---|
334 | |
---|
335 | end = strchr(cp, quot); |
---|
336 | if (end == NULL) { |
---|
337 | error("Unterminated quote"); |
---|
338 | goto fail; |
---|
339 | } |
---|
340 | if (cp == end) { |
---|
341 | error("Empty quotes"); |
---|
342 | goto fail; |
---|
343 | } |
---|
344 | *cpp = end + 1 + strspn(end + 1, WHITESPACE); |
---|
345 | } else { |
---|
346 | /* Read to end of filename */ |
---|
347 | end = strpbrk(cp, WHITESPACE); |
---|
348 | if (end == NULL) |
---|
349 | end = strchr(cp, '\0'); |
---|
350 | *cpp = end + strspn(end, WHITESPACE); |
---|
351 | } |
---|
352 | |
---|
353 | i = end - cp; |
---|
354 | |
---|
355 | *path = xmalloc(i + 1); |
---|
356 | memcpy(*path, cp, i); |
---|
357 | (*path)[i] = '\0'; |
---|
358 | return(0); |
---|
359 | |
---|
360 | fail: |
---|
361 | *path = NULL; |
---|
362 | return (-1); |
---|
363 | } |
---|
364 | |
---|
365 | static int |
---|
366 | is_dir(char *path) |
---|
367 | { |
---|
368 | struct stat sb; |
---|
369 | |
---|
370 | /* XXX: report errors? */ |
---|
371 | if (stat(path, &sb) == -1) |
---|
372 | return(0); |
---|
373 | |
---|
374 | return(sb.st_mode & S_IFDIR); |
---|
375 | } |
---|
376 | |
---|
377 | static int |
---|
378 | remote_is_dir(struct sftp_conn *conn, char *path) |
---|
379 | { |
---|
380 | Attrib *a; |
---|
381 | |
---|
382 | /* XXX: report errors? */ |
---|
383 | if ((a = do_stat(conn, path, 1)) == NULL) |
---|
384 | return(0); |
---|
385 | if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) |
---|
386 | return(0); |
---|
387 | return(a->perm & S_IFDIR); |
---|
388 | } |
---|
389 | |
---|
390 | static int |
---|
391 | process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag) |
---|
392 | { |
---|
393 | char *abs_src = NULL; |
---|
394 | char *abs_dst = NULL; |
---|
395 | char *tmp; |
---|
396 | glob_t g; |
---|
397 | int err = 0; |
---|
398 | int i; |
---|
399 | |
---|
400 | abs_src = xstrdup(src); |
---|
401 | abs_src = make_absolute(abs_src, pwd); |
---|
402 | |
---|
403 | memset(&g, 0, sizeof(g)); |
---|
404 | debug3("Looking up %s", abs_src); |
---|
405 | if (remote_glob(conn, abs_src, 0, NULL, &g)) { |
---|
406 | error("File \"%s\" not found.", abs_src); |
---|
407 | err = -1; |
---|
408 | goto out; |
---|
409 | } |
---|
410 | |
---|
411 | /* Only one match, dst may be file, directory or unspecified */ |
---|
412 | if (g.gl_pathv[0] && g.gl_matchc == 1) { |
---|
413 | if (dst) { |
---|
414 | /* If directory specified, append filename */ |
---|
415 | if (is_dir(dst)) { |
---|
416 | if (infer_path(g.gl_pathv[0], &tmp)) { |
---|
417 | err = 1; |
---|
418 | goto out; |
---|
419 | } |
---|
420 | abs_dst = path_append(dst, tmp); |
---|
421 | xfree(tmp); |
---|
422 | } else |
---|
423 | abs_dst = xstrdup(dst); |
---|
424 | } else if (infer_path(g.gl_pathv[0], &abs_dst)) { |
---|
425 | err = -1; |
---|
426 | goto out; |
---|
427 | } |
---|
428 | printf("Fetching %s to %s\n", g.gl_pathv[0], abs_dst); |
---|
429 | err = do_download(conn, g.gl_pathv[0], abs_dst, pflag); |
---|
430 | goto out; |
---|
431 | } |
---|
432 | |
---|
433 | /* Multiple matches, dst may be directory or unspecified */ |
---|
434 | if (dst && !is_dir(dst)) { |
---|
435 | error("Multiple files match, but \"%s\" is not a directory", |
---|
436 | dst); |
---|
437 | err = -1; |
---|
438 | goto out; |
---|
439 | } |
---|
440 | |
---|
441 | for (i = 0; g.gl_pathv[i]; i++) { |
---|
442 | if (infer_path(g.gl_pathv[i], &tmp)) { |
---|
443 | err = -1; |
---|
444 | goto out; |
---|
445 | } |
---|
446 | if (dst) { |
---|
447 | abs_dst = path_append(dst, tmp); |
---|
448 | xfree(tmp); |
---|
449 | } else |
---|
450 | abs_dst = tmp; |
---|
451 | |
---|
452 | printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst); |
---|
453 | if (do_download(conn, g.gl_pathv[i], abs_dst, pflag) == -1) |
---|
454 | err = -1; |
---|
455 | xfree(abs_dst); |
---|
456 | abs_dst = NULL; |
---|
457 | } |
---|
458 | |
---|
459 | out: |
---|
460 | xfree(abs_src); |
---|
461 | if (abs_dst) |
---|
462 | xfree(abs_dst); |
---|
463 | globfree(&g); |
---|
464 | return(err); |
---|
465 | } |
---|
466 | |
---|
467 | static int |
---|
468 | process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag) |
---|
469 | { |
---|
470 | char *tmp_dst = NULL; |
---|
471 | char *abs_dst = NULL; |
---|
472 | char *tmp; |
---|
473 | glob_t g; |
---|
474 | int err = 0; |
---|
475 | int i; |
---|
476 | |
---|
477 | if (dst) { |
---|
478 | tmp_dst = xstrdup(dst); |
---|
479 | tmp_dst = make_absolute(tmp_dst, pwd); |
---|
480 | } |
---|
481 | |
---|
482 | memset(&g, 0, sizeof(g)); |
---|
483 | debug3("Looking up %s", src); |
---|
484 | if (glob(src, 0, NULL, &g)) { |
---|
485 | error("File \"%s\" not found.", src); |
---|
486 | err = -1; |
---|
487 | goto out; |
---|
488 | } |
---|
489 | |
---|
490 | /* Only one match, dst may be file, directory or unspecified */ |
---|
491 | if (g.gl_pathv[0] && g.gl_matchc == 1) { |
---|
492 | if (tmp_dst) { |
---|
493 | /* If directory specified, append filename */ |
---|
494 | if (remote_is_dir(conn, tmp_dst)) { |
---|
495 | if (infer_path(g.gl_pathv[0], &tmp)) { |
---|
496 | err = 1; |
---|
497 | goto out; |
---|
498 | } |
---|
499 | abs_dst = path_append(tmp_dst, tmp); |
---|
500 | xfree(tmp); |
---|
501 | } else |
---|
502 | abs_dst = xstrdup(tmp_dst); |
---|
503 | } else { |
---|
504 | if (infer_path(g.gl_pathv[0], &abs_dst)) { |
---|
505 | err = -1; |
---|
506 | goto out; |
---|
507 | } |
---|
508 | abs_dst = make_absolute(abs_dst, pwd); |
---|
509 | } |
---|
510 | printf("Uploading %s to %s\n", g.gl_pathv[0], abs_dst); |
---|
511 | err = do_upload(conn, g.gl_pathv[0], abs_dst, pflag); |
---|
512 | goto out; |
---|
513 | } |
---|
514 | |
---|
515 | /* Multiple matches, dst may be directory or unspecified */ |
---|
516 | if (tmp_dst && !remote_is_dir(conn, tmp_dst)) { |
---|
517 | error("Multiple files match, but \"%s\" is not a directory", |
---|
518 | tmp_dst); |
---|
519 | err = -1; |
---|
520 | goto out; |
---|
521 | } |
---|
522 | |
---|
523 | for (i = 0; g.gl_pathv[i]; i++) { |
---|
524 | if (infer_path(g.gl_pathv[i], &tmp)) { |
---|
525 | err = -1; |
---|
526 | goto out; |
---|
527 | } |
---|
528 | if (tmp_dst) { |
---|
529 | abs_dst = path_append(tmp_dst, tmp); |
---|
530 | xfree(tmp); |
---|
531 | } else |
---|
532 | abs_dst = make_absolute(tmp, pwd); |
---|
533 | |
---|
534 | printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst); |
---|
535 | if (do_upload(conn, g.gl_pathv[i], abs_dst, pflag) == -1) |
---|
536 | err = -1; |
---|
537 | } |
---|
538 | |
---|
539 | out: |
---|
540 | if (abs_dst) |
---|
541 | xfree(abs_dst); |
---|
542 | if (tmp_dst) |
---|
543 | xfree(tmp_dst); |
---|
544 | return(err); |
---|
545 | } |
---|
546 | |
---|
547 | static int |
---|
548 | sdirent_comp(const void *aa, const void *bb) |
---|
549 | { |
---|
550 | SFTP_DIRENT *a = *(SFTP_DIRENT **)aa; |
---|
551 | SFTP_DIRENT *b = *(SFTP_DIRENT **)bb; |
---|
552 | |
---|
553 | return (strcmp(a->filename, b->filename)); |
---|
554 | } |
---|
555 | |
---|
556 | /* sftp ls.1 replacement for directories */ |
---|
557 | static int |
---|
558 | do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag) |
---|
559 | { |
---|
560 | int n; |
---|
561 | SFTP_DIRENT **d; |
---|
562 | |
---|
563 | if ((n = do_readdir(conn, path, &d)) != 0) |
---|
564 | return (n); |
---|
565 | |
---|
566 | /* Count entries for sort */ |
---|
567 | for (n = 0; d[n] != NULL; n++) |
---|
568 | ; |
---|
569 | |
---|
570 | qsort(d, n, sizeof(*d), sdirent_comp); |
---|
571 | |
---|
572 | for (n = 0; d[n] != NULL; n++) { |
---|
573 | char *tmp, *fname; |
---|
574 | |
---|
575 | tmp = path_append(path, d[n]->filename); |
---|
576 | fname = path_strip(tmp, strip_path); |
---|
577 | xfree(tmp); |
---|
578 | |
---|
579 | if (lflag) { |
---|
580 | char *lname; |
---|
581 | struct stat sb; |
---|
582 | |
---|
583 | memset(&sb, 0, sizeof(sb)); |
---|
584 | attrib_to_stat(&d[n]->a, &sb); |
---|
585 | lname = ls_file(fname, &sb, 1); |
---|
586 | printf("%s\n", lname); |
---|
587 | xfree(lname); |
---|
588 | } else { |
---|
589 | /* XXX - multicolumn display would be nice here */ |
---|
590 | printf("%s\n", fname); |
---|
591 | } |
---|
592 | |
---|
593 | xfree(fname); |
---|
594 | } |
---|
595 | |
---|
596 | free_sftp_dirents(d); |
---|
597 | return (0); |
---|
598 | } |
---|
599 | |
---|
600 | /* sftp ls.1 replacement which handles path globs */ |
---|
601 | static int |
---|
602 | do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path, |
---|
603 | int lflag) |
---|
604 | { |
---|
605 | glob_t g; |
---|
606 | int i; |
---|
607 | Attrib *a; |
---|
608 | struct stat sb; |
---|
609 | |
---|
610 | memset(&g, 0, sizeof(g)); |
---|
611 | |
---|
612 | if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE, |
---|
613 | NULL, &g)) { |
---|
614 | error("Can't ls: \"%s\" not found", path); |
---|
615 | return (-1); |
---|
616 | } |
---|
617 | |
---|
618 | /* |
---|
619 | * If the glob returns a single match, which is the same as the |
---|
620 | * input glob, and it is a directory, then just list its contents |
---|
621 | */ |
---|
622 | if (g.gl_pathc == 1 && |
---|
623 | strncmp(path, g.gl_pathv[0], strlen(g.gl_pathv[0]) - 1) == 0) { |
---|
624 | if ((a = do_lstat(conn, path, 1)) == NULL) { |
---|
625 | globfree(&g); |
---|
626 | return (-1); |
---|
627 | } |
---|
628 | if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) && |
---|
629 | S_ISDIR(a->perm)) { |
---|
630 | globfree(&g); |
---|
631 | return (do_ls_dir(conn, path, strip_path, lflag)); |
---|
632 | } |
---|
633 | } |
---|
634 | |
---|
635 | for (i = 0; g.gl_pathv[i]; i++) { |
---|
636 | char *fname, *lname; |
---|
637 | |
---|
638 | fname = path_strip(g.gl_pathv[i], strip_path); |
---|
639 | |
---|
640 | if (lflag) { |
---|
641 | /* |
---|
642 | * XXX: this is slow - 1 roundtrip per path |
---|
643 | * A solution to this is to fork glob() and |
---|
644 | * build a sftp specific version which keeps the |
---|
645 | * attribs (which currently get thrown away) |
---|
646 | * that the server returns as well as the filenames. |
---|
647 | */ |
---|
648 | memset(&sb, 0, sizeof(sb)); |
---|
649 | a = do_lstat(conn, g.gl_pathv[i], 1); |
---|
650 | if (a != NULL) |
---|
651 | attrib_to_stat(a, &sb); |
---|
652 | lname = ls_file(fname, &sb, 1); |
---|
653 | printf("%s\n", lname); |
---|
654 | xfree(lname); |
---|
655 | } else { |
---|
656 | /* XXX - multicolumn display would be nice here */ |
---|
657 | printf("%s\n", fname); |
---|
658 | } |
---|
659 | xfree(fname); |
---|
660 | } |
---|
661 | |
---|
662 | if (g.gl_pathc) |
---|
663 | globfree(&g); |
---|
664 | |
---|
665 | return (0); |
---|
666 | } |
---|
667 | |
---|
668 | static int |
---|
669 | parse_args(const char **cpp, int *pflag, int *lflag, |
---|
670 | unsigned long *n_arg, char **path1, char **path2) |
---|
671 | { |
---|
672 | const char *cmd, *cp = *cpp; |
---|
673 | char *cp2; |
---|
674 | int base = 0; |
---|
675 | long l; |
---|
676 | int i, cmdnum; |
---|
677 | |
---|
678 | /* Skip leading whitespace */ |
---|
679 | cp = cp + strspn(cp, WHITESPACE); |
---|
680 | |
---|
681 | /* Ignore blank lines */ |
---|
682 | if (!*cp) |
---|
683 | return(-1); |
---|
684 | |
---|
685 | /* Figure out which command we have */ |
---|
686 | for (i = 0; cmds[i].c; i++) { |
---|
687 | int cmdlen = strlen(cmds[i].c); |
---|
688 | |
---|
689 | /* Check for command followed by whitespace */ |
---|
690 | if (!strncasecmp(cp, cmds[i].c, cmdlen) && |
---|
691 | strchr(WHITESPACE, cp[cmdlen])) { |
---|
692 | cp += cmdlen; |
---|
693 | cp = cp + strspn(cp, WHITESPACE); |
---|
694 | break; |
---|
695 | } |
---|
696 | } |
---|
697 | cmdnum = cmds[i].n; |
---|
698 | cmd = cmds[i].c; |
---|
699 | |
---|
700 | /* Special case */ |
---|
701 | if (*cp == '!') { |
---|
702 | cp++; |
---|
703 | cmdnum = I_SHELL; |
---|
704 | } else if (cmdnum == -1) { |
---|
705 | error("Invalid command."); |
---|
706 | return(-1); |
---|
707 | } |
---|
708 | |
---|
709 | /* Get arguments and parse flags */ |
---|
710 | *lflag = *pflag = *n_arg = 0; |
---|
711 | *path1 = *path2 = NULL; |
---|
712 | switch (cmdnum) { |
---|
713 | case I_GET: |
---|
714 | case I_PUT: |
---|
715 | if (parse_getput_flags(&cp, pflag)) |
---|
716 | return(-1); |
---|
717 | /* Get first pathname (mandatory) */ |
---|
718 | if (get_pathname(&cp, path1)) |
---|
719 | return(-1); |
---|
720 | if (*path1 == NULL) { |
---|
721 | error("You must specify at least one path after a " |
---|
722 | "%s command.", cmd); |
---|
723 | return(-1); |
---|
724 | } |
---|
725 | /* Try to get second pathname (optional) */ |
---|
726 | if (get_pathname(&cp, path2)) |
---|
727 | return(-1); |
---|
728 | break; |
---|
729 | case I_RENAME: |
---|
730 | case I_SYMLINK: |
---|
731 | if (get_pathname(&cp, path1)) |
---|
732 | return(-1); |
---|
733 | if (get_pathname(&cp, path2)) |
---|
734 | return(-1); |
---|
735 | if (!*path1 || !*path2) { |
---|
736 | error("You must specify two paths after a %s " |
---|
737 | "command.", cmd); |
---|
738 | return(-1); |
---|
739 | } |
---|
740 | break; |
---|
741 | case I_RM: |
---|
742 | case I_MKDIR: |
---|
743 | case I_RMDIR: |
---|
744 | case I_CHDIR: |
---|
745 | case I_LCHDIR: |
---|
746 | case I_LMKDIR: |
---|
747 | /* Get pathname (mandatory) */ |
---|
748 | if (get_pathname(&cp, path1)) |
---|
749 | return(-1); |
---|
750 | if (*path1 == NULL) { |
---|
751 | error("You must specify a path after a %s command.", |
---|
752 | cmd); |
---|
753 | return(-1); |
---|
754 | } |
---|
755 | break; |
---|
756 | case I_LS: |
---|
757 | if (parse_ls_flags(&cp, lflag)) |
---|
758 | return(-1); |
---|
759 | /* Path is optional */ |
---|
760 | if (get_pathname(&cp, path1)) |
---|
761 | return(-1); |
---|
762 | break; |
---|
763 | case I_LLS: |
---|
764 | case I_SHELL: |
---|
765 | /* Uses the rest of the line */ |
---|
766 | break; |
---|
767 | case I_LUMASK: |
---|
768 | base = 8; |
---|
769 | case I_CHMOD: |
---|
770 | base = 8; |
---|
771 | case I_CHOWN: |
---|
772 | case I_CHGRP: |
---|
773 | /* Get numeric arg (mandatory) */ |
---|
774 | l = strtol(cp, &cp2, base); |
---|
775 | if (cp2 == cp || ((l == LONG_MIN || l == LONG_MAX) && |
---|
776 | errno == ERANGE) || l < 0) { |
---|
777 | error("You must supply a numeric argument " |
---|
778 | "to the %s command.", cmd); |
---|
779 | return(-1); |
---|
780 | } |
---|
781 | cp = cp2; |
---|
782 | *n_arg = l; |
---|
783 | if (cmdnum == I_LUMASK && strchr(WHITESPACE, *cp)) |
---|
784 | break; |
---|
785 | if (cmdnum == I_LUMASK || !strchr(WHITESPACE, *cp)) { |
---|
786 | error("You must supply a numeric argument " |
---|
787 | "to the %s command.", cmd); |
---|
788 | return(-1); |
---|
789 | } |
---|
790 | cp += strspn(cp, WHITESPACE); |
---|
791 | |
---|
792 | /* Get pathname (mandatory) */ |
---|
793 | if (get_pathname(&cp, path1)) |
---|
794 | return(-1); |
---|
795 | if (*path1 == NULL) { |
---|
796 | error("You must specify a path after a %s command.", |
---|
797 | cmd); |
---|
798 | return(-1); |
---|
799 | } |
---|
800 | break; |
---|
801 | case I_QUIT: |
---|
802 | case I_PWD: |
---|
803 | case I_LPWD: |
---|
804 | case I_HELP: |
---|
805 | case I_VERSION: |
---|
806 | break; |
---|
807 | default: |
---|
808 | fatal("Command not implemented"); |
---|
809 | } |
---|
810 | |
---|
811 | *cpp = cp; |
---|
812 | return(cmdnum); |
---|
813 | } |
---|
814 | |
---|
815 | static int |
---|
816 | parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd) |
---|
817 | { |
---|
818 | char *path1, *path2, *tmp; |
---|
819 | int pflag, lflag, cmdnum, i; |
---|
820 | unsigned long n_arg; |
---|
821 | Attrib a, *aa; |
---|
822 | char path_buf[MAXPATHLEN]; |
---|
823 | int err = 0; |
---|
824 | glob_t g; |
---|
825 | |
---|
826 | path1 = path2 = NULL; |
---|
827 | cmdnum = parse_args(&cmd, &pflag, &lflag, &n_arg, |
---|
828 | &path1, &path2); |
---|
829 | |
---|
830 | memset(&g, 0, sizeof(g)); |
---|
831 | |
---|
832 | /* Perform command */ |
---|
833 | switch (cmdnum) { |
---|
834 | case -1: |
---|
835 | break; |
---|
836 | case I_GET: |
---|
837 | err = process_get(conn, path1, path2, *pwd, pflag); |
---|
838 | break; |
---|
839 | case I_PUT: |
---|
840 | err = process_put(conn, path1, path2, *pwd, pflag); |
---|
841 | break; |
---|
842 | case I_RENAME: |
---|
843 | path1 = make_absolute(path1, *pwd); |
---|
844 | path2 = make_absolute(path2, *pwd); |
---|
845 | err = do_rename(conn, path1, path2); |
---|
846 | break; |
---|
847 | case I_SYMLINK: |
---|
848 | path2 = make_absolute(path2, *pwd); |
---|
849 | err = do_symlink(conn, path1, path2); |
---|
850 | break; |
---|
851 | case I_RM: |
---|
852 | path1 = make_absolute(path1, *pwd); |
---|
853 | remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); |
---|
854 | for (i = 0; g.gl_pathv[i]; i++) { |
---|
855 | printf("Removing %s\n", g.gl_pathv[i]); |
---|
856 | if (do_rm(conn, g.gl_pathv[i]) == -1) |
---|
857 | err = -1; |
---|
858 | } |
---|
859 | break; |
---|
860 | case I_MKDIR: |
---|
861 | path1 = make_absolute(path1, *pwd); |
---|
862 | attrib_clear(&a); |
---|
863 | a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; |
---|
864 | a.perm = 0777; |
---|
865 | err = do_mkdir(conn, path1, &a); |
---|
866 | break; |
---|
867 | case I_RMDIR: |
---|
868 | path1 = make_absolute(path1, *pwd); |
---|
869 | err = do_rmdir(conn, path1); |
---|
870 | break; |
---|
871 | case I_CHDIR: |
---|
872 | path1 = make_absolute(path1, *pwd); |
---|
873 | if ((tmp = do_realpath(conn, path1)) == NULL) { |
---|
874 | err = 1; |
---|
875 | break; |
---|
876 | } |
---|
877 | if ((aa = do_stat(conn, tmp, 0)) == NULL) { |
---|
878 | xfree(tmp); |
---|
879 | err = 1; |
---|
880 | break; |
---|
881 | } |
---|
882 | if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) { |
---|
883 | error("Can't change directory: Can't check target"); |
---|
884 | xfree(tmp); |
---|
885 | err = 1; |
---|
886 | break; |
---|
887 | } |
---|
888 | if (!S_ISDIR(aa->perm)) { |
---|
889 | error("Can't change directory: \"%s\" is not " |
---|
890 | "a directory", tmp); |
---|
891 | xfree(tmp); |
---|
892 | err = 1; |
---|
893 | break; |
---|
894 | } |
---|
895 | xfree(*pwd); |
---|
896 | *pwd = tmp; |
---|
897 | break; |
---|
898 | case I_LS: |
---|
899 | if (!path1) { |
---|
900 | do_globbed_ls(conn, *pwd, *pwd, lflag); |
---|
901 | break; |
---|
902 | } |
---|
903 | |
---|
904 | /* Strip pwd off beginning of non-absolute paths */ |
---|
905 | tmp = NULL; |
---|
906 | if (*path1 != '/') |
---|
907 | tmp = *pwd; |
---|
908 | |
---|
909 | path1 = make_absolute(path1, *pwd); |
---|
910 | |
---|
911 | do_globbed_ls(conn, path1, tmp, lflag); |
---|
912 | break; |
---|
913 | case I_LCHDIR: |
---|
914 | if (chdir(path1) == -1) { |
---|
915 | error("Couldn't change local directory to " |
---|
916 | "\"%s\": %s", path1, strerror(errno)); |
---|
917 | err = 1; |
---|
918 | } |
---|
919 | break; |
---|
920 | case I_LMKDIR: |
---|
921 | if (mkdir(path1, 0777) == -1) { |
---|
922 | error("Couldn't create local directory " |
---|
923 | "\"%s\": %s", path1, strerror(errno)); |
---|
924 | err = 1; |
---|
925 | } |
---|
926 | break; |
---|
927 | case I_LLS: |
---|
928 | local_do_ls(cmd); |
---|
929 | break; |
---|
930 | case I_SHELL: |
---|
931 | local_do_shell(cmd); |
---|
932 | break; |
---|
933 | case I_LUMASK: |
---|
934 | umask(n_arg); |
---|
935 | printf("Local umask: %03lo\n", n_arg); |
---|
936 | break; |
---|
937 | case I_CHMOD: |
---|
938 | path1 = make_absolute(path1, *pwd); |
---|
939 | attrib_clear(&a); |
---|
940 | a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; |
---|
941 | a.perm = n_arg; |
---|
942 | remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); |
---|
943 | for (i = 0; g.gl_pathv[i]; i++) { |
---|
944 | printf("Changing mode on %s\n", g.gl_pathv[i]); |
---|
945 | do_setstat(conn, g.gl_pathv[i], &a); |
---|
946 | } |
---|
947 | break; |
---|
948 | case I_CHOWN: |
---|
949 | path1 = make_absolute(path1, *pwd); |
---|
950 | remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); |
---|
951 | for (i = 0; g.gl_pathv[i]; i++) { |
---|
952 | if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) |
---|
953 | continue; |
---|
954 | if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) { |
---|
955 | error("Can't get current ownership of " |
---|
956 | "remote file \"%s\"", g.gl_pathv[i]); |
---|
957 | continue; |
---|
958 | } |
---|
959 | printf("Changing owner on %s\n", g.gl_pathv[i]); |
---|
960 | aa->flags &= SSH2_FILEXFER_ATTR_UIDGID; |
---|
961 | aa->uid = n_arg; |
---|
962 | do_setstat(conn, g.gl_pathv[i], aa); |
---|
963 | } |
---|
964 | break; |
---|
965 | case I_CHGRP: |
---|
966 | path1 = make_absolute(path1, *pwd); |
---|
967 | remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); |
---|
968 | for (i = 0; g.gl_pathv[i]; i++) { |
---|
969 | if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) |
---|
970 | continue; |
---|
971 | if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) { |
---|
972 | error("Can't get current ownership of " |
---|
973 | "remote file \"%s\"", g.gl_pathv[i]); |
---|
974 | continue; |
---|
975 | } |
---|
976 | printf("Changing group on %s\n", g.gl_pathv[i]); |
---|
977 | aa->flags &= SSH2_FILEXFER_ATTR_UIDGID; |
---|
978 | aa->gid = n_arg; |
---|
979 | do_setstat(conn, g.gl_pathv[i], aa); |
---|
980 | } |
---|
981 | break; |
---|
982 | case I_PWD: |
---|
983 | printf("Remote working directory: %s\n", *pwd); |
---|
984 | break; |
---|
985 | case I_LPWD: |
---|
986 | if (!getcwd(path_buf, sizeof(path_buf))) |
---|
987 | error("Couldn't get local cwd: %s", |
---|
988 | strerror(errno)); |
---|
989 | else |
---|
990 | printf("Local working directory: %s\n", |
---|
991 | path_buf); |
---|
992 | break; |
---|
993 | case I_QUIT: |
---|
994 | return(-1); |
---|
995 | case I_HELP: |
---|
996 | help(); |
---|
997 | break; |
---|
998 | case I_VERSION: |
---|
999 | printf("SFTP protocol version %u\n", sftp_proto_version(conn)); |
---|
1000 | break; |
---|
1001 | default: |
---|
1002 | fatal("%d is not implemented", cmdnum); |
---|
1003 | } |
---|
1004 | |
---|
1005 | if (g.gl_pathc) |
---|
1006 | globfree(&g); |
---|
1007 | if (path1) |
---|
1008 | xfree(path1); |
---|
1009 | if (path2) |
---|
1010 | xfree(path2); |
---|
1011 | |
---|
1012 | /* If an error occurs in batch mode we should abort. */ |
---|
1013 | if (infile != stdin && err > 0) |
---|
1014 | return -1; |
---|
1015 | |
---|
1016 | return(0); |
---|
1017 | } |
---|
1018 | |
---|
1019 | void |
---|
1020 | interactive_loop(int fd_in, int fd_out, char *file1, char *file2) |
---|
1021 | { |
---|
1022 | char *pwd; |
---|
1023 | char *dir = NULL; |
---|
1024 | char cmd[2048]; |
---|
1025 | struct sftp_conn *conn; |
---|
1026 | |
---|
1027 | conn = do_init(fd_in, fd_out, copy_buffer_len, num_requests); |
---|
1028 | if (conn == NULL) |
---|
1029 | fatal("Couldn't initialise connection to server"); |
---|
1030 | |
---|
1031 | pwd = do_realpath(conn, "."); |
---|
1032 | if (pwd == NULL) |
---|
1033 | fatal("Need cwd"); |
---|
1034 | |
---|
1035 | if (file1 != NULL) { |
---|
1036 | dir = xstrdup(file1); |
---|
1037 | dir = make_absolute(dir, pwd); |
---|
1038 | |
---|
1039 | if (remote_is_dir(conn, dir) && file2 == NULL) { |
---|
1040 | printf("Changing to: %s\n", dir); |
---|
1041 | snprintf(cmd, sizeof cmd, "cd \"%s\"", dir); |
---|
1042 | parse_dispatch_command(conn, cmd, &pwd); |
---|
1043 | } else { |
---|
1044 | if (file2 == NULL) |
---|
1045 | snprintf(cmd, sizeof cmd, "get %s", dir); |
---|
1046 | else |
---|
1047 | snprintf(cmd, sizeof cmd, "get %s %s", dir, |
---|
1048 | file2); |
---|
1049 | |
---|
1050 | parse_dispatch_command(conn, cmd, &pwd); |
---|
1051 | xfree(dir); |
---|
1052 | return; |
---|
1053 | } |
---|
1054 | xfree(dir); |
---|
1055 | } |
---|
1056 | #if HAVE_SETVBUF |
---|
1057 | setvbuf(stdout, NULL, _IOLBF, 0); |
---|
1058 | setvbuf(infile, NULL, _IOLBF, 0); |
---|
1059 | #else |
---|
1060 | setlinebuf(stdout); |
---|
1061 | setlinebuf(infile); |
---|
1062 | #endif |
---|
1063 | |
---|
1064 | for (;;) { |
---|
1065 | char *cp; |
---|
1066 | |
---|
1067 | printf("sftp> "); |
---|
1068 | |
---|
1069 | /* XXX: use libedit */ |
---|
1070 | if (fgets(cmd, sizeof(cmd), infile) == NULL) { |
---|
1071 | printf("\n"); |
---|
1072 | break; |
---|
1073 | } else if (infile != stdin) /* Bluff typing */ |
---|
1074 | printf("%s", cmd); |
---|
1075 | |
---|
1076 | cp = strrchr(cmd, '\n'); |
---|
1077 | if (cp) |
---|
1078 | *cp = '\0'; |
---|
1079 | |
---|
1080 | if (parse_dispatch_command(conn, cmd, &pwd)) |
---|
1081 | break; |
---|
1082 | } |
---|
1083 | xfree(pwd); |
---|
1084 | } |
---|