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