1296853Sdes/* $OpenBSD: sftp.c,v 1.172 2016/02/15 09:47:49 dtucker Exp $ */ 276259Sgreen/* 3126274Sdes * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> 476259Sgreen * 5126274Sdes * Permission to use, copy, modify, and distribute this software for any 6126274Sdes * purpose with or without fee is hereby granted, provided that the above 7126274Sdes * copyright notice and this permission notice appear in all copies. 876259Sgreen * 9126274Sdes * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10126274Sdes * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11126274Sdes * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12126274Sdes * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13126274Sdes * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14126274Sdes * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15126274Sdes * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 1676259Sgreen */ 1776259Sgreen 1876259Sgreen#include "includes.h" 1976259Sgreen 20295367Sdes#include <sys/param.h> /* MIN MAX */ 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 42255767Sdes#ifdef HAVE_LOCALE_H 43255767Sdes# include <locale.h> 44255767Sdes#endif 45146998Sdes#ifdef USE_LIBEDIT 46146998Sdes#include <histedit.h> 47146998Sdes#else 48146998Sdestypedef void EditLine; 49146998Sdes#endif 50295367Sdes#include <limits.h> 51162852Sdes#include <signal.h> 52162852Sdes#include <stdlib.h> 53162852Sdes#include <stdio.h> 54162852Sdes#include <string.h> 55162852Sdes#include <unistd.h> 56162852Sdes#include <stdarg.h> 57146998Sdes 58181111Sdes#ifdef HAVE_UTIL_H 59181111Sdes# include <util.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" 68295367Sdes#include "ssherr.h" 69295367Sdes#include "sshbuf.h" 7076259Sgreen#include "sftp-common.h" 7176259Sgreen#include "sftp-client.h" 7276259Sgreen 73204917Sdes#define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */ 74294693Sdes#define DEFAULT_NUM_REQUESTS 64 /* # concurrent outstanding requests */ 75204917Sdes 76126274Sdes/* File to read commands from */ 77126274SdesFILE* infile; 78126274Sdes 79126274Sdes/* Are we in batchfile mode? */ 80126274Sdesint batchmode = 0; 81126274Sdes 82126274Sdes/* PID of ssh transport process */ 83126274Sdesstatic pid_t sshpid = -1; 84126274Sdes 85255767Sdes/* Suppress diagnositic messages */ 86255767Sdesint quiet = 0; 87255767Sdes 88126274Sdes/* This is set to 0 if the progressmeter is not desired. */ 89128456Sdesint showprogress = 1; 90126274Sdes 91204917Sdes/* When this option is set, we always recursively download/upload directories */ 92204917Sdesint global_rflag = 0; 93204917Sdes 94295367Sdes/* When this option is set, we resume download or upload if possible */ 95255767Sdesint global_aflag = 0; 96255767Sdes 97204917Sdes/* When this option is set, the file transfers will always preserve times */ 98204917Sdesint global_pflag = 0; 99204917Sdes 100262566Sdes/* When this option is set, transfers will have fsync() called on each file */ 101262566Sdesint global_fflag = 0; 102262566Sdes 103137015Sdes/* SIGINT received during command processing */ 104137015Sdesvolatile sig_atomic_t interrupted = 0; 105137015Sdes 106137015Sdes/* I wish qsort() took a separate ctx for the comparison function...*/ 107137015Sdesint sort_flag; 108137015Sdes 109204917Sdes/* Context used for commandline completion */ 110204917Sdesstruct complete_ctx { 111204917Sdes struct sftp_conn *conn; 112204917Sdes char **remote_pathp; 113204917Sdes}; 114204917Sdes 115126274Sdesint remote_glob(struct sftp_conn *, const char *, int, 116126274Sdes int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */ 117126274Sdes 11898937Sdesextern char *__progname; 11998937Sdes 120126274Sdes/* Separators for interactive commands */ 121126274Sdes#define WHITESPACE " \t\r\n" 12276259Sgreen 123137015Sdes/* ls flags */ 124204917Sdes#define LS_LONG_VIEW 0x0001 /* Full view ala ls -l */ 125204917Sdes#define LS_SHORT_VIEW 0x0002 /* Single row view ala ls -1 */ 126204917Sdes#define LS_NUMERIC_VIEW 0x0004 /* Long view with numeric uid/gid */ 127204917Sdes#define LS_NAME_SORT 0x0008 /* Sort by name (default) */ 128204917Sdes#define LS_TIME_SORT 0x0010 /* Sort by mtime */ 129204917Sdes#define LS_SIZE_SORT 0x0020 /* Sort by file size */ 130204917Sdes#define LS_REVERSE_SORT 0x0040 /* Reverse sort order */ 131204917Sdes#define LS_SHOW_ALL 0x0080 /* Don't skip filenames starting with '.' */ 132204917Sdes#define LS_SI_UNITS 0x0100 /* Display sizes as K, M, G, etc. */ 133113908Sdes 134204917Sdes#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS) 135137015Sdes#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT) 136137015Sdes 137126274Sdes/* Commands for interactive mode */ 138262566Sdesenum sftp_command { 139262566Sdes I_CHDIR = 1, 140262566Sdes I_CHGRP, 141262566Sdes I_CHMOD, 142262566Sdes I_CHOWN, 143262566Sdes I_DF, 144262566Sdes I_GET, 145262566Sdes I_HELP, 146262566Sdes I_LCHDIR, 147262566Sdes I_LINK, 148262566Sdes I_LLS, 149262566Sdes I_LMKDIR, 150262566Sdes I_LPWD, 151262566Sdes I_LS, 152262566Sdes I_LUMASK, 153262566Sdes I_MKDIR, 154262566Sdes I_PUT, 155262566Sdes I_PWD, 156262566Sdes I_QUIT, 157295367Sdes I_REGET, 158262566Sdes I_RENAME, 159295367Sdes I_REPUT, 160262566Sdes I_RM, 161262566Sdes I_RMDIR, 162262566Sdes I_SHELL, 163262566Sdes I_SYMLINK, 164262566Sdes I_VERSION, 165262566Sdes I_PROGRESS, 166262566Sdes}; 167126274Sdes 168126274Sdesstruct CMD { 169126274Sdes const char *c; 170126274Sdes const int n; 171204917Sdes const int t; 172126274Sdes}; 173126274Sdes 174204917Sdes/* Type of completion */ 175204917Sdes#define NOARGS 0 176204917Sdes#define REMOTE 1 177204917Sdes#define LOCAL 2 178204917Sdes 179126274Sdesstatic const struct CMD cmds[] = { 180204917Sdes { "bye", I_QUIT, NOARGS }, 181204917Sdes { "cd", I_CHDIR, REMOTE }, 182204917Sdes { "chdir", I_CHDIR, REMOTE }, 183204917Sdes { "chgrp", I_CHGRP, REMOTE }, 184204917Sdes { "chmod", I_CHMOD, REMOTE }, 185204917Sdes { "chown", I_CHOWN, REMOTE }, 186204917Sdes { "df", I_DF, REMOTE }, 187204917Sdes { "dir", I_LS, REMOTE }, 188204917Sdes { "exit", I_QUIT, NOARGS }, 189204917Sdes { "get", I_GET, REMOTE }, 190204917Sdes { "help", I_HELP, NOARGS }, 191204917Sdes { "lcd", I_LCHDIR, LOCAL }, 192204917Sdes { "lchdir", I_LCHDIR, LOCAL }, 193204917Sdes { "lls", I_LLS, LOCAL }, 194204917Sdes { "lmkdir", I_LMKDIR, LOCAL }, 195221420Sdes { "ln", I_LINK, REMOTE }, 196204917Sdes { "lpwd", I_LPWD, LOCAL }, 197204917Sdes { "ls", I_LS, REMOTE }, 198204917Sdes { "lumask", I_LUMASK, NOARGS }, 199204917Sdes { "mkdir", I_MKDIR, REMOTE }, 200215116Sdes { "mget", I_GET, REMOTE }, 201215116Sdes { "mput", I_PUT, LOCAL }, 202204917Sdes { "progress", I_PROGRESS, NOARGS }, 203204917Sdes { "put", I_PUT, LOCAL }, 204204917Sdes { "pwd", I_PWD, REMOTE }, 205204917Sdes { "quit", I_QUIT, NOARGS }, 206255767Sdes { "reget", I_REGET, REMOTE }, 207204917Sdes { "rename", I_RENAME, REMOTE }, 208295367Sdes { "reput", I_REPUT, LOCAL }, 209204917Sdes { "rm", I_RM, REMOTE }, 210204917Sdes { "rmdir", I_RMDIR, REMOTE }, 211204917Sdes { "symlink", I_SYMLINK, REMOTE }, 212204917Sdes { "version", I_VERSION, NOARGS }, 213204917Sdes { "!", I_SHELL, NOARGS }, 214204917Sdes { "?", I_HELP, NOARGS }, 215204917Sdes { NULL, -1, -1 } 216126274Sdes}; 217126274Sdes 218204917Sdesint interactive_loop(struct sftp_conn *, char *file1, char *file2); 219126274Sdes 220181111Sdes/* ARGSUSED */ 22192555Sdesstatic void 222137015Sdeskillchild(int signo) 223137015Sdes{ 224146998Sdes if (sshpid > 1) { 225137015Sdes kill(sshpid, SIGTERM); 226146998Sdes waitpid(sshpid, NULL, 0); 227146998Sdes } 228137015Sdes 229137015Sdes _exit(1); 230137015Sdes} 231137015Sdes 232181111Sdes/* ARGSUSED */ 233137015Sdesstatic void 234137015Sdescmd_interrupt(int signo) 235137015Sdes{ 236137015Sdes const char msg[] = "\rInterrupt \n"; 237146998Sdes int olderrno = errno; 238137015Sdes 239255767Sdes (void)write(STDERR_FILENO, msg, sizeof(msg) - 1); 240137015Sdes interrupted = 1; 241146998Sdes errno = olderrno; 242137015Sdes} 243137015Sdes 244137015Sdesstatic void 245126274Sdeshelp(void) 246126274Sdes{ 247192595Sdes printf("Available commands:\n" 248192595Sdes "bye Quit sftp\n" 249192595Sdes "cd path Change remote directory to 'path'\n" 250192595Sdes "chgrp grp path Change group of file 'path' to 'grp'\n" 251192595Sdes "chmod mode path Change permissions of file 'path' to 'mode'\n" 252192595Sdes "chown own path Change owner of file 'path' to 'own'\n" 253192595Sdes "df [-hi] [path] Display statistics for current directory or\n" 254192595Sdes " filesystem containing 'path'\n" 255192595Sdes "exit Quit sftp\n" 256295367Sdes "get [-afPpRr] remote [local] Download file\n" 257295367Sdes "reget [-fPpRr] remote [local] Resume download file\n" 258295367Sdes "reput [-fPpRr] [local] remote Resume upload file\n" 259192595Sdes "help Display this help text\n" 260192595Sdes "lcd path Change local directory to 'path'\n" 261192595Sdes "lls [ls-options [path]] Display local directory listing\n" 262192595Sdes "lmkdir path Create local directory\n" 263221420Sdes "ln [-s] oldpath newpath Link remote file (-s for symlink)\n" 264192595Sdes "lpwd Print local working directory\n" 265204917Sdes "ls [-1afhlnrSt] [path] Display remote directory listing\n" 266192595Sdes "lumask umask Set local umask to 'umask'\n" 267192595Sdes "mkdir path Create remote directory\n" 268192595Sdes "progress Toggle display of progress meter\n" 269295367Sdes "put [-afPpRr] local [remote] Upload file\n" 270192595Sdes "pwd Display remote working directory\n" 271192595Sdes "quit Quit sftp\n" 272192595Sdes "rename oldpath newpath Rename remote file\n" 273192595Sdes "rm path Delete remote file\n" 274192595Sdes "rmdir path Remove remote directory\n" 275192595Sdes "symlink oldpath newpath Symlink remote file\n" 276192595Sdes "version Show SFTP version\n" 277192595Sdes "!command Execute 'command' in local shell\n" 278192595Sdes "! Escape to local shell\n" 279192595Sdes "? Synonym for help\n"); 280126274Sdes} 281126274Sdes 282126274Sdesstatic void 283126274Sdeslocal_do_shell(const char *args) 284126274Sdes{ 285126274Sdes int status; 286126274Sdes char *shell; 287126274Sdes pid_t pid; 288126274Sdes 289126274Sdes if (!*args) 290126274Sdes args = NULL; 291126274Sdes 292221420Sdes if ((shell = getenv("SHELL")) == NULL || *shell == '\0') 293126274Sdes shell = _PATH_BSHELL; 294126274Sdes 295126274Sdes if ((pid = fork()) == -1) 296126274Sdes fatal("Couldn't fork: %s", strerror(errno)); 297126274Sdes 298126274Sdes if (pid == 0) { 299126274Sdes /* XXX: child has pipe fds to ssh subproc open - issue? */ 300126274Sdes if (args) { 301126274Sdes debug3("Executing %s -c \"%s\"", shell, args); 302126274Sdes execl(shell, shell, "-c", args, (char *)NULL); 303126274Sdes } else { 304126274Sdes debug3("Executing %s", shell); 305126274Sdes execl(shell, shell, (char *)NULL); 306126274Sdes } 307126274Sdes fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell, 308126274Sdes strerror(errno)); 309126274Sdes _exit(1); 310126274Sdes } 311126274Sdes while (waitpid(pid, &status, 0) == -1) 312126274Sdes if (errno != EINTR) 313126274Sdes fatal("Couldn't wait for child: %s", strerror(errno)); 314126274Sdes if (!WIFEXITED(status)) 315162852Sdes error("Shell exited abnormally"); 316126274Sdes else if (WEXITSTATUS(status)) 317126274Sdes error("Shell exited with status %d", WEXITSTATUS(status)); 318126274Sdes} 319126274Sdes 320126274Sdesstatic void 321126274Sdeslocal_do_ls(const char *args) 322126274Sdes{ 323126274Sdes if (!args || !*args) 324126274Sdes local_do_shell(_PATH_LS); 325126274Sdes else { 326126274Sdes int len = strlen(_PATH_LS " ") + strlen(args) + 1; 327126274Sdes char *buf = xmalloc(len); 328126274Sdes 329126274Sdes /* XXX: quoting - rip quoting code from ftp? */ 330126274Sdes snprintf(buf, len, _PATH_LS " %s", args); 331126274Sdes local_do_shell(buf); 332255767Sdes free(buf); 333126274Sdes } 334126274Sdes} 335126274Sdes 336126274Sdes/* Strip one path (usually the pwd) from the start of another */ 337126274Sdesstatic char * 338126274Sdespath_strip(char *path, char *strip) 339126274Sdes{ 340126274Sdes size_t len; 341126274Sdes 342126274Sdes if (strip == NULL) 343126274Sdes return (xstrdup(path)); 344126274Sdes 345126274Sdes len = strlen(strip); 346146998Sdes if (strncmp(path, strip, len) == 0) { 347126274Sdes if (strip[len - 1] != '/' && path[len] == '/') 348126274Sdes len++; 349126274Sdes return (xstrdup(path + len)); 350126274Sdes } 351126274Sdes 352126274Sdes return (xstrdup(path)); 353126274Sdes} 354126274Sdes 355126274Sdesstatic char * 356126274Sdesmake_absolute(char *p, char *pwd) 357126274Sdes{ 358137015Sdes char *abs_str; 359126274Sdes 360126274Sdes /* Derelativise */ 361126274Sdes if (p && p[0] != '/') { 362137015Sdes abs_str = path_append(pwd, p); 363255767Sdes free(p); 364137015Sdes return(abs_str); 365126274Sdes } else 366126274Sdes return(p); 367126274Sdes} 368126274Sdes 369126274Sdesstatic int 370255767Sdesparse_getput_flags(const char *cmd, char **argv, int argc, 371262566Sdes int *aflag, int *fflag, int *pflag, int *rflag) 372126274Sdes{ 373181111Sdes extern int opterr, optind, optopt, optreset; 374181111Sdes int ch; 375126274Sdes 376181111Sdes optind = optreset = 1; 377181111Sdes opterr = 0; 378181111Sdes 379262566Sdes *aflag = *fflag = *rflag = *pflag = 0; 380262566Sdes while ((ch = getopt(argc, argv, "afPpRr")) != -1) { 381181111Sdes switch (ch) { 382255767Sdes case 'a': 383255767Sdes *aflag = 1; 384255767Sdes break; 385262566Sdes case 'f': 386262566Sdes *fflag = 1; 387262566Sdes break; 388126274Sdes case 'p': 389126274Sdes case 'P': 390126274Sdes *pflag = 1; 391126274Sdes break; 392204917Sdes case 'r': 393204917Sdes case 'R': 394204917Sdes *rflag = 1; 395204917Sdes break; 396126274Sdes default: 397181111Sdes error("%s: Invalid flag -%c", cmd, optopt); 398181111Sdes return -1; 399126274Sdes } 400126274Sdes } 401126274Sdes 402181111Sdes return optind; 403126274Sdes} 404126274Sdes 405126274Sdesstatic int 406221420Sdesparse_link_flags(const char *cmd, char **argv, int argc, int *sflag) 407221420Sdes{ 408221420Sdes extern int opterr, optind, optopt, optreset; 409221420Sdes int ch; 410221420Sdes 411221420Sdes optind = optreset = 1; 412221420Sdes opterr = 0; 413221420Sdes 414221420Sdes *sflag = 0; 415221420Sdes while ((ch = getopt(argc, argv, "s")) != -1) { 416221420Sdes switch (ch) { 417221420Sdes case 's': 418221420Sdes *sflag = 1; 419221420Sdes break; 420221420Sdes default: 421221420Sdes error("%s: Invalid flag -%c", cmd, optopt); 422221420Sdes return -1; 423221420Sdes } 424221420Sdes } 425221420Sdes 426221420Sdes return optind; 427221420Sdes} 428221420Sdes 429221420Sdesstatic int 430262566Sdesparse_rename_flags(const char *cmd, char **argv, int argc, int *lflag) 431262566Sdes{ 432262566Sdes extern int opterr, optind, optopt, optreset; 433262566Sdes int ch; 434262566Sdes 435262566Sdes optind = optreset = 1; 436262566Sdes opterr = 0; 437262566Sdes 438262566Sdes *lflag = 0; 439262566Sdes while ((ch = getopt(argc, argv, "l")) != -1) { 440262566Sdes switch (ch) { 441262566Sdes case 'l': 442262566Sdes *lflag = 1; 443262566Sdes break; 444262566Sdes default: 445262566Sdes error("%s: Invalid flag -%c", cmd, optopt); 446262566Sdes return -1; 447262566Sdes } 448262566Sdes } 449262566Sdes 450262566Sdes return optind; 451262566Sdes} 452262566Sdes 453262566Sdesstatic int 454181111Sdesparse_ls_flags(char **argv, int argc, int *lflag) 455126274Sdes{ 456181111Sdes extern int opterr, optind, optopt, optreset; 457181111Sdes int ch; 458126274Sdes 459181111Sdes optind = optreset = 1; 460181111Sdes opterr = 0; 461181111Sdes 462137015Sdes *lflag = LS_NAME_SORT; 463204917Sdes while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) { 464181111Sdes switch (ch) { 465181111Sdes case '1': 466181111Sdes *lflag &= ~VIEW_FLAGS; 467181111Sdes *lflag |= LS_SHORT_VIEW; 468181111Sdes break; 469181111Sdes case 'S': 470181111Sdes *lflag &= ~SORT_FLAGS; 471181111Sdes *lflag |= LS_SIZE_SORT; 472181111Sdes break; 473181111Sdes case 'a': 474181111Sdes *lflag |= LS_SHOW_ALL; 475181111Sdes break; 476181111Sdes case 'f': 477181111Sdes *lflag &= ~SORT_FLAGS; 478181111Sdes break; 479204917Sdes case 'h': 480204917Sdes *lflag |= LS_SI_UNITS; 481204917Sdes break; 482181111Sdes case 'l': 483204917Sdes *lflag &= ~LS_SHORT_VIEW; 484181111Sdes *lflag |= LS_LONG_VIEW; 485181111Sdes break; 486181111Sdes case 'n': 487204917Sdes *lflag &= ~LS_SHORT_VIEW; 488181111Sdes *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW; 489181111Sdes break; 490181111Sdes case 'r': 491181111Sdes *lflag |= LS_REVERSE_SORT; 492181111Sdes break; 493181111Sdes case 't': 494181111Sdes *lflag &= ~SORT_FLAGS; 495181111Sdes *lflag |= LS_TIME_SORT; 496181111Sdes break; 497181111Sdes default: 498181111Sdes error("ls: Invalid flag -%c", optopt); 499181111Sdes return -1; 500126274Sdes } 501126274Sdes } 502126274Sdes 503181111Sdes return optind; 504126274Sdes} 505126274Sdes 506126274Sdesstatic int 507181111Sdesparse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag) 508126274Sdes{ 509181111Sdes extern int opterr, optind, optopt, optreset; 510181111Sdes int ch; 511126274Sdes 512181111Sdes optind = optreset = 1; 513181111Sdes opterr = 0; 514126274Sdes 515181111Sdes *hflag = *iflag = 0; 516181111Sdes while ((ch = getopt(argc, argv, "hi")) != -1) { 517181111Sdes switch (ch) { 518181111Sdes case 'h': 519181111Sdes *hflag = 1; 520181111Sdes break; 521181111Sdes case 'i': 522181111Sdes *iflag = 1; 523181111Sdes break; 524181111Sdes default: 525181111Sdes error("%s: Invalid flag -%c", cmd, optopt); 526181111Sdes return -1; 527126274Sdes } 528126274Sdes } 529126274Sdes 530181111Sdes return optind; 531126274Sdes} 532126274Sdes 533126274Sdesstatic int 534262566Sdesparse_no_flags(const char *cmd, char **argv, int argc) 535262566Sdes{ 536262566Sdes extern int opterr, optind, optopt, optreset; 537262566Sdes int ch; 538262566Sdes 539262566Sdes optind = optreset = 1; 540262566Sdes opterr = 0; 541262566Sdes 542262566Sdes while ((ch = getopt(argc, argv, "")) != -1) { 543262566Sdes switch (ch) { 544262566Sdes default: 545262566Sdes error("%s: Invalid flag -%c", cmd, optopt); 546262566Sdes return -1; 547262566Sdes } 548262566Sdes } 549262566Sdes 550262566Sdes return optind; 551262566Sdes} 552262566Sdes 553262566Sdesstatic int 554126274Sdesis_dir(char *path) 555126274Sdes{ 556126274Sdes struct stat sb; 557126274Sdes 558126274Sdes /* XXX: report errors? */ 559126274Sdes if (stat(path, &sb) == -1) 560126274Sdes return(0); 561126274Sdes 562162852Sdes return(S_ISDIR(sb.st_mode)); 563126274Sdes} 564126274Sdes 565126274Sdesstatic int 566126274Sdesremote_is_dir(struct sftp_conn *conn, char *path) 567126274Sdes{ 568126274Sdes Attrib *a; 569126274Sdes 570126274Sdes /* XXX: report errors? */ 571126274Sdes if ((a = do_stat(conn, path, 1)) == NULL) 572126274Sdes return(0); 573126274Sdes if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) 574126274Sdes return(0); 575162852Sdes return(S_ISDIR(a->perm)); 576126274Sdes} 577126274Sdes 578204917Sdes/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */ 579126274Sdesstatic int 580204917Sdespathname_is_dir(char *pathname) 581126274Sdes{ 582204917Sdes size_t l = strlen(pathname); 583204917Sdes 584204917Sdes return l > 0 && pathname[l - 1] == '/'; 585204917Sdes} 586204917Sdes 587204917Sdesstatic int 588204917Sdesprocess_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, 589262566Sdes int pflag, int rflag, int resume, int fflag) 590204917Sdes{ 591126274Sdes char *abs_src = NULL; 592126274Sdes char *abs_dst = NULL; 593126274Sdes glob_t g; 594204917Sdes char *filename, *tmp=NULL; 595295367Sdes int i, r, err = 0; 596126274Sdes 597126274Sdes abs_src = xstrdup(src); 598126274Sdes abs_src = make_absolute(abs_src, pwd); 599204917Sdes memset(&g, 0, sizeof(g)); 600126274Sdes 601126274Sdes debug3("Looking up %s", abs_src); 602295367Sdes if ((r = remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) != 0) { 603295367Sdes if (r == GLOB_NOSPACE) { 604295367Sdes error("Too many matches for \"%s\".", abs_src); 605295367Sdes } else { 606295367Sdes error("File \"%s\" not found.", abs_src); 607295367Sdes } 608126274Sdes err = -1; 609126274Sdes goto out; 610126274Sdes } 611126274Sdes 612204917Sdes /* 613204917Sdes * If multiple matches then dst must be a directory or 614204917Sdes * unspecified. 615204917Sdes */ 616204917Sdes if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) { 617204917Sdes error("Multiple source paths, but destination " 618204917Sdes "\"%s\" is not a directory", dst); 619126274Sdes err = -1; 620126274Sdes goto out; 621126274Sdes } 622126274Sdes 623137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 624204917Sdes tmp = xstrdup(g.gl_pathv[i]); 625204917Sdes if ((filename = basename(tmp)) == NULL) { 626204917Sdes error("basename %s: %s", tmp, strerror(errno)); 627255767Sdes free(tmp); 628126274Sdes err = -1; 629126274Sdes goto out; 630126274Sdes } 631126274Sdes 632126274Sdes if (g.gl_matchc == 1 && dst) { 633126274Sdes if (is_dir(dst)) { 634204917Sdes abs_dst = path_append(dst, filename); 635204917Sdes } else { 636126274Sdes abs_dst = xstrdup(dst); 637204917Sdes } 638126274Sdes } else if (dst) { 639204917Sdes abs_dst = path_append(dst, filename); 640204917Sdes } else { 641204917Sdes abs_dst = xstrdup(filename); 642204917Sdes } 643255767Sdes free(tmp); 644126274Sdes 645255767Sdes resume |= global_aflag; 646255767Sdes if (!quiet && resume) 647255767Sdes printf("Resuming %s to %s\n", g.gl_pathv[i], abs_dst); 648255767Sdes else if (!quiet && !resume) 649255767Sdes printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst); 650204917Sdes if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) { 651255767Sdes if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL, 652262566Sdes pflag || global_pflag, 1, resume, 653262566Sdes fflag || global_fflag) == -1) 654204917Sdes err = -1; 655204917Sdes } else { 656204917Sdes if (do_download(conn, g.gl_pathv[i], abs_dst, NULL, 657262566Sdes pflag || global_pflag, resume, 658262566Sdes fflag || global_fflag) == -1) 659204917Sdes err = -1; 660204917Sdes } 661255767Sdes free(abs_dst); 662126274Sdes abs_dst = NULL; 663126274Sdes } 664126274Sdes 665126274Sdesout: 666255767Sdes free(abs_src); 667126274Sdes globfree(&g); 668126274Sdes return(err); 669126274Sdes} 670126274Sdes 671126274Sdesstatic int 672204917Sdesprocess_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, 673295367Sdes int pflag, int rflag, int resume, int fflag) 674126274Sdes{ 675126274Sdes char *tmp_dst = NULL; 676126274Sdes char *abs_dst = NULL; 677204917Sdes char *tmp = NULL, *filename = NULL; 678126274Sdes glob_t g; 679126274Sdes int err = 0; 680204917Sdes int i, dst_is_dir = 1; 681181111Sdes struct stat sb; 682126274Sdes 683126274Sdes if (dst) { 684126274Sdes tmp_dst = xstrdup(dst); 685126274Sdes tmp_dst = make_absolute(tmp_dst, pwd); 686126274Sdes } 687126274Sdes 688126274Sdes memset(&g, 0, sizeof(g)); 689126274Sdes debug3("Looking up %s", src); 690204917Sdes if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) { 691126274Sdes error("File \"%s\" not found.", src); 692126274Sdes err = -1; 693126274Sdes goto out; 694126274Sdes } 695126274Sdes 696204917Sdes /* If we aren't fetching to pwd then stash this status for later */ 697204917Sdes if (tmp_dst != NULL) 698204917Sdes dst_is_dir = remote_is_dir(conn, tmp_dst); 699204917Sdes 700126274Sdes /* If multiple matches, dst may be directory or unspecified */ 701204917Sdes if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) { 702204917Sdes error("Multiple paths match, but destination " 703204917Sdes "\"%s\" is not a directory", tmp_dst); 704126274Sdes err = -1; 705126274Sdes goto out; 706126274Sdes } 707126274Sdes 708137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 709181111Sdes if (stat(g.gl_pathv[i], &sb) == -1) { 710181111Sdes err = -1; 711181111Sdes error("stat %s: %s", g.gl_pathv[i], strerror(errno)); 712181111Sdes continue; 713181111Sdes } 714262566Sdes 715204917Sdes tmp = xstrdup(g.gl_pathv[i]); 716204917Sdes if ((filename = basename(tmp)) == NULL) { 717204917Sdes error("basename %s: %s", tmp, strerror(errno)); 718255767Sdes free(tmp); 719126274Sdes err = -1; 720126274Sdes goto out; 721126274Sdes } 722126274Sdes 723126274Sdes if (g.gl_matchc == 1 && tmp_dst) { 724126274Sdes /* If directory specified, append filename */ 725204917Sdes if (dst_is_dir) 726204917Sdes abs_dst = path_append(tmp_dst, filename); 727204917Sdes else 728126274Sdes abs_dst = xstrdup(tmp_dst); 729126274Sdes } else if (tmp_dst) { 730204917Sdes abs_dst = path_append(tmp_dst, filename); 731204917Sdes } else { 732204917Sdes abs_dst = make_absolute(xstrdup(filename), pwd); 733204917Sdes } 734255767Sdes free(tmp); 735126274Sdes 736295367Sdes resume |= global_aflag; 737295367Sdes if (!quiet && resume) 738295367Sdes printf("Resuming upload of %s to %s\n", g.gl_pathv[i], 739295367Sdes abs_dst); 740295367Sdes else if (!quiet && !resume) 741255767Sdes printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst); 742204917Sdes if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) { 743204917Sdes if (upload_dir(conn, g.gl_pathv[i], abs_dst, 744295367Sdes pflag || global_pflag, 1, resume, 745262566Sdes fflag || global_fflag) == -1) 746204917Sdes err = -1; 747204917Sdes } else { 748204917Sdes if (do_upload(conn, g.gl_pathv[i], abs_dst, 749295367Sdes pflag || global_pflag, resume, 750262566Sdes fflag || global_fflag) == -1) 751204917Sdes err = -1; 752204917Sdes } 753126274Sdes } 754126274Sdes 755126274Sdesout: 756255767Sdes free(abs_dst); 757255767Sdes free(tmp_dst); 758126274Sdes globfree(&g); 759126274Sdes return(err); 760126274Sdes} 761126274Sdes 762126274Sdesstatic int 763126274Sdessdirent_comp(const void *aa, const void *bb) 764126274Sdes{ 765126274Sdes SFTP_DIRENT *a = *(SFTP_DIRENT **)aa; 766126274Sdes SFTP_DIRENT *b = *(SFTP_DIRENT **)bb; 767137015Sdes int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1; 768126274Sdes 769137015Sdes#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1)) 770137015Sdes if (sort_flag & LS_NAME_SORT) 771137015Sdes return (rmul * strcmp(a->filename, b->filename)); 772137015Sdes else if (sort_flag & LS_TIME_SORT) 773137015Sdes return (rmul * NCMP(a->a.mtime, b->a.mtime)); 774137015Sdes else if (sort_flag & LS_SIZE_SORT) 775137015Sdes return (rmul * NCMP(a->a.size, b->a.size)); 776137015Sdes 777137015Sdes fatal("Unknown ls sort type"); 778126274Sdes} 779126274Sdes 780126274Sdes/* sftp ls.1 replacement for directories */ 781126274Sdesstatic int 782126274Sdesdo_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag) 783126274Sdes{ 784149749Sdes int n; 785149749Sdes u_int c = 1, colspace = 0, columns = 1; 786126274Sdes SFTP_DIRENT **d; 787126274Sdes 788126274Sdes if ((n = do_readdir(conn, path, &d)) != 0) 789126274Sdes return (n); 790126274Sdes 791137015Sdes if (!(lflag & LS_SHORT_VIEW)) { 792149749Sdes u_int m = 0, width = 80; 793126274Sdes struct winsize ws; 794126274Sdes char *tmp; 795126274Sdes 796126274Sdes /* Count entries for sort and find longest filename */ 797137015Sdes for (n = 0; d[n] != NULL; n++) { 798137015Sdes if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL)) 799137015Sdes m = MAX(m, strlen(d[n]->filename)); 800137015Sdes } 801126274Sdes 802126274Sdes /* Add any subpath that also needs to be counted */ 803126274Sdes tmp = path_strip(path, strip_path); 804126274Sdes m += strlen(tmp); 805255767Sdes free(tmp); 806126274Sdes 807126274Sdes if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1) 808126274Sdes width = ws.ws_col; 809126274Sdes 810126274Sdes columns = width / (m + 2); 811126274Sdes columns = MAX(columns, 1); 812126274Sdes colspace = width / columns; 813126274Sdes colspace = MIN(colspace, width); 814126274Sdes } 815126274Sdes 816137015Sdes if (lflag & SORT_FLAGS) { 817157016Sdes for (n = 0; d[n] != NULL; n++) 818157016Sdes ; /* count entries */ 819137015Sdes sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT); 820137015Sdes qsort(d, n, sizeof(*d), sdirent_comp); 821137015Sdes } 822126274Sdes 823137015Sdes for (n = 0; d[n] != NULL && !interrupted; n++) { 824126274Sdes char *tmp, *fname; 825126274Sdes 826137015Sdes if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL)) 827137015Sdes continue; 828137015Sdes 829126274Sdes tmp = path_append(path, d[n]->filename); 830126274Sdes fname = path_strip(tmp, strip_path); 831255767Sdes free(tmp); 832126274Sdes 833137015Sdes if (lflag & LS_LONG_VIEW) { 834204917Sdes if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) { 835137015Sdes char *lname; 836137015Sdes struct stat sb; 837126274Sdes 838137015Sdes memset(&sb, 0, sizeof(sb)); 839137015Sdes attrib_to_stat(&d[n]->a, &sb); 840204917Sdes lname = ls_file(fname, &sb, 1, 841204917Sdes (lflag & LS_SI_UNITS)); 842137015Sdes printf("%s\n", lname); 843255767Sdes free(lname); 844137015Sdes } else 845137015Sdes printf("%s\n", d[n]->longname); 846126274Sdes } else { 847126274Sdes printf("%-*s", colspace, fname); 848126274Sdes if (c >= columns) { 849126274Sdes printf("\n"); 850126274Sdes c = 1; 851126274Sdes } else 852126274Sdes c++; 853126274Sdes } 854126274Sdes 855255767Sdes free(fname); 856126274Sdes } 857126274Sdes 858137015Sdes if (!(lflag & LS_LONG_VIEW) && (c != 1)) 859126274Sdes printf("\n"); 860126274Sdes 861126274Sdes free_sftp_dirents(d); 862126274Sdes return (0); 863126274Sdes} 864126274Sdes 865126274Sdes/* sftp ls.1 replacement which handles path globs */ 866126274Sdesstatic int 867126274Sdesdo_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path, 868126274Sdes int lflag) 869126274Sdes{ 870221420Sdes char *fname, *lname; 871126274Sdes glob_t g; 872295367Sdes int err, r; 873221420Sdes struct winsize ws; 874221420Sdes u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80; 875126274Sdes 876126274Sdes memset(&g, 0, sizeof(g)); 877126274Sdes 878295367Sdes if ((r = remote_glob(conn, path, 879240075Sdes GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT, 880295367Sdes NULL, &g)) != 0 || 881221420Sdes (g.gl_pathc && !g.gl_matchc)) { 882146998Sdes if (g.gl_pathc) 883146998Sdes globfree(&g); 884295367Sdes if (r == GLOB_NOSPACE) { 885295367Sdes error("Can't ls: Too many matches for \"%s\"", path); 886295367Sdes } else { 887295367Sdes error("Can't ls: \"%s\" not found", path); 888295367Sdes } 889221420Sdes return -1; 890126274Sdes } 891126274Sdes 892137015Sdes if (interrupted) 893137015Sdes goto out; 894137015Sdes 895126274Sdes /* 896146998Sdes * If the glob returns a single match and it is a directory, 897146998Sdes * then just list its contents. 898126274Sdes */ 899221420Sdes if (g.gl_matchc == 1 && g.gl_statv[0] != NULL && 900221420Sdes S_ISDIR(g.gl_statv[0]->st_mode)) { 901221420Sdes err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag); 902221420Sdes globfree(&g); 903221420Sdes return err; 904126274Sdes } 905126274Sdes 906221420Sdes if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1) 907221420Sdes width = ws.ws_col; 908221420Sdes 909137015Sdes if (!(lflag & LS_SHORT_VIEW)) { 910126274Sdes /* Count entries for sort and find longest filename */ 911126274Sdes for (i = 0; g.gl_pathv[i]; i++) 912126274Sdes m = MAX(m, strlen(g.gl_pathv[i])); 913126274Sdes 914126274Sdes columns = width / (m + 2); 915126274Sdes columns = MAX(columns, 1); 916126274Sdes colspace = width / columns; 917126274Sdes } 918126274Sdes 919240075Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 920126274Sdes fname = path_strip(g.gl_pathv[i], strip_path); 921137015Sdes if (lflag & LS_LONG_VIEW) { 922221420Sdes if (g.gl_statv[i] == NULL) { 923221420Sdes error("no stat information for %s", fname); 924221420Sdes continue; 925221420Sdes } 926221420Sdes lname = ls_file(fname, g.gl_statv[i], 1, 927221420Sdes (lflag & LS_SI_UNITS)); 928126274Sdes printf("%s\n", lname); 929255767Sdes free(lname); 930126274Sdes } else { 931126274Sdes printf("%-*s", colspace, fname); 932126274Sdes if (c >= columns) { 933126274Sdes printf("\n"); 934126274Sdes c = 1; 935126274Sdes } else 936126274Sdes c++; 937126274Sdes } 938255767Sdes free(fname); 939126274Sdes } 940126274Sdes 941137015Sdes if (!(lflag & LS_LONG_VIEW) && (c != 1)) 942126274Sdes printf("\n"); 943126274Sdes 944137015Sdes out: 945126274Sdes if (g.gl_pathc) 946126274Sdes globfree(&g); 947126274Sdes 948221420Sdes return 0; 949126274Sdes} 950126274Sdes 951126274Sdesstatic int 952181111Sdesdo_df(struct sftp_conn *conn, char *path, int hflag, int iflag) 953181111Sdes{ 954181111Sdes struct sftp_statvfs st; 955181111Sdes char s_used[FMT_SCALED_STRSIZE]; 956181111Sdes char s_avail[FMT_SCALED_STRSIZE]; 957181111Sdes char s_root[FMT_SCALED_STRSIZE]; 958181111Sdes char s_total[FMT_SCALED_STRSIZE]; 959204917Sdes unsigned long long ffree; 960181111Sdes 961181111Sdes if (do_statvfs(conn, path, &st, 1) == -1) 962181111Sdes return -1; 963181111Sdes if (iflag) { 964204917Sdes ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0; 965181111Sdes printf(" Inodes Used Avail " 966181111Sdes "(root) %%Capacity\n"); 967181111Sdes printf("%11llu %11llu %11llu %11llu %3llu%%\n", 968181111Sdes (unsigned long long)st.f_files, 969181111Sdes (unsigned long long)(st.f_files - st.f_ffree), 970181111Sdes (unsigned long long)st.f_favail, 971204917Sdes (unsigned long long)st.f_ffree, ffree); 972181111Sdes } else if (hflag) { 973181111Sdes strlcpy(s_used, "error", sizeof(s_used)); 974181111Sdes strlcpy(s_avail, "error", sizeof(s_avail)); 975181111Sdes strlcpy(s_root, "error", sizeof(s_root)); 976181111Sdes strlcpy(s_total, "error", sizeof(s_total)); 977181111Sdes fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used); 978181111Sdes fmt_scaled(st.f_bavail * st.f_frsize, s_avail); 979181111Sdes fmt_scaled(st.f_bfree * st.f_frsize, s_root); 980181111Sdes fmt_scaled(st.f_blocks * st.f_frsize, s_total); 981181111Sdes printf(" Size Used Avail (root) %%Capacity\n"); 982181111Sdes printf("%7sB %7sB %7sB %7sB %3llu%%\n", 983181111Sdes s_total, s_used, s_avail, s_root, 984181111Sdes (unsigned long long)(100 * (st.f_blocks - st.f_bfree) / 985181111Sdes st.f_blocks)); 986181111Sdes } else { 987181111Sdes printf(" Size Used Avail " 988181111Sdes "(root) %%Capacity\n"); 989181111Sdes printf("%12llu %12llu %12llu %12llu %3llu%%\n", 990181111Sdes (unsigned long long)(st.f_frsize * st.f_blocks / 1024), 991181111Sdes (unsigned long long)(st.f_frsize * 992181111Sdes (st.f_blocks - st.f_bfree) / 1024), 993181111Sdes (unsigned long long)(st.f_frsize * st.f_bavail / 1024), 994181111Sdes (unsigned long long)(st.f_frsize * st.f_bfree / 1024), 995181111Sdes (unsigned long long)(100 * (st.f_blocks - st.f_bfree) / 996181111Sdes st.f_blocks)); 997181111Sdes } 998181111Sdes return 0; 999181111Sdes} 1000181111Sdes 1001181111Sdes/* 1002181111Sdes * Undo escaping of glob sequences in place. Used to undo extra escaping 1003181111Sdes * applied in makeargv() when the string is destined for a function that 1004181111Sdes * does not glob it. 1005181111Sdes */ 1006181111Sdesstatic void 1007181111Sdesundo_glob_escape(char *s) 1008181111Sdes{ 1009181111Sdes size_t i, j; 1010181111Sdes 1011181111Sdes for (i = j = 0;;) { 1012181111Sdes if (s[i] == '\0') { 1013181111Sdes s[j] = '\0'; 1014181111Sdes return; 1015181111Sdes } 1016181111Sdes if (s[i] != '\\') { 1017181111Sdes s[j++] = s[i++]; 1018181111Sdes continue; 1019181111Sdes } 1020181111Sdes /* s[i] == '\\' */ 1021181111Sdes ++i; 1022181111Sdes switch (s[i]) { 1023181111Sdes case '?': 1024181111Sdes case '[': 1025181111Sdes case '*': 1026181111Sdes case '\\': 1027181111Sdes s[j++] = s[i++]; 1028181111Sdes break; 1029181111Sdes case '\0': 1030181111Sdes s[j++] = '\\'; 1031181111Sdes s[j] = '\0'; 1032181111Sdes return; 1033181111Sdes default: 1034181111Sdes s[j++] = '\\'; 1035181111Sdes s[j++] = s[i++]; 1036181111Sdes break; 1037181111Sdes } 1038181111Sdes } 1039181111Sdes} 1040181111Sdes 1041181111Sdes/* 1042181111Sdes * Split a string into an argument vector using sh(1)-style quoting, 1043181111Sdes * comment and escaping rules, but with some tweaks to handle glob(3) 1044181111Sdes * wildcards. 1045204917Sdes * The "sloppy" flag allows for recovery from missing terminating quote, for 1046204917Sdes * use in parsing incomplete commandlines during tab autocompletion. 1047204917Sdes * 1048181111Sdes * Returns NULL on error or a NULL-terminated array of arguments. 1049204917Sdes * 1050204917Sdes * If "lastquote" is not NULL, the quoting character used for the last 1051204917Sdes * argument is placed in *lastquote ("\0", "'" or "\""). 1052262566Sdes * 1053204917Sdes * If "terminated" is not NULL, *terminated will be set to 1 when the 1054204917Sdes * last argument's quote has been properly terminated or 0 otherwise. 1055204917Sdes * This parameter is only of use if "sloppy" is set. 1056181111Sdes */ 1057181111Sdes#define MAXARGS 128 1058181111Sdes#define MAXARGLEN 8192 1059181111Sdesstatic char ** 1060204917Sdesmakeargv(const char *arg, int *argcp, int sloppy, char *lastquote, 1061204917Sdes u_int *terminated) 1062181111Sdes{ 1063181111Sdes int argc, quot; 1064181111Sdes size_t i, j; 1065181111Sdes static char argvs[MAXARGLEN]; 1066181111Sdes static char *argv[MAXARGS + 1]; 1067181111Sdes enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q; 1068181111Sdes 1069181111Sdes *argcp = argc = 0; 1070181111Sdes if (strlen(arg) > sizeof(argvs) - 1) { 1071181111Sdes args_too_longs: 1072181111Sdes error("string too long"); 1073181111Sdes return NULL; 1074181111Sdes } 1075204917Sdes if (terminated != NULL) 1076204917Sdes *terminated = 1; 1077204917Sdes if (lastquote != NULL) 1078204917Sdes *lastquote = '\0'; 1079181111Sdes state = MA_START; 1080181111Sdes i = j = 0; 1081181111Sdes for (;;) { 1082248619Sdes if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){ 1083248619Sdes error("Too many arguments."); 1084248619Sdes return NULL; 1085248619Sdes } 1086262566Sdes if (isspace((unsigned char)arg[i])) { 1087181111Sdes if (state == MA_UNQUOTED) { 1088181111Sdes /* Terminate current argument */ 1089181111Sdes argvs[j++] = '\0'; 1090181111Sdes argc++; 1091181111Sdes state = MA_START; 1092181111Sdes } else if (state != MA_START) 1093181111Sdes argvs[j++] = arg[i]; 1094181111Sdes } else if (arg[i] == '"' || arg[i] == '\'') { 1095181111Sdes q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE; 1096181111Sdes if (state == MA_START) { 1097181111Sdes argv[argc] = argvs + j; 1098181111Sdes state = q; 1099204917Sdes if (lastquote != NULL) 1100204917Sdes *lastquote = arg[i]; 1101262566Sdes } else if (state == MA_UNQUOTED) 1102181111Sdes state = q; 1103181111Sdes else if (state == q) 1104181111Sdes state = MA_UNQUOTED; 1105181111Sdes else 1106181111Sdes argvs[j++] = arg[i]; 1107181111Sdes } else if (arg[i] == '\\') { 1108181111Sdes if (state == MA_SQUOTE || state == MA_DQUOTE) { 1109181111Sdes quot = state == MA_SQUOTE ? '\'' : '"'; 1110181111Sdes /* Unescape quote we are in */ 1111181111Sdes /* XXX support \n and friends? */ 1112181111Sdes if (arg[i + 1] == quot) { 1113181111Sdes i++; 1114181111Sdes argvs[j++] = arg[i]; 1115181111Sdes } else if (arg[i + 1] == '?' || 1116181111Sdes arg[i + 1] == '[' || arg[i + 1] == '*') { 1117181111Sdes /* 1118181111Sdes * Special case for sftp: append 1119181111Sdes * double-escaped glob sequence - 1120181111Sdes * glob will undo one level of 1121181111Sdes * escaping. NB. string can grow here. 1122181111Sdes */ 1123181111Sdes if (j >= sizeof(argvs) - 5) 1124181111Sdes goto args_too_longs; 1125181111Sdes argvs[j++] = '\\'; 1126181111Sdes argvs[j++] = arg[i++]; 1127181111Sdes argvs[j++] = '\\'; 1128181111Sdes argvs[j++] = arg[i]; 1129181111Sdes } else { 1130181111Sdes argvs[j++] = arg[i++]; 1131181111Sdes argvs[j++] = arg[i]; 1132181111Sdes } 1133181111Sdes } else { 1134181111Sdes if (state == MA_START) { 1135181111Sdes argv[argc] = argvs + j; 1136181111Sdes state = MA_UNQUOTED; 1137204917Sdes if (lastquote != NULL) 1138204917Sdes *lastquote = '\0'; 1139181111Sdes } 1140181111Sdes if (arg[i + 1] == '?' || arg[i + 1] == '[' || 1141181111Sdes arg[i + 1] == '*' || arg[i + 1] == '\\') { 1142181111Sdes /* 1143181111Sdes * Special case for sftp: append 1144181111Sdes * escaped glob sequence - 1145181111Sdes * glob will undo one level of 1146181111Sdes * escaping. 1147181111Sdes */ 1148181111Sdes argvs[j++] = arg[i++]; 1149181111Sdes argvs[j++] = arg[i]; 1150181111Sdes } else { 1151181111Sdes /* Unescape everything */ 1152181111Sdes /* XXX support \n and friends? */ 1153181111Sdes i++; 1154181111Sdes argvs[j++] = arg[i]; 1155181111Sdes } 1156181111Sdes } 1157181111Sdes } else if (arg[i] == '#') { 1158181111Sdes if (state == MA_SQUOTE || state == MA_DQUOTE) 1159181111Sdes argvs[j++] = arg[i]; 1160181111Sdes else 1161181111Sdes goto string_done; 1162181111Sdes } else if (arg[i] == '\0') { 1163181111Sdes if (state == MA_SQUOTE || state == MA_DQUOTE) { 1164204917Sdes if (sloppy) { 1165204917Sdes state = MA_UNQUOTED; 1166204917Sdes if (terminated != NULL) 1167204917Sdes *terminated = 0; 1168204917Sdes goto string_done; 1169204917Sdes } 1170181111Sdes error("Unterminated quoted argument"); 1171181111Sdes return NULL; 1172181111Sdes } 1173181111Sdes string_done: 1174181111Sdes if (state == MA_UNQUOTED) { 1175181111Sdes argvs[j++] = '\0'; 1176181111Sdes argc++; 1177181111Sdes } 1178181111Sdes break; 1179181111Sdes } else { 1180181111Sdes if (state == MA_START) { 1181181111Sdes argv[argc] = argvs + j; 1182181111Sdes state = MA_UNQUOTED; 1183204917Sdes if (lastquote != NULL) 1184204917Sdes *lastquote = '\0'; 1185181111Sdes } 1186181111Sdes if ((state == MA_SQUOTE || state == MA_DQUOTE) && 1187181111Sdes (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) { 1188181111Sdes /* 1189181111Sdes * Special case for sftp: escape quoted 1190181111Sdes * glob(3) wildcards. NB. string can grow 1191181111Sdes * here. 1192181111Sdes */ 1193181111Sdes if (j >= sizeof(argvs) - 3) 1194181111Sdes goto args_too_longs; 1195181111Sdes argvs[j++] = '\\'; 1196181111Sdes argvs[j++] = arg[i]; 1197181111Sdes } else 1198181111Sdes argvs[j++] = arg[i]; 1199181111Sdes } 1200181111Sdes i++; 1201181111Sdes } 1202181111Sdes *argcp = argc; 1203181111Sdes return argv; 1204181111Sdes} 1205181111Sdes 1206181111Sdesstatic int 1207295367Sdesparse_args(const char **cpp, int *ignore_errors, int *aflag, 1208295367Sdes int *fflag, int *hflag, int *iflag, int *lflag, int *pflag, 1209295367Sdes int *rflag, int *sflag, 1210262566Sdes unsigned long *n_arg, char **path1, char **path2) 1211126274Sdes{ 1212126274Sdes const char *cmd, *cp = *cpp; 1213181111Sdes char *cp2, **argv; 1214126274Sdes int base = 0; 1215126274Sdes long l; 1216181111Sdes int i, cmdnum, optidx, argc; 1217126274Sdes 1218126274Sdes /* Skip leading whitespace */ 1219126274Sdes cp = cp + strspn(cp, WHITESPACE); 1220126274Sdes 1221126274Sdes /* Check for leading '-' (disable error processing) */ 1222262566Sdes *ignore_errors = 0; 1223126274Sdes if (*cp == '-') { 1224262566Sdes *ignore_errors = 1; 1225126274Sdes cp++; 1226204917Sdes cp = cp + strspn(cp, WHITESPACE); 1227126274Sdes } 1228126274Sdes 1229204917Sdes /* Ignore blank lines and lines which begin with comment '#' char */ 1230204917Sdes if (*cp == '\0' || *cp == '#') 1231204917Sdes return (0); 1232204917Sdes 1233204917Sdes if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL) 1234181111Sdes return -1; 1235181111Sdes 1236126274Sdes /* Figure out which command we have */ 1237181111Sdes for (i = 0; cmds[i].c != NULL; i++) { 1238248619Sdes if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0) 1239126274Sdes break; 1240126274Sdes } 1241126274Sdes cmdnum = cmds[i].n; 1242126274Sdes cmd = cmds[i].c; 1243126274Sdes 1244126274Sdes /* Special case */ 1245126274Sdes if (*cp == '!') { 1246126274Sdes cp++; 1247126274Sdes cmdnum = I_SHELL; 1248126274Sdes } else if (cmdnum == -1) { 1249126274Sdes error("Invalid command."); 1250181111Sdes return -1; 1251126274Sdes } 1252126274Sdes 1253126274Sdes /* Get arguments and parse flags */ 1254262566Sdes *aflag = *fflag = *hflag = *iflag = *lflag = *pflag = 0; 1255262566Sdes *rflag = *sflag = 0; 1256126274Sdes *path1 = *path2 = NULL; 1257181111Sdes optidx = 1; 1258126274Sdes switch (cmdnum) { 1259126274Sdes case I_GET: 1260255767Sdes case I_REGET: 1261295367Sdes case I_REPUT: 1262126274Sdes case I_PUT: 1263221420Sdes if ((optidx = parse_getput_flags(cmd, argv, argc, 1264262566Sdes aflag, fflag, pflag, rflag)) == -1) 1265181111Sdes return -1; 1266126274Sdes /* Get first pathname (mandatory) */ 1267181111Sdes if (argc - optidx < 1) { 1268126274Sdes error("You must specify at least one path after a " 1269126274Sdes "%s command.", cmd); 1270181111Sdes return -1; 1271126274Sdes } 1272181111Sdes *path1 = xstrdup(argv[optidx]); 1273181111Sdes /* Get second pathname (optional) */ 1274181111Sdes if (argc - optidx > 1) { 1275181111Sdes *path2 = xstrdup(argv[optidx + 1]); 1276181111Sdes /* Destination is not globbed */ 1277181111Sdes undo_glob_escape(*path2); 1278181111Sdes } 1279126274Sdes break; 1280221420Sdes case I_LINK: 1281221420Sdes if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1) 1282221420Sdes return -1; 1283262566Sdes goto parse_two_paths; 1284262566Sdes case I_RENAME: 1285262566Sdes if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1) 1286262566Sdes return -1; 1287262566Sdes goto parse_two_paths; 1288221420Sdes case I_SYMLINK: 1289262566Sdes if ((optidx = parse_no_flags(cmd, argv, argc)) == -1) 1290262566Sdes return -1; 1291262566Sdes parse_two_paths: 1292181111Sdes if (argc - optidx < 2) { 1293126274Sdes error("You must specify two paths after a %s " 1294126274Sdes "command.", cmd); 1295181111Sdes return -1; 1296126274Sdes } 1297181111Sdes *path1 = xstrdup(argv[optidx]); 1298181111Sdes *path2 = xstrdup(argv[optidx + 1]); 1299181111Sdes /* Paths are not globbed */ 1300181111Sdes undo_glob_escape(*path1); 1301181111Sdes undo_glob_escape(*path2); 1302126274Sdes break; 1303126274Sdes case I_RM: 1304126274Sdes case I_MKDIR: 1305126274Sdes case I_RMDIR: 1306126274Sdes case I_CHDIR: 1307126274Sdes case I_LCHDIR: 1308126274Sdes case I_LMKDIR: 1309262566Sdes if ((optidx = parse_no_flags(cmd, argv, argc)) == -1) 1310262566Sdes return -1; 1311126274Sdes /* Get pathname (mandatory) */ 1312181111Sdes if (argc - optidx < 1) { 1313126274Sdes error("You must specify a path after a %s command.", 1314126274Sdes cmd); 1315181111Sdes return -1; 1316126274Sdes } 1317181111Sdes *path1 = xstrdup(argv[optidx]); 1318181111Sdes /* Only "rm" globs */ 1319181111Sdes if (cmdnum != I_RM) 1320181111Sdes undo_glob_escape(*path1); 1321126274Sdes break; 1322181111Sdes case I_DF: 1323181111Sdes if ((optidx = parse_df_flags(cmd, argv, argc, hflag, 1324181111Sdes iflag)) == -1) 1325181111Sdes return -1; 1326181111Sdes /* Default to current directory if no path specified */ 1327181111Sdes if (argc - optidx < 1) 1328181111Sdes *path1 = NULL; 1329181111Sdes else { 1330181111Sdes *path1 = xstrdup(argv[optidx]); 1331181111Sdes undo_glob_escape(*path1); 1332181111Sdes } 1333181111Sdes break; 1334126274Sdes case I_LS: 1335181111Sdes if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1) 1336126274Sdes return(-1); 1337126274Sdes /* Path is optional */ 1338181111Sdes if (argc - optidx > 0) 1339181111Sdes *path1 = xstrdup(argv[optidx]); 1340126274Sdes break; 1341126274Sdes case I_LLS: 1342181111Sdes /* Skip ls command and following whitespace */ 1343181111Sdes cp = cp + strlen(cmd) + strspn(cp, WHITESPACE); 1344126274Sdes case I_SHELL: 1345126274Sdes /* Uses the rest of the line */ 1346126274Sdes break; 1347126274Sdes case I_LUMASK: 1348126274Sdes case I_CHMOD: 1349126274Sdes base = 8; 1350126274Sdes case I_CHOWN: 1351126274Sdes case I_CHGRP: 1352262566Sdes if ((optidx = parse_no_flags(cmd, argv, argc)) == -1) 1353262566Sdes return -1; 1354126274Sdes /* Get numeric arg (mandatory) */ 1355181111Sdes if (argc - optidx < 1) 1356181111Sdes goto need_num_arg; 1357164146Sdes errno = 0; 1358181111Sdes l = strtol(argv[optidx], &cp2, base); 1359181111Sdes if (cp2 == argv[optidx] || *cp2 != '\0' || 1360181111Sdes ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) || 1361181111Sdes l < 0) { 1362181111Sdes need_num_arg: 1363126274Sdes error("You must supply a numeric argument " 1364126274Sdes "to the %s command.", cmd); 1365181111Sdes return -1; 1366126274Sdes } 1367126274Sdes *n_arg = l; 1368181111Sdes if (cmdnum == I_LUMASK) 1369126274Sdes break; 1370126274Sdes /* Get pathname (mandatory) */ 1371181111Sdes if (argc - optidx < 2) { 1372126274Sdes error("You must specify a path after a %s command.", 1373126274Sdes cmd); 1374181111Sdes return -1; 1375126274Sdes } 1376181111Sdes *path1 = xstrdup(argv[optidx + 1]); 1377126274Sdes break; 1378126274Sdes case I_QUIT: 1379126274Sdes case I_PWD: 1380126274Sdes case I_LPWD: 1381126274Sdes case I_HELP: 1382126274Sdes case I_VERSION: 1383126274Sdes case I_PROGRESS: 1384262566Sdes if ((optidx = parse_no_flags(cmd, argv, argc)) == -1) 1385262566Sdes return -1; 1386126274Sdes break; 1387126274Sdes default: 1388126274Sdes fatal("Command not implemented"); 1389126274Sdes } 1390126274Sdes 1391126274Sdes *cpp = cp; 1392126274Sdes return(cmdnum); 1393126274Sdes} 1394126274Sdes 1395126274Sdesstatic int 1396126274Sdesparse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, 1397126274Sdes int err_abort) 1398126274Sdes{ 1399126274Sdes char *path1, *path2, *tmp; 1400295367Sdes int ignore_errors = 0, aflag = 0, fflag = 0, hflag = 0, 1401295367Sdes iflag = 0; 1402262566Sdes int lflag = 0, pflag = 0, rflag = 0, sflag = 0; 1403221420Sdes int cmdnum, i; 1404192595Sdes unsigned long n_arg = 0; 1405126274Sdes Attrib a, *aa; 1406295367Sdes char path_buf[PATH_MAX]; 1407126274Sdes int err = 0; 1408126274Sdes glob_t g; 1409126274Sdes 1410126274Sdes path1 = path2 = NULL; 1411262566Sdes cmdnum = parse_args(&cmd, &ignore_errors, &aflag, &fflag, &hflag, 1412262566Sdes &iflag, &lflag, &pflag, &rflag, &sflag, &n_arg, &path1, &path2); 1413262566Sdes if (ignore_errors != 0) 1414126274Sdes err_abort = 0; 1415126274Sdes 1416126274Sdes memset(&g, 0, sizeof(g)); 1417126274Sdes 1418126274Sdes /* Perform command */ 1419126274Sdes switch (cmdnum) { 1420126274Sdes case 0: 1421126274Sdes /* Blank line */ 1422126274Sdes break; 1423126274Sdes case -1: 1424126274Sdes /* Unrecognized command */ 1425126274Sdes err = -1; 1426126274Sdes break; 1427255767Sdes case I_REGET: 1428255767Sdes aflag = 1; 1429255767Sdes /* FALLTHROUGH */ 1430126274Sdes case I_GET: 1431255767Sdes err = process_get(conn, path1, path2, *pwd, pflag, 1432262566Sdes rflag, aflag, fflag); 1433126274Sdes break; 1434295367Sdes case I_REPUT: 1435295367Sdes aflag = 1; 1436295367Sdes /* FALLTHROUGH */ 1437126274Sdes case I_PUT: 1438262566Sdes err = process_put(conn, path1, path2, *pwd, pflag, 1439295367Sdes rflag, aflag, fflag); 1440126274Sdes break; 1441126274Sdes case I_RENAME: 1442126274Sdes path1 = make_absolute(path1, *pwd); 1443126274Sdes path2 = make_absolute(path2, *pwd); 1444262566Sdes err = do_rename(conn, path1, path2, lflag); 1445126274Sdes break; 1446126274Sdes case I_SYMLINK: 1447221420Sdes sflag = 1; 1448221420Sdes case I_LINK: 1449262566Sdes if (!sflag) 1450262566Sdes path1 = make_absolute(path1, *pwd); 1451126274Sdes path2 = make_absolute(path2, *pwd); 1452221420Sdes err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2); 1453126274Sdes break; 1454126274Sdes case I_RM: 1455126274Sdes path1 = make_absolute(path1, *pwd); 1456126274Sdes remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); 1457137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 1458255767Sdes if (!quiet) 1459255767Sdes printf("Removing %s\n", g.gl_pathv[i]); 1460126274Sdes err = do_rm(conn, g.gl_pathv[i]); 1461126274Sdes if (err != 0 && err_abort) 1462126274Sdes break; 1463126274Sdes } 1464126274Sdes break; 1465126274Sdes case I_MKDIR: 1466126274Sdes path1 = make_absolute(path1, *pwd); 1467126274Sdes attrib_clear(&a); 1468126274Sdes a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; 1469126274Sdes a.perm = 0777; 1470204917Sdes err = do_mkdir(conn, path1, &a, 1); 1471126274Sdes break; 1472126274Sdes case I_RMDIR: 1473126274Sdes path1 = make_absolute(path1, *pwd); 1474126274Sdes err = do_rmdir(conn, path1); 1475126274Sdes break; 1476126274Sdes case I_CHDIR: 1477126274Sdes path1 = make_absolute(path1, *pwd); 1478126274Sdes if ((tmp = do_realpath(conn, path1)) == NULL) { 1479126274Sdes err = 1; 1480126274Sdes break; 1481126274Sdes } 1482126274Sdes if ((aa = do_stat(conn, tmp, 0)) == NULL) { 1483255767Sdes free(tmp); 1484126274Sdes err = 1; 1485126274Sdes break; 1486126274Sdes } 1487126274Sdes if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) { 1488126274Sdes error("Can't change directory: Can't check target"); 1489255767Sdes free(tmp); 1490126274Sdes err = 1; 1491126274Sdes break; 1492126274Sdes } 1493126274Sdes if (!S_ISDIR(aa->perm)) { 1494126274Sdes error("Can't change directory: \"%s\" is not " 1495126274Sdes "a directory", tmp); 1496255767Sdes free(tmp); 1497126274Sdes err = 1; 1498126274Sdes break; 1499126274Sdes } 1500255767Sdes free(*pwd); 1501126274Sdes *pwd = tmp; 1502126274Sdes break; 1503126274Sdes case I_LS: 1504126274Sdes if (!path1) { 1505215116Sdes do_ls_dir(conn, *pwd, *pwd, lflag); 1506126274Sdes break; 1507126274Sdes } 1508126274Sdes 1509126274Sdes /* Strip pwd off beginning of non-absolute paths */ 1510126274Sdes tmp = NULL; 1511126274Sdes if (*path1 != '/') 1512126274Sdes tmp = *pwd; 1513126274Sdes 1514126274Sdes path1 = make_absolute(path1, *pwd); 1515126274Sdes err = do_globbed_ls(conn, path1, tmp, lflag); 1516126274Sdes break; 1517181111Sdes case I_DF: 1518181111Sdes /* Default to current directory if no path specified */ 1519181111Sdes if (path1 == NULL) 1520181111Sdes path1 = xstrdup(*pwd); 1521181111Sdes path1 = make_absolute(path1, *pwd); 1522181111Sdes err = do_df(conn, path1, hflag, iflag); 1523181111Sdes break; 1524126274Sdes case I_LCHDIR: 1525295367Sdes tmp = tilde_expand_filename(path1, getuid()); 1526295367Sdes free(path1); 1527295367Sdes path1 = tmp; 1528126274Sdes if (chdir(path1) == -1) { 1529126274Sdes error("Couldn't change local directory to " 1530126274Sdes "\"%s\": %s", path1, strerror(errno)); 1531126274Sdes err = 1; 1532126274Sdes } 1533126274Sdes break; 1534126274Sdes case I_LMKDIR: 1535126274Sdes if (mkdir(path1, 0777) == -1) { 1536126274Sdes error("Couldn't create local directory " 1537126274Sdes "\"%s\": %s", path1, strerror(errno)); 1538126274Sdes err = 1; 1539126274Sdes } 1540126274Sdes break; 1541126274Sdes case I_LLS: 1542126274Sdes local_do_ls(cmd); 1543126274Sdes break; 1544126274Sdes case I_SHELL: 1545126274Sdes local_do_shell(cmd); 1546126274Sdes break; 1547126274Sdes case I_LUMASK: 1548126274Sdes umask(n_arg); 1549126274Sdes printf("Local umask: %03lo\n", n_arg); 1550126274Sdes break; 1551126274Sdes case I_CHMOD: 1552126274Sdes path1 = make_absolute(path1, *pwd); 1553126274Sdes attrib_clear(&a); 1554126274Sdes a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; 1555126274Sdes a.perm = n_arg; 1556126274Sdes remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); 1557137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 1558255767Sdes if (!quiet) 1559255767Sdes printf("Changing mode on %s\n", g.gl_pathv[i]); 1560126274Sdes err = do_setstat(conn, g.gl_pathv[i], &a); 1561126274Sdes if (err != 0 && err_abort) 1562126274Sdes break; 1563126274Sdes } 1564126274Sdes break; 1565126274Sdes case I_CHOWN: 1566126274Sdes case I_CHGRP: 1567126274Sdes path1 = make_absolute(path1, *pwd); 1568126274Sdes remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); 1569137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 1570126274Sdes if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) { 1571192595Sdes if (err_abort) { 1572192595Sdes err = -1; 1573126274Sdes break; 1574192595Sdes } else 1575126274Sdes continue; 1576126274Sdes } 1577126274Sdes if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) { 1578126274Sdes error("Can't get current ownership of " 1579126274Sdes "remote file \"%s\"", g.gl_pathv[i]); 1580192595Sdes if (err_abort) { 1581192595Sdes err = -1; 1582126274Sdes break; 1583192595Sdes } else 1584126274Sdes continue; 1585126274Sdes } 1586126274Sdes aa->flags &= SSH2_FILEXFER_ATTR_UIDGID; 1587126274Sdes if (cmdnum == I_CHOWN) { 1588255767Sdes if (!quiet) 1589255767Sdes printf("Changing owner on %s\n", 1590255767Sdes g.gl_pathv[i]); 1591126274Sdes aa->uid = n_arg; 1592126274Sdes } else { 1593255767Sdes if (!quiet) 1594255767Sdes printf("Changing group on %s\n", 1595255767Sdes g.gl_pathv[i]); 1596126274Sdes aa->gid = n_arg; 1597126274Sdes } 1598126274Sdes err = do_setstat(conn, g.gl_pathv[i], aa); 1599126274Sdes if (err != 0 && err_abort) 1600126274Sdes break; 1601126274Sdes } 1602126274Sdes break; 1603126274Sdes case I_PWD: 1604126274Sdes printf("Remote working directory: %s\n", *pwd); 1605126274Sdes break; 1606126274Sdes case I_LPWD: 1607126274Sdes if (!getcwd(path_buf, sizeof(path_buf))) { 1608126274Sdes error("Couldn't get local cwd: %s", strerror(errno)); 1609126274Sdes err = -1; 1610126274Sdes break; 1611126274Sdes } 1612126274Sdes printf("Local working directory: %s\n", path_buf); 1613126274Sdes break; 1614126274Sdes case I_QUIT: 1615126274Sdes /* Processed below */ 1616126274Sdes break; 1617126274Sdes case I_HELP: 1618126274Sdes help(); 1619126274Sdes break; 1620126274Sdes case I_VERSION: 1621126274Sdes printf("SFTP protocol version %u\n", sftp_proto_version(conn)); 1622126274Sdes break; 1623126274Sdes case I_PROGRESS: 1624126274Sdes showprogress = !showprogress; 1625126274Sdes if (showprogress) 1626126274Sdes printf("Progress meter enabled\n"); 1627126274Sdes else 1628126274Sdes printf("Progress meter disabled\n"); 1629126274Sdes break; 1630126274Sdes default: 1631126274Sdes fatal("%d is not implemented", cmdnum); 1632126274Sdes } 1633126274Sdes 1634126274Sdes if (g.gl_pathc) 1635126274Sdes globfree(&g); 1636255767Sdes free(path1); 1637255767Sdes free(path2); 1638126274Sdes 1639126274Sdes /* If an unignored error occurs in batch mode we should abort. */ 1640126274Sdes if (err_abort && err != 0) 1641126274Sdes return (-1); 1642126274Sdes else if (cmdnum == I_QUIT) 1643126274Sdes return (1); 1644126274Sdes 1645126274Sdes return (0); 1646126274Sdes} 1647126274Sdes 1648146998Sdes#ifdef USE_LIBEDIT 1649146998Sdesstatic char * 1650146998Sdesprompt(EditLine *el) 1651146998Sdes{ 1652146998Sdes return ("sftp> "); 1653146998Sdes} 1654146998Sdes 1655204917Sdes/* Display entries in 'list' after skipping the first 'len' chars */ 1656204917Sdesstatic void 1657204917Sdescomplete_display(char **list, u_int len) 1658204917Sdes{ 1659204917Sdes u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen; 1660204917Sdes struct winsize ws; 1661204917Sdes char *tmp; 1662204917Sdes 1663204917Sdes /* Count entries for sort and find longest */ 1664262566Sdes for (y = 0; list[y]; y++) 1665204917Sdes m = MAX(m, strlen(list[y])); 1666204917Sdes 1667204917Sdes if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1) 1668204917Sdes width = ws.ws_col; 1669204917Sdes 1670204917Sdes m = m > len ? m - len : 0; 1671204917Sdes columns = width / (m + 2); 1672204917Sdes columns = MAX(columns, 1); 1673204917Sdes colspace = width / columns; 1674204917Sdes colspace = MIN(colspace, width); 1675204917Sdes 1676204917Sdes printf("\n"); 1677204917Sdes m = 1; 1678204917Sdes for (y = 0; list[y]; y++) { 1679204917Sdes llen = strlen(list[y]); 1680204917Sdes tmp = llen > len ? list[y] + len : ""; 1681204917Sdes printf("%-*s", colspace, tmp); 1682204917Sdes if (m >= columns) { 1683204917Sdes printf("\n"); 1684204917Sdes m = 1; 1685204917Sdes } else 1686204917Sdes m++; 1687204917Sdes } 1688204917Sdes printf("\n"); 1689204917Sdes} 1690204917Sdes 1691204917Sdes/* 1692204917Sdes * Given a "list" of words that begin with a common prefix of "word", 1693204917Sdes * attempt to find an autocompletion to extends "word" by the next 1694204917Sdes * characters common to all entries in "list". 1695204917Sdes */ 1696204917Sdesstatic char * 1697204917Sdescomplete_ambiguous(const char *word, char **list, size_t count) 1698204917Sdes{ 1699204917Sdes if (word == NULL) 1700204917Sdes return NULL; 1701204917Sdes 1702204917Sdes if (count > 0) { 1703204917Sdes u_int y, matchlen = strlen(list[0]); 1704204917Sdes 1705204917Sdes /* Find length of common stem */ 1706204917Sdes for (y = 1; list[y]; y++) { 1707204917Sdes u_int x; 1708204917Sdes 1709262566Sdes for (x = 0; x < matchlen; x++) 1710262566Sdes if (list[0][x] != list[y][x]) 1711204917Sdes break; 1712204917Sdes 1713204917Sdes matchlen = x; 1714204917Sdes } 1715204917Sdes 1716204917Sdes if (matchlen > strlen(word)) { 1717204917Sdes char *tmp = xstrdup(list[0]); 1718204917Sdes 1719204917Sdes tmp[matchlen] = '\0'; 1720204917Sdes return tmp; 1721204917Sdes } 1722262566Sdes } 1723204917Sdes 1724204917Sdes return xstrdup(word); 1725204917Sdes} 1726204917Sdes 1727204917Sdes/* Autocomplete a sftp command */ 1728204917Sdesstatic int 1729204917Sdescomplete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote, 1730204917Sdes int terminated) 1731204917Sdes{ 1732204917Sdes u_int y, count = 0, cmdlen, tmplen; 1733204917Sdes char *tmp, **list, argterm[3]; 1734204917Sdes const LineInfo *lf; 1735204917Sdes 1736204917Sdes list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *)); 1737204917Sdes 1738204917Sdes /* No command specified: display all available commands */ 1739204917Sdes if (cmd == NULL) { 1740204917Sdes for (y = 0; cmds[y].c; y++) 1741204917Sdes list[count++] = xstrdup(cmds[y].c); 1742262566Sdes 1743204917Sdes list[count] = NULL; 1744204917Sdes complete_display(list, 0); 1745204917Sdes 1746262566Sdes for (y = 0; list[y] != NULL; y++) 1747262566Sdes free(list[y]); 1748255767Sdes free(list); 1749204917Sdes return count; 1750204917Sdes } 1751204917Sdes 1752204917Sdes /* Prepare subset of commands that start with "cmd" */ 1753204917Sdes cmdlen = strlen(cmd); 1754204917Sdes for (y = 0; cmds[y].c; y++) { 1755262566Sdes if (!strncasecmp(cmd, cmds[y].c, cmdlen)) 1756204917Sdes list[count++] = xstrdup(cmds[y].c); 1757204917Sdes } 1758204917Sdes list[count] = NULL; 1759204917Sdes 1760240075Sdes if (count == 0) { 1761255767Sdes free(list); 1762204917Sdes return 0; 1763240075Sdes } 1764204917Sdes 1765204917Sdes /* Complete ambigious command */ 1766204917Sdes tmp = complete_ambiguous(cmd, list, count); 1767204917Sdes if (count > 1) 1768204917Sdes complete_display(list, 0); 1769204917Sdes 1770262566Sdes for (y = 0; list[y]; y++) 1771262566Sdes free(list[y]); 1772255767Sdes free(list); 1773204917Sdes 1774204917Sdes if (tmp != NULL) { 1775204917Sdes tmplen = strlen(tmp); 1776204917Sdes cmdlen = strlen(cmd); 1777204917Sdes /* If cmd may be extended then do so */ 1778204917Sdes if (tmplen > cmdlen) 1779204917Sdes if (el_insertstr(el, tmp + cmdlen) == -1) 1780204917Sdes fatal("el_insertstr failed."); 1781204917Sdes lf = el_line(el); 1782204917Sdes /* Terminate argument cleanly */ 1783204917Sdes if (count == 1) { 1784204917Sdes y = 0; 1785204917Sdes if (!terminated) 1786204917Sdes argterm[y++] = quote; 1787204917Sdes if (lastarg || *(lf->cursor) != ' ') 1788204917Sdes argterm[y++] = ' '; 1789204917Sdes argterm[y] = '\0'; 1790204917Sdes if (y > 0 && el_insertstr(el, argterm) == -1) 1791204917Sdes fatal("el_insertstr failed."); 1792204917Sdes } 1793255767Sdes free(tmp); 1794204917Sdes } 1795204917Sdes 1796204917Sdes return count; 1797204917Sdes} 1798204917Sdes 1799204917Sdes/* 1800204917Sdes * Determine whether a particular sftp command's arguments (if any) 1801204917Sdes * represent local or remote files. 1802204917Sdes */ 1803204917Sdesstatic int 1804204917Sdescomplete_is_remote(char *cmd) { 1805204917Sdes int i; 1806204917Sdes 1807204917Sdes if (cmd == NULL) 1808204917Sdes return -1; 1809204917Sdes 1810204917Sdes for (i = 0; cmds[i].c; i++) { 1811262566Sdes if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c))) 1812204917Sdes return cmds[i].t; 1813204917Sdes } 1814204917Sdes 1815204917Sdes return -1; 1816204917Sdes} 1817204917Sdes 1818204917Sdes/* Autocomplete a filename "file" */ 1819204917Sdesstatic int 1820204917Sdescomplete_match(EditLine *el, struct sftp_conn *conn, char *remote_path, 1821204917Sdes char *file, int remote, int lastarg, char quote, int terminated) 1822204917Sdes{ 1823204917Sdes glob_t g; 1824255767Sdes char *tmp, *tmp2, ins[8]; 1825248619Sdes u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs; 1826255767Sdes int clen; 1827204917Sdes const LineInfo *lf; 1828262566Sdes 1829204917Sdes /* Glob from "file" location */ 1830204917Sdes if (file == NULL) 1831204917Sdes tmp = xstrdup("*"); 1832204917Sdes else 1833204917Sdes xasprintf(&tmp, "%s*", file); 1834204917Sdes 1835248619Sdes /* Check if the path is absolute. */ 1836248619Sdes isabs = tmp[0] == '/'; 1837248619Sdes 1838204917Sdes memset(&g, 0, sizeof(g)); 1839204917Sdes if (remote != LOCAL) { 1840204917Sdes tmp = make_absolute(tmp, remote_path); 1841204917Sdes remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g); 1842262566Sdes } else 1843204917Sdes glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g); 1844262566Sdes 1845204917Sdes /* Determine length of pwd so we can trim completion display */ 1846204917Sdes for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) { 1847204917Sdes /* Terminate counting on first unescaped glob metacharacter */ 1848204917Sdes if (tmp[tmplen] == '*' || tmp[tmplen] == '?') { 1849204917Sdes if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0') 1850204917Sdes hadglob = 1; 1851204917Sdes break; 1852204917Sdes } 1853204917Sdes if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0') 1854204917Sdes tmplen++; 1855204917Sdes if (tmp[tmplen] == '/') 1856204917Sdes pwdlen = tmplen + 1; /* track last seen '/' */ 1857204917Sdes } 1858255767Sdes free(tmp); 1859295367Sdes tmp = NULL; 1860204917Sdes 1861262566Sdes if (g.gl_matchc == 0) 1862204917Sdes goto out; 1863204917Sdes 1864204917Sdes if (g.gl_matchc > 1) 1865204917Sdes complete_display(g.gl_pathv, pwdlen); 1866204917Sdes 1867204917Sdes /* Don't try to extend globs */ 1868204917Sdes if (file == NULL || hadglob) 1869204917Sdes goto out; 1870204917Sdes 1871204917Sdes tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc); 1872248619Sdes tmp = path_strip(tmp2, isabs ? NULL : remote_path); 1873255767Sdes free(tmp2); 1874204917Sdes 1875204917Sdes if (tmp == NULL) 1876204917Sdes goto out; 1877204917Sdes 1878204917Sdes tmplen = strlen(tmp); 1879204917Sdes filelen = strlen(file); 1880204917Sdes 1881248619Sdes /* Count the number of escaped characters in the input string. */ 1882248619Sdes cesc = isesc = 0; 1883248619Sdes for (i = 0; i < filelen; i++) { 1884248619Sdes if (!isesc && file[i] == '\\' && i + 1 < filelen){ 1885248619Sdes isesc = 1; 1886248619Sdes cesc++; 1887248619Sdes } else 1888248619Sdes isesc = 0; 1889248619Sdes } 1890248619Sdes 1891248619Sdes if (tmplen > (filelen - cesc)) { 1892248619Sdes tmp2 = tmp + filelen - cesc; 1893262566Sdes len = strlen(tmp2); 1894204917Sdes /* quote argument on way out */ 1895255767Sdes for (i = 0; i < len; i += clen) { 1896255767Sdes if ((clen = mblen(tmp2 + i, len - i)) < 0 || 1897255767Sdes (size_t)clen > sizeof(ins) - 2) 1898255767Sdes fatal("invalid multibyte character"); 1899204917Sdes ins[0] = '\\'; 1900255767Sdes memcpy(ins + 1, tmp2 + i, clen); 1901255767Sdes ins[clen + 1] = '\0'; 1902204917Sdes switch (tmp2[i]) { 1903204917Sdes case '\'': 1904204917Sdes case '"': 1905204917Sdes case '\\': 1906204917Sdes case '\t': 1907221420Sdes case '[': 1908204917Sdes case ' ': 1909248619Sdes case '#': 1910248619Sdes case '*': 1911204917Sdes if (quote == '\0' || tmp2[i] == quote) { 1912204917Sdes if (el_insertstr(el, ins) == -1) 1913204917Sdes fatal("el_insertstr " 1914204917Sdes "failed."); 1915204917Sdes break; 1916204917Sdes } 1917204917Sdes /* FALLTHROUGH */ 1918204917Sdes default: 1919204917Sdes if (el_insertstr(el, ins + 1) == -1) 1920204917Sdes fatal("el_insertstr failed."); 1921204917Sdes break; 1922204917Sdes } 1923204917Sdes } 1924204917Sdes } 1925204917Sdes 1926204917Sdes lf = el_line(el); 1927204917Sdes if (g.gl_matchc == 1) { 1928204917Sdes i = 0; 1929295367Sdes if (!terminated && quote != '\0') 1930204917Sdes ins[i++] = quote; 1931204917Sdes if (*(lf->cursor - 1) != '/' && 1932204917Sdes (lastarg || *(lf->cursor) != ' ')) 1933204917Sdes ins[i++] = ' '; 1934204917Sdes ins[i] = '\0'; 1935204917Sdes if (i > 0 && el_insertstr(el, ins) == -1) 1936204917Sdes fatal("el_insertstr failed."); 1937204917Sdes } 1938255767Sdes free(tmp); 1939204917Sdes 1940204917Sdes out: 1941204917Sdes globfree(&g); 1942204917Sdes return g.gl_matchc; 1943204917Sdes} 1944204917Sdes 1945204917Sdes/* tab-completion hook function, called via libedit */ 1946204917Sdesstatic unsigned char 1947204917Sdescomplete(EditLine *el, int ch) 1948204917Sdes{ 1949262566Sdes char **argv, *line, quote; 1950255767Sdes int argc, carg; 1951255767Sdes u_int cursor, len, terminated, ret = CC_ERROR; 1952204917Sdes const LineInfo *lf; 1953204917Sdes struct complete_ctx *complete_ctx; 1954204917Sdes 1955204917Sdes lf = el_line(el); 1956204917Sdes if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0) 1957204917Sdes fatal("%s: el_get failed", __func__); 1958204917Sdes 1959204917Sdes /* Figure out which argument the cursor points to */ 1960204917Sdes cursor = lf->cursor - lf->buffer; 1961295367Sdes line = xmalloc(cursor + 1); 1962204917Sdes memcpy(line, lf->buffer, cursor); 1963204917Sdes line[cursor] = '\0'; 1964204917Sdes argv = makeargv(line, &carg, 1, "e, &terminated); 1965255767Sdes free(line); 1966204917Sdes 1967204917Sdes /* Get all the arguments on the line */ 1968204917Sdes len = lf->lastchar - lf->buffer; 1969295367Sdes line = xmalloc(len + 1); 1970204917Sdes memcpy(line, lf->buffer, len); 1971204917Sdes line[len] = '\0'; 1972204917Sdes argv = makeargv(line, &argc, 1, NULL, NULL); 1973204917Sdes 1974204917Sdes /* Ensure cursor is at EOL or a argument boundary */ 1975204917Sdes if (line[cursor] != ' ' && line[cursor] != '\0' && 1976204917Sdes line[cursor] != '\n') { 1977255767Sdes free(line); 1978204917Sdes return ret; 1979204917Sdes } 1980204917Sdes 1981204917Sdes if (carg == 0) { 1982204917Sdes /* Show all available commands */ 1983204917Sdes complete_cmd_parse(el, NULL, argc == carg, '\0', 1); 1984204917Sdes ret = CC_REDISPLAY; 1985204917Sdes } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') { 1986204917Sdes /* Handle the command parsing */ 1987204917Sdes if (complete_cmd_parse(el, argv[0], argc == carg, 1988262566Sdes quote, terminated) != 0) 1989204917Sdes ret = CC_REDISPLAY; 1990204917Sdes } else if (carg >= 1) { 1991204917Sdes /* Handle file parsing */ 1992204917Sdes int remote = complete_is_remote(argv[0]); 1993204917Sdes char *filematch = NULL; 1994204917Sdes 1995204917Sdes if (carg > 1 && line[cursor-1] != ' ') 1996204917Sdes filematch = argv[carg - 1]; 1997204917Sdes 1998204917Sdes if (remote != 0 && 1999204917Sdes complete_match(el, complete_ctx->conn, 2000204917Sdes *complete_ctx->remote_pathp, filematch, 2001262566Sdes remote, carg == argc, quote, terminated) != 0) 2002204917Sdes ret = CC_REDISPLAY; 2003204917Sdes } 2004204917Sdes 2005262566Sdes free(line); 2006204917Sdes return ret; 2007204917Sdes} 2008204917Sdes#endif /* USE_LIBEDIT */ 2009204917Sdes 2010126274Sdesint 2011204917Sdesinteractive_loop(struct sftp_conn *conn, char *file1, char *file2) 2012126274Sdes{ 2013204917Sdes char *remote_path; 2014126274Sdes char *dir = NULL; 2015126274Sdes char cmd[2048]; 2016149749Sdes int err, interactive; 2017146998Sdes EditLine *el = NULL; 2018146998Sdes#ifdef USE_LIBEDIT 2019146998Sdes History *hl = NULL; 2020146998Sdes HistEvent hev; 2021146998Sdes extern char *__progname; 2022204917Sdes struct complete_ctx complete_ctx; 2023126274Sdes 2024146998Sdes if (!batchmode && isatty(STDIN_FILENO)) { 2025146998Sdes if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL) 2026146998Sdes fatal("Couldn't initialise editline"); 2027146998Sdes if ((hl = history_init()) == NULL) 2028146998Sdes fatal("Couldn't initialise editline history"); 2029146998Sdes history(hl, &hev, H_SETSIZE, 100); 2030146998Sdes el_set(el, EL_HIST, history, hl); 2031146998Sdes 2032146998Sdes el_set(el, EL_PROMPT, prompt); 2033146998Sdes el_set(el, EL_EDITOR, "emacs"); 2034146998Sdes el_set(el, EL_TERMINAL, NULL); 2035146998Sdes el_set(el, EL_SIGNAL, 1); 2036146998Sdes el_source(el, NULL); 2037204917Sdes 2038204917Sdes /* Tab Completion */ 2039262566Sdes el_set(el, EL_ADDFN, "ftp-complete", 2040221420Sdes "Context sensitive argument completion", complete); 2041204917Sdes complete_ctx.conn = conn; 2042204917Sdes complete_ctx.remote_pathp = &remote_path; 2043204917Sdes el_set(el, EL_CLIENTDATA, (void*)&complete_ctx); 2044204917Sdes el_set(el, EL_BIND, "^I", "ftp-complete", NULL); 2045262566Sdes /* enable ctrl-left-arrow and ctrl-right-arrow */ 2046262566Sdes el_set(el, EL_BIND, "\\e[1;5C", "em-next-word", NULL); 2047262566Sdes el_set(el, EL_BIND, "\\e[5C", "em-next-word", NULL); 2048262566Sdes el_set(el, EL_BIND, "\\e[1;5D", "ed-prev-word", NULL); 2049262566Sdes el_set(el, EL_BIND, "\\e\\e[D", "ed-prev-word", NULL); 2050262566Sdes /* make ^w match ksh behaviour */ 2051262566Sdes el_set(el, EL_BIND, "^w", "ed-delete-prev-word", NULL); 2052146998Sdes } 2053146998Sdes#endif /* USE_LIBEDIT */ 2054146998Sdes 2055204917Sdes remote_path = do_realpath(conn, "."); 2056204917Sdes if (remote_path == NULL) 2057126274Sdes fatal("Need cwd"); 2058126274Sdes 2059126274Sdes if (file1 != NULL) { 2060126274Sdes dir = xstrdup(file1); 2061204917Sdes dir = make_absolute(dir, remote_path); 2062126274Sdes 2063126274Sdes if (remote_is_dir(conn, dir) && file2 == NULL) { 2064255767Sdes if (!quiet) 2065255767Sdes printf("Changing to: %s\n", dir); 2066126274Sdes snprintf(cmd, sizeof cmd, "cd \"%s\"", dir); 2067204917Sdes if (parse_dispatch_command(conn, cmd, 2068204917Sdes &remote_path, 1) != 0) { 2069255767Sdes free(dir); 2070255767Sdes free(remote_path); 2071255767Sdes free(conn); 2072126274Sdes return (-1); 2073146998Sdes } 2074126274Sdes } else { 2075248619Sdes /* XXX this is wrong wrt quoting */ 2076255767Sdes snprintf(cmd, sizeof cmd, "get%s %s%s%s", 2077255767Sdes global_aflag ? " -a" : "", dir, 2078255767Sdes file2 == NULL ? "" : " ", 2079255767Sdes file2 == NULL ? "" : file2); 2080204917Sdes err = parse_dispatch_command(conn, cmd, 2081204917Sdes &remote_path, 1); 2082255767Sdes free(dir); 2083255767Sdes free(remote_path); 2084255767Sdes free(conn); 2085126274Sdes return (err); 2086126274Sdes } 2087255767Sdes free(dir); 2088126274Sdes } 2089126274Sdes 2090295367Sdes setvbuf(stdout, NULL, _IOLBF, 0); 2091295367Sdes setvbuf(infile, NULL, _IOLBF, 0); 2092126274Sdes 2093149749Sdes interactive = !batchmode && isatty(STDIN_FILENO); 2094126274Sdes err = 0; 2095126274Sdes for (;;) { 2096126274Sdes char *cp; 2097126274Sdes 2098137015Sdes signal(SIGINT, SIG_IGN); 2099137015Sdes 2100146998Sdes if (el == NULL) { 2101149749Sdes if (interactive) 2102149749Sdes printf("sftp> "); 2103146998Sdes if (fgets(cmd, sizeof(cmd), infile) == NULL) { 2104149749Sdes if (interactive) 2105149749Sdes printf("\n"); 2106146998Sdes break; 2107146998Sdes } 2108149749Sdes if (!interactive) { /* Echo command */ 2109149749Sdes printf("sftp> %s", cmd); 2110149749Sdes if (strlen(cmd) > 0 && 2111149749Sdes cmd[strlen(cmd) - 1] != '\n') 2112149749Sdes printf("\n"); 2113149749Sdes } 2114146998Sdes } else { 2115146998Sdes#ifdef USE_LIBEDIT 2116146998Sdes const char *line; 2117146998Sdes int count = 0; 2118126274Sdes 2119204917Sdes if ((line = el_gets(el, &count)) == NULL || 2120204917Sdes count <= 0) { 2121149749Sdes printf("\n"); 2122149749Sdes break; 2123149749Sdes } 2124146998Sdes history(hl, &hev, H_ENTER, line); 2125146998Sdes if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) { 2126146998Sdes fprintf(stderr, "Error: input line too long\n"); 2127146998Sdes continue; 2128146998Sdes } 2129146998Sdes#endif /* USE_LIBEDIT */ 2130126274Sdes } 2131126274Sdes 2132126274Sdes cp = strrchr(cmd, '\n'); 2133126274Sdes if (cp) 2134126274Sdes *cp = '\0'; 2135126274Sdes 2136137015Sdes /* Handle user interrupts gracefully during commands */ 2137137015Sdes interrupted = 0; 2138137015Sdes signal(SIGINT, cmd_interrupt); 2139137015Sdes 2140204917Sdes err = parse_dispatch_command(conn, cmd, &remote_path, 2141204917Sdes batchmode); 2142126274Sdes if (err != 0) 2143126274Sdes break; 2144126274Sdes } 2145255767Sdes free(remote_path); 2146255767Sdes free(conn); 2147126274Sdes 2148149749Sdes#ifdef USE_LIBEDIT 2149149749Sdes if (el != NULL) 2150149749Sdes el_end(el); 2151149749Sdes#endif /* USE_LIBEDIT */ 2152149749Sdes 2153126274Sdes /* err == 1 signifies normal "quit" exit */ 2154126274Sdes return (err >= 0 ? 0 : -1); 2155126274Sdes} 2156126274Sdes 2157126274Sdesstatic void 2158124208Sdesconnect_to_server(char *path, char **args, int *in, int *out) 2159124208Sdes{ 216076259Sgreen int c_in, c_out; 216199060Sdes 216276259Sgreen#ifdef USE_PIPES 216376259Sgreen int pin[2], pout[2]; 216499060Sdes 216576259Sgreen if ((pipe(pin) == -1) || (pipe(pout) == -1)) 216676259Sgreen fatal("pipe: %s", strerror(errno)); 216776259Sgreen *in = pin[0]; 216876259Sgreen *out = pout[1]; 216976259Sgreen c_in = pout[0]; 217076259Sgreen c_out = pin[1]; 217176259Sgreen#else /* USE_PIPES */ 217276259Sgreen int inout[2]; 217399060Sdes 217476259Sgreen if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1) 217576259Sgreen fatal("socketpair: %s", strerror(errno)); 217676259Sgreen *in = *out = inout[0]; 217776259Sgreen c_in = c_out = inout[1]; 217876259Sgreen#endif /* USE_PIPES */ 217976259Sgreen 2180124208Sdes if ((sshpid = fork()) == -1) 218176259Sgreen fatal("fork: %s", strerror(errno)); 2182124208Sdes else if (sshpid == 0) { 218376259Sgreen if ((dup2(c_in, STDIN_FILENO) == -1) || 218476259Sgreen (dup2(c_out, STDOUT_FILENO) == -1)) { 218576259Sgreen fprintf(stderr, "dup2: %s\n", strerror(errno)); 2186137015Sdes _exit(1); 218776259Sgreen } 218876259Sgreen close(*in); 218976259Sgreen close(*out); 219076259Sgreen close(c_in); 219176259Sgreen close(c_out); 2192137015Sdes 2193137015Sdes /* 2194137015Sdes * The underlying ssh is in the same process group, so we must 2195137015Sdes * ignore SIGINT if we want to gracefully abort commands, 2196137015Sdes * otherwise the signal will make it to the ssh process and 2197204917Sdes * kill it too. Contrawise, since sftp sends SIGTERMs to the 2198204917Sdes * underlying ssh, it must *not* ignore that signal. 2199137015Sdes */ 2200137015Sdes signal(SIGINT, SIG_IGN); 2201204917Sdes signal(SIGTERM, SIG_DFL); 2202137015Sdes execvp(path, args); 220392555Sdes fprintf(stderr, "exec: %s: %s\n", path, strerror(errno)); 2204137015Sdes _exit(1); 220576259Sgreen } 220676259Sgreen 2207124208Sdes signal(SIGTERM, killchild); 2208124208Sdes signal(SIGINT, killchild); 2209124208Sdes signal(SIGHUP, killchild); 221076259Sgreen close(c_in); 221176259Sgreen close(c_out); 221276259Sgreen} 221376259Sgreen 221492555Sdesstatic void 221576259Sgreenusage(void) 221676259Sgreen{ 221792555Sdes extern char *__progname; 221898675Sdes 221992555Sdes fprintf(stderr, 2220262566Sdes "usage: %s [-1246aCfpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n" 2221204917Sdes " [-D sftp_server_path] [-F ssh_config] " 2222221420Sdes "[-i identity_file] [-l limit]\n" 2223204917Sdes " [-o ssh_option] [-P port] [-R num_requests] " 2224204917Sdes "[-S program]\n" 2225204917Sdes " [-s subsystem | sftp_server] host\n" 2226192595Sdes " %s [user@]host[:file ...]\n" 2227192595Sdes " %s [user@]host[:dir[/]]\n" 2228204917Sdes " %s -b batchfile [user@]host\n", 2229204917Sdes __progname, __progname, __progname, __progname); 223076259Sgreen exit(1); 223176259Sgreen} 223276259Sgreen 223376259Sgreenint 223476259Sgreenmain(int argc, char **argv) 223576259Sgreen{ 2236113908Sdes int in, out, ch, err; 2237204917Sdes char *host = NULL, *userhost, *cp, *file2 = NULL; 223892555Sdes int debug_level = 0, sshver = 2; 223992555Sdes char *file1 = NULL, *sftp_server = NULL; 224092555Sdes char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL; 2241221420Sdes const char *errstr; 224292555Sdes LogLevel ll = SYSLOG_LEVEL_INFO; 224392555Sdes arglist args; 224476259Sgreen extern int optind; 224576259Sgreen extern char *optarg; 2246204917Sdes struct sftp_conn *conn; 2247204917Sdes size_t copy_buffer_len = DEFAULT_COPY_BUFLEN; 2248204917Sdes size_t num_requests = DEFAULT_NUM_REQUESTS; 2249221420Sdes long long limit_kbps = 0; 225076259Sgreen 2251296853Sdes ssh_malloc_init(); /* must be called before any mallocs */ 2252157016Sdes /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ 2253157016Sdes sanitise_stdfd(); 2254255767Sdes setlocale(LC_CTYPE, ""); 2255157016Sdes 2256124208Sdes __progname = ssh_get_progname(argv[0]); 2257157016Sdes memset(&args, '\0', sizeof(args)); 225892555Sdes args.list = NULL; 2259162852Sdes addargs(&args, "%s", ssh_program); 226092555Sdes addargs(&args, "-oForwardX11 no"); 226192555Sdes addargs(&args, "-oForwardAgent no"); 2262157016Sdes addargs(&args, "-oPermitLocalCommand no"); 226392555Sdes addargs(&args, "-oClearAllForwardings yes"); 2264126274Sdes 226592555Sdes ll = SYSLOG_LEVEL_INFO; 2266126274Sdes infile = stdin; 226776259Sgreen 2268204917Sdes while ((ch = getopt(argc, argv, 2269262566Sdes "1246afhpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) { 227076259Sgreen switch (ch) { 2271204917Sdes /* Passed through to ssh(1) */ 2272204917Sdes case '4': 2273204917Sdes case '6': 227476259Sgreen case 'C': 2275204917Sdes addargs(&args, "-%c", ch); 227676259Sgreen break; 2277204917Sdes /* Passed through to ssh(1) with argument */ 2278204917Sdes case 'F': 2279204917Sdes case 'c': 2280204917Sdes case 'i': 2281204917Sdes case 'o': 2282204917Sdes addargs(&args, "-%c", ch); 2283204917Sdes addargs(&args, "%s", optarg); 2284204917Sdes break; 2285204917Sdes case 'q': 2286255767Sdes ll = SYSLOG_LEVEL_ERROR; 2287255767Sdes quiet = 1; 2288204917Sdes showprogress = 0; 2289204917Sdes addargs(&args, "-%c", ch); 2290204917Sdes break; 2291204917Sdes case 'P': 2292204917Sdes addargs(&args, "-oPort %s", optarg); 2293204917Sdes break; 229476259Sgreen case 'v': 229592555Sdes if (debug_level < 3) { 229692555Sdes addargs(&args, "-v"); 229792555Sdes ll = SYSLOG_LEVEL_DEBUG1 + debug_level; 229892555Sdes } 229992555Sdes debug_level++; 230076259Sgreen break; 230176259Sgreen case '1': 230292555Sdes sshver = 1; 230376259Sgreen if (sftp_server == NULL) 230476259Sgreen sftp_server = _PATH_SFTP_SERVER; 230576259Sgreen break; 2306204917Sdes case '2': 2307204917Sdes sshver = 2; 230876259Sgreen break; 2309255767Sdes case 'a': 2310255767Sdes global_aflag = 1; 2311255767Sdes break; 2312204917Sdes case 'B': 2313204917Sdes copy_buffer_len = strtol(optarg, &cp, 10); 2314204917Sdes if (copy_buffer_len == 0 || *cp != '\0') 2315204917Sdes fatal("Invalid buffer size \"%s\"", optarg); 231676259Sgreen break; 231776259Sgreen case 'b': 2318126274Sdes if (batchmode) 2319126274Sdes fatal("Batch file already specified."); 2320126274Sdes 2321126274Sdes /* Allow "-" as stdin */ 2322137015Sdes if (strcmp(optarg, "-") != 0 && 2323149749Sdes (infile = fopen(optarg, "r")) == NULL) 2324126274Sdes fatal("%s (%s).", strerror(errno), optarg); 2325113908Sdes showprogress = 0; 2326255767Sdes quiet = batchmode = 1; 2327146998Sdes addargs(&args, "-obatchmode yes"); 232876259Sgreen break; 2329262566Sdes case 'f': 2330262566Sdes global_fflag = 1; 2331262566Sdes break; 2332204917Sdes case 'p': 2333204917Sdes global_pflag = 1; 2334204917Sdes break; 2335204917Sdes case 'D': 233692555Sdes sftp_direct = optarg; 233792555Sdes break; 2338221420Sdes case 'l': 2339221420Sdes limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024, 2340221420Sdes &errstr); 2341221420Sdes if (errstr != NULL) 2342221420Sdes usage(); 2343221420Sdes limit_kbps *= 1024; /* kbps */ 2344221420Sdes break; 2345204917Sdes case 'r': 2346204917Sdes global_rflag = 1; 234792555Sdes break; 234892555Sdes case 'R': 234992555Sdes num_requests = strtol(optarg, &cp, 10); 235092555Sdes if (num_requests == 0 || *cp != '\0') 235198675Sdes fatal("Invalid number of requests \"%s\"", 235292555Sdes optarg); 235392555Sdes break; 2354204917Sdes case 's': 2355204917Sdes sftp_server = optarg; 2356204917Sdes break; 2357204917Sdes case 'S': 2358204917Sdes ssh_program = optarg; 2359204917Sdes replacearg(&args, 0, "%s", ssh_program); 2360204917Sdes break; 236176259Sgreen case 'h': 236276259Sgreen default: 236376259Sgreen usage(); 236476259Sgreen } 236576259Sgreen } 236676259Sgreen 2367128456Sdes if (!isatty(STDERR_FILENO)) 2368128456Sdes showprogress = 0; 2369128456Sdes 237098675Sdes log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1); 237198675Sdes 237292555Sdes if (sftp_direct == NULL) { 237392555Sdes if (optind == argc || argc > (optind + 2)) 237492555Sdes usage(); 237576259Sgreen 237692555Sdes userhost = xstrdup(argv[optind]); 237792555Sdes file2 = argv[optind+1]; 237876259Sgreen 2379113908Sdes if ((host = strrchr(userhost, '@')) == NULL) 238092555Sdes host = userhost; 238192555Sdes else { 238292555Sdes *host++ = '\0'; 238392555Sdes if (!userhost[0]) { 238492555Sdes fprintf(stderr, "Missing username\n"); 238592555Sdes usage(); 238692555Sdes } 2387204917Sdes addargs(&args, "-l"); 2388204917Sdes addargs(&args, "%s", userhost); 238992555Sdes } 239092555Sdes 2391126274Sdes if ((cp = colon(host)) != NULL) { 2392126274Sdes *cp++ = '\0'; 2393126274Sdes file1 = cp; 2394126274Sdes } 2395126274Sdes 239692555Sdes host = cleanhostname(host); 239792555Sdes if (!*host) { 239892555Sdes fprintf(stderr, "Missing hostname\n"); 239976259Sgreen usage(); 240076259Sgreen } 240176259Sgreen 240292555Sdes addargs(&args, "-oProtocol %d", sshver); 240376259Sgreen 240492555Sdes /* no subsystem if the server-spec contains a '/' */ 240592555Sdes if (sftp_server == NULL || strchr(sftp_server, '/') == NULL) 240692555Sdes addargs(&args, "-s"); 240776259Sgreen 2408204917Sdes addargs(&args, "--"); 240992555Sdes addargs(&args, "%s", host); 241098675Sdes addargs(&args, "%s", (sftp_server != NULL ? 241192555Sdes sftp_server : "sftp")); 241276259Sgreen 2413124208Sdes connect_to_server(ssh_program, args.list, &in, &out); 241492555Sdes } else { 241592555Sdes args.list = NULL; 241692555Sdes addargs(&args, "sftp-server"); 241776259Sgreen 2418124208Sdes connect_to_server(sftp_direct, args.list, &in, &out); 241992555Sdes } 2420157016Sdes freeargs(&args); 242176259Sgreen 2422221420Sdes conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps); 2423204917Sdes if (conn == NULL) 2424204917Sdes fatal("Couldn't initialise connection to server"); 242576259Sgreen 2426255767Sdes if (!quiet) { 2427204917Sdes if (sftp_direct == NULL) 2428204917Sdes fprintf(stderr, "Connected to %s.\n", host); 2429204917Sdes else 2430204917Sdes fprintf(stderr, "Attached to %s.\n", sftp_direct); 2431204917Sdes } 2432204917Sdes 2433204917Sdes err = interactive_loop(conn, file1, file2); 2434204917Sdes 243598937Sdes#if !defined(USE_PIPES) 2436149749Sdes shutdown(in, SHUT_RDWR); 2437149749Sdes shutdown(out, SHUT_RDWR); 243898937Sdes#endif 243998937Sdes 244076259Sgreen close(in); 244176259Sgreen close(out); 2442126274Sdes if (batchmode) 244376259Sgreen fclose(infile); 244476259Sgreen 244598675Sdes while (waitpid(sshpid, NULL, 0) == -1) 244698675Sdes if (errno != EINTR) 244798675Sdes fatal("Couldn't wait for ssh process: %s", 244898675Sdes strerror(errno)); 244976259Sgreen 2450113908Sdes exit(err == 0 ? 0 : 1); 245176259Sgreen} 2452