sftp.c revision 240075
1240075Sdes/* $OpenBSD: sftp.c,v 1.136 2012/06/22 14:36:33 dtucker Exp $ */ 2224638Sbrooks/* $FreeBSD: head/crypto/openssh/sftp.c 240075 2012-09-03 16:51:41Z des $ */ 376259Sgreen/* 4126274Sdes * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> 576259Sgreen * 6126274Sdes * Permission to use, copy, modify, and distribute this software for any 7126274Sdes * purpose with or without fee is hereby granted, provided that the above 8126274Sdes * copyright notice and this permission notice appear in all copies. 976259Sgreen * 10126274Sdes * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11126274Sdes * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12126274Sdes * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13126274Sdes * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14126274Sdes * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15126274Sdes * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16126274Sdes * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 1776259Sgreen */ 1876259Sgreen 1976259Sgreen#include "includes.h" 2076259Sgreen 21162852Sdes#include <sys/types.h> 22162852Sdes#include <sys/ioctl.h> 23162852Sdes#ifdef HAVE_SYS_STAT_H 24162852Sdes# include <sys/stat.h> 25162852Sdes#endif 26162852Sdes#include <sys/param.h> 27162852Sdes#include <sys/socket.h> 28162852Sdes#include <sys/wait.h> 29181111Sdes#ifdef HAVE_SYS_STATVFS_H 30181111Sdes#include <sys/statvfs.h> 31181111Sdes#endif 3276259Sgreen 33181111Sdes#include <ctype.h> 34162852Sdes#include <errno.h> 35162852Sdes 36162852Sdes#ifdef HAVE_PATHS_H 37162852Sdes# include <paths.h> 38162852Sdes#endif 39204917Sdes#ifdef HAVE_LIBGEN_H 40204917Sdes#include <libgen.h> 41204917Sdes#endif 42146998Sdes#ifdef USE_LIBEDIT 43146998Sdes#include <histedit.h> 44146998Sdes#else 45146998Sdestypedef void EditLine; 46146998Sdes#endif 47162852Sdes#include <signal.h> 48162852Sdes#include <stdlib.h> 49162852Sdes#include <stdio.h> 50162852Sdes#include <string.h> 51162852Sdes#include <unistd.h> 52162852Sdes#include <stdarg.h> 53146998Sdes 54181111Sdes#ifdef HAVE_UTIL_H 55181111Sdes# include <util.h> 56181111Sdes#endif 57181111Sdes 58181111Sdes#ifdef HAVE_LIBUTIL_H 59181111Sdes# include <libutil.h> 60181111Sdes#endif 61181111Sdes 6276259Sgreen#include "xmalloc.h" 6376259Sgreen#include "log.h" 6476259Sgreen#include "pathnames.h" 6592555Sdes#include "misc.h" 6676259Sgreen 6776259Sgreen#include "sftp.h" 68162852Sdes#include "buffer.h" 6976259Sgreen#include "sftp-common.h" 7076259Sgreen#include "sftp-client.h" 7176259Sgreen 72204917Sdes#define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */ 73224638Sbrooks#define DEFAULT_NUM_REQUESTS 256 /* # concurrent outstanding requests */ 74204917Sdes 75126274Sdes/* File to read commands from */ 76126274SdesFILE* infile; 77126274Sdes 78126274Sdes/* Are we in batchfile mode? */ 79126274Sdesint batchmode = 0; 80126274Sdes 81126274Sdes/* PID of ssh transport process */ 82126274Sdesstatic pid_t sshpid = -1; 83126274Sdes 84126274Sdes/* This is set to 0 if the progressmeter is not desired. */ 85128456Sdesint showprogress = 1; 86126274Sdes 87204917Sdes/* When this option is set, we always recursively download/upload directories */ 88204917Sdesint global_rflag = 0; 89204917Sdes 90204917Sdes/* When this option is set, the file transfers will always preserve times */ 91204917Sdesint global_pflag = 0; 92204917Sdes 93137015Sdes/* SIGINT received during command processing */ 94137015Sdesvolatile sig_atomic_t interrupted = 0; 95137015Sdes 96137015Sdes/* I wish qsort() took a separate ctx for the comparison function...*/ 97137015Sdesint sort_flag; 98137015Sdes 99204917Sdes/* Context used for commandline completion */ 100204917Sdesstruct complete_ctx { 101204917Sdes struct sftp_conn *conn; 102204917Sdes char **remote_pathp; 103204917Sdes}; 104204917Sdes 105126274Sdesint remote_glob(struct sftp_conn *, const char *, int, 106126274Sdes int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */ 107126274Sdes 10898937Sdesextern char *__progname; 10998937Sdes 110126274Sdes/* Separators for interactive commands */ 111126274Sdes#define WHITESPACE " \t\r\n" 11276259Sgreen 113137015Sdes/* ls flags */ 114204917Sdes#define LS_LONG_VIEW 0x0001 /* Full view ala ls -l */ 115204917Sdes#define LS_SHORT_VIEW 0x0002 /* Single row view ala ls -1 */ 116204917Sdes#define LS_NUMERIC_VIEW 0x0004 /* Long view with numeric uid/gid */ 117204917Sdes#define LS_NAME_SORT 0x0008 /* Sort by name (default) */ 118204917Sdes#define LS_TIME_SORT 0x0010 /* Sort by mtime */ 119204917Sdes#define LS_SIZE_SORT 0x0020 /* Sort by file size */ 120204917Sdes#define LS_REVERSE_SORT 0x0040 /* Reverse sort order */ 121204917Sdes#define LS_SHOW_ALL 0x0080 /* Don't skip filenames starting with '.' */ 122204917Sdes#define LS_SI_UNITS 0x0100 /* Display sizes as K, M, G, etc. */ 123113908Sdes 124204917Sdes#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS) 125137015Sdes#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT) 126137015Sdes 127126274Sdes/* Commands for interactive mode */ 128126274Sdes#define I_CHDIR 1 129126274Sdes#define I_CHGRP 2 130126274Sdes#define I_CHMOD 3 131126274Sdes#define I_CHOWN 4 132181111Sdes#define I_DF 24 133126274Sdes#define I_GET 5 134126274Sdes#define I_HELP 6 135126274Sdes#define I_LCHDIR 7 136221420Sdes#define I_LINK 25 137126274Sdes#define I_LLS 8 138126274Sdes#define I_LMKDIR 9 139126274Sdes#define I_LPWD 10 140126274Sdes#define I_LS 11 141126274Sdes#define I_LUMASK 12 142126274Sdes#define I_MKDIR 13 143126274Sdes#define I_PUT 14 144126274Sdes#define I_PWD 15 145126274Sdes#define I_QUIT 16 146126274Sdes#define I_RENAME 17 147126274Sdes#define I_RM 18 148126274Sdes#define I_RMDIR 19 149126274Sdes#define I_SHELL 20 150126274Sdes#define I_SYMLINK 21 151126274Sdes#define I_VERSION 22 152126274Sdes#define I_PROGRESS 23 153126274Sdes 154126274Sdesstruct CMD { 155126274Sdes const char *c; 156126274Sdes const int n; 157204917Sdes const int t; 158126274Sdes}; 159126274Sdes 160204917Sdes/* Type of completion */ 161204917Sdes#define NOARGS 0 162204917Sdes#define REMOTE 1 163204917Sdes#define LOCAL 2 164204917Sdes 165126274Sdesstatic const struct CMD cmds[] = { 166204917Sdes { "bye", I_QUIT, NOARGS }, 167204917Sdes { "cd", I_CHDIR, REMOTE }, 168204917Sdes { "chdir", I_CHDIR, REMOTE }, 169204917Sdes { "chgrp", I_CHGRP, REMOTE }, 170204917Sdes { "chmod", I_CHMOD, REMOTE }, 171204917Sdes { "chown", I_CHOWN, REMOTE }, 172204917Sdes { "df", I_DF, REMOTE }, 173204917Sdes { "dir", I_LS, REMOTE }, 174204917Sdes { "exit", I_QUIT, NOARGS }, 175204917Sdes { "get", I_GET, REMOTE }, 176204917Sdes { "help", I_HELP, NOARGS }, 177204917Sdes { "lcd", I_LCHDIR, LOCAL }, 178204917Sdes { "lchdir", I_LCHDIR, LOCAL }, 179204917Sdes { "lls", I_LLS, LOCAL }, 180204917Sdes { "lmkdir", I_LMKDIR, LOCAL }, 181221420Sdes { "ln", I_LINK, REMOTE }, 182204917Sdes { "lpwd", I_LPWD, LOCAL }, 183204917Sdes { "ls", I_LS, REMOTE }, 184204917Sdes { "lumask", I_LUMASK, NOARGS }, 185204917Sdes { "mkdir", I_MKDIR, REMOTE }, 186215116Sdes { "mget", I_GET, REMOTE }, 187215116Sdes { "mput", I_PUT, LOCAL }, 188204917Sdes { "progress", I_PROGRESS, NOARGS }, 189204917Sdes { "put", I_PUT, LOCAL }, 190204917Sdes { "pwd", I_PWD, REMOTE }, 191204917Sdes { "quit", I_QUIT, NOARGS }, 192204917Sdes { "rename", I_RENAME, REMOTE }, 193204917Sdes { "rm", I_RM, REMOTE }, 194204917Sdes { "rmdir", I_RMDIR, REMOTE }, 195204917Sdes { "symlink", I_SYMLINK, REMOTE }, 196204917Sdes { "version", I_VERSION, NOARGS }, 197204917Sdes { "!", I_SHELL, NOARGS }, 198204917Sdes { "?", I_HELP, NOARGS }, 199204917Sdes { NULL, -1, -1 } 200126274Sdes}; 201126274Sdes 202204917Sdesint interactive_loop(struct sftp_conn *, char *file1, char *file2); 203126274Sdes 204181111Sdes/* ARGSUSED */ 20592555Sdesstatic void 206137015Sdeskillchild(int signo) 207137015Sdes{ 208146998Sdes if (sshpid > 1) { 209137015Sdes kill(sshpid, SIGTERM); 210146998Sdes waitpid(sshpid, NULL, 0); 211146998Sdes } 212137015Sdes 213137015Sdes _exit(1); 214137015Sdes} 215137015Sdes 216181111Sdes/* ARGSUSED */ 217137015Sdesstatic void 218137015Sdescmd_interrupt(int signo) 219137015Sdes{ 220137015Sdes const char msg[] = "\rInterrupt \n"; 221146998Sdes int olderrno = errno; 222137015Sdes 223137015Sdes write(STDERR_FILENO, msg, sizeof(msg) - 1); 224137015Sdes interrupted = 1; 225146998Sdes errno = olderrno; 226137015Sdes} 227137015Sdes 228137015Sdesstatic void 229126274Sdeshelp(void) 230126274Sdes{ 231192595Sdes printf("Available commands:\n" 232192595Sdes "bye Quit sftp\n" 233192595Sdes "cd path Change remote directory to 'path'\n" 234192595Sdes "chgrp grp path Change group of file 'path' to 'grp'\n" 235192595Sdes "chmod mode path Change permissions of file 'path' to 'mode'\n" 236192595Sdes "chown own path Change owner of file 'path' to 'own'\n" 237192595Sdes "df [-hi] [path] Display statistics for current directory or\n" 238192595Sdes " filesystem containing 'path'\n" 239192595Sdes "exit Quit sftp\n" 240204917Sdes "get [-Ppr] remote [local] Download file\n" 241192595Sdes "help Display this help text\n" 242192595Sdes "lcd path Change local directory to 'path'\n" 243192595Sdes "lls [ls-options [path]] Display local directory listing\n" 244192595Sdes "lmkdir path Create local directory\n" 245221420Sdes "ln [-s] oldpath newpath Link remote file (-s for symlink)\n" 246192595Sdes "lpwd Print local working directory\n" 247204917Sdes "ls [-1afhlnrSt] [path] Display remote directory listing\n" 248192595Sdes "lumask umask Set local umask to 'umask'\n" 249192595Sdes "mkdir path Create remote directory\n" 250192595Sdes "progress Toggle display of progress meter\n" 251204917Sdes "put [-Ppr] local [remote] Upload file\n" 252192595Sdes "pwd Display remote working directory\n" 253192595Sdes "quit Quit sftp\n" 254192595Sdes "rename oldpath newpath Rename remote file\n" 255192595Sdes "rm path Delete remote file\n" 256192595Sdes "rmdir path Remove remote directory\n" 257192595Sdes "symlink oldpath newpath Symlink remote file\n" 258192595Sdes "version Show SFTP version\n" 259192595Sdes "!command Execute 'command' in local shell\n" 260192595Sdes "! Escape to local shell\n" 261192595Sdes "? Synonym for help\n"); 262126274Sdes} 263126274Sdes 264126274Sdesstatic void 265126274Sdeslocal_do_shell(const char *args) 266126274Sdes{ 267126274Sdes int status; 268126274Sdes char *shell; 269126274Sdes pid_t pid; 270126274Sdes 271126274Sdes if (!*args) 272126274Sdes args = NULL; 273126274Sdes 274221420Sdes if ((shell = getenv("SHELL")) == NULL || *shell == '\0') 275126274Sdes shell = _PATH_BSHELL; 276126274Sdes 277126274Sdes if ((pid = fork()) == -1) 278126274Sdes fatal("Couldn't fork: %s", strerror(errno)); 279126274Sdes 280126274Sdes if (pid == 0) { 281126274Sdes /* XXX: child has pipe fds to ssh subproc open - issue? */ 282126274Sdes if (args) { 283126274Sdes debug3("Executing %s -c \"%s\"", shell, args); 284126274Sdes execl(shell, shell, "-c", args, (char *)NULL); 285126274Sdes } else { 286126274Sdes debug3("Executing %s", shell); 287126274Sdes execl(shell, shell, (char *)NULL); 288126274Sdes } 289126274Sdes fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell, 290126274Sdes strerror(errno)); 291126274Sdes _exit(1); 292126274Sdes } 293126274Sdes while (waitpid(pid, &status, 0) == -1) 294126274Sdes if (errno != EINTR) 295126274Sdes fatal("Couldn't wait for child: %s", strerror(errno)); 296126274Sdes if (!WIFEXITED(status)) 297162852Sdes error("Shell exited abnormally"); 298126274Sdes else if (WEXITSTATUS(status)) 299126274Sdes error("Shell exited with status %d", WEXITSTATUS(status)); 300126274Sdes} 301126274Sdes 302126274Sdesstatic void 303126274Sdeslocal_do_ls(const char *args) 304126274Sdes{ 305126274Sdes if (!args || !*args) 306126274Sdes local_do_shell(_PATH_LS); 307126274Sdes else { 308126274Sdes int len = strlen(_PATH_LS " ") + strlen(args) + 1; 309126274Sdes char *buf = xmalloc(len); 310126274Sdes 311126274Sdes /* XXX: quoting - rip quoting code from ftp? */ 312126274Sdes snprintf(buf, len, _PATH_LS " %s", args); 313126274Sdes local_do_shell(buf); 314126274Sdes xfree(buf); 315126274Sdes } 316126274Sdes} 317126274Sdes 318126274Sdes/* Strip one path (usually the pwd) from the start of another */ 319126274Sdesstatic char * 320126274Sdespath_strip(char *path, char *strip) 321126274Sdes{ 322126274Sdes size_t len; 323126274Sdes 324126274Sdes if (strip == NULL) 325126274Sdes return (xstrdup(path)); 326126274Sdes 327126274Sdes len = strlen(strip); 328146998Sdes if (strncmp(path, strip, len) == 0) { 329126274Sdes if (strip[len - 1] != '/' && path[len] == '/') 330126274Sdes len++; 331126274Sdes return (xstrdup(path + len)); 332126274Sdes } 333126274Sdes 334126274Sdes return (xstrdup(path)); 335126274Sdes} 336126274Sdes 337126274Sdesstatic char * 338126274Sdesmake_absolute(char *p, char *pwd) 339126274Sdes{ 340137015Sdes char *abs_str; 341126274Sdes 342126274Sdes /* Derelativise */ 343126274Sdes if (p && p[0] != '/') { 344137015Sdes abs_str = path_append(pwd, p); 345126274Sdes xfree(p); 346137015Sdes return(abs_str); 347126274Sdes } else 348126274Sdes return(p); 349126274Sdes} 350126274Sdes 351126274Sdesstatic int 352204917Sdesparse_getput_flags(const char *cmd, char **argv, int argc, int *pflag, 353204917Sdes int *rflag) 354126274Sdes{ 355181111Sdes extern int opterr, optind, optopt, optreset; 356181111Sdes int ch; 357126274Sdes 358181111Sdes optind = optreset = 1; 359181111Sdes opterr = 0; 360181111Sdes 361204917Sdes *rflag = *pflag = 0; 362204917Sdes while ((ch = getopt(argc, argv, "PpRr")) != -1) { 363181111Sdes switch (ch) { 364126274Sdes case 'p': 365126274Sdes case 'P': 366126274Sdes *pflag = 1; 367126274Sdes break; 368204917Sdes case 'r': 369204917Sdes case 'R': 370204917Sdes *rflag = 1; 371204917Sdes break; 372126274Sdes default: 373181111Sdes error("%s: Invalid flag -%c", cmd, optopt); 374181111Sdes return -1; 375126274Sdes } 376126274Sdes } 377126274Sdes 378181111Sdes return optind; 379126274Sdes} 380126274Sdes 381126274Sdesstatic int 382221420Sdesparse_link_flags(const char *cmd, char **argv, int argc, int *sflag) 383221420Sdes{ 384221420Sdes extern int opterr, optind, optopt, optreset; 385221420Sdes int ch; 386221420Sdes 387221420Sdes optind = optreset = 1; 388221420Sdes opterr = 0; 389221420Sdes 390221420Sdes *sflag = 0; 391221420Sdes while ((ch = getopt(argc, argv, "s")) != -1) { 392221420Sdes switch (ch) { 393221420Sdes case 's': 394221420Sdes *sflag = 1; 395221420Sdes break; 396221420Sdes default: 397221420Sdes error("%s: Invalid flag -%c", cmd, optopt); 398221420Sdes return -1; 399221420Sdes } 400221420Sdes } 401221420Sdes 402221420Sdes return optind; 403221420Sdes} 404221420Sdes 405221420Sdesstatic int 406181111Sdesparse_ls_flags(char **argv, int argc, int *lflag) 407126274Sdes{ 408181111Sdes extern int opterr, optind, optopt, optreset; 409181111Sdes int ch; 410126274Sdes 411181111Sdes optind = optreset = 1; 412181111Sdes opterr = 0; 413181111Sdes 414137015Sdes *lflag = LS_NAME_SORT; 415204917Sdes while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) { 416181111Sdes switch (ch) { 417181111Sdes case '1': 418181111Sdes *lflag &= ~VIEW_FLAGS; 419181111Sdes *lflag |= LS_SHORT_VIEW; 420181111Sdes break; 421181111Sdes case 'S': 422181111Sdes *lflag &= ~SORT_FLAGS; 423181111Sdes *lflag |= LS_SIZE_SORT; 424181111Sdes break; 425181111Sdes case 'a': 426181111Sdes *lflag |= LS_SHOW_ALL; 427181111Sdes break; 428181111Sdes case 'f': 429181111Sdes *lflag &= ~SORT_FLAGS; 430181111Sdes break; 431204917Sdes case 'h': 432204917Sdes *lflag |= LS_SI_UNITS; 433204917Sdes break; 434181111Sdes case 'l': 435204917Sdes *lflag &= ~LS_SHORT_VIEW; 436181111Sdes *lflag |= LS_LONG_VIEW; 437181111Sdes break; 438181111Sdes case 'n': 439204917Sdes *lflag &= ~LS_SHORT_VIEW; 440181111Sdes *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW; 441181111Sdes break; 442181111Sdes case 'r': 443181111Sdes *lflag |= LS_REVERSE_SORT; 444181111Sdes break; 445181111Sdes case 't': 446181111Sdes *lflag &= ~SORT_FLAGS; 447181111Sdes *lflag |= LS_TIME_SORT; 448181111Sdes break; 449181111Sdes default: 450181111Sdes error("ls: Invalid flag -%c", optopt); 451181111Sdes return -1; 452126274Sdes } 453126274Sdes } 454126274Sdes 455181111Sdes return optind; 456126274Sdes} 457126274Sdes 458126274Sdesstatic int 459181111Sdesparse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag) 460126274Sdes{ 461181111Sdes extern int opterr, optind, optopt, optreset; 462181111Sdes int ch; 463126274Sdes 464181111Sdes optind = optreset = 1; 465181111Sdes opterr = 0; 466126274Sdes 467181111Sdes *hflag = *iflag = 0; 468181111Sdes while ((ch = getopt(argc, argv, "hi")) != -1) { 469181111Sdes switch (ch) { 470181111Sdes case 'h': 471181111Sdes *hflag = 1; 472181111Sdes break; 473181111Sdes case 'i': 474181111Sdes *iflag = 1; 475181111Sdes break; 476181111Sdes default: 477181111Sdes error("%s: Invalid flag -%c", cmd, optopt); 478181111Sdes return -1; 479126274Sdes } 480126274Sdes } 481126274Sdes 482181111Sdes return optind; 483126274Sdes} 484126274Sdes 485126274Sdesstatic int 486126274Sdesis_dir(char *path) 487126274Sdes{ 488126274Sdes struct stat sb; 489126274Sdes 490126274Sdes /* XXX: report errors? */ 491126274Sdes if (stat(path, &sb) == -1) 492126274Sdes return(0); 493126274Sdes 494162852Sdes return(S_ISDIR(sb.st_mode)); 495126274Sdes} 496126274Sdes 497126274Sdesstatic int 498126274Sdesremote_is_dir(struct sftp_conn *conn, char *path) 499126274Sdes{ 500126274Sdes Attrib *a; 501126274Sdes 502126274Sdes /* XXX: report errors? */ 503126274Sdes if ((a = do_stat(conn, path, 1)) == NULL) 504126274Sdes return(0); 505126274Sdes if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) 506126274Sdes return(0); 507162852Sdes return(S_ISDIR(a->perm)); 508126274Sdes} 509126274Sdes 510204917Sdes/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */ 511126274Sdesstatic int 512204917Sdespathname_is_dir(char *pathname) 513126274Sdes{ 514204917Sdes size_t l = strlen(pathname); 515204917Sdes 516204917Sdes return l > 0 && pathname[l - 1] == '/'; 517204917Sdes} 518204917Sdes 519204917Sdesstatic int 520204917Sdesprocess_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, 521204917Sdes int pflag, int rflag) 522204917Sdes{ 523126274Sdes char *abs_src = NULL; 524126274Sdes char *abs_dst = NULL; 525126274Sdes glob_t g; 526204917Sdes char *filename, *tmp=NULL; 527204917Sdes int i, err = 0; 528126274Sdes 529126274Sdes abs_src = xstrdup(src); 530126274Sdes abs_src = make_absolute(abs_src, pwd); 531204917Sdes memset(&g, 0, sizeof(g)); 532126274Sdes 533126274Sdes debug3("Looking up %s", abs_src); 534204917Sdes if (remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) { 535126274Sdes error("File \"%s\" not found.", abs_src); 536126274Sdes err = -1; 537126274Sdes goto out; 538126274Sdes } 539126274Sdes 540204917Sdes /* 541204917Sdes * If multiple matches then dst must be a directory or 542204917Sdes * unspecified. 543204917Sdes */ 544204917Sdes if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) { 545204917Sdes error("Multiple source paths, but destination " 546204917Sdes "\"%s\" is not a directory", dst); 547126274Sdes err = -1; 548126274Sdes goto out; 549126274Sdes } 550126274Sdes 551137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 552204917Sdes tmp = xstrdup(g.gl_pathv[i]); 553204917Sdes if ((filename = basename(tmp)) == NULL) { 554204917Sdes error("basename %s: %s", tmp, strerror(errno)); 555204917Sdes xfree(tmp); 556126274Sdes err = -1; 557126274Sdes goto out; 558126274Sdes } 559126274Sdes 560126274Sdes if (g.gl_matchc == 1 && dst) { 561126274Sdes if (is_dir(dst)) { 562204917Sdes abs_dst = path_append(dst, filename); 563204917Sdes } else { 564126274Sdes abs_dst = xstrdup(dst); 565204917Sdes } 566126274Sdes } else if (dst) { 567204917Sdes abs_dst = path_append(dst, filename); 568204917Sdes } else { 569204917Sdes abs_dst = xstrdup(filename); 570204917Sdes } 571204917Sdes xfree(tmp); 572126274Sdes 573126274Sdes printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst); 574204917Sdes if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) { 575204917Sdes if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL, 576204917Sdes pflag || global_pflag, 1) == -1) 577204917Sdes err = -1; 578204917Sdes } else { 579204917Sdes if (do_download(conn, g.gl_pathv[i], abs_dst, NULL, 580204917Sdes pflag || global_pflag) == -1) 581204917Sdes err = -1; 582204917Sdes } 583126274Sdes xfree(abs_dst); 584126274Sdes abs_dst = NULL; 585126274Sdes } 586126274Sdes 587126274Sdesout: 588126274Sdes xfree(abs_src); 589126274Sdes globfree(&g); 590126274Sdes return(err); 591126274Sdes} 592126274Sdes 593126274Sdesstatic int 594204917Sdesprocess_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, 595204917Sdes int pflag, int rflag) 596126274Sdes{ 597126274Sdes char *tmp_dst = NULL; 598126274Sdes char *abs_dst = NULL; 599204917Sdes char *tmp = NULL, *filename = NULL; 600126274Sdes glob_t g; 601126274Sdes int err = 0; 602204917Sdes int i, dst_is_dir = 1; 603181111Sdes struct stat sb; 604126274Sdes 605126274Sdes if (dst) { 606126274Sdes tmp_dst = xstrdup(dst); 607126274Sdes tmp_dst = make_absolute(tmp_dst, pwd); 608126274Sdes } 609126274Sdes 610126274Sdes memset(&g, 0, sizeof(g)); 611126274Sdes debug3("Looking up %s", src); 612204917Sdes if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) { 613126274Sdes error("File \"%s\" not found.", src); 614126274Sdes err = -1; 615126274Sdes goto out; 616126274Sdes } 617126274Sdes 618204917Sdes /* If we aren't fetching to pwd then stash this status for later */ 619204917Sdes if (tmp_dst != NULL) 620204917Sdes dst_is_dir = remote_is_dir(conn, tmp_dst); 621204917Sdes 622126274Sdes /* If multiple matches, dst may be directory or unspecified */ 623204917Sdes if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) { 624204917Sdes error("Multiple paths match, but destination " 625204917Sdes "\"%s\" is not a directory", tmp_dst); 626126274Sdes err = -1; 627126274Sdes goto out; 628126274Sdes } 629126274Sdes 630137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 631181111Sdes if (stat(g.gl_pathv[i], &sb) == -1) { 632181111Sdes err = -1; 633181111Sdes error("stat %s: %s", g.gl_pathv[i], strerror(errno)); 634181111Sdes continue; 635181111Sdes } 636204917Sdes 637204917Sdes tmp = xstrdup(g.gl_pathv[i]); 638204917Sdes if ((filename = basename(tmp)) == NULL) { 639204917Sdes error("basename %s: %s", tmp, strerror(errno)); 640204917Sdes xfree(tmp); 641126274Sdes err = -1; 642126274Sdes goto out; 643126274Sdes } 644126274Sdes 645126274Sdes if (g.gl_matchc == 1 && tmp_dst) { 646126274Sdes /* If directory specified, append filename */ 647204917Sdes if (dst_is_dir) 648204917Sdes abs_dst = path_append(tmp_dst, filename); 649204917Sdes else 650126274Sdes abs_dst = xstrdup(tmp_dst); 651126274Sdes } else if (tmp_dst) { 652204917Sdes abs_dst = path_append(tmp_dst, filename); 653204917Sdes } else { 654204917Sdes abs_dst = make_absolute(xstrdup(filename), pwd); 655204917Sdes } 656204917Sdes xfree(tmp); 657126274Sdes 658126274Sdes printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst); 659204917Sdes if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) { 660204917Sdes if (upload_dir(conn, g.gl_pathv[i], abs_dst, 661204917Sdes pflag || global_pflag, 1) == -1) 662204917Sdes err = -1; 663204917Sdes } else { 664204917Sdes if (do_upload(conn, g.gl_pathv[i], abs_dst, 665204917Sdes pflag || global_pflag) == -1) 666204917Sdes err = -1; 667204917Sdes } 668126274Sdes } 669126274Sdes 670126274Sdesout: 671126274Sdes if (abs_dst) 672126274Sdes xfree(abs_dst); 673126274Sdes if (tmp_dst) 674126274Sdes xfree(tmp_dst); 675126274Sdes globfree(&g); 676126274Sdes return(err); 677126274Sdes} 678126274Sdes 679126274Sdesstatic int 680126274Sdessdirent_comp(const void *aa, const void *bb) 681126274Sdes{ 682126274Sdes SFTP_DIRENT *a = *(SFTP_DIRENT **)aa; 683126274Sdes SFTP_DIRENT *b = *(SFTP_DIRENT **)bb; 684137015Sdes int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1; 685126274Sdes 686137015Sdes#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1)) 687137015Sdes if (sort_flag & LS_NAME_SORT) 688137015Sdes return (rmul * strcmp(a->filename, b->filename)); 689137015Sdes else if (sort_flag & LS_TIME_SORT) 690137015Sdes return (rmul * NCMP(a->a.mtime, b->a.mtime)); 691137015Sdes else if (sort_flag & LS_SIZE_SORT) 692137015Sdes return (rmul * NCMP(a->a.size, b->a.size)); 693137015Sdes 694137015Sdes fatal("Unknown ls sort type"); 695126274Sdes} 696126274Sdes 697126274Sdes/* sftp ls.1 replacement for directories */ 698126274Sdesstatic int 699126274Sdesdo_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag) 700126274Sdes{ 701149749Sdes int n; 702149749Sdes u_int c = 1, colspace = 0, columns = 1; 703126274Sdes SFTP_DIRENT **d; 704126274Sdes 705126274Sdes if ((n = do_readdir(conn, path, &d)) != 0) 706126274Sdes return (n); 707126274Sdes 708137015Sdes if (!(lflag & LS_SHORT_VIEW)) { 709149749Sdes u_int m = 0, width = 80; 710126274Sdes struct winsize ws; 711126274Sdes char *tmp; 712126274Sdes 713126274Sdes /* Count entries for sort and find longest filename */ 714137015Sdes for (n = 0; d[n] != NULL; n++) { 715137015Sdes if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL)) 716137015Sdes m = MAX(m, strlen(d[n]->filename)); 717137015Sdes } 718126274Sdes 719126274Sdes /* Add any subpath that also needs to be counted */ 720126274Sdes tmp = path_strip(path, strip_path); 721126274Sdes m += strlen(tmp); 722126274Sdes xfree(tmp); 723126274Sdes 724126274Sdes if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1) 725126274Sdes width = ws.ws_col; 726126274Sdes 727126274Sdes columns = width / (m + 2); 728126274Sdes columns = MAX(columns, 1); 729126274Sdes colspace = width / columns; 730126274Sdes colspace = MIN(colspace, width); 731126274Sdes } 732126274Sdes 733137015Sdes if (lflag & SORT_FLAGS) { 734157016Sdes for (n = 0; d[n] != NULL; n++) 735157016Sdes ; /* count entries */ 736137015Sdes sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT); 737137015Sdes qsort(d, n, sizeof(*d), sdirent_comp); 738137015Sdes } 739126274Sdes 740137015Sdes for (n = 0; d[n] != NULL && !interrupted; n++) { 741126274Sdes char *tmp, *fname; 742126274Sdes 743137015Sdes if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL)) 744137015Sdes continue; 745137015Sdes 746126274Sdes tmp = path_append(path, d[n]->filename); 747126274Sdes fname = path_strip(tmp, strip_path); 748126274Sdes xfree(tmp); 749126274Sdes 750137015Sdes if (lflag & LS_LONG_VIEW) { 751204917Sdes if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) { 752137015Sdes char *lname; 753137015Sdes struct stat sb; 754126274Sdes 755137015Sdes memset(&sb, 0, sizeof(sb)); 756137015Sdes attrib_to_stat(&d[n]->a, &sb); 757204917Sdes lname = ls_file(fname, &sb, 1, 758204917Sdes (lflag & LS_SI_UNITS)); 759137015Sdes printf("%s\n", lname); 760137015Sdes xfree(lname); 761137015Sdes } else 762137015Sdes printf("%s\n", d[n]->longname); 763126274Sdes } else { 764126274Sdes printf("%-*s", colspace, fname); 765126274Sdes if (c >= columns) { 766126274Sdes printf("\n"); 767126274Sdes c = 1; 768126274Sdes } else 769126274Sdes c++; 770126274Sdes } 771126274Sdes 772126274Sdes xfree(fname); 773126274Sdes } 774126274Sdes 775137015Sdes if (!(lflag & LS_LONG_VIEW) && (c != 1)) 776126274Sdes printf("\n"); 777126274Sdes 778126274Sdes free_sftp_dirents(d); 779126274Sdes return (0); 780126274Sdes} 781126274Sdes 782126274Sdes/* sftp ls.1 replacement which handles path globs */ 783126274Sdesstatic int 784126274Sdesdo_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path, 785126274Sdes int lflag) 786126274Sdes{ 787221420Sdes char *fname, *lname; 788126274Sdes glob_t g; 789221420Sdes int err; 790221420Sdes struct winsize ws; 791221420Sdes u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80; 792126274Sdes 793126274Sdes memset(&g, 0, sizeof(g)); 794126274Sdes 795221420Sdes if (remote_glob(conn, path, 796240075Sdes GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT, 797240075Sdes NULL, &g) || 798221420Sdes (g.gl_pathc && !g.gl_matchc)) { 799146998Sdes if (g.gl_pathc) 800146998Sdes globfree(&g); 801126274Sdes error("Can't ls: \"%s\" not found", path); 802221420Sdes return -1; 803126274Sdes } 804126274Sdes 805137015Sdes if (interrupted) 806137015Sdes goto out; 807137015Sdes 808126274Sdes /* 809146998Sdes * If the glob returns a single match and it is a directory, 810146998Sdes * then just list its contents. 811126274Sdes */ 812221420Sdes if (g.gl_matchc == 1 && g.gl_statv[0] != NULL && 813221420Sdes S_ISDIR(g.gl_statv[0]->st_mode)) { 814221420Sdes err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag); 815221420Sdes globfree(&g); 816221420Sdes return err; 817126274Sdes } 818126274Sdes 819221420Sdes if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1) 820221420Sdes width = ws.ws_col; 821221420Sdes 822137015Sdes if (!(lflag & LS_SHORT_VIEW)) { 823126274Sdes /* Count entries for sort and find longest filename */ 824126274Sdes for (i = 0; g.gl_pathv[i]; i++) 825126274Sdes m = MAX(m, strlen(g.gl_pathv[i])); 826126274Sdes 827126274Sdes columns = width / (m + 2); 828126274Sdes columns = MAX(columns, 1); 829126274Sdes colspace = width / columns; 830126274Sdes } 831126274Sdes 832240075Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 833126274Sdes fname = path_strip(g.gl_pathv[i], strip_path); 834137015Sdes if (lflag & LS_LONG_VIEW) { 835221420Sdes if (g.gl_statv[i] == NULL) { 836221420Sdes error("no stat information for %s", fname); 837221420Sdes continue; 838221420Sdes } 839221420Sdes lname = ls_file(fname, g.gl_statv[i], 1, 840221420Sdes (lflag & LS_SI_UNITS)); 841126274Sdes printf("%s\n", lname); 842126274Sdes xfree(lname); 843126274Sdes } else { 844126274Sdes printf("%-*s", colspace, fname); 845126274Sdes if (c >= columns) { 846126274Sdes printf("\n"); 847126274Sdes c = 1; 848126274Sdes } else 849126274Sdes c++; 850126274Sdes } 851126274Sdes xfree(fname); 852126274Sdes } 853126274Sdes 854137015Sdes if (!(lflag & LS_LONG_VIEW) && (c != 1)) 855126274Sdes printf("\n"); 856126274Sdes 857137015Sdes out: 858126274Sdes if (g.gl_pathc) 859126274Sdes globfree(&g); 860126274Sdes 861221420Sdes return 0; 862126274Sdes} 863126274Sdes 864126274Sdesstatic int 865181111Sdesdo_df(struct sftp_conn *conn, char *path, int hflag, int iflag) 866181111Sdes{ 867181111Sdes struct sftp_statvfs st; 868181111Sdes char s_used[FMT_SCALED_STRSIZE]; 869181111Sdes char s_avail[FMT_SCALED_STRSIZE]; 870181111Sdes char s_root[FMT_SCALED_STRSIZE]; 871181111Sdes char s_total[FMT_SCALED_STRSIZE]; 872204917Sdes unsigned long long ffree; 873181111Sdes 874181111Sdes if (do_statvfs(conn, path, &st, 1) == -1) 875181111Sdes return -1; 876181111Sdes if (iflag) { 877204917Sdes ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0; 878181111Sdes printf(" Inodes Used Avail " 879181111Sdes "(root) %%Capacity\n"); 880181111Sdes printf("%11llu %11llu %11llu %11llu %3llu%%\n", 881181111Sdes (unsigned long long)st.f_files, 882181111Sdes (unsigned long long)(st.f_files - st.f_ffree), 883181111Sdes (unsigned long long)st.f_favail, 884204917Sdes (unsigned long long)st.f_ffree, ffree); 885181111Sdes } else if (hflag) { 886181111Sdes strlcpy(s_used, "error", sizeof(s_used)); 887181111Sdes strlcpy(s_avail, "error", sizeof(s_avail)); 888181111Sdes strlcpy(s_root, "error", sizeof(s_root)); 889181111Sdes strlcpy(s_total, "error", sizeof(s_total)); 890181111Sdes fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used); 891181111Sdes fmt_scaled(st.f_bavail * st.f_frsize, s_avail); 892181111Sdes fmt_scaled(st.f_bfree * st.f_frsize, s_root); 893181111Sdes fmt_scaled(st.f_blocks * st.f_frsize, s_total); 894181111Sdes printf(" Size Used Avail (root) %%Capacity\n"); 895181111Sdes printf("%7sB %7sB %7sB %7sB %3llu%%\n", 896181111Sdes s_total, s_used, s_avail, s_root, 897181111Sdes (unsigned long long)(100 * (st.f_blocks - st.f_bfree) / 898181111Sdes st.f_blocks)); 899181111Sdes } else { 900181111Sdes printf(" Size Used Avail " 901181111Sdes "(root) %%Capacity\n"); 902181111Sdes printf("%12llu %12llu %12llu %12llu %3llu%%\n", 903181111Sdes (unsigned long long)(st.f_frsize * st.f_blocks / 1024), 904181111Sdes (unsigned long long)(st.f_frsize * 905181111Sdes (st.f_blocks - st.f_bfree) / 1024), 906181111Sdes (unsigned long long)(st.f_frsize * st.f_bavail / 1024), 907181111Sdes (unsigned long long)(st.f_frsize * st.f_bfree / 1024), 908181111Sdes (unsigned long long)(100 * (st.f_blocks - st.f_bfree) / 909181111Sdes st.f_blocks)); 910181111Sdes } 911181111Sdes return 0; 912181111Sdes} 913181111Sdes 914181111Sdes/* 915181111Sdes * Undo escaping of glob sequences in place. Used to undo extra escaping 916181111Sdes * applied in makeargv() when the string is destined for a function that 917181111Sdes * does not glob it. 918181111Sdes */ 919181111Sdesstatic void 920181111Sdesundo_glob_escape(char *s) 921181111Sdes{ 922181111Sdes size_t i, j; 923181111Sdes 924181111Sdes for (i = j = 0;;) { 925181111Sdes if (s[i] == '\0') { 926181111Sdes s[j] = '\0'; 927181111Sdes return; 928181111Sdes } 929181111Sdes if (s[i] != '\\') { 930181111Sdes s[j++] = s[i++]; 931181111Sdes continue; 932181111Sdes } 933181111Sdes /* s[i] == '\\' */ 934181111Sdes ++i; 935181111Sdes switch (s[i]) { 936181111Sdes case '?': 937181111Sdes case '[': 938181111Sdes case '*': 939181111Sdes case '\\': 940181111Sdes s[j++] = s[i++]; 941181111Sdes break; 942181111Sdes case '\0': 943181111Sdes s[j++] = '\\'; 944181111Sdes s[j] = '\0'; 945181111Sdes return; 946181111Sdes default: 947181111Sdes s[j++] = '\\'; 948181111Sdes s[j++] = s[i++]; 949181111Sdes break; 950181111Sdes } 951181111Sdes } 952181111Sdes} 953181111Sdes 954181111Sdes/* 955181111Sdes * Split a string into an argument vector using sh(1)-style quoting, 956181111Sdes * comment and escaping rules, but with some tweaks to handle glob(3) 957181111Sdes * wildcards. 958204917Sdes * The "sloppy" flag allows for recovery from missing terminating quote, for 959204917Sdes * use in parsing incomplete commandlines during tab autocompletion. 960204917Sdes * 961181111Sdes * Returns NULL on error or a NULL-terminated array of arguments. 962204917Sdes * 963204917Sdes * If "lastquote" is not NULL, the quoting character used for the last 964204917Sdes * argument is placed in *lastquote ("\0", "'" or "\""). 965204917Sdes * 966204917Sdes * If "terminated" is not NULL, *terminated will be set to 1 when the 967204917Sdes * last argument's quote has been properly terminated or 0 otherwise. 968204917Sdes * This parameter is only of use if "sloppy" is set. 969181111Sdes */ 970181111Sdes#define MAXARGS 128 971181111Sdes#define MAXARGLEN 8192 972181111Sdesstatic char ** 973204917Sdesmakeargv(const char *arg, int *argcp, int sloppy, char *lastquote, 974204917Sdes u_int *terminated) 975181111Sdes{ 976181111Sdes int argc, quot; 977181111Sdes size_t i, j; 978181111Sdes static char argvs[MAXARGLEN]; 979181111Sdes static char *argv[MAXARGS + 1]; 980181111Sdes enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q; 981181111Sdes 982181111Sdes *argcp = argc = 0; 983181111Sdes if (strlen(arg) > sizeof(argvs) - 1) { 984181111Sdes args_too_longs: 985181111Sdes error("string too long"); 986181111Sdes return NULL; 987181111Sdes } 988204917Sdes if (terminated != NULL) 989204917Sdes *terminated = 1; 990204917Sdes if (lastquote != NULL) 991204917Sdes *lastquote = '\0'; 992181111Sdes state = MA_START; 993181111Sdes i = j = 0; 994181111Sdes for (;;) { 995181111Sdes if (isspace(arg[i])) { 996181111Sdes if (state == MA_UNQUOTED) { 997181111Sdes /* Terminate current argument */ 998181111Sdes argvs[j++] = '\0'; 999181111Sdes argc++; 1000181111Sdes state = MA_START; 1001181111Sdes } else if (state != MA_START) 1002181111Sdes argvs[j++] = arg[i]; 1003181111Sdes } else if (arg[i] == '"' || arg[i] == '\'') { 1004181111Sdes q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE; 1005181111Sdes if (state == MA_START) { 1006181111Sdes argv[argc] = argvs + j; 1007181111Sdes state = q; 1008204917Sdes if (lastquote != NULL) 1009204917Sdes *lastquote = arg[i]; 1010181111Sdes } else if (state == MA_UNQUOTED) 1011181111Sdes state = q; 1012181111Sdes else if (state == q) 1013181111Sdes state = MA_UNQUOTED; 1014181111Sdes else 1015181111Sdes argvs[j++] = arg[i]; 1016181111Sdes } else if (arg[i] == '\\') { 1017181111Sdes if (state == MA_SQUOTE || state == MA_DQUOTE) { 1018181111Sdes quot = state == MA_SQUOTE ? '\'' : '"'; 1019181111Sdes /* Unescape quote we are in */ 1020181111Sdes /* XXX support \n and friends? */ 1021181111Sdes if (arg[i + 1] == quot) { 1022181111Sdes i++; 1023181111Sdes argvs[j++] = arg[i]; 1024181111Sdes } else if (arg[i + 1] == '?' || 1025181111Sdes arg[i + 1] == '[' || arg[i + 1] == '*') { 1026181111Sdes /* 1027181111Sdes * Special case for sftp: append 1028181111Sdes * double-escaped glob sequence - 1029181111Sdes * glob will undo one level of 1030181111Sdes * escaping. NB. string can grow here. 1031181111Sdes */ 1032181111Sdes if (j >= sizeof(argvs) - 5) 1033181111Sdes goto args_too_longs; 1034181111Sdes argvs[j++] = '\\'; 1035181111Sdes argvs[j++] = arg[i++]; 1036181111Sdes argvs[j++] = '\\'; 1037181111Sdes argvs[j++] = arg[i]; 1038181111Sdes } else { 1039181111Sdes argvs[j++] = arg[i++]; 1040181111Sdes argvs[j++] = arg[i]; 1041181111Sdes } 1042181111Sdes } else { 1043181111Sdes if (state == MA_START) { 1044181111Sdes argv[argc] = argvs + j; 1045181111Sdes state = MA_UNQUOTED; 1046204917Sdes if (lastquote != NULL) 1047204917Sdes *lastquote = '\0'; 1048181111Sdes } 1049181111Sdes if (arg[i + 1] == '?' || arg[i + 1] == '[' || 1050181111Sdes arg[i + 1] == '*' || arg[i + 1] == '\\') { 1051181111Sdes /* 1052181111Sdes * Special case for sftp: append 1053181111Sdes * escaped glob sequence - 1054181111Sdes * glob will undo one level of 1055181111Sdes * escaping. 1056181111Sdes */ 1057181111Sdes argvs[j++] = arg[i++]; 1058181111Sdes argvs[j++] = arg[i]; 1059181111Sdes } else { 1060181111Sdes /* Unescape everything */ 1061181111Sdes /* XXX support \n and friends? */ 1062181111Sdes i++; 1063181111Sdes argvs[j++] = arg[i]; 1064181111Sdes } 1065181111Sdes } 1066181111Sdes } else if (arg[i] == '#') { 1067181111Sdes if (state == MA_SQUOTE || state == MA_DQUOTE) 1068181111Sdes argvs[j++] = arg[i]; 1069181111Sdes else 1070181111Sdes goto string_done; 1071181111Sdes } else if (arg[i] == '\0') { 1072181111Sdes if (state == MA_SQUOTE || state == MA_DQUOTE) { 1073204917Sdes if (sloppy) { 1074204917Sdes state = MA_UNQUOTED; 1075204917Sdes if (terminated != NULL) 1076204917Sdes *terminated = 0; 1077204917Sdes goto string_done; 1078204917Sdes } 1079181111Sdes error("Unterminated quoted argument"); 1080181111Sdes return NULL; 1081181111Sdes } 1082181111Sdes string_done: 1083181111Sdes if (state == MA_UNQUOTED) { 1084181111Sdes argvs[j++] = '\0'; 1085181111Sdes argc++; 1086181111Sdes } 1087181111Sdes break; 1088181111Sdes } else { 1089181111Sdes if (state == MA_START) { 1090181111Sdes argv[argc] = argvs + j; 1091181111Sdes state = MA_UNQUOTED; 1092204917Sdes if (lastquote != NULL) 1093204917Sdes *lastquote = '\0'; 1094181111Sdes } 1095181111Sdes if ((state == MA_SQUOTE || state == MA_DQUOTE) && 1096181111Sdes (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) { 1097181111Sdes /* 1098181111Sdes * Special case for sftp: escape quoted 1099181111Sdes * glob(3) wildcards. NB. string can grow 1100181111Sdes * here. 1101181111Sdes */ 1102181111Sdes if (j >= sizeof(argvs) - 3) 1103181111Sdes goto args_too_longs; 1104181111Sdes argvs[j++] = '\\'; 1105181111Sdes argvs[j++] = arg[i]; 1106181111Sdes } else 1107181111Sdes argvs[j++] = arg[i]; 1108181111Sdes } 1109181111Sdes i++; 1110181111Sdes } 1111181111Sdes *argcp = argc; 1112181111Sdes return argv; 1113181111Sdes} 1114181111Sdes 1115181111Sdesstatic int 1116204917Sdesparse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag, 1117221420Sdes int *hflag, int *sflag, unsigned long *n_arg, char **path1, char **path2) 1118126274Sdes{ 1119126274Sdes const char *cmd, *cp = *cpp; 1120181111Sdes char *cp2, **argv; 1121126274Sdes int base = 0; 1122126274Sdes long l; 1123181111Sdes int i, cmdnum, optidx, argc; 1124126274Sdes 1125126274Sdes /* Skip leading whitespace */ 1126126274Sdes cp = cp + strspn(cp, WHITESPACE); 1127126274Sdes 1128126274Sdes /* Check for leading '-' (disable error processing) */ 1129126274Sdes *iflag = 0; 1130126274Sdes if (*cp == '-') { 1131126274Sdes *iflag = 1; 1132126274Sdes cp++; 1133204917Sdes cp = cp + strspn(cp, WHITESPACE); 1134126274Sdes } 1135126274Sdes 1136204917Sdes /* Ignore blank lines and lines which begin with comment '#' char */ 1137204917Sdes if (*cp == '\0' || *cp == '#') 1138204917Sdes return (0); 1139204917Sdes 1140204917Sdes if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL) 1141181111Sdes return -1; 1142181111Sdes 1143126274Sdes /* Figure out which command we have */ 1144181111Sdes for (i = 0; cmds[i].c != NULL; i++) { 1145181111Sdes if (strcasecmp(cmds[i].c, argv[0]) == 0) 1146126274Sdes break; 1147126274Sdes } 1148126274Sdes cmdnum = cmds[i].n; 1149126274Sdes cmd = cmds[i].c; 1150126274Sdes 1151126274Sdes /* Special case */ 1152126274Sdes if (*cp == '!') { 1153126274Sdes cp++; 1154126274Sdes cmdnum = I_SHELL; 1155126274Sdes } else if (cmdnum == -1) { 1156126274Sdes error("Invalid command."); 1157181111Sdes return -1; 1158126274Sdes } 1159126274Sdes 1160126274Sdes /* Get arguments and parse flags */ 1161204917Sdes *lflag = *pflag = *rflag = *hflag = *n_arg = 0; 1162126274Sdes *path1 = *path2 = NULL; 1163181111Sdes optidx = 1; 1164126274Sdes switch (cmdnum) { 1165126274Sdes case I_GET: 1166126274Sdes case I_PUT: 1167221420Sdes if ((optidx = parse_getput_flags(cmd, argv, argc, 1168221420Sdes pflag, rflag)) == -1) 1169181111Sdes return -1; 1170126274Sdes /* Get first pathname (mandatory) */ 1171181111Sdes if (argc - optidx < 1) { 1172126274Sdes error("You must specify at least one path after a " 1173126274Sdes "%s command.", cmd); 1174181111Sdes return -1; 1175126274Sdes } 1176181111Sdes *path1 = xstrdup(argv[optidx]); 1177181111Sdes /* Get second pathname (optional) */ 1178181111Sdes if (argc - optidx > 1) { 1179181111Sdes *path2 = xstrdup(argv[optidx + 1]); 1180181111Sdes /* Destination is not globbed */ 1181181111Sdes undo_glob_escape(*path2); 1182181111Sdes } 1183126274Sdes break; 1184221420Sdes case I_LINK: 1185221420Sdes if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1) 1186221420Sdes return -1; 1187221420Sdes case I_SYMLINK: 1188126274Sdes case I_RENAME: 1189181111Sdes if (argc - optidx < 2) { 1190126274Sdes error("You must specify two paths after a %s " 1191126274Sdes "command.", cmd); 1192181111Sdes return -1; 1193126274Sdes } 1194181111Sdes *path1 = xstrdup(argv[optidx]); 1195181111Sdes *path2 = xstrdup(argv[optidx + 1]); 1196181111Sdes /* Paths are not globbed */ 1197181111Sdes undo_glob_escape(*path1); 1198181111Sdes undo_glob_escape(*path2); 1199126274Sdes break; 1200126274Sdes case I_RM: 1201126274Sdes case I_MKDIR: 1202126274Sdes case I_RMDIR: 1203126274Sdes case I_CHDIR: 1204126274Sdes case I_LCHDIR: 1205126274Sdes case I_LMKDIR: 1206126274Sdes /* Get pathname (mandatory) */ 1207181111Sdes if (argc - optidx < 1) { 1208126274Sdes error("You must specify a path after a %s command.", 1209126274Sdes cmd); 1210181111Sdes return -1; 1211126274Sdes } 1212181111Sdes *path1 = xstrdup(argv[optidx]); 1213181111Sdes /* Only "rm" globs */ 1214181111Sdes if (cmdnum != I_RM) 1215181111Sdes undo_glob_escape(*path1); 1216126274Sdes break; 1217181111Sdes case I_DF: 1218181111Sdes if ((optidx = parse_df_flags(cmd, argv, argc, hflag, 1219181111Sdes iflag)) == -1) 1220181111Sdes return -1; 1221181111Sdes /* Default to current directory if no path specified */ 1222181111Sdes if (argc - optidx < 1) 1223181111Sdes *path1 = NULL; 1224181111Sdes else { 1225181111Sdes *path1 = xstrdup(argv[optidx]); 1226181111Sdes undo_glob_escape(*path1); 1227181111Sdes } 1228181111Sdes break; 1229126274Sdes case I_LS: 1230181111Sdes if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1) 1231126274Sdes return(-1); 1232126274Sdes /* Path is optional */ 1233181111Sdes if (argc - optidx > 0) 1234181111Sdes *path1 = xstrdup(argv[optidx]); 1235126274Sdes break; 1236126274Sdes case I_LLS: 1237181111Sdes /* Skip ls command and following whitespace */ 1238181111Sdes cp = cp + strlen(cmd) + strspn(cp, WHITESPACE); 1239126274Sdes case I_SHELL: 1240126274Sdes /* Uses the rest of the line */ 1241126274Sdes break; 1242126274Sdes case I_LUMASK: 1243126274Sdes case I_CHMOD: 1244126274Sdes base = 8; 1245126274Sdes case I_CHOWN: 1246126274Sdes case I_CHGRP: 1247126274Sdes /* Get numeric arg (mandatory) */ 1248181111Sdes if (argc - optidx < 1) 1249181111Sdes goto need_num_arg; 1250164146Sdes errno = 0; 1251181111Sdes l = strtol(argv[optidx], &cp2, base); 1252181111Sdes if (cp2 == argv[optidx] || *cp2 != '\0' || 1253181111Sdes ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) || 1254181111Sdes l < 0) { 1255181111Sdes need_num_arg: 1256126274Sdes error("You must supply a numeric argument " 1257126274Sdes "to the %s command.", cmd); 1258181111Sdes return -1; 1259126274Sdes } 1260126274Sdes *n_arg = l; 1261181111Sdes if (cmdnum == I_LUMASK) 1262126274Sdes break; 1263126274Sdes /* Get pathname (mandatory) */ 1264181111Sdes if (argc - optidx < 2) { 1265126274Sdes error("You must specify a path after a %s command.", 1266126274Sdes cmd); 1267181111Sdes return -1; 1268126274Sdes } 1269181111Sdes *path1 = xstrdup(argv[optidx + 1]); 1270126274Sdes break; 1271126274Sdes case I_QUIT: 1272126274Sdes case I_PWD: 1273126274Sdes case I_LPWD: 1274126274Sdes case I_HELP: 1275126274Sdes case I_VERSION: 1276126274Sdes case I_PROGRESS: 1277126274Sdes break; 1278126274Sdes default: 1279126274Sdes fatal("Command not implemented"); 1280126274Sdes } 1281126274Sdes 1282126274Sdes *cpp = cp; 1283126274Sdes return(cmdnum); 1284126274Sdes} 1285126274Sdes 1286126274Sdesstatic int 1287126274Sdesparse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, 1288126274Sdes int err_abort) 1289126274Sdes{ 1290126274Sdes char *path1, *path2, *tmp; 1291221420Sdes int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, sflag = 0; 1292221420Sdes int cmdnum, i; 1293192595Sdes unsigned long n_arg = 0; 1294126274Sdes Attrib a, *aa; 1295126274Sdes char path_buf[MAXPATHLEN]; 1296126274Sdes int err = 0; 1297126274Sdes glob_t g; 1298126274Sdes 1299126274Sdes path1 = path2 = NULL; 1300221420Sdes cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag, 1301221420Sdes &sflag, &n_arg, &path1, &path2); 1302126274Sdes 1303126274Sdes if (iflag != 0) 1304126274Sdes err_abort = 0; 1305126274Sdes 1306126274Sdes memset(&g, 0, sizeof(g)); 1307126274Sdes 1308126274Sdes /* Perform command */ 1309126274Sdes switch (cmdnum) { 1310126274Sdes case 0: 1311126274Sdes /* Blank line */ 1312126274Sdes break; 1313126274Sdes case -1: 1314126274Sdes /* Unrecognized command */ 1315126274Sdes err = -1; 1316126274Sdes break; 1317126274Sdes case I_GET: 1318204917Sdes err = process_get(conn, path1, path2, *pwd, pflag, rflag); 1319126274Sdes break; 1320126274Sdes case I_PUT: 1321204917Sdes err = process_put(conn, path1, path2, *pwd, pflag, rflag); 1322126274Sdes break; 1323126274Sdes case I_RENAME: 1324126274Sdes path1 = make_absolute(path1, *pwd); 1325126274Sdes path2 = make_absolute(path2, *pwd); 1326126274Sdes err = do_rename(conn, path1, path2); 1327126274Sdes break; 1328126274Sdes case I_SYMLINK: 1329221420Sdes sflag = 1; 1330221420Sdes case I_LINK: 1331221420Sdes path1 = make_absolute(path1, *pwd); 1332126274Sdes path2 = make_absolute(path2, *pwd); 1333221420Sdes err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2); 1334126274Sdes break; 1335126274Sdes case I_RM: 1336126274Sdes path1 = make_absolute(path1, *pwd); 1337126274Sdes remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); 1338137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 1339126274Sdes printf("Removing %s\n", g.gl_pathv[i]); 1340126274Sdes err = do_rm(conn, g.gl_pathv[i]); 1341126274Sdes if (err != 0 && err_abort) 1342126274Sdes break; 1343126274Sdes } 1344126274Sdes break; 1345126274Sdes case I_MKDIR: 1346126274Sdes path1 = make_absolute(path1, *pwd); 1347126274Sdes attrib_clear(&a); 1348126274Sdes a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; 1349126274Sdes a.perm = 0777; 1350204917Sdes err = do_mkdir(conn, path1, &a, 1); 1351126274Sdes break; 1352126274Sdes case I_RMDIR: 1353126274Sdes path1 = make_absolute(path1, *pwd); 1354126274Sdes err = do_rmdir(conn, path1); 1355126274Sdes break; 1356126274Sdes case I_CHDIR: 1357126274Sdes path1 = make_absolute(path1, *pwd); 1358126274Sdes if ((tmp = do_realpath(conn, path1)) == NULL) { 1359126274Sdes err = 1; 1360126274Sdes break; 1361126274Sdes } 1362126274Sdes if ((aa = do_stat(conn, tmp, 0)) == NULL) { 1363126274Sdes xfree(tmp); 1364126274Sdes err = 1; 1365126274Sdes break; 1366126274Sdes } 1367126274Sdes if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) { 1368126274Sdes error("Can't change directory: Can't check target"); 1369126274Sdes xfree(tmp); 1370126274Sdes err = 1; 1371126274Sdes break; 1372126274Sdes } 1373126274Sdes if (!S_ISDIR(aa->perm)) { 1374126274Sdes error("Can't change directory: \"%s\" is not " 1375126274Sdes "a directory", tmp); 1376126274Sdes xfree(tmp); 1377126274Sdes err = 1; 1378126274Sdes break; 1379126274Sdes } 1380126274Sdes xfree(*pwd); 1381126274Sdes *pwd = tmp; 1382126274Sdes break; 1383126274Sdes case I_LS: 1384126274Sdes if (!path1) { 1385215116Sdes do_ls_dir(conn, *pwd, *pwd, lflag); 1386126274Sdes break; 1387126274Sdes } 1388126274Sdes 1389126274Sdes /* Strip pwd off beginning of non-absolute paths */ 1390126274Sdes tmp = NULL; 1391126274Sdes if (*path1 != '/') 1392126274Sdes tmp = *pwd; 1393126274Sdes 1394126274Sdes path1 = make_absolute(path1, *pwd); 1395126274Sdes err = do_globbed_ls(conn, path1, tmp, lflag); 1396126274Sdes break; 1397181111Sdes case I_DF: 1398181111Sdes /* Default to current directory if no path specified */ 1399181111Sdes if (path1 == NULL) 1400181111Sdes path1 = xstrdup(*pwd); 1401181111Sdes path1 = make_absolute(path1, *pwd); 1402181111Sdes err = do_df(conn, path1, hflag, iflag); 1403181111Sdes break; 1404126274Sdes case I_LCHDIR: 1405126274Sdes if (chdir(path1) == -1) { 1406126274Sdes error("Couldn't change local directory to " 1407126274Sdes "\"%s\": %s", path1, strerror(errno)); 1408126274Sdes err = 1; 1409126274Sdes } 1410126274Sdes break; 1411126274Sdes case I_LMKDIR: 1412126274Sdes if (mkdir(path1, 0777) == -1) { 1413126274Sdes error("Couldn't create local directory " 1414126274Sdes "\"%s\": %s", path1, strerror(errno)); 1415126274Sdes err = 1; 1416126274Sdes } 1417126274Sdes break; 1418126274Sdes case I_LLS: 1419126274Sdes local_do_ls(cmd); 1420126274Sdes break; 1421126274Sdes case I_SHELL: 1422126274Sdes local_do_shell(cmd); 1423126274Sdes break; 1424126274Sdes case I_LUMASK: 1425126274Sdes umask(n_arg); 1426126274Sdes printf("Local umask: %03lo\n", n_arg); 1427126274Sdes break; 1428126274Sdes case I_CHMOD: 1429126274Sdes path1 = make_absolute(path1, *pwd); 1430126274Sdes attrib_clear(&a); 1431126274Sdes a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; 1432126274Sdes a.perm = n_arg; 1433126274Sdes remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); 1434137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 1435126274Sdes printf("Changing mode on %s\n", g.gl_pathv[i]); 1436126274Sdes err = do_setstat(conn, g.gl_pathv[i], &a); 1437126274Sdes if (err != 0 && err_abort) 1438126274Sdes break; 1439126274Sdes } 1440126274Sdes break; 1441126274Sdes case I_CHOWN: 1442126274Sdes case I_CHGRP: 1443126274Sdes path1 = make_absolute(path1, *pwd); 1444126274Sdes remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); 1445137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 1446126274Sdes if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) { 1447192595Sdes if (err_abort) { 1448192595Sdes err = -1; 1449126274Sdes break; 1450192595Sdes } else 1451126274Sdes continue; 1452126274Sdes } 1453126274Sdes if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) { 1454126274Sdes error("Can't get current ownership of " 1455126274Sdes "remote file \"%s\"", g.gl_pathv[i]); 1456192595Sdes if (err_abort) { 1457192595Sdes err = -1; 1458126274Sdes break; 1459192595Sdes } else 1460126274Sdes continue; 1461126274Sdes } 1462126274Sdes aa->flags &= SSH2_FILEXFER_ATTR_UIDGID; 1463126274Sdes if (cmdnum == I_CHOWN) { 1464126274Sdes printf("Changing owner on %s\n", g.gl_pathv[i]); 1465126274Sdes aa->uid = n_arg; 1466126274Sdes } else { 1467126274Sdes printf("Changing group on %s\n", g.gl_pathv[i]); 1468126274Sdes aa->gid = n_arg; 1469126274Sdes } 1470126274Sdes err = do_setstat(conn, g.gl_pathv[i], aa); 1471126274Sdes if (err != 0 && err_abort) 1472126274Sdes break; 1473126274Sdes } 1474126274Sdes break; 1475126274Sdes case I_PWD: 1476126274Sdes printf("Remote working directory: %s\n", *pwd); 1477126274Sdes break; 1478126274Sdes case I_LPWD: 1479126274Sdes if (!getcwd(path_buf, sizeof(path_buf))) { 1480126274Sdes error("Couldn't get local cwd: %s", strerror(errno)); 1481126274Sdes err = -1; 1482126274Sdes break; 1483126274Sdes } 1484126274Sdes printf("Local working directory: %s\n", path_buf); 1485126274Sdes break; 1486126274Sdes case I_QUIT: 1487126274Sdes /* Processed below */ 1488126274Sdes break; 1489126274Sdes case I_HELP: 1490126274Sdes help(); 1491126274Sdes break; 1492126274Sdes case I_VERSION: 1493126274Sdes printf("SFTP protocol version %u\n", sftp_proto_version(conn)); 1494126274Sdes break; 1495126274Sdes case I_PROGRESS: 1496126274Sdes showprogress = !showprogress; 1497126274Sdes if (showprogress) 1498126274Sdes printf("Progress meter enabled\n"); 1499126274Sdes else 1500126274Sdes printf("Progress meter disabled\n"); 1501126274Sdes break; 1502126274Sdes default: 1503126274Sdes fatal("%d is not implemented", cmdnum); 1504126274Sdes } 1505126274Sdes 1506126274Sdes if (g.gl_pathc) 1507126274Sdes globfree(&g); 1508126274Sdes if (path1) 1509126274Sdes xfree(path1); 1510126274Sdes if (path2) 1511126274Sdes xfree(path2); 1512126274Sdes 1513126274Sdes /* If an unignored error occurs in batch mode we should abort. */ 1514126274Sdes if (err_abort && err != 0) 1515126274Sdes return (-1); 1516126274Sdes else if (cmdnum == I_QUIT) 1517126274Sdes return (1); 1518126274Sdes 1519126274Sdes return (0); 1520126274Sdes} 1521126274Sdes 1522146998Sdes#ifdef USE_LIBEDIT 1523146998Sdesstatic char * 1524146998Sdesprompt(EditLine *el) 1525146998Sdes{ 1526146998Sdes return ("sftp> "); 1527146998Sdes} 1528146998Sdes 1529204917Sdes/* Display entries in 'list' after skipping the first 'len' chars */ 1530204917Sdesstatic void 1531204917Sdescomplete_display(char **list, u_int len) 1532204917Sdes{ 1533204917Sdes u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen; 1534204917Sdes struct winsize ws; 1535204917Sdes char *tmp; 1536204917Sdes 1537204917Sdes /* Count entries for sort and find longest */ 1538204917Sdes for (y = 0; list[y]; y++) 1539204917Sdes m = MAX(m, strlen(list[y])); 1540204917Sdes 1541204917Sdes if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1) 1542204917Sdes width = ws.ws_col; 1543204917Sdes 1544204917Sdes m = m > len ? m - len : 0; 1545204917Sdes columns = width / (m + 2); 1546204917Sdes columns = MAX(columns, 1); 1547204917Sdes colspace = width / columns; 1548204917Sdes colspace = MIN(colspace, width); 1549204917Sdes 1550204917Sdes printf("\n"); 1551204917Sdes m = 1; 1552204917Sdes for (y = 0; list[y]; y++) { 1553204917Sdes llen = strlen(list[y]); 1554204917Sdes tmp = llen > len ? list[y] + len : ""; 1555204917Sdes printf("%-*s", colspace, tmp); 1556204917Sdes if (m >= columns) { 1557204917Sdes printf("\n"); 1558204917Sdes m = 1; 1559204917Sdes } else 1560204917Sdes m++; 1561204917Sdes } 1562204917Sdes printf("\n"); 1563204917Sdes} 1564204917Sdes 1565204917Sdes/* 1566204917Sdes * Given a "list" of words that begin with a common prefix of "word", 1567204917Sdes * attempt to find an autocompletion to extends "word" by the next 1568204917Sdes * characters common to all entries in "list". 1569204917Sdes */ 1570204917Sdesstatic char * 1571204917Sdescomplete_ambiguous(const char *word, char **list, size_t count) 1572204917Sdes{ 1573204917Sdes if (word == NULL) 1574204917Sdes return NULL; 1575204917Sdes 1576204917Sdes if (count > 0) { 1577204917Sdes u_int y, matchlen = strlen(list[0]); 1578204917Sdes 1579204917Sdes /* Find length of common stem */ 1580204917Sdes for (y = 1; list[y]; y++) { 1581204917Sdes u_int x; 1582204917Sdes 1583204917Sdes for (x = 0; x < matchlen; x++) 1584204917Sdes if (list[0][x] != list[y][x]) 1585204917Sdes break; 1586204917Sdes 1587204917Sdes matchlen = x; 1588204917Sdes } 1589204917Sdes 1590204917Sdes if (matchlen > strlen(word)) { 1591204917Sdes char *tmp = xstrdup(list[0]); 1592204917Sdes 1593204917Sdes tmp[matchlen] = '\0'; 1594204917Sdes return tmp; 1595204917Sdes } 1596204917Sdes } 1597204917Sdes 1598204917Sdes return xstrdup(word); 1599204917Sdes} 1600204917Sdes 1601204917Sdes/* Autocomplete a sftp command */ 1602204917Sdesstatic int 1603204917Sdescomplete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote, 1604204917Sdes int terminated) 1605204917Sdes{ 1606204917Sdes u_int y, count = 0, cmdlen, tmplen; 1607204917Sdes char *tmp, **list, argterm[3]; 1608204917Sdes const LineInfo *lf; 1609204917Sdes 1610204917Sdes list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *)); 1611204917Sdes 1612204917Sdes /* No command specified: display all available commands */ 1613204917Sdes if (cmd == NULL) { 1614204917Sdes for (y = 0; cmds[y].c; y++) 1615204917Sdes list[count++] = xstrdup(cmds[y].c); 1616204917Sdes 1617204917Sdes list[count] = NULL; 1618204917Sdes complete_display(list, 0); 1619204917Sdes 1620204917Sdes for (y = 0; list[y] != NULL; y++) 1621204917Sdes xfree(list[y]); 1622204917Sdes xfree(list); 1623204917Sdes return count; 1624204917Sdes } 1625204917Sdes 1626204917Sdes /* Prepare subset of commands that start with "cmd" */ 1627204917Sdes cmdlen = strlen(cmd); 1628204917Sdes for (y = 0; cmds[y].c; y++) { 1629204917Sdes if (!strncasecmp(cmd, cmds[y].c, cmdlen)) 1630204917Sdes list[count++] = xstrdup(cmds[y].c); 1631204917Sdes } 1632204917Sdes list[count] = NULL; 1633204917Sdes 1634240075Sdes if (count == 0) { 1635240075Sdes xfree(list); 1636204917Sdes return 0; 1637240075Sdes } 1638204917Sdes 1639204917Sdes /* Complete ambigious command */ 1640204917Sdes tmp = complete_ambiguous(cmd, list, count); 1641204917Sdes if (count > 1) 1642204917Sdes complete_display(list, 0); 1643204917Sdes 1644204917Sdes for (y = 0; list[y]; y++) 1645204917Sdes xfree(list[y]); 1646204917Sdes xfree(list); 1647204917Sdes 1648204917Sdes if (tmp != NULL) { 1649204917Sdes tmplen = strlen(tmp); 1650204917Sdes cmdlen = strlen(cmd); 1651204917Sdes /* If cmd may be extended then do so */ 1652204917Sdes if (tmplen > cmdlen) 1653204917Sdes if (el_insertstr(el, tmp + cmdlen) == -1) 1654204917Sdes fatal("el_insertstr failed."); 1655204917Sdes lf = el_line(el); 1656204917Sdes /* Terminate argument cleanly */ 1657204917Sdes if (count == 1) { 1658204917Sdes y = 0; 1659204917Sdes if (!terminated) 1660204917Sdes argterm[y++] = quote; 1661204917Sdes if (lastarg || *(lf->cursor) != ' ') 1662204917Sdes argterm[y++] = ' '; 1663204917Sdes argterm[y] = '\0'; 1664204917Sdes if (y > 0 && el_insertstr(el, argterm) == -1) 1665204917Sdes fatal("el_insertstr failed."); 1666204917Sdes } 1667204917Sdes xfree(tmp); 1668204917Sdes } 1669204917Sdes 1670204917Sdes return count; 1671204917Sdes} 1672204917Sdes 1673204917Sdes/* 1674204917Sdes * Determine whether a particular sftp command's arguments (if any) 1675204917Sdes * represent local or remote files. 1676204917Sdes */ 1677204917Sdesstatic int 1678204917Sdescomplete_is_remote(char *cmd) { 1679204917Sdes int i; 1680204917Sdes 1681204917Sdes if (cmd == NULL) 1682204917Sdes return -1; 1683204917Sdes 1684204917Sdes for (i = 0; cmds[i].c; i++) { 1685204917Sdes if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c))) 1686204917Sdes return cmds[i].t; 1687204917Sdes } 1688204917Sdes 1689204917Sdes return -1; 1690204917Sdes} 1691204917Sdes 1692204917Sdes/* Autocomplete a filename "file" */ 1693204917Sdesstatic int 1694204917Sdescomplete_match(EditLine *el, struct sftp_conn *conn, char *remote_path, 1695204917Sdes char *file, int remote, int lastarg, char quote, int terminated) 1696204917Sdes{ 1697204917Sdes glob_t g; 1698204917Sdes char *tmp, *tmp2, ins[3]; 1699204917Sdes u_int i, hadglob, pwdlen, len, tmplen, filelen; 1700204917Sdes const LineInfo *lf; 1701204917Sdes 1702204917Sdes /* Glob from "file" location */ 1703204917Sdes if (file == NULL) 1704204917Sdes tmp = xstrdup("*"); 1705204917Sdes else 1706204917Sdes xasprintf(&tmp, "%s*", file); 1707204917Sdes 1708204917Sdes memset(&g, 0, sizeof(g)); 1709204917Sdes if (remote != LOCAL) { 1710204917Sdes tmp = make_absolute(tmp, remote_path); 1711204917Sdes remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g); 1712204917Sdes } else 1713204917Sdes glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g); 1714204917Sdes 1715204917Sdes /* Determine length of pwd so we can trim completion display */ 1716204917Sdes for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) { 1717204917Sdes /* Terminate counting on first unescaped glob metacharacter */ 1718204917Sdes if (tmp[tmplen] == '*' || tmp[tmplen] == '?') { 1719204917Sdes if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0') 1720204917Sdes hadglob = 1; 1721204917Sdes break; 1722204917Sdes } 1723204917Sdes if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0') 1724204917Sdes tmplen++; 1725204917Sdes if (tmp[tmplen] == '/') 1726204917Sdes pwdlen = tmplen + 1; /* track last seen '/' */ 1727204917Sdes } 1728204917Sdes xfree(tmp); 1729204917Sdes 1730204917Sdes if (g.gl_matchc == 0) 1731204917Sdes goto out; 1732204917Sdes 1733204917Sdes if (g.gl_matchc > 1) 1734204917Sdes complete_display(g.gl_pathv, pwdlen); 1735204917Sdes 1736204917Sdes tmp = NULL; 1737204917Sdes /* Don't try to extend globs */ 1738204917Sdes if (file == NULL || hadglob) 1739204917Sdes goto out; 1740204917Sdes 1741204917Sdes tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc); 1742204917Sdes tmp = path_strip(tmp2, remote_path); 1743204917Sdes xfree(tmp2); 1744204917Sdes 1745204917Sdes if (tmp == NULL) 1746204917Sdes goto out; 1747204917Sdes 1748204917Sdes tmplen = strlen(tmp); 1749204917Sdes filelen = strlen(file); 1750204917Sdes 1751204917Sdes if (tmplen > filelen) { 1752204917Sdes tmp2 = tmp + filelen; 1753204917Sdes len = strlen(tmp2); 1754204917Sdes /* quote argument on way out */ 1755204917Sdes for (i = 0; i < len; i++) { 1756204917Sdes ins[0] = '\\'; 1757204917Sdes ins[1] = tmp2[i]; 1758204917Sdes ins[2] = '\0'; 1759204917Sdes switch (tmp2[i]) { 1760204917Sdes case '\'': 1761204917Sdes case '"': 1762204917Sdes case '\\': 1763204917Sdes case '\t': 1764221420Sdes case '[': 1765204917Sdes case ' ': 1766204917Sdes if (quote == '\0' || tmp2[i] == quote) { 1767204917Sdes if (el_insertstr(el, ins) == -1) 1768204917Sdes fatal("el_insertstr " 1769204917Sdes "failed."); 1770204917Sdes break; 1771204917Sdes } 1772204917Sdes /* FALLTHROUGH */ 1773204917Sdes default: 1774204917Sdes if (el_insertstr(el, ins + 1) == -1) 1775204917Sdes fatal("el_insertstr failed."); 1776204917Sdes break; 1777204917Sdes } 1778204917Sdes } 1779204917Sdes } 1780204917Sdes 1781204917Sdes lf = el_line(el); 1782204917Sdes if (g.gl_matchc == 1) { 1783204917Sdes i = 0; 1784204917Sdes if (!terminated) 1785204917Sdes ins[i++] = quote; 1786204917Sdes if (*(lf->cursor - 1) != '/' && 1787204917Sdes (lastarg || *(lf->cursor) != ' ')) 1788204917Sdes ins[i++] = ' '; 1789204917Sdes ins[i] = '\0'; 1790204917Sdes if (i > 0 && el_insertstr(el, ins) == -1) 1791204917Sdes fatal("el_insertstr failed."); 1792204917Sdes } 1793204917Sdes xfree(tmp); 1794204917Sdes 1795204917Sdes out: 1796204917Sdes globfree(&g); 1797204917Sdes return g.gl_matchc; 1798204917Sdes} 1799204917Sdes 1800204917Sdes/* tab-completion hook function, called via libedit */ 1801204917Sdesstatic unsigned char 1802204917Sdescomplete(EditLine *el, int ch) 1803204917Sdes{ 1804204917Sdes char **argv, *line, quote; 1805204917Sdes u_int argc, carg, cursor, len, terminated, ret = CC_ERROR; 1806204917Sdes const LineInfo *lf; 1807204917Sdes struct complete_ctx *complete_ctx; 1808204917Sdes 1809204917Sdes lf = el_line(el); 1810204917Sdes if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0) 1811204917Sdes fatal("%s: el_get failed", __func__); 1812204917Sdes 1813204917Sdes /* Figure out which argument the cursor points to */ 1814204917Sdes cursor = lf->cursor - lf->buffer; 1815204917Sdes line = (char *)xmalloc(cursor + 1); 1816204917Sdes memcpy(line, lf->buffer, cursor); 1817204917Sdes line[cursor] = '\0'; 1818204917Sdes argv = makeargv(line, &carg, 1, "e, &terminated); 1819204917Sdes xfree(line); 1820204917Sdes 1821204917Sdes /* Get all the arguments on the line */ 1822204917Sdes len = lf->lastchar - lf->buffer; 1823204917Sdes line = (char *)xmalloc(len + 1); 1824204917Sdes memcpy(line, lf->buffer, len); 1825204917Sdes line[len] = '\0'; 1826204917Sdes argv = makeargv(line, &argc, 1, NULL, NULL); 1827204917Sdes 1828204917Sdes /* Ensure cursor is at EOL or a argument boundary */ 1829204917Sdes if (line[cursor] != ' ' && line[cursor] != '\0' && 1830204917Sdes line[cursor] != '\n') { 1831204917Sdes xfree(line); 1832204917Sdes return ret; 1833204917Sdes } 1834204917Sdes 1835204917Sdes if (carg == 0) { 1836204917Sdes /* Show all available commands */ 1837204917Sdes complete_cmd_parse(el, NULL, argc == carg, '\0', 1); 1838204917Sdes ret = CC_REDISPLAY; 1839204917Sdes } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') { 1840204917Sdes /* Handle the command parsing */ 1841204917Sdes if (complete_cmd_parse(el, argv[0], argc == carg, 1842204917Sdes quote, terminated) != 0) 1843204917Sdes ret = CC_REDISPLAY; 1844204917Sdes } else if (carg >= 1) { 1845204917Sdes /* Handle file parsing */ 1846204917Sdes int remote = complete_is_remote(argv[0]); 1847204917Sdes char *filematch = NULL; 1848204917Sdes 1849204917Sdes if (carg > 1 && line[cursor-1] != ' ') 1850204917Sdes filematch = argv[carg - 1]; 1851204917Sdes 1852204917Sdes if (remote != 0 && 1853204917Sdes complete_match(el, complete_ctx->conn, 1854204917Sdes *complete_ctx->remote_pathp, filematch, 1855204917Sdes remote, carg == argc, quote, terminated) != 0) 1856204917Sdes ret = CC_REDISPLAY; 1857204917Sdes } 1858204917Sdes 1859204917Sdes xfree(line); 1860204917Sdes return ret; 1861204917Sdes} 1862204917Sdes#endif /* USE_LIBEDIT */ 1863204917Sdes 1864126274Sdesint 1865204917Sdesinteractive_loop(struct sftp_conn *conn, char *file1, char *file2) 1866126274Sdes{ 1867204917Sdes char *remote_path; 1868126274Sdes char *dir = NULL; 1869126274Sdes char cmd[2048]; 1870149749Sdes int err, interactive; 1871146998Sdes EditLine *el = NULL; 1872146998Sdes#ifdef USE_LIBEDIT 1873146998Sdes History *hl = NULL; 1874146998Sdes HistEvent hev; 1875146998Sdes extern char *__progname; 1876204917Sdes struct complete_ctx complete_ctx; 1877126274Sdes 1878146998Sdes if (!batchmode && isatty(STDIN_FILENO)) { 1879146998Sdes if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL) 1880146998Sdes fatal("Couldn't initialise editline"); 1881146998Sdes if ((hl = history_init()) == NULL) 1882146998Sdes fatal("Couldn't initialise editline history"); 1883146998Sdes history(hl, &hev, H_SETSIZE, 100); 1884146998Sdes el_set(el, EL_HIST, history, hl); 1885146998Sdes 1886146998Sdes el_set(el, EL_PROMPT, prompt); 1887146998Sdes el_set(el, EL_EDITOR, "emacs"); 1888146998Sdes el_set(el, EL_TERMINAL, NULL); 1889146998Sdes el_set(el, EL_SIGNAL, 1); 1890146998Sdes el_source(el, NULL); 1891204917Sdes 1892204917Sdes /* Tab Completion */ 1893204917Sdes el_set(el, EL_ADDFN, "ftp-complete", 1894221420Sdes "Context sensitive argument completion", complete); 1895204917Sdes complete_ctx.conn = conn; 1896204917Sdes complete_ctx.remote_pathp = &remote_path; 1897204917Sdes el_set(el, EL_CLIENTDATA, (void*)&complete_ctx); 1898204917Sdes el_set(el, EL_BIND, "^I", "ftp-complete", NULL); 1899146998Sdes } 1900146998Sdes#endif /* USE_LIBEDIT */ 1901146998Sdes 1902204917Sdes remote_path = do_realpath(conn, "."); 1903204917Sdes if (remote_path == NULL) 1904126274Sdes fatal("Need cwd"); 1905126274Sdes 1906126274Sdes if (file1 != NULL) { 1907126274Sdes dir = xstrdup(file1); 1908204917Sdes dir = make_absolute(dir, remote_path); 1909126274Sdes 1910126274Sdes if (remote_is_dir(conn, dir) && file2 == NULL) { 1911126274Sdes printf("Changing to: %s\n", dir); 1912126274Sdes snprintf(cmd, sizeof cmd, "cd \"%s\"", dir); 1913204917Sdes if (parse_dispatch_command(conn, cmd, 1914204917Sdes &remote_path, 1) != 0) { 1915146998Sdes xfree(dir); 1916204917Sdes xfree(remote_path); 1917162852Sdes xfree(conn); 1918126274Sdes return (-1); 1919146998Sdes } 1920126274Sdes } else { 1921126274Sdes if (file2 == NULL) 1922126274Sdes snprintf(cmd, sizeof cmd, "get %s", dir); 1923126274Sdes else 1924126274Sdes snprintf(cmd, sizeof cmd, "get %s %s", dir, 1925126274Sdes file2); 1926126274Sdes 1927204917Sdes err = parse_dispatch_command(conn, cmd, 1928204917Sdes &remote_path, 1); 1929126274Sdes xfree(dir); 1930204917Sdes xfree(remote_path); 1931162852Sdes xfree(conn); 1932126274Sdes return (err); 1933126274Sdes } 1934126274Sdes xfree(dir); 1935126274Sdes } 1936126274Sdes 1937149749Sdes setlinebuf(stdout); 1938149749Sdes setlinebuf(infile); 1939126274Sdes 1940149749Sdes interactive = !batchmode && isatty(STDIN_FILENO); 1941126274Sdes err = 0; 1942126274Sdes for (;;) { 1943126274Sdes char *cp; 1944126274Sdes 1945137015Sdes signal(SIGINT, SIG_IGN); 1946137015Sdes 1947146998Sdes if (el == NULL) { 1948149749Sdes if (interactive) 1949149749Sdes printf("sftp> "); 1950146998Sdes if (fgets(cmd, sizeof(cmd), infile) == NULL) { 1951149749Sdes if (interactive) 1952149749Sdes printf("\n"); 1953146998Sdes break; 1954146998Sdes } 1955149749Sdes if (!interactive) { /* Echo command */ 1956149749Sdes printf("sftp> %s", cmd); 1957149749Sdes if (strlen(cmd) > 0 && 1958149749Sdes cmd[strlen(cmd) - 1] != '\n') 1959149749Sdes printf("\n"); 1960149749Sdes } 1961146998Sdes } else { 1962146998Sdes#ifdef USE_LIBEDIT 1963146998Sdes const char *line; 1964146998Sdes int count = 0; 1965126274Sdes 1966204917Sdes if ((line = el_gets(el, &count)) == NULL || 1967204917Sdes count <= 0) { 1968149749Sdes printf("\n"); 1969149749Sdes break; 1970149749Sdes } 1971146998Sdes history(hl, &hev, H_ENTER, line); 1972146998Sdes if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) { 1973146998Sdes fprintf(stderr, "Error: input line too long\n"); 1974146998Sdes continue; 1975146998Sdes } 1976146998Sdes#endif /* USE_LIBEDIT */ 1977126274Sdes } 1978126274Sdes 1979126274Sdes cp = strrchr(cmd, '\n'); 1980126274Sdes if (cp) 1981126274Sdes *cp = '\0'; 1982126274Sdes 1983137015Sdes /* Handle user interrupts gracefully during commands */ 1984137015Sdes interrupted = 0; 1985137015Sdes signal(SIGINT, cmd_interrupt); 1986137015Sdes 1987204917Sdes err = parse_dispatch_command(conn, cmd, &remote_path, 1988204917Sdes batchmode); 1989126274Sdes if (err != 0) 1990126274Sdes break; 1991126274Sdes } 1992204917Sdes xfree(remote_path); 1993162852Sdes xfree(conn); 1994126274Sdes 1995149749Sdes#ifdef USE_LIBEDIT 1996149749Sdes if (el != NULL) 1997149749Sdes el_end(el); 1998149749Sdes#endif /* USE_LIBEDIT */ 1999149749Sdes 2000126274Sdes /* err == 1 signifies normal "quit" exit */ 2001126274Sdes return (err >= 0 ? 0 : -1); 2002126274Sdes} 2003126274Sdes 2004126274Sdesstatic void 2005124208Sdesconnect_to_server(char *path, char **args, int *in, int *out) 2006124208Sdes{ 200776259Sgreen int c_in, c_out; 200899060Sdes 200976259Sgreen#ifdef USE_PIPES 201076259Sgreen int pin[2], pout[2]; 201199060Sdes 201276259Sgreen if ((pipe(pin) == -1) || (pipe(pout) == -1)) 201376259Sgreen fatal("pipe: %s", strerror(errno)); 201476259Sgreen *in = pin[0]; 201576259Sgreen *out = pout[1]; 201676259Sgreen c_in = pout[0]; 201776259Sgreen c_out = pin[1]; 201876259Sgreen#else /* USE_PIPES */ 201976259Sgreen int inout[2]; 202099060Sdes 202176259Sgreen if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1) 202276259Sgreen fatal("socketpair: %s", strerror(errno)); 202376259Sgreen *in = *out = inout[0]; 202476259Sgreen c_in = c_out = inout[1]; 202576259Sgreen#endif /* USE_PIPES */ 202676259Sgreen 2027124208Sdes if ((sshpid = fork()) == -1) 202876259Sgreen fatal("fork: %s", strerror(errno)); 2029124208Sdes else if (sshpid == 0) { 203076259Sgreen if ((dup2(c_in, STDIN_FILENO) == -1) || 203176259Sgreen (dup2(c_out, STDOUT_FILENO) == -1)) { 203276259Sgreen fprintf(stderr, "dup2: %s\n", strerror(errno)); 2033137015Sdes _exit(1); 203476259Sgreen } 203576259Sgreen close(*in); 203676259Sgreen close(*out); 203776259Sgreen close(c_in); 203876259Sgreen close(c_out); 2039137015Sdes 2040137015Sdes /* 2041137015Sdes * The underlying ssh is in the same process group, so we must 2042137015Sdes * ignore SIGINT if we want to gracefully abort commands, 2043137015Sdes * otherwise the signal will make it to the ssh process and 2044204917Sdes * kill it too. Contrawise, since sftp sends SIGTERMs to the 2045204917Sdes * underlying ssh, it must *not* ignore that signal. 2046137015Sdes */ 2047137015Sdes signal(SIGINT, SIG_IGN); 2048204917Sdes signal(SIGTERM, SIG_DFL); 2049137015Sdes execvp(path, args); 205092555Sdes fprintf(stderr, "exec: %s: %s\n", path, strerror(errno)); 2051137015Sdes _exit(1); 205276259Sgreen } 205376259Sgreen 2054124208Sdes signal(SIGTERM, killchild); 2055124208Sdes signal(SIGINT, killchild); 2056124208Sdes signal(SIGHUP, killchild); 205776259Sgreen close(c_in); 205876259Sgreen close(c_out); 205976259Sgreen} 206076259Sgreen 206192555Sdesstatic void 206276259Sgreenusage(void) 206376259Sgreen{ 206492555Sdes extern char *__progname; 206598675Sdes 206692555Sdes fprintf(stderr, 2067204917Sdes "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n" 2068204917Sdes " [-D sftp_server_path] [-F ssh_config] " 2069221420Sdes "[-i identity_file] [-l limit]\n" 2070204917Sdes " [-o ssh_option] [-P port] [-R num_requests] " 2071204917Sdes "[-S program]\n" 2072204917Sdes " [-s subsystem | sftp_server] host\n" 2073192595Sdes " %s [user@]host[:file ...]\n" 2074192595Sdes " %s [user@]host[:dir[/]]\n" 2075204917Sdes " %s -b batchfile [user@]host\n", 2076204917Sdes __progname, __progname, __progname, __progname); 207776259Sgreen exit(1); 207876259Sgreen} 207976259Sgreen 208076259Sgreenint 208176259Sgreenmain(int argc, char **argv) 208276259Sgreen{ 2083113908Sdes int in, out, ch, err; 2084204917Sdes char *host = NULL, *userhost, *cp, *file2 = NULL; 208592555Sdes int debug_level = 0, sshver = 2; 208692555Sdes char *file1 = NULL, *sftp_server = NULL; 208792555Sdes char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL; 2088221420Sdes const char *errstr; 208992555Sdes LogLevel ll = SYSLOG_LEVEL_INFO; 209092555Sdes arglist args; 209176259Sgreen extern int optind; 209276259Sgreen extern char *optarg; 2093204917Sdes struct sftp_conn *conn; 2094204917Sdes size_t copy_buffer_len = DEFAULT_COPY_BUFLEN; 2095204917Sdes size_t num_requests = DEFAULT_NUM_REQUESTS; 2096221420Sdes long long limit_kbps = 0; 209776259Sgreen 2098157016Sdes /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ 2099157016Sdes sanitise_stdfd(); 2100157016Sdes 2101124208Sdes __progname = ssh_get_progname(argv[0]); 2102157016Sdes memset(&args, '\0', sizeof(args)); 210392555Sdes args.list = NULL; 2104162852Sdes addargs(&args, "%s", ssh_program); 210592555Sdes addargs(&args, "-oForwardX11 no"); 210692555Sdes addargs(&args, "-oForwardAgent no"); 2107157016Sdes addargs(&args, "-oPermitLocalCommand no"); 210892555Sdes addargs(&args, "-oClearAllForwardings yes"); 2109126274Sdes 211092555Sdes ll = SYSLOG_LEVEL_INFO; 2111126274Sdes infile = stdin; 211276259Sgreen 2113204917Sdes while ((ch = getopt(argc, argv, 2114221420Sdes "1246hpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) { 211576259Sgreen switch (ch) { 2116204917Sdes /* Passed through to ssh(1) */ 2117204917Sdes case '4': 2118204917Sdes case '6': 211976259Sgreen case 'C': 2120204917Sdes addargs(&args, "-%c", ch); 212176259Sgreen break; 2122204917Sdes /* Passed through to ssh(1) with argument */ 2123204917Sdes case 'F': 2124204917Sdes case 'c': 2125204917Sdes case 'i': 2126204917Sdes case 'o': 2127204917Sdes addargs(&args, "-%c", ch); 2128204917Sdes addargs(&args, "%s", optarg); 2129204917Sdes break; 2130204917Sdes case 'q': 2131204917Sdes showprogress = 0; 2132204917Sdes addargs(&args, "-%c", ch); 2133204917Sdes break; 2134204917Sdes case 'P': 2135204917Sdes addargs(&args, "-oPort %s", optarg); 2136204917Sdes break; 213776259Sgreen case 'v': 213892555Sdes if (debug_level < 3) { 213992555Sdes addargs(&args, "-v"); 214092555Sdes ll = SYSLOG_LEVEL_DEBUG1 + debug_level; 214192555Sdes } 214292555Sdes debug_level++; 214376259Sgreen break; 214476259Sgreen case '1': 214592555Sdes sshver = 1; 214676259Sgreen if (sftp_server == NULL) 214776259Sgreen sftp_server = _PATH_SFTP_SERVER; 214876259Sgreen break; 2149204917Sdes case '2': 2150204917Sdes sshver = 2; 215176259Sgreen break; 2152204917Sdes case 'B': 2153204917Sdes copy_buffer_len = strtol(optarg, &cp, 10); 2154204917Sdes if (copy_buffer_len == 0 || *cp != '\0') 2155204917Sdes fatal("Invalid buffer size \"%s\"", optarg); 215676259Sgreen break; 215776259Sgreen case 'b': 2158126274Sdes if (batchmode) 2159126274Sdes fatal("Batch file already specified."); 2160126274Sdes 2161126274Sdes /* Allow "-" as stdin */ 2162137015Sdes if (strcmp(optarg, "-") != 0 && 2163149749Sdes (infile = fopen(optarg, "r")) == NULL) 2164126274Sdes fatal("%s (%s).", strerror(errno), optarg); 2165113908Sdes showprogress = 0; 2166126274Sdes batchmode = 1; 2167146998Sdes addargs(&args, "-obatchmode yes"); 216876259Sgreen break; 2169204917Sdes case 'p': 2170204917Sdes global_pflag = 1; 2171204917Sdes break; 2172204917Sdes case 'D': 217392555Sdes sftp_direct = optarg; 217492555Sdes break; 2175221420Sdes case 'l': 2176221420Sdes limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024, 2177221420Sdes &errstr); 2178221420Sdes if (errstr != NULL) 2179221420Sdes usage(); 2180221420Sdes limit_kbps *= 1024; /* kbps */ 2181221420Sdes break; 2182204917Sdes case 'r': 2183204917Sdes global_rflag = 1; 218492555Sdes break; 218592555Sdes case 'R': 218692555Sdes num_requests = strtol(optarg, &cp, 10); 218792555Sdes if (num_requests == 0 || *cp != '\0') 218898675Sdes fatal("Invalid number of requests \"%s\"", 218992555Sdes optarg); 219092555Sdes break; 2191204917Sdes case 's': 2192204917Sdes sftp_server = optarg; 2193204917Sdes break; 2194204917Sdes case 'S': 2195204917Sdes ssh_program = optarg; 2196204917Sdes replacearg(&args, 0, "%s", ssh_program); 2197204917Sdes break; 219876259Sgreen case 'h': 219976259Sgreen default: 220076259Sgreen usage(); 220176259Sgreen } 220276259Sgreen } 220376259Sgreen 2204128456Sdes if (!isatty(STDERR_FILENO)) 2205128456Sdes showprogress = 0; 2206128456Sdes 220798675Sdes log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1); 220898675Sdes 220992555Sdes if (sftp_direct == NULL) { 221092555Sdes if (optind == argc || argc > (optind + 2)) 221192555Sdes usage(); 221276259Sgreen 221392555Sdes userhost = xstrdup(argv[optind]); 221492555Sdes file2 = argv[optind+1]; 221576259Sgreen 2216113908Sdes if ((host = strrchr(userhost, '@')) == NULL) 221792555Sdes host = userhost; 221892555Sdes else { 221992555Sdes *host++ = '\0'; 222092555Sdes if (!userhost[0]) { 222192555Sdes fprintf(stderr, "Missing username\n"); 222292555Sdes usage(); 222392555Sdes } 2224204917Sdes addargs(&args, "-l"); 2225204917Sdes addargs(&args, "%s", userhost); 222692555Sdes } 222792555Sdes 2228126274Sdes if ((cp = colon(host)) != NULL) { 2229126274Sdes *cp++ = '\0'; 2230126274Sdes file1 = cp; 2231126274Sdes } 2232126274Sdes 223392555Sdes host = cleanhostname(host); 223492555Sdes if (!*host) { 223592555Sdes fprintf(stderr, "Missing hostname\n"); 223676259Sgreen usage(); 223776259Sgreen } 223876259Sgreen 223992555Sdes addargs(&args, "-oProtocol %d", sshver); 224076259Sgreen 224192555Sdes /* no subsystem if the server-spec contains a '/' */ 224292555Sdes if (sftp_server == NULL || strchr(sftp_server, '/') == NULL) 224392555Sdes addargs(&args, "-s"); 224476259Sgreen 2245204917Sdes addargs(&args, "--"); 224692555Sdes addargs(&args, "%s", host); 224798675Sdes addargs(&args, "%s", (sftp_server != NULL ? 224892555Sdes sftp_server : "sftp")); 224976259Sgreen 2250124208Sdes connect_to_server(ssh_program, args.list, &in, &out); 225192555Sdes } else { 225292555Sdes args.list = NULL; 225392555Sdes addargs(&args, "sftp-server"); 225476259Sgreen 2255124208Sdes connect_to_server(sftp_direct, args.list, &in, &out); 225692555Sdes } 2257157016Sdes freeargs(&args); 225876259Sgreen 2259221420Sdes conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps); 2260204917Sdes if (conn == NULL) 2261204917Sdes fatal("Couldn't initialise connection to server"); 226276259Sgreen 2263204917Sdes if (!batchmode) { 2264204917Sdes if (sftp_direct == NULL) 2265204917Sdes fprintf(stderr, "Connected to %s.\n", host); 2266204917Sdes else 2267204917Sdes fprintf(stderr, "Attached to %s.\n", sftp_direct); 2268204917Sdes } 2269204917Sdes 2270204917Sdes err = interactive_loop(conn, file1, file2); 2271204917Sdes 227298937Sdes#if !defined(USE_PIPES) 2273149749Sdes shutdown(in, SHUT_RDWR); 2274149749Sdes shutdown(out, SHUT_RDWR); 227598937Sdes#endif 227698937Sdes 227776259Sgreen close(in); 227876259Sgreen close(out); 2279126274Sdes if (batchmode) 228076259Sgreen fclose(infile); 228176259Sgreen 228298675Sdes while (waitpid(sshpid, NULL, 0) == -1) 228398675Sdes if (errno != EINTR) 228498675Sdes fatal("Couldn't wait for ssh process: %s", 228598675Sdes strerror(errno)); 228676259Sgreen 2287113908Sdes exit(err == 0 ? 0 : 1); 228876259Sgreen} 2289