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