1262566Sdes/* $OpenBSD: sftp.c,v 1.158 2013/11/20 20:54:10 deraadt Exp $ */ 2224638Sbrooks/* $FreeBSD$ */ 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 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 50162852Sdes#include <signal.h> 51162852Sdes#include <stdlib.h> 52162852Sdes#include <stdio.h> 53162852Sdes#include <string.h> 54162852Sdes#include <unistd.h> 55162852Sdes#include <stdarg.h> 56146998Sdes 57181111Sdes#ifdef HAVE_UTIL_H 58181111Sdes# include <util.h> 59181111Sdes#endif 60181111Sdes 6176259Sgreen#include "xmalloc.h" 6276259Sgreen#include "log.h" 6376259Sgreen#include "pathnames.h" 6492555Sdes#include "misc.h" 6576259Sgreen 6676259Sgreen#include "sftp.h" 67162852Sdes#include "buffer.h" 6876259Sgreen#include "sftp-common.h" 6976259Sgreen#include "sftp-client.h" 7076259Sgreen 71204917Sdes#define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */ 72224638Sbrooks#define DEFAULT_NUM_REQUESTS 256 /* # concurrent outstanding requests */ 73204917Sdes 74126274Sdes/* File to read commands from */ 75126274SdesFILE* infile; 76126274Sdes 77126274Sdes/* Are we in batchfile mode? */ 78126274Sdesint batchmode = 0; 79126274Sdes 80126274Sdes/* PID of ssh transport process */ 81126274Sdesstatic pid_t sshpid = -1; 82126274Sdes 83255767Sdes/* Suppress diagnositic messages */ 84255767Sdesint quiet = 0; 85255767Sdes 86126274Sdes/* This is set to 0 if the progressmeter is not desired. */ 87128456Sdesint showprogress = 1; 88126274Sdes 89204917Sdes/* When this option is set, we always recursively download/upload directories */ 90204917Sdesint global_rflag = 0; 91204917Sdes 92255767Sdes/* When this option is set, we resume download if possible */ 93255767Sdesint global_aflag = 0; 94255767Sdes 95204917Sdes/* When this option is set, the file transfers will always preserve times */ 96204917Sdesint global_pflag = 0; 97204917Sdes 98262566Sdes/* When this option is set, transfers will have fsync() called on each file */ 99262566Sdesint global_fflag = 0; 100262566Sdes 101137015Sdes/* SIGINT received during command processing */ 102137015Sdesvolatile sig_atomic_t interrupted = 0; 103137015Sdes 104137015Sdes/* I wish qsort() took a separate ctx for the comparison function...*/ 105137015Sdesint sort_flag; 106137015Sdes 107204917Sdes/* Context used for commandline completion */ 108204917Sdesstruct complete_ctx { 109204917Sdes struct sftp_conn *conn; 110204917Sdes char **remote_pathp; 111204917Sdes}; 112204917Sdes 113126274Sdesint remote_glob(struct sftp_conn *, const char *, int, 114126274Sdes int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */ 115126274Sdes 11698937Sdesextern char *__progname; 11798937Sdes 118126274Sdes/* Separators for interactive commands */ 119126274Sdes#define WHITESPACE " \t\r\n" 12076259Sgreen 121137015Sdes/* ls flags */ 122204917Sdes#define LS_LONG_VIEW 0x0001 /* Full view ala ls -l */ 123204917Sdes#define LS_SHORT_VIEW 0x0002 /* Single row view ala ls -1 */ 124204917Sdes#define LS_NUMERIC_VIEW 0x0004 /* Long view with numeric uid/gid */ 125204917Sdes#define LS_NAME_SORT 0x0008 /* Sort by name (default) */ 126204917Sdes#define LS_TIME_SORT 0x0010 /* Sort by mtime */ 127204917Sdes#define LS_SIZE_SORT 0x0020 /* Sort by file size */ 128204917Sdes#define LS_REVERSE_SORT 0x0040 /* Reverse sort order */ 129204917Sdes#define LS_SHOW_ALL 0x0080 /* Don't skip filenames starting with '.' */ 130204917Sdes#define LS_SI_UNITS 0x0100 /* Display sizes as K, M, G, etc. */ 131113908Sdes 132204917Sdes#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS) 133137015Sdes#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT) 134137015Sdes 135126274Sdes/* Commands for interactive mode */ 136262566Sdesenum sftp_command { 137262566Sdes I_CHDIR = 1, 138262566Sdes I_CHGRP, 139262566Sdes I_CHMOD, 140262566Sdes I_CHOWN, 141262566Sdes I_DF, 142262566Sdes I_GET, 143262566Sdes I_HELP, 144262566Sdes I_LCHDIR, 145262566Sdes I_LINK, 146262566Sdes I_LLS, 147262566Sdes I_LMKDIR, 148262566Sdes I_LPWD, 149262566Sdes I_LS, 150262566Sdes I_LUMASK, 151262566Sdes I_MKDIR, 152262566Sdes I_PUT, 153262566Sdes I_PWD, 154262566Sdes I_QUIT, 155262566Sdes I_RENAME, 156262566Sdes I_RM, 157262566Sdes I_RMDIR, 158262566Sdes I_SHELL, 159262566Sdes I_SYMLINK, 160262566Sdes I_VERSION, 161262566Sdes I_PROGRESS, 162262566Sdes I_REGET, 163262566Sdes}; 164126274Sdes 165126274Sdesstruct CMD { 166126274Sdes const char *c; 167126274Sdes const int n; 168204917Sdes const int t; 169126274Sdes}; 170126274Sdes 171204917Sdes/* Type of completion */ 172204917Sdes#define NOARGS 0 173204917Sdes#define REMOTE 1 174204917Sdes#define LOCAL 2 175204917Sdes 176126274Sdesstatic const struct CMD cmds[] = { 177204917Sdes { "bye", I_QUIT, NOARGS }, 178204917Sdes { "cd", I_CHDIR, REMOTE }, 179204917Sdes { "chdir", I_CHDIR, REMOTE }, 180204917Sdes { "chgrp", I_CHGRP, REMOTE }, 181204917Sdes { "chmod", I_CHMOD, REMOTE }, 182204917Sdes { "chown", I_CHOWN, REMOTE }, 183204917Sdes { "df", I_DF, REMOTE }, 184204917Sdes { "dir", I_LS, REMOTE }, 185204917Sdes { "exit", I_QUIT, NOARGS }, 186204917Sdes { "get", I_GET, REMOTE }, 187204917Sdes { "help", I_HELP, NOARGS }, 188204917Sdes { "lcd", I_LCHDIR, LOCAL }, 189204917Sdes { "lchdir", I_LCHDIR, LOCAL }, 190204917Sdes { "lls", I_LLS, LOCAL }, 191204917Sdes { "lmkdir", I_LMKDIR, LOCAL }, 192221420Sdes { "ln", I_LINK, REMOTE }, 193204917Sdes { "lpwd", I_LPWD, LOCAL }, 194204917Sdes { "ls", I_LS, REMOTE }, 195204917Sdes { "lumask", I_LUMASK, NOARGS }, 196204917Sdes { "mkdir", I_MKDIR, REMOTE }, 197215116Sdes { "mget", I_GET, REMOTE }, 198215116Sdes { "mput", I_PUT, LOCAL }, 199204917Sdes { "progress", I_PROGRESS, NOARGS }, 200204917Sdes { "put", I_PUT, LOCAL }, 201204917Sdes { "pwd", I_PWD, REMOTE }, 202204917Sdes { "quit", I_QUIT, NOARGS }, 203255767Sdes { "reget", I_REGET, REMOTE }, 204204917Sdes { "rename", I_RENAME, REMOTE }, 205204917Sdes { "rm", I_RM, REMOTE }, 206204917Sdes { "rmdir", I_RMDIR, REMOTE }, 207204917Sdes { "symlink", I_SYMLINK, REMOTE }, 208204917Sdes { "version", I_VERSION, NOARGS }, 209204917Sdes { "!", I_SHELL, NOARGS }, 210204917Sdes { "?", I_HELP, NOARGS }, 211204917Sdes { NULL, -1, -1 } 212126274Sdes}; 213126274Sdes 214204917Sdesint interactive_loop(struct sftp_conn *, char *file1, char *file2); 215126274Sdes 216181111Sdes/* ARGSUSED */ 21792555Sdesstatic void 218137015Sdeskillchild(int signo) 219137015Sdes{ 220146998Sdes if (sshpid > 1) { 221137015Sdes kill(sshpid, SIGTERM); 222146998Sdes waitpid(sshpid, NULL, 0); 223146998Sdes } 224137015Sdes 225137015Sdes _exit(1); 226137015Sdes} 227137015Sdes 228181111Sdes/* ARGSUSED */ 229137015Sdesstatic void 230137015Sdescmd_interrupt(int signo) 231137015Sdes{ 232137015Sdes const char msg[] = "\rInterrupt \n"; 233146998Sdes int olderrno = errno; 234137015Sdes 235255767Sdes (void)write(STDERR_FILENO, msg, sizeof(msg) - 1); 236137015Sdes interrupted = 1; 237146998Sdes errno = olderrno; 238137015Sdes} 239137015Sdes 240137015Sdesstatic void 241126274Sdeshelp(void) 242126274Sdes{ 243192595Sdes printf("Available commands:\n" 244192595Sdes "bye Quit sftp\n" 245192595Sdes "cd path Change remote directory to 'path'\n" 246192595Sdes "chgrp grp path Change group of file 'path' to 'grp'\n" 247192595Sdes "chmod mode path Change permissions of file 'path' to 'mode'\n" 248192595Sdes "chown own path Change owner of file 'path' to 'own'\n" 249192595Sdes "df [-hi] [path] Display statistics for current directory or\n" 250192595Sdes " filesystem containing 'path'\n" 251192595Sdes "exit Quit sftp\n" 252204917Sdes "get [-Ppr] remote [local] Download file\n" 253255767Sdes "reget remote [local] Resume download file\n" 254192595Sdes "help Display this help text\n" 255192595Sdes "lcd path Change local directory to 'path'\n" 256192595Sdes "lls [ls-options [path]] Display local directory listing\n" 257192595Sdes "lmkdir path Create local directory\n" 258221420Sdes "ln [-s] oldpath newpath Link remote file (-s for symlink)\n" 259192595Sdes "lpwd Print local working directory\n" 260204917Sdes "ls [-1afhlnrSt] [path] Display remote directory listing\n" 261192595Sdes "lumask umask Set local umask to 'umask'\n" 262192595Sdes "mkdir path Create remote directory\n" 263192595Sdes "progress Toggle display of progress meter\n" 264204917Sdes "put [-Ppr] local [remote] Upload file\n" 265192595Sdes "pwd Display remote working directory\n" 266192595Sdes "quit Quit sftp\n" 267192595Sdes "rename oldpath newpath Rename remote file\n" 268192595Sdes "rm path Delete remote file\n" 269192595Sdes "rmdir path Remove remote directory\n" 270192595Sdes "symlink oldpath newpath Symlink remote file\n" 271192595Sdes "version Show SFTP version\n" 272192595Sdes "!command Execute 'command' in local shell\n" 273192595Sdes "! Escape to local shell\n" 274192595Sdes "? Synonym for help\n"); 275126274Sdes} 276126274Sdes 277126274Sdesstatic void 278126274Sdeslocal_do_shell(const char *args) 279126274Sdes{ 280126274Sdes int status; 281126274Sdes char *shell; 282126274Sdes pid_t pid; 283126274Sdes 284126274Sdes if (!*args) 285126274Sdes args = NULL; 286126274Sdes 287221420Sdes if ((shell = getenv("SHELL")) == NULL || *shell == '\0') 288126274Sdes shell = _PATH_BSHELL; 289126274Sdes 290126274Sdes if ((pid = fork()) == -1) 291126274Sdes fatal("Couldn't fork: %s", strerror(errno)); 292126274Sdes 293126274Sdes if (pid == 0) { 294126274Sdes /* XXX: child has pipe fds to ssh subproc open - issue? */ 295126274Sdes if (args) { 296126274Sdes debug3("Executing %s -c \"%s\"", shell, args); 297126274Sdes execl(shell, shell, "-c", args, (char *)NULL); 298126274Sdes } else { 299126274Sdes debug3("Executing %s", shell); 300126274Sdes execl(shell, shell, (char *)NULL); 301126274Sdes } 302126274Sdes fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell, 303126274Sdes strerror(errno)); 304126274Sdes _exit(1); 305126274Sdes } 306126274Sdes while (waitpid(pid, &status, 0) == -1) 307126274Sdes if (errno != EINTR) 308126274Sdes fatal("Couldn't wait for child: %s", strerror(errno)); 309126274Sdes if (!WIFEXITED(status)) 310162852Sdes error("Shell exited abnormally"); 311126274Sdes else if (WEXITSTATUS(status)) 312126274Sdes error("Shell exited with status %d", WEXITSTATUS(status)); 313126274Sdes} 314126274Sdes 315126274Sdesstatic void 316126274Sdeslocal_do_ls(const char *args) 317126274Sdes{ 318126274Sdes if (!args || !*args) 319126274Sdes local_do_shell(_PATH_LS); 320126274Sdes else { 321126274Sdes int len = strlen(_PATH_LS " ") + strlen(args) + 1; 322126274Sdes char *buf = xmalloc(len); 323126274Sdes 324126274Sdes /* XXX: quoting - rip quoting code from ftp? */ 325126274Sdes snprintf(buf, len, _PATH_LS " %s", args); 326126274Sdes local_do_shell(buf); 327255767Sdes free(buf); 328126274Sdes } 329126274Sdes} 330126274Sdes 331126274Sdes/* Strip one path (usually the pwd) from the start of another */ 332126274Sdesstatic char * 333126274Sdespath_strip(char *path, char *strip) 334126274Sdes{ 335126274Sdes size_t len; 336126274Sdes 337126274Sdes if (strip == NULL) 338126274Sdes return (xstrdup(path)); 339126274Sdes 340126274Sdes len = strlen(strip); 341146998Sdes if (strncmp(path, strip, len) == 0) { 342126274Sdes if (strip[len - 1] != '/' && path[len] == '/') 343126274Sdes len++; 344126274Sdes return (xstrdup(path + len)); 345126274Sdes } 346126274Sdes 347126274Sdes return (xstrdup(path)); 348126274Sdes} 349126274Sdes 350126274Sdesstatic char * 351126274Sdesmake_absolute(char *p, char *pwd) 352126274Sdes{ 353137015Sdes char *abs_str; 354126274Sdes 355126274Sdes /* Derelativise */ 356126274Sdes if (p && p[0] != '/') { 357137015Sdes abs_str = path_append(pwd, p); 358255767Sdes free(p); 359137015Sdes return(abs_str); 360126274Sdes } else 361126274Sdes return(p); 362126274Sdes} 363126274Sdes 364126274Sdesstatic int 365255767Sdesparse_getput_flags(const char *cmd, char **argv, int argc, 366262566Sdes int *aflag, int *fflag, int *pflag, int *rflag) 367126274Sdes{ 368181111Sdes extern int opterr, optind, optopt, optreset; 369181111Sdes int ch; 370126274Sdes 371181111Sdes optind = optreset = 1; 372181111Sdes opterr = 0; 373181111Sdes 374262566Sdes *aflag = *fflag = *rflag = *pflag = 0; 375262566Sdes while ((ch = getopt(argc, argv, "afPpRr")) != -1) { 376181111Sdes switch (ch) { 377255767Sdes case 'a': 378255767Sdes *aflag = 1; 379255767Sdes break; 380262566Sdes case 'f': 381262566Sdes *fflag = 1; 382262566Sdes break; 383126274Sdes case 'p': 384126274Sdes case 'P': 385126274Sdes *pflag = 1; 386126274Sdes break; 387204917Sdes case 'r': 388204917Sdes case 'R': 389204917Sdes *rflag = 1; 390204917Sdes break; 391126274Sdes default: 392181111Sdes error("%s: Invalid flag -%c", cmd, optopt); 393181111Sdes return -1; 394126274Sdes } 395126274Sdes } 396126274Sdes 397181111Sdes return optind; 398126274Sdes} 399126274Sdes 400126274Sdesstatic int 401221420Sdesparse_link_flags(const char *cmd, char **argv, int argc, int *sflag) 402221420Sdes{ 403221420Sdes extern int opterr, optind, optopt, optreset; 404221420Sdes int ch; 405221420Sdes 406221420Sdes optind = optreset = 1; 407221420Sdes opterr = 0; 408221420Sdes 409221420Sdes *sflag = 0; 410221420Sdes while ((ch = getopt(argc, argv, "s")) != -1) { 411221420Sdes switch (ch) { 412221420Sdes case 's': 413221420Sdes *sflag = 1; 414221420Sdes break; 415221420Sdes default: 416221420Sdes error("%s: Invalid flag -%c", cmd, optopt); 417221420Sdes return -1; 418221420Sdes } 419221420Sdes } 420221420Sdes 421221420Sdes return optind; 422221420Sdes} 423221420Sdes 424221420Sdesstatic int 425262566Sdesparse_rename_flags(const char *cmd, char **argv, int argc, int *lflag) 426262566Sdes{ 427262566Sdes extern int opterr, optind, optopt, optreset; 428262566Sdes int ch; 429262566Sdes 430262566Sdes optind = optreset = 1; 431262566Sdes opterr = 0; 432262566Sdes 433262566Sdes *lflag = 0; 434262566Sdes while ((ch = getopt(argc, argv, "l")) != -1) { 435262566Sdes switch (ch) { 436262566Sdes case 'l': 437262566Sdes *lflag = 1; 438262566Sdes break; 439262566Sdes default: 440262566Sdes error("%s: Invalid flag -%c", cmd, optopt); 441262566Sdes return -1; 442262566Sdes } 443262566Sdes } 444262566Sdes 445262566Sdes return optind; 446262566Sdes} 447262566Sdes 448262566Sdesstatic int 449181111Sdesparse_ls_flags(char **argv, int argc, int *lflag) 450126274Sdes{ 451181111Sdes extern int opterr, optind, optopt, optreset; 452181111Sdes int ch; 453126274Sdes 454181111Sdes optind = optreset = 1; 455181111Sdes opterr = 0; 456181111Sdes 457137015Sdes *lflag = LS_NAME_SORT; 458204917Sdes while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) { 459181111Sdes switch (ch) { 460181111Sdes case '1': 461181111Sdes *lflag &= ~VIEW_FLAGS; 462181111Sdes *lflag |= LS_SHORT_VIEW; 463181111Sdes break; 464181111Sdes case 'S': 465181111Sdes *lflag &= ~SORT_FLAGS; 466181111Sdes *lflag |= LS_SIZE_SORT; 467181111Sdes break; 468181111Sdes case 'a': 469181111Sdes *lflag |= LS_SHOW_ALL; 470181111Sdes break; 471181111Sdes case 'f': 472181111Sdes *lflag &= ~SORT_FLAGS; 473181111Sdes break; 474204917Sdes case 'h': 475204917Sdes *lflag |= LS_SI_UNITS; 476204917Sdes break; 477181111Sdes case 'l': 478204917Sdes *lflag &= ~LS_SHORT_VIEW; 479181111Sdes *lflag |= LS_LONG_VIEW; 480181111Sdes break; 481181111Sdes case 'n': 482204917Sdes *lflag &= ~LS_SHORT_VIEW; 483181111Sdes *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW; 484181111Sdes break; 485181111Sdes case 'r': 486181111Sdes *lflag |= LS_REVERSE_SORT; 487181111Sdes break; 488181111Sdes case 't': 489181111Sdes *lflag &= ~SORT_FLAGS; 490181111Sdes *lflag |= LS_TIME_SORT; 491181111Sdes break; 492181111Sdes default: 493181111Sdes error("ls: Invalid flag -%c", optopt); 494181111Sdes return -1; 495126274Sdes } 496126274Sdes } 497126274Sdes 498181111Sdes return optind; 499126274Sdes} 500126274Sdes 501126274Sdesstatic int 502181111Sdesparse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag) 503126274Sdes{ 504181111Sdes extern int opterr, optind, optopt, optreset; 505181111Sdes int ch; 506126274Sdes 507181111Sdes optind = optreset = 1; 508181111Sdes opterr = 0; 509126274Sdes 510181111Sdes *hflag = *iflag = 0; 511181111Sdes while ((ch = getopt(argc, argv, "hi")) != -1) { 512181111Sdes switch (ch) { 513181111Sdes case 'h': 514181111Sdes *hflag = 1; 515181111Sdes break; 516181111Sdes case 'i': 517181111Sdes *iflag = 1; 518181111Sdes break; 519181111Sdes default: 520181111Sdes error("%s: Invalid flag -%c", cmd, optopt); 521181111Sdes return -1; 522126274Sdes } 523126274Sdes } 524126274Sdes 525181111Sdes return optind; 526126274Sdes} 527126274Sdes 528126274Sdesstatic int 529262566Sdesparse_no_flags(const char *cmd, char **argv, int argc) 530262566Sdes{ 531262566Sdes extern int opterr, optind, optopt, optreset; 532262566Sdes int ch; 533262566Sdes 534262566Sdes optind = optreset = 1; 535262566Sdes opterr = 0; 536262566Sdes 537262566Sdes while ((ch = getopt(argc, argv, "")) != -1) { 538262566Sdes switch (ch) { 539262566Sdes default: 540262566Sdes error("%s: Invalid flag -%c", cmd, optopt); 541262566Sdes return -1; 542262566Sdes } 543262566Sdes } 544262566Sdes 545262566Sdes return optind; 546262566Sdes} 547262566Sdes 548262566Sdesstatic int 549126274Sdesis_dir(char *path) 550126274Sdes{ 551126274Sdes struct stat sb; 552126274Sdes 553126274Sdes /* XXX: report errors? */ 554126274Sdes if (stat(path, &sb) == -1) 555126274Sdes return(0); 556126274Sdes 557162852Sdes return(S_ISDIR(sb.st_mode)); 558126274Sdes} 559126274Sdes 560126274Sdesstatic int 561126274Sdesremote_is_dir(struct sftp_conn *conn, char *path) 562126274Sdes{ 563126274Sdes Attrib *a; 564126274Sdes 565126274Sdes /* XXX: report errors? */ 566126274Sdes if ((a = do_stat(conn, path, 1)) == NULL) 567126274Sdes return(0); 568126274Sdes if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) 569126274Sdes return(0); 570162852Sdes return(S_ISDIR(a->perm)); 571126274Sdes} 572126274Sdes 573204917Sdes/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */ 574126274Sdesstatic int 575204917Sdespathname_is_dir(char *pathname) 576126274Sdes{ 577204917Sdes size_t l = strlen(pathname); 578204917Sdes 579204917Sdes return l > 0 && pathname[l - 1] == '/'; 580204917Sdes} 581204917Sdes 582204917Sdesstatic int 583204917Sdesprocess_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, 584262566Sdes int pflag, int rflag, int resume, int fflag) 585204917Sdes{ 586126274Sdes char *abs_src = NULL; 587126274Sdes char *abs_dst = NULL; 588126274Sdes glob_t g; 589204917Sdes char *filename, *tmp=NULL; 590204917Sdes int i, err = 0; 591126274Sdes 592126274Sdes abs_src = xstrdup(src); 593126274Sdes abs_src = make_absolute(abs_src, pwd); 594204917Sdes memset(&g, 0, sizeof(g)); 595126274Sdes 596126274Sdes debug3("Looking up %s", abs_src); 597204917Sdes if (remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) { 598126274Sdes error("File \"%s\" not found.", abs_src); 599126274Sdes err = -1; 600126274Sdes goto out; 601126274Sdes } 602126274Sdes 603204917Sdes /* 604204917Sdes * If multiple matches then dst must be a directory or 605204917Sdes * unspecified. 606204917Sdes */ 607204917Sdes if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) { 608204917Sdes error("Multiple source paths, but destination " 609204917Sdes "\"%s\" is not a directory", dst); 610126274Sdes err = -1; 611126274Sdes goto out; 612126274Sdes } 613126274Sdes 614137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 615204917Sdes tmp = xstrdup(g.gl_pathv[i]); 616204917Sdes if ((filename = basename(tmp)) == NULL) { 617204917Sdes error("basename %s: %s", tmp, strerror(errno)); 618255767Sdes free(tmp); 619126274Sdes err = -1; 620126274Sdes goto out; 621126274Sdes } 622126274Sdes 623126274Sdes if (g.gl_matchc == 1 && dst) { 624126274Sdes if (is_dir(dst)) { 625204917Sdes abs_dst = path_append(dst, filename); 626204917Sdes } else { 627126274Sdes abs_dst = xstrdup(dst); 628204917Sdes } 629126274Sdes } else if (dst) { 630204917Sdes abs_dst = path_append(dst, filename); 631204917Sdes } else { 632204917Sdes abs_dst = xstrdup(filename); 633204917Sdes } 634255767Sdes free(tmp); 635126274Sdes 636255767Sdes resume |= global_aflag; 637255767Sdes if (!quiet && resume) 638255767Sdes printf("Resuming %s to %s\n", g.gl_pathv[i], abs_dst); 639255767Sdes else if (!quiet && !resume) 640255767Sdes printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst); 641204917Sdes if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) { 642255767Sdes if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL, 643262566Sdes pflag || global_pflag, 1, resume, 644262566Sdes fflag || global_fflag) == -1) 645204917Sdes err = -1; 646204917Sdes } else { 647204917Sdes if (do_download(conn, g.gl_pathv[i], abs_dst, NULL, 648262566Sdes pflag || global_pflag, resume, 649262566Sdes fflag || global_fflag) == -1) 650204917Sdes err = -1; 651204917Sdes } 652255767Sdes free(abs_dst); 653126274Sdes abs_dst = NULL; 654126274Sdes } 655126274Sdes 656126274Sdesout: 657255767Sdes free(abs_src); 658126274Sdes globfree(&g); 659126274Sdes return(err); 660126274Sdes} 661126274Sdes 662126274Sdesstatic int 663204917Sdesprocess_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, 664262566Sdes int pflag, int rflag, int fflag) 665126274Sdes{ 666126274Sdes char *tmp_dst = NULL; 667126274Sdes char *abs_dst = NULL; 668204917Sdes char *tmp = NULL, *filename = NULL; 669126274Sdes glob_t g; 670126274Sdes int err = 0; 671204917Sdes int i, dst_is_dir = 1; 672181111Sdes struct stat sb; 673126274Sdes 674126274Sdes if (dst) { 675126274Sdes tmp_dst = xstrdup(dst); 676126274Sdes tmp_dst = make_absolute(tmp_dst, pwd); 677126274Sdes } 678126274Sdes 679126274Sdes memset(&g, 0, sizeof(g)); 680126274Sdes debug3("Looking up %s", src); 681204917Sdes if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) { 682126274Sdes error("File \"%s\" not found.", src); 683126274Sdes err = -1; 684126274Sdes goto out; 685126274Sdes } 686126274Sdes 687204917Sdes /* If we aren't fetching to pwd then stash this status for later */ 688204917Sdes if (tmp_dst != NULL) 689204917Sdes dst_is_dir = remote_is_dir(conn, tmp_dst); 690204917Sdes 691126274Sdes /* If multiple matches, dst may be directory or unspecified */ 692204917Sdes if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) { 693204917Sdes error("Multiple paths match, but destination " 694204917Sdes "\"%s\" is not a directory", tmp_dst); 695126274Sdes err = -1; 696126274Sdes goto out; 697126274Sdes } 698126274Sdes 699137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 700181111Sdes if (stat(g.gl_pathv[i], &sb) == -1) { 701181111Sdes err = -1; 702181111Sdes error("stat %s: %s", g.gl_pathv[i], strerror(errno)); 703181111Sdes continue; 704181111Sdes } 705262566Sdes 706204917Sdes tmp = xstrdup(g.gl_pathv[i]); 707204917Sdes if ((filename = basename(tmp)) == NULL) { 708204917Sdes error("basename %s: %s", tmp, strerror(errno)); 709255767Sdes free(tmp); 710126274Sdes err = -1; 711126274Sdes goto out; 712126274Sdes } 713126274Sdes 714126274Sdes if (g.gl_matchc == 1 && tmp_dst) { 715126274Sdes /* If directory specified, append filename */ 716204917Sdes if (dst_is_dir) 717204917Sdes abs_dst = path_append(tmp_dst, filename); 718204917Sdes else 719126274Sdes abs_dst = xstrdup(tmp_dst); 720126274Sdes } else if (tmp_dst) { 721204917Sdes abs_dst = path_append(tmp_dst, filename); 722204917Sdes } else { 723204917Sdes abs_dst = make_absolute(xstrdup(filename), pwd); 724204917Sdes } 725255767Sdes free(tmp); 726126274Sdes 727255767Sdes if (!quiet) 728255767Sdes printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst); 729204917Sdes if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) { 730204917Sdes if (upload_dir(conn, g.gl_pathv[i], abs_dst, 731262566Sdes pflag || global_pflag, 1, 732262566Sdes fflag || global_fflag) == -1) 733204917Sdes err = -1; 734204917Sdes } else { 735204917Sdes if (do_upload(conn, g.gl_pathv[i], abs_dst, 736262566Sdes pflag || global_pflag, 737262566Sdes fflag || global_fflag) == -1) 738204917Sdes err = -1; 739204917Sdes } 740126274Sdes } 741126274Sdes 742126274Sdesout: 743255767Sdes free(abs_dst); 744255767Sdes free(tmp_dst); 745126274Sdes globfree(&g); 746126274Sdes return(err); 747126274Sdes} 748126274Sdes 749126274Sdesstatic int 750126274Sdessdirent_comp(const void *aa, const void *bb) 751126274Sdes{ 752126274Sdes SFTP_DIRENT *a = *(SFTP_DIRENT **)aa; 753126274Sdes SFTP_DIRENT *b = *(SFTP_DIRENT **)bb; 754137015Sdes int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1; 755126274Sdes 756137015Sdes#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1)) 757137015Sdes if (sort_flag & LS_NAME_SORT) 758137015Sdes return (rmul * strcmp(a->filename, b->filename)); 759137015Sdes else if (sort_flag & LS_TIME_SORT) 760137015Sdes return (rmul * NCMP(a->a.mtime, b->a.mtime)); 761137015Sdes else if (sort_flag & LS_SIZE_SORT) 762137015Sdes return (rmul * NCMP(a->a.size, b->a.size)); 763137015Sdes 764137015Sdes fatal("Unknown ls sort type"); 765126274Sdes} 766126274Sdes 767126274Sdes/* sftp ls.1 replacement for directories */ 768126274Sdesstatic int 769126274Sdesdo_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag) 770126274Sdes{ 771149749Sdes int n; 772149749Sdes u_int c = 1, colspace = 0, columns = 1; 773126274Sdes SFTP_DIRENT **d; 774126274Sdes 775126274Sdes if ((n = do_readdir(conn, path, &d)) != 0) 776126274Sdes return (n); 777126274Sdes 778137015Sdes if (!(lflag & LS_SHORT_VIEW)) { 779149749Sdes u_int m = 0, width = 80; 780126274Sdes struct winsize ws; 781126274Sdes char *tmp; 782126274Sdes 783126274Sdes /* Count entries for sort and find longest filename */ 784137015Sdes for (n = 0; d[n] != NULL; n++) { 785137015Sdes if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL)) 786137015Sdes m = MAX(m, strlen(d[n]->filename)); 787137015Sdes } 788126274Sdes 789126274Sdes /* Add any subpath that also needs to be counted */ 790126274Sdes tmp = path_strip(path, strip_path); 791126274Sdes m += strlen(tmp); 792255767Sdes free(tmp); 793126274Sdes 794126274Sdes if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1) 795126274Sdes width = ws.ws_col; 796126274Sdes 797126274Sdes columns = width / (m + 2); 798126274Sdes columns = MAX(columns, 1); 799126274Sdes colspace = width / columns; 800126274Sdes colspace = MIN(colspace, width); 801126274Sdes } 802126274Sdes 803137015Sdes if (lflag & SORT_FLAGS) { 804157016Sdes for (n = 0; d[n] != NULL; n++) 805157016Sdes ; /* count entries */ 806137015Sdes sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT); 807137015Sdes qsort(d, n, sizeof(*d), sdirent_comp); 808137015Sdes } 809126274Sdes 810137015Sdes for (n = 0; d[n] != NULL && !interrupted; n++) { 811126274Sdes char *tmp, *fname; 812126274Sdes 813137015Sdes if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL)) 814137015Sdes continue; 815137015Sdes 816126274Sdes tmp = path_append(path, d[n]->filename); 817126274Sdes fname = path_strip(tmp, strip_path); 818255767Sdes free(tmp); 819126274Sdes 820137015Sdes if (lflag & LS_LONG_VIEW) { 821204917Sdes if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) { 822137015Sdes char *lname; 823137015Sdes struct stat sb; 824126274Sdes 825137015Sdes memset(&sb, 0, sizeof(sb)); 826137015Sdes attrib_to_stat(&d[n]->a, &sb); 827204917Sdes lname = ls_file(fname, &sb, 1, 828204917Sdes (lflag & LS_SI_UNITS)); 829137015Sdes printf("%s\n", lname); 830255767Sdes free(lname); 831137015Sdes } else 832137015Sdes printf("%s\n", d[n]->longname); 833126274Sdes } else { 834126274Sdes printf("%-*s", colspace, fname); 835126274Sdes if (c >= columns) { 836126274Sdes printf("\n"); 837126274Sdes c = 1; 838126274Sdes } else 839126274Sdes c++; 840126274Sdes } 841126274Sdes 842255767Sdes free(fname); 843126274Sdes } 844126274Sdes 845137015Sdes if (!(lflag & LS_LONG_VIEW) && (c != 1)) 846126274Sdes printf("\n"); 847126274Sdes 848126274Sdes free_sftp_dirents(d); 849126274Sdes return (0); 850126274Sdes} 851126274Sdes 852126274Sdes/* sftp ls.1 replacement which handles path globs */ 853126274Sdesstatic int 854126274Sdesdo_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path, 855126274Sdes int lflag) 856126274Sdes{ 857221420Sdes char *fname, *lname; 858126274Sdes glob_t g; 859221420Sdes int err; 860221420Sdes struct winsize ws; 861221420Sdes u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80; 862126274Sdes 863126274Sdes memset(&g, 0, sizeof(g)); 864126274Sdes 865221420Sdes if (remote_glob(conn, path, 866240075Sdes GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT, 867240075Sdes NULL, &g) || 868221420Sdes (g.gl_pathc && !g.gl_matchc)) { 869146998Sdes if (g.gl_pathc) 870146998Sdes globfree(&g); 871126274Sdes error("Can't ls: \"%s\" not found", path); 872221420Sdes return -1; 873126274Sdes } 874126274Sdes 875137015Sdes if (interrupted) 876137015Sdes goto out; 877137015Sdes 878126274Sdes /* 879146998Sdes * If the glob returns a single match and it is a directory, 880146998Sdes * then just list its contents. 881126274Sdes */ 882221420Sdes if (g.gl_matchc == 1 && g.gl_statv[0] != NULL && 883221420Sdes S_ISDIR(g.gl_statv[0]->st_mode)) { 884221420Sdes err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag); 885221420Sdes globfree(&g); 886221420Sdes return err; 887126274Sdes } 888126274Sdes 889221420Sdes if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1) 890221420Sdes width = ws.ws_col; 891221420Sdes 892137015Sdes if (!(lflag & LS_SHORT_VIEW)) { 893126274Sdes /* Count entries for sort and find longest filename */ 894126274Sdes for (i = 0; g.gl_pathv[i]; i++) 895126274Sdes m = MAX(m, strlen(g.gl_pathv[i])); 896126274Sdes 897126274Sdes columns = width / (m + 2); 898126274Sdes columns = MAX(columns, 1); 899126274Sdes colspace = width / columns; 900126274Sdes } 901126274Sdes 902240075Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 903126274Sdes fname = path_strip(g.gl_pathv[i], strip_path); 904137015Sdes if (lflag & LS_LONG_VIEW) { 905221420Sdes if (g.gl_statv[i] == NULL) { 906221420Sdes error("no stat information for %s", fname); 907221420Sdes continue; 908221420Sdes } 909221420Sdes lname = ls_file(fname, g.gl_statv[i], 1, 910221420Sdes (lflag & LS_SI_UNITS)); 911126274Sdes printf("%s\n", lname); 912255767Sdes free(lname); 913126274Sdes } else { 914126274Sdes printf("%-*s", colspace, fname); 915126274Sdes if (c >= columns) { 916126274Sdes printf("\n"); 917126274Sdes c = 1; 918126274Sdes } else 919126274Sdes c++; 920126274Sdes } 921255767Sdes free(fname); 922126274Sdes } 923126274Sdes 924137015Sdes if (!(lflag & LS_LONG_VIEW) && (c != 1)) 925126274Sdes printf("\n"); 926126274Sdes 927137015Sdes out: 928126274Sdes if (g.gl_pathc) 929126274Sdes globfree(&g); 930126274Sdes 931221420Sdes return 0; 932126274Sdes} 933126274Sdes 934126274Sdesstatic int 935181111Sdesdo_df(struct sftp_conn *conn, char *path, int hflag, int iflag) 936181111Sdes{ 937181111Sdes struct sftp_statvfs st; 938181111Sdes char s_used[FMT_SCALED_STRSIZE]; 939181111Sdes char s_avail[FMT_SCALED_STRSIZE]; 940181111Sdes char s_root[FMT_SCALED_STRSIZE]; 941181111Sdes char s_total[FMT_SCALED_STRSIZE]; 942204917Sdes unsigned long long ffree; 943181111Sdes 944181111Sdes if (do_statvfs(conn, path, &st, 1) == -1) 945181111Sdes return -1; 946181111Sdes if (iflag) { 947204917Sdes ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0; 948181111Sdes printf(" Inodes Used Avail " 949181111Sdes "(root) %%Capacity\n"); 950181111Sdes printf("%11llu %11llu %11llu %11llu %3llu%%\n", 951181111Sdes (unsigned long long)st.f_files, 952181111Sdes (unsigned long long)(st.f_files - st.f_ffree), 953181111Sdes (unsigned long long)st.f_favail, 954204917Sdes (unsigned long long)st.f_ffree, ffree); 955181111Sdes } else if (hflag) { 956181111Sdes strlcpy(s_used, "error", sizeof(s_used)); 957181111Sdes strlcpy(s_avail, "error", sizeof(s_avail)); 958181111Sdes strlcpy(s_root, "error", sizeof(s_root)); 959181111Sdes strlcpy(s_total, "error", sizeof(s_total)); 960181111Sdes fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used); 961181111Sdes fmt_scaled(st.f_bavail * st.f_frsize, s_avail); 962181111Sdes fmt_scaled(st.f_bfree * st.f_frsize, s_root); 963181111Sdes fmt_scaled(st.f_blocks * st.f_frsize, s_total); 964181111Sdes printf(" Size Used Avail (root) %%Capacity\n"); 965181111Sdes printf("%7sB %7sB %7sB %7sB %3llu%%\n", 966181111Sdes s_total, s_used, s_avail, s_root, 967181111Sdes (unsigned long long)(100 * (st.f_blocks - st.f_bfree) / 968181111Sdes st.f_blocks)); 969181111Sdes } else { 970181111Sdes printf(" Size Used Avail " 971181111Sdes "(root) %%Capacity\n"); 972181111Sdes printf("%12llu %12llu %12llu %12llu %3llu%%\n", 973181111Sdes (unsigned long long)(st.f_frsize * st.f_blocks / 1024), 974181111Sdes (unsigned long long)(st.f_frsize * 975181111Sdes (st.f_blocks - st.f_bfree) / 1024), 976181111Sdes (unsigned long long)(st.f_frsize * st.f_bavail / 1024), 977181111Sdes (unsigned long long)(st.f_frsize * st.f_bfree / 1024), 978181111Sdes (unsigned long long)(100 * (st.f_blocks - st.f_bfree) / 979181111Sdes st.f_blocks)); 980181111Sdes } 981181111Sdes return 0; 982181111Sdes} 983181111Sdes 984181111Sdes/* 985181111Sdes * Undo escaping of glob sequences in place. Used to undo extra escaping 986181111Sdes * applied in makeargv() when the string is destined for a function that 987181111Sdes * does not glob it. 988181111Sdes */ 989181111Sdesstatic void 990181111Sdesundo_glob_escape(char *s) 991181111Sdes{ 992181111Sdes size_t i, j; 993181111Sdes 994181111Sdes for (i = j = 0;;) { 995181111Sdes if (s[i] == '\0') { 996181111Sdes s[j] = '\0'; 997181111Sdes return; 998181111Sdes } 999181111Sdes if (s[i] != '\\') { 1000181111Sdes s[j++] = s[i++]; 1001181111Sdes continue; 1002181111Sdes } 1003181111Sdes /* s[i] == '\\' */ 1004181111Sdes ++i; 1005181111Sdes switch (s[i]) { 1006181111Sdes case '?': 1007181111Sdes case '[': 1008181111Sdes case '*': 1009181111Sdes case '\\': 1010181111Sdes s[j++] = s[i++]; 1011181111Sdes break; 1012181111Sdes case '\0': 1013181111Sdes s[j++] = '\\'; 1014181111Sdes s[j] = '\0'; 1015181111Sdes return; 1016181111Sdes default: 1017181111Sdes s[j++] = '\\'; 1018181111Sdes s[j++] = s[i++]; 1019181111Sdes break; 1020181111Sdes } 1021181111Sdes } 1022181111Sdes} 1023181111Sdes 1024181111Sdes/* 1025181111Sdes * Split a string into an argument vector using sh(1)-style quoting, 1026181111Sdes * comment and escaping rules, but with some tweaks to handle glob(3) 1027181111Sdes * wildcards. 1028204917Sdes * The "sloppy" flag allows for recovery from missing terminating quote, for 1029204917Sdes * use in parsing incomplete commandlines during tab autocompletion. 1030204917Sdes * 1031181111Sdes * Returns NULL on error or a NULL-terminated array of arguments. 1032204917Sdes * 1033204917Sdes * If "lastquote" is not NULL, the quoting character used for the last 1034204917Sdes * argument is placed in *lastquote ("\0", "'" or "\""). 1035262566Sdes * 1036204917Sdes * If "terminated" is not NULL, *terminated will be set to 1 when the 1037204917Sdes * last argument's quote has been properly terminated or 0 otherwise. 1038204917Sdes * This parameter is only of use if "sloppy" is set. 1039181111Sdes */ 1040181111Sdes#define MAXARGS 128 1041181111Sdes#define MAXARGLEN 8192 1042181111Sdesstatic char ** 1043204917Sdesmakeargv(const char *arg, int *argcp, int sloppy, char *lastquote, 1044204917Sdes u_int *terminated) 1045181111Sdes{ 1046181111Sdes int argc, quot; 1047181111Sdes size_t i, j; 1048181111Sdes static char argvs[MAXARGLEN]; 1049181111Sdes static char *argv[MAXARGS + 1]; 1050181111Sdes enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q; 1051181111Sdes 1052181111Sdes *argcp = argc = 0; 1053181111Sdes if (strlen(arg) > sizeof(argvs) - 1) { 1054181111Sdes args_too_longs: 1055181111Sdes error("string too long"); 1056181111Sdes return NULL; 1057181111Sdes } 1058204917Sdes if (terminated != NULL) 1059204917Sdes *terminated = 1; 1060204917Sdes if (lastquote != NULL) 1061204917Sdes *lastquote = '\0'; 1062181111Sdes state = MA_START; 1063181111Sdes i = j = 0; 1064181111Sdes for (;;) { 1065248619Sdes if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){ 1066248619Sdes error("Too many arguments."); 1067248619Sdes return NULL; 1068248619Sdes } 1069262566Sdes if (isspace((unsigned char)arg[i])) { 1070181111Sdes if (state == MA_UNQUOTED) { 1071181111Sdes /* Terminate current argument */ 1072181111Sdes argvs[j++] = '\0'; 1073181111Sdes argc++; 1074181111Sdes state = MA_START; 1075181111Sdes } else if (state != MA_START) 1076181111Sdes argvs[j++] = arg[i]; 1077181111Sdes } else if (arg[i] == '"' || arg[i] == '\'') { 1078181111Sdes q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE; 1079181111Sdes if (state == MA_START) { 1080181111Sdes argv[argc] = argvs + j; 1081181111Sdes state = q; 1082204917Sdes if (lastquote != NULL) 1083204917Sdes *lastquote = arg[i]; 1084262566Sdes } else if (state == MA_UNQUOTED) 1085181111Sdes state = q; 1086181111Sdes else if (state == q) 1087181111Sdes state = MA_UNQUOTED; 1088181111Sdes else 1089181111Sdes argvs[j++] = arg[i]; 1090181111Sdes } else if (arg[i] == '\\') { 1091181111Sdes if (state == MA_SQUOTE || state == MA_DQUOTE) { 1092181111Sdes quot = state == MA_SQUOTE ? '\'' : '"'; 1093181111Sdes /* Unescape quote we are in */ 1094181111Sdes /* XXX support \n and friends? */ 1095181111Sdes if (arg[i + 1] == quot) { 1096181111Sdes i++; 1097181111Sdes argvs[j++] = arg[i]; 1098181111Sdes } else if (arg[i + 1] == '?' || 1099181111Sdes arg[i + 1] == '[' || arg[i + 1] == '*') { 1100181111Sdes /* 1101181111Sdes * Special case for sftp: append 1102181111Sdes * double-escaped glob sequence - 1103181111Sdes * glob will undo one level of 1104181111Sdes * escaping. NB. string can grow here. 1105181111Sdes */ 1106181111Sdes if (j >= sizeof(argvs) - 5) 1107181111Sdes goto args_too_longs; 1108181111Sdes argvs[j++] = '\\'; 1109181111Sdes argvs[j++] = arg[i++]; 1110181111Sdes argvs[j++] = '\\'; 1111181111Sdes argvs[j++] = arg[i]; 1112181111Sdes } else { 1113181111Sdes argvs[j++] = arg[i++]; 1114181111Sdes argvs[j++] = arg[i]; 1115181111Sdes } 1116181111Sdes } else { 1117181111Sdes if (state == MA_START) { 1118181111Sdes argv[argc] = argvs + j; 1119181111Sdes state = MA_UNQUOTED; 1120204917Sdes if (lastquote != NULL) 1121204917Sdes *lastquote = '\0'; 1122181111Sdes } 1123181111Sdes if (arg[i + 1] == '?' || arg[i + 1] == '[' || 1124181111Sdes arg[i + 1] == '*' || arg[i + 1] == '\\') { 1125181111Sdes /* 1126181111Sdes * Special case for sftp: append 1127181111Sdes * escaped glob sequence - 1128181111Sdes * glob will undo one level of 1129181111Sdes * escaping. 1130181111Sdes */ 1131181111Sdes argvs[j++] = arg[i++]; 1132181111Sdes argvs[j++] = arg[i]; 1133181111Sdes } else { 1134181111Sdes /* Unescape everything */ 1135181111Sdes /* XXX support \n and friends? */ 1136181111Sdes i++; 1137181111Sdes argvs[j++] = arg[i]; 1138181111Sdes } 1139181111Sdes } 1140181111Sdes } else if (arg[i] == '#') { 1141181111Sdes if (state == MA_SQUOTE || state == MA_DQUOTE) 1142181111Sdes argvs[j++] = arg[i]; 1143181111Sdes else 1144181111Sdes goto string_done; 1145181111Sdes } else if (arg[i] == '\0') { 1146181111Sdes if (state == MA_SQUOTE || state == MA_DQUOTE) { 1147204917Sdes if (sloppy) { 1148204917Sdes state = MA_UNQUOTED; 1149204917Sdes if (terminated != NULL) 1150204917Sdes *terminated = 0; 1151204917Sdes goto string_done; 1152204917Sdes } 1153181111Sdes error("Unterminated quoted argument"); 1154181111Sdes return NULL; 1155181111Sdes } 1156181111Sdes string_done: 1157181111Sdes if (state == MA_UNQUOTED) { 1158181111Sdes argvs[j++] = '\0'; 1159181111Sdes argc++; 1160181111Sdes } 1161181111Sdes break; 1162181111Sdes } else { 1163181111Sdes if (state == MA_START) { 1164181111Sdes argv[argc] = argvs + j; 1165181111Sdes state = MA_UNQUOTED; 1166204917Sdes if (lastquote != NULL) 1167204917Sdes *lastquote = '\0'; 1168181111Sdes } 1169181111Sdes if ((state == MA_SQUOTE || state == MA_DQUOTE) && 1170181111Sdes (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) { 1171181111Sdes /* 1172181111Sdes * Special case for sftp: escape quoted 1173181111Sdes * glob(3) wildcards. NB. string can grow 1174181111Sdes * here. 1175181111Sdes */ 1176181111Sdes if (j >= sizeof(argvs) - 3) 1177181111Sdes goto args_too_longs; 1178181111Sdes argvs[j++] = '\\'; 1179181111Sdes argvs[j++] = arg[i]; 1180181111Sdes } else 1181181111Sdes argvs[j++] = arg[i]; 1182181111Sdes } 1183181111Sdes i++; 1184181111Sdes } 1185181111Sdes *argcp = argc; 1186181111Sdes return argv; 1187181111Sdes} 1188181111Sdes 1189181111Sdesstatic int 1190262566Sdesparse_args(const char **cpp, int *ignore_errors, int *aflag, int *fflag, 1191262566Sdes int *hflag, int *iflag, int *lflag, int *pflag, int *rflag, int *sflag, 1192262566Sdes unsigned long *n_arg, char **path1, char **path2) 1193126274Sdes{ 1194126274Sdes const char *cmd, *cp = *cpp; 1195181111Sdes char *cp2, **argv; 1196126274Sdes int base = 0; 1197126274Sdes long l; 1198181111Sdes int i, cmdnum, optidx, argc; 1199126274Sdes 1200126274Sdes /* Skip leading whitespace */ 1201126274Sdes cp = cp + strspn(cp, WHITESPACE); 1202126274Sdes 1203126274Sdes /* Check for leading '-' (disable error processing) */ 1204262566Sdes *ignore_errors = 0; 1205126274Sdes if (*cp == '-') { 1206262566Sdes *ignore_errors = 1; 1207126274Sdes cp++; 1208204917Sdes cp = cp + strspn(cp, WHITESPACE); 1209126274Sdes } 1210126274Sdes 1211204917Sdes /* Ignore blank lines and lines which begin with comment '#' char */ 1212204917Sdes if (*cp == '\0' || *cp == '#') 1213204917Sdes return (0); 1214204917Sdes 1215204917Sdes if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL) 1216181111Sdes return -1; 1217181111Sdes 1218126274Sdes /* Figure out which command we have */ 1219181111Sdes for (i = 0; cmds[i].c != NULL; i++) { 1220248619Sdes if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0) 1221126274Sdes break; 1222126274Sdes } 1223126274Sdes cmdnum = cmds[i].n; 1224126274Sdes cmd = cmds[i].c; 1225126274Sdes 1226126274Sdes /* Special case */ 1227126274Sdes if (*cp == '!') { 1228126274Sdes cp++; 1229126274Sdes cmdnum = I_SHELL; 1230126274Sdes } else if (cmdnum == -1) { 1231126274Sdes error("Invalid command."); 1232181111Sdes return -1; 1233126274Sdes } 1234126274Sdes 1235126274Sdes /* Get arguments and parse flags */ 1236262566Sdes *aflag = *fflag = *hflag = *iflag = *lflag = *pflag = 0; 1237262566Sdes *rflag = *sflag = 0; 1238126274Sdes *path1 = *path2 = NULL; 1239181111Sdes optidx = 1; 1240126274Sdes switch (cmdnum) { 1241126274Sdes case I_GET: 1242255767Sdes case I_REGET: 1243126274Sdes case I_PUT: 1244221420Sdes if ((optidx = parse_getput_flags(cmd, argv, argc, 1245262566Sdes aflag, fflag, pflag, rflag)) == -1) 1246181111Sdes return -1; 1247126274Sdes /* Get first pathname (mandatory) */ 1248181111Sdes if (argc - optidx < 1) { 1249126274Sdes error("You must specify at least one path after a " 1250126274Sdes "%s command.", cmd); 1251181111Sdes return -1; 1252126274Sdes } 1253181111Sdes *path1 = xstrdup(argv[optidx]); 1254181111Sdes /* Get second pathname (optional) */ 1255181111Sdes if (argc - optidx > 1) { 1256181111Sdes *path2 = xstrdup(argv[optidx + 1]); 1257181111Sdes /* Destination is not globbed */ 1258181111Sdes undo_glob_escape(*path2); 1259181111Sdes } 1260255767Sdes if (*aflag && cmdnum == I_PUT) { 1261255767Sdes /* XXX implement resume for uploads */ 1262255767Sdes error("Resume is not supported for uploads"); 1263255767Sdes return -1; 1264255767Sdes } 1265126274Sdes break; 1266221420Sdes case I_LINK: 1267221420Sdes if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1) 1268221420Sdes return -1; 1269262566Sdes goto parse_two_paths; 1270262566Sdes case I_RENAME: 1271262566Sdes if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1) 1272262566Sdes return -1; 1273262566Sdes goto parse_two_paths; 1274221420Sdes case I_SYMLINK: 1275262566Sdes if ((optidx = parse_no_flags(cmd, argv, argc)) == -1) 1276262566Sdes return -1; 1277262566Sdes parse_two_paths: 1278181111Sdes if (argc - optidx < 2) { 1279126274Sdes error("You must specify two paths after a %s " 1280126274Sdes "command.", cmd); 1281181111Sdes return -1; 1282126274Sdes } 1283181111Sdes *path1 = xstrdup(argv[optidx]); 1284181111Sdes *path2 = xstrdup(argv[optidx + 1]); 1285181111Sdes /* Paths are not globbed */ 1286181111Sdes undo_glob_escape(*path1); 1287181111Sdes undo_glob_escape(*path2); 1288126274Sdes break; 1289126274Sdes case I_RM: 1290126274Sdes case I_MKDIR: 1291126274Sdes case I_RMDIR: 1292126274Sdes case I_CHDIR: 1293126274Sdes case I_LCHDIR: 1294126274Sdes case I_LMKDIR: 1295262566Sdes if ((optidx = parse_no_flags(cmd, argv, argc)) == -1) 1296262566Sdes return -1; 1297126274Sdes /* Get pathname (mandatory) */ 1298181111Sdes if (argc - optidx < 1) { 1299126274Sdes error("You must specify a path after a %s command.", 1300126274Sdes cmd); 1301181111Sdes return -1; 1302126274Sdes } 1303181111Sdes *path1 = xstrdup(argv[optidx]); 1304181111Sdes /* Only "rm" globs */ 1305181111Sdes if (cmdnum != I_RM) 1306181111Sdes undo_glob_escape(*path1); 1307126274Sdes break; 1308181111Sdes case I_DF: 1309181111Sdes if ((optidx = parse_df_flags(cmd, argv, argc, hflag, 1310181111Sdes iflag)) == -1) 1311181111Sdes return -1; 1312181111Sdes /* Default to current directory if no path specified */ 1313181111Sdes if (argc - optidx < 1) 1314181111Sdes *path1 = NULL; 1315181111Sdes else { 1316181111Sdes *path1 = xstrdup(argv[optidx]); 1317181111Sdes undo_glob_escape(*path1); 1318181111Sdes } 1319181111Sdes break; 1320126274Sdes case I_LS: 1321181111Sdes if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1) 1322126274Sdes return(-1); 1323126274Sdes /* Path is optional */ 1324181111Sdes if (argc - optidx > 0) 1325181111Sdes *path1 = xstrdup(argv[optidx]); 1326126274Sdes break; 1327126274Sdes case I_LLS: 1328181111Sdes /* Skip ls command and following whitespace */ 1329181111Sdes cp = cp + strlen(cmd) + strspn(cp, WHITESPACE); 1330126274Sdes case I_SHELL: 1331126274Sdes /* Uses the rest of the line */ 1332126274Sdes break; 1333126274Sdes case I_LUMASK: 1334126274Sdes case I_CHMOD: 1335126274Sdes base = 8; 1336126274Sdes case I_CHOWN: 1337126274Sdes case I_CHGRP: 1338262566Sdes if ((optidx = parse_no_flags(cmd, argv, argc)) == -1) 1339262566Sdes return -1; 1340126274Sdes /* Get numeric arg (mandatory) */ 1341181111Sdes if (argc - optidx < 1) 1342181111Sdes goto need_num_arg; 1343164146Sdes errno = 0; 1344181111Sdes l = strtol(argv[optidx], &cp2, base); 1345181111Sdes if (cp2 == argv[optidx] || *cp2 != '\0' || 1346181111Sdes ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) || 1347181111Sdes l < 0) { 1348181111Sdes need_num_arg: 1349126274Sdes error("You must supply a numeric argument " 1350126274Sdes "to the %s command.", cmd); 1351181111Sdes return -1; 1352126274Sdes } 1353126274Sdes *n_arg = l; 1354181111Sdes if (cmdnum == I_LUMASK) 1355126274Sdes break; 1356126274Sdes /* Get pathname (mandatory) */ 1357181111Sdes if (argc - optidx < 2) { 1358126274Sdes error("You must specify a path after a %s command.", 1359126274Sdes cmd); 1360181111Sdes return -1; 1361126274Sdes } 1362181111Sdes *path1 = xstrdup(argv[optidx + 1]); 1363126274Sdes break; 1364126274Sdes case I_QUIT: 1365126274Sdes case I_PWD: 1366126274Sdes case I_LPWD: 1367126274Sdes case I_HELP: 1368126274Sdes case I_VERSION: 1369126274Sdes case I_PROGRESS: 1370262566Sdes if ((optidx = parse_no_flags(cmd, argv, argc)) == -1) 1371262566Sdes return -1; 1372126274Sdes break; 1373126274Sdes default: 1374126274Sdes fatal("Command not implemented"); 1375126274Sdes } 1376126274Sdes 1377126274Sdes *cpp = cp; 1378126274Sdes return(cmdnum); 1379126274Sdes} 1380126274Sdes 1381126274Sdesstatic int 1382126274Sdesparse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, 1383126274Sdes int err_abort) 1384126274Sdes{ 1385126274Sdes char *path1, *path2, *tmp; 1386262566Sdes int ignore_errors = 0, aflag = 0, fflag = 0, hflag = 0, iflag = 0; 1387262566Sdes int lflag = 0, pflag = 0, rflag = 0, sflag = 0; 1388221420Sdes int cmdnum, i; 1389192595Sdes unsigned long n_arg = 0; 1390126274Sdes Attrib a, *aa; 1391126274Sdes char path_buf[MAXPATHLEN]; 1392126274Sdes int err = 0; 1393126274Sdes glob_t g; 1394126274Sdes 1395126274Sdes path1 = path2 = NULL; 1396262566Sdes cmdnum = parse_args(&cmd, &ignore_errors, &aflag, &fflag, &hflag, 1397262566Sdes &iflag, &lflag, &pflag, &rflag, &sflag, &n_arg, &path1, &path2); 1398262566Sdes if (ignore_errors != 0) 1399126274Sdes err_abort = 0; 1400126274Sdes 1401126274Sdes memset(&g, 0, sizeof(g)); 1402126274Sdes 1403126274Sdes /* Perform command */ 1404126274Sdes switch (cmdnum) { 1405126274Sdes case 0: 1406126274Sdes /* Blank line */ 1407126274Sdes break; 1408126274Sdes case -1: 1409126274Sdes /* Unrecognized command */ 1410126274Sdes err = -1; 1411126274Sdes break; 1412255767Sdes case I_REGET: 1413255767Sdes aflag = 1; 1414255767Sdes /* FALLTHROUGH */ 1415126274Sdes case I_GET: 1416255767Sdes err = process_get(conn, path1, path2, *pwd, pflag, 1417262566Sdes rflag, aflag, fflag); 1418126274Sdes break; 1419126274Sdes case I_PUT: 1420262566Sdes err = process_put(conn, path1, path2, *pwd, pflag, 1421262566Sdes rflag, fflag); 1422126274Sdes break; 1423126274Sdes case I_RENAME: 1424126274Sdes path1 = make_absolute(path1, *pwd); 1425126274Sdes path2 = make_absolute(path2, *pwd); 1426262566Sdes err = do_rename(conn, path1, path2, lflag); 1427126274Sdes break; 1428126274Sdes case I_SYMLINK: 1429221420Sdes sflag = 1; 1430221420Sdes case I_LINK: 1431262566Sdes if (!sflag) 1432262566Sdes path1 = make_absolute(path1, *pwd); 1433126274Sdes path2 = make_absolute(path2, *pwd); 1434221420Sdes err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2); 1435126274Sdes break; 1436126274Sdes case I_RM: 1437126274Sdes path1 = make_absolute(path1, *pwd); 1438126274Sdes remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); 1439137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 1440255767Sdes if (!quiet) 1441255767Sdes printf("Removing %s\n", g.gl_pathv[i]); 1442126274Sdes err = do_rm(conn, g.gl_pathv[i]); 1443126274Sdes if (err != 0 && err_abort) 1444126274Sdes break; 1445126274Sdes } 1446126274Sdes break; 1447126274Sdes case I_MKDIR: 1448126274Sdes path1 = make_absolute(path1, *pwd); 1449126274Sdes attrib_clear(&a); 1450126274Sdes a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; 1451126274Sdes a.perm = 0777; 1452204917Sdes err = do_mkdir(conn, path1, &a, 1); 1453126274Sdes break; 1454126274Sdes case I_RMDIR: 1455126274Sdes path1 = make_absolute(path1, *pwd); 1456126274Sdes err = do_rmdir(conn, path1); 1457126274Sdes break; 1458126274Sdes case I_CHDIR: 1459126274Sdes path1 = make_absolute(path1, *pwd); 1460126274Sdes if ((tmp = do_realpath(conn, path1)) == NULL) { 1461126274Sdes err = 1; 1462126274Sdes break; 1463126274Sdes } 1464126274Sdes if ((aa = do_stat(conn, tmp, 0)) == NULL) { 1465255767Sdes free(tmp); 1466126274Sdes err = 1; 1467126274Sdes break; 1468126274Sdes } 1469126274Sdes if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) { 1470126274Sdes error("Can't change directory: Can't check target"); 1471255767Sdes free(tmp); 1472126274Sdes err = 1; 1473126274Sdes break; 1474126274Sdes } 1475126274Sdes if (!S_ISDIR(aa->perm)) { 1476126274Sdes error("Can't change directory: \"%s\" is not " 1477126274Sdes "a directory", tmp); 1478255767Sdes free(tmp); 1479126274Sdes err = 1; 1480126274Sdes break; 1481126274Sdes } 1482255767Sdes free(*pwd); 1483126274Sdes *pwd = tmp; 1484126274Sdes break; 1485126274Sdes case I_LS: 1486126274Sdes if (!path1) { 1487215116Sdes do_ls_dir(conn, *pwd, *pwd, lflag); 1488126274Sdes break; 1489126274Sdes } 1490126274Sdes 1491126274Sdes /* Strip pwd off beginning of non-absolute paths */ 1492126274Sdes tmp = NULL; 1493126274Sdes if (*path1 != '/') 1494126274Sdes tmp = *pwd; 1495126274Sdes 1496126274Sdes path1 = make_absolute(path1, *pwd); 1497126274Sdes err = do_globbed_ls(conn, path1, tmp, lflag); 1498126274Sdes break; 1499181111Sdes case I_DF: 1500181111Sdes /* Default to current directory if no path specified */ 1501181111Sdes if (path1 == NULL) 1502181111Sdes path1 = xstrdup(*pwd); 1503181111Sdes path1 = make_absolute(path1, *pwd); 1504181111Sdes err = do_df(conn, path1, hflag, iflag); 1505181111Sdes break; 1506126274Sdes case I_LCHDIR: 1507126274Sdes if (chdir(path1) == -1) { 1508126274Sdes error("Couldn't change local directory to " 1509126274Sdes "\"%s\": %s", path1, strerror(errno)); 1510126274Sdes err = 1; 1511126274Sdes } 1512126274Sdes break; 1513126274Sdes case I_LMKDIR: 1514126274Sdes if (mkdir(path1, 0777) == -1) { 1515126274Sdes error("Couldn't create local directory " 1516126274Sdes "\"%s\": %s", path1, strerror(errno)); 1517126274Sdes err = 1; 1518126274Sdes } 1519126274Sdes break; 1520126274Sdes case I_LLS: 1521126274Sdes local_do_ls(cmd); 1522126274Sdes break; 1523126274Sdes case I_SHELL: 1524126274Sdes local_do_shell(cmd); 1525126274Sdes break; 1526126274Sdes case I_LUMASK: 1527126274Sdes umask(n_arg); 1528126274Sdes printf("Local umask: %03lo\n", n_arg); 1529126274Sdes break; 1530126274Sdes case I_CHMOD: 1531126274Sdes path1 = make_absolute(path1, *pwd); 1532126274Sdes attrib_clear(&a); 1533126274Sdes a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; 1534126274Sdes a.perm = n_arg; 1535126274Sdes remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); 1536137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 1537255767Sdes if (!quiet) 1538255767Sdes printf("Changing mode on %s\n", g.gl_pathv[i]); 1539126274Sdes err = do_setstat(conn, g.gl_pathv[i], &a); 1540126274Sdes if (err != 0 && err_abort) 1541126274Sdes break; 1542126274Sdes } 1543126274Sdes break; 1544126274Sdes case I_CHOWN: 1545126274Sdes case I_CHGRP: 1546126274Sdes path1 = make_absolute(path1, *pwd); 1547126274Sdes remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); 1548137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 1549126274Sdes if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) { 1550192595Sdes if (err_abort) { 1551192595Sdes err = -1; 1552126274Sdes break; 1553192595Sdes } else 1554126274Sdes continue; 1555126274Sdes } 1556126274Sdes if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) { 1557126274Sdes error("Can't get current ownership of " 1558126274Sdes "remote file \"%s\"", g.gl_pathv[i]); 1559192595Sdes if (err_abort) { 1560192595Sdes err = -1; 1561126274Sdes break; 1562192595Sdes } else 1563126274Sdes continue; 1564126274Sdes } 1565126274Sdes aa->flags &= SSH2_FILEXFER_ATTR_UIDGID; 1566126274Sdes if (cmdnum == I_CHOWN) { 1567255767Sdes if (!quiet) 1568255767Sdes printf("Changing owner on %s\n", 1569255767Sdes g.gl_pathv[i]); 1570126274Sdes aa->uid = n_arg; 1571126274Sdes } else { 1572255767Sdes if (!quiet) 1573255767Sdes printf("Changing group on %s\n", 1574255767Sdes g.gl_pathv[i]); 1575126274Sdes aa->gid = n_arg; 1576126274Sdes } 1577126274Sdes err = do_setstat(conn, g.gl_pathv[i], aa); 1578126274Sdes if (err != 0 && err_abort) 1579126274Sdes break; 1580126274Sdes } 1581126274Sdes break; 1582126274Sdes case I_PWD: 1583126274Sdes printf("Remote working directory: %s\n", *pwd); 1584126274Sdes break; 1585126274Sdes case I_LPWD: 1586126274Sdes if (!getcwd(path_buf, sizeof(path_buf))) { 1587126274Sdes error("Couldn't get local cwd: %s", strerror(errno)); 1588126274Sdes err = -1; 1589126274Sdes break; 1590126274Sdes } 1591126274Sdes printf("Local working directory: %s\n", path_buf); 1592126274Sdes break; 1593126274Sdes case I_QUIT: 1594126274Sdes /* Processed below */ 1595126274Sdes break; 1596126274Sdes case I_HELP: 1597126274Sdes help(); 1598126274Sdes break; 1599126274Sdes case I_VERSION: 1600126274Sdes printf("SFTP protocol version %u\n", sftp_proto_version(conn)); 1601126274Sdes break; 1602126274Sdes case I_PROGRESS: 1603126274Sdes showprogress = !showprogress; 1604126274Sdes if (showprogress) 1605126274Sdes printf("Progress meter enabled\n"); 1606126274Sdes else 1607126274Sdes printf("Progress meter disabled\n"); 1608126274Sdes break; 1609126274Sdes default: 1610126274Sdes fatal("%d is not implemented", cmdnum); 1611126274Sdes } 1612126274Sdes 1613126274Sdes if (g.gl_pathc) 1614126274Sdes globfree(&g); 1615255767Sdes free(path1); 1616255767Sdes free(path2); 1617126274Sdes 1618126274Sdes /* If an unignored error occurs in batch mode we should abort. */ 1619126274Sdes if (err_abort && err != 0) 1620126274Sdes return (-1); 1621126274Sdes else if (cmdnum == I_QUIT) 1622126274Sdes return (1); 1623126274Sdes 1624126274Sdes return (0); 1625126274Sdes} 1626126274Sdes 1627146998Sdes#ifdef USE_LIBEDIT 1628146998Sdesstatic char * 1629146998Sdesprompt(EditLine *el) 1630146998Sdes{ 1631146998Sdes return ("sftp> "); 1632146998Sdes} 1633146998Sdes 1634204917Sdes/* Display entries in 'list' after skipping the first 'len' chars */ 1635204917Sdesstatic void 1636204917Sdescomplete_display(char **list, u_int len) 1637204917Sdes{ 1638204917Sdes u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen; 1639204917Sdes struct winsize ws; 1640204917Sdes char *tmp; 1641204917Sdes 1642204917Sdes /* Count entries for sort and find longest */ 1643262566Sdes for (y = 0; list[y]; y++) 1644204917Sdes m = MAX(m, strlen(list[y])); 1645204917Sdes 1646204917Sdes if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1) 1647204917Sdes width = ws.ws_col; 1648204917Sdes 1649204917Sdes m = m > len ? m - len : 0; 1650204917Sdes columns = width / (m + 2); 1651204917Sdes columns = MAX(columns, 1); 1652204917Sdes colspace = width / columns; 1653204917Sdes colspace = MIN(colspace, width); 1654204917Sdes 1655204917Sdes printf("\n"); 1656204917Sdes m = 1; 1657204917Sdes for (y = 0; list[y]; y++) { 1658204917Sdes llen = strlen(list[y]); 1659204917Sdes tmp = llen > len ? list[y] + len : ""; 1660204917Sdes printf("%-*s", colspace, tmp); 1661204917Sdes if (m >= columns) { 1662204917Sdes printf("\n"); 1663204917Sdes m = 1; 1664204917Sdes } else 1665204917Sdes m++; 1666204917Sdes } 1667204917Sdes printf("\n"); 1668204917Sdes} 1669204917Sdes 1670204917Sdes/* 1671204917Sdes * Given a "list" of words that begin with a common prefix of "word", 1672204917Sdes * attempt to find an autocompletion to extends "word" by the next 1673204917Sdes * characters common to all entries in "list". 1674204917Sdes */ 1675204917Sdesstatic char * 1676204917Sdescomplete_ambiguous(const char *word, char **list, size_t count) 1677204917Sdes{ 1678204917Sdes if (word == NULL) 1679204917Sdes return NULL; 1680204917Sdes 1681204917Sdes if (count > 0) { 1682204917Sdes u_int y, matchlen = strlen(list[0]); 1683204917Sdes 1684204917Sdes /* Find length of common stem */ 1685204917Sdes for (y = 1; list[y]; y++) { 1686204917Sdes u_int x; 1687204917Sdes 1688262566Sdes for (x = 0; x < matchlen; x++) 1689262566Sdes if (list[0][x] != list[y][x]) 1690204917Sdes break; 1691204917Sdes 1692204917Sdes matchlen = x; 1693204917Sdes } 1694204917Sdes 1695204917Sdes if (matchlen > strlen(word)) { 1696204917Sdes char *tmp = xstrdup(list[0]); 1697204917Sdes 1698204917Sdes tmp[matchlen] = '\0'; 1699204917Sdes return tmp; 1700204917Sdes } 1701262566Sdes } 1702204917Sdes 1703204917Sdes return xstrdup(word); 1704204917Sdes} 1705204917Sdes 1706204917Sdes/* Autocomplete a sftp command */ 1707204917Sdesstatic int 1708204917Sdescomplete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote, 1709204917Sdes int terminated) 1710204917Sdes{ 1711204917Sdes u_int y, count = 0, cmdlen, tmplen; 1712204917Sdes char *tmp, **list, argterm[3]; 1713204917Sdes const LineInfo *lf; 1714204917Sdes 1715204917Sdes list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *)); 1716204917Sdes 1717204917Sdes /* No command specified: display all available commands */ 1718204917Sdes if (cmd == NULL) { 1719204917Sdes for (y = 0; cmds[y].c; y++) 1720204917Sdes list[count++] = xstrdup(cmds[y].c); 1721262566Sdes 1722204917Sdes list[count] = NULL; 1723204917Sdes complete_display(list, 0); 1724204917Sdes 1725262566Sdes for (y = 0; list[y] != NULL; y++) 1726262566Sdes free(list[y]); 1727255767Sdes free(list); 1728204917Sdes return count; 1729204917Sdes } 1730204917Sdes 1731204917Sdes /* Prepare subset of commands that start with "cmd" */ 1732204917Sdes cmdlen = strlen(cmd); 1733204917Sdes for (y = 0; cmds[y].c; y++) { 1734262566Sdes if (!strncasecmp(cmd, cmds[y].c, cmdlen)) 1735204917Sdes list[count++] = xstrdup(cmds[y].c); 1736204917Sdes } 1737204917Sdes list[count] = NULL; 1738204917Sdes 1739240075Sdes if (count == 0) { 1740255767Sdes free(list); 1741204917Sdes return 0; 1742240075Sdes } 1743204917Sdes 1744204917Sdes /* Complete ambigious command */ 1745204917Sdes tmp = complete_ambiguous(cmd, list, count); 1746204917Sdes if (count > 1) 1747204917Sdes complete_display(list, 0); 1748204917Sdes 1749262566Sdes for (y = 0; list[y]; y++) 1750262566Sdes free(list[y]); 1751255767Sdes free(list); 1752204917Sdes 1753204917Sdes if (tmp != NULL) { 1754204917Sdes tmplen = strlen(tmp); 1755204917Sdes cmdlen = strlen(cmd); 1756204917Sdes /* If cmd may be extended then do so */ 1757204917Sdes if (tmplen > cmdlen) 1758204917Sdes if (el_insertstr(el, tmp + cmdlen) == -1) 1759204917Sdes fatal("el_insertstr failed."); 1760204917Sdes lf = el_line(el); 1761204917Sdes /* Terminate argument cleanly */ 1762204917Sdes if (count == 1) { 1763204917Sdes y = 0; 1764204917Sdes if (!terminated) 1765204917Sdes argterm[y++] = quote; 1766204917Sdes if (lastarg || *(lf->cursor) != ' ') 1767204917Sdes argterm[y++] = ' '; 1768204917Sdes argterm[y] = '\0'; 1769204917Sdes if (y > 0 && el_insertstr(el, argterm) == -1) 1770204917Sdes fatal("el_insertstr failed."); 1771204917Sdes } 1772255767Sdes free(tmp); 1773204917Sdes } 1774204917Sdes 1775204917Sdes return count; 1776204917Sdes} 1777204917Sdes 1778204917Sdes/* 1779204917Sdes * Determine whether a particular sftp command's arguments (if any) 1780204917Sdes * represent local or remote files. 1781204917Sdes */ 1782204917Sdesstatic int 1783204917Sdescomplete_is_remote(char *cmd) { 1784204917Sdes int i; 1785204917Sdes 1786204917Sdes if (cmd == NULL) 1787204917Sdes return -1; 1788204917Sdes 1789204917Sdes for (i = 0; cmds[i].c; i++) { 1790262566Sdes if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c))) 1791204917Sdes return cmds[i].t; 1792204917Sdes } 1793204917Sdes 1794204917Sdes return -1; 1795204917Sdes} 1796204917Sdes 1797204917Sdes/* Autocomplete a filename "file" */ 1798204917Sdesstatic int 1799204917Sdescomplete_match(EditLine *el, struct sftp_conn *conn, char *remote_path, 1800204917Sdes char *file, int remote, int lastarg, char quote, int terminated) 1801204917Sdes{ 1802204917Sdes glob_t g; 1803255767Sdes char *tmp, *tmp2, ins[8]; 1804248619Sdes u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs; 1805255767Sdes int clen; 1806204917Sdes const LineInfo *lf; 1807262566Sdes 1808204917Sdes /* Glob from "file" location */ 1809204917Sdes if (file == NULL) 1810204917Sdes tmp = xstrdup("*"); 1811204917Sdes else 1812204917Sdes xasprintf(&tmp, "%s*", file); 1813204917Sdes 1814248619Sdes /* Check if the path is absolute. */ 1815248619Sdes isabs = tmp[0] == '/'; 1816248619Sdes 1817204917Sdes memset(&g, 0, sizeof(g)); 1818204917Sdes if (remote != LOCAL) { 1819204917Sdes tmp = make_absolute(tmp, remote_path); 1820204917Sdes remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g); 1821262566Sdes } else 1822204917Sdes glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g); 1823262566Sdes 1824204917Sdes /* Determine length of pwd so we can trim completion display */ 1825204917Sdes for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) { 1826204917Sdes /* Terminate counting on first unescaped glob metacharacter */ 1827204917Sdes if (tmp[tmplen] == '*' || tmp[tmplen] == '?') { 1828204917Sdes if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0') 1829204917Sdes hadglob = 1; 1830204917Sdes break; 1831204917Sdes } 1832204917Sdes if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0') 1833204917Sdes tmplen++; 1834204917Sdes if (tmp[tmplen] == '/') 1835204917Sdes pwdlen = tmplen + 1; /* track last seen '/' */ 1836204917Sdes } 1837255767Sdes free(tmp); 1838204917Sdes 1839262566Sdes if (g.gl_matchc == 0) 1840204917Sdes goto out; 1841204917Sdes 1842204917Sdes if (g.gl_matchc > 1) 1843204917Sdes complete_display(g.gl_pathv, pwdlen); 1844204917Sdes 1845204917Sdes tmp = NULL; 1846204917Sdes /* Don't try to extend globs */ 1847204917Sdes if (file == NULL || hadglob) 1848204917Sdes goto out; 1849204917Sdes 1850204917Sdes tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc); 1851248619Sdes tmp = path_strip(tmp2, isabs ? NULL : remote_path); 1852255767Sdes free(tmp2); 1853204917Sdes 1854204917Sdes if (tmp == NULL) 1855204917Sdes goto out; 1856204917Sdes 1857204917Sdes tmplen = strlen(tmp); 1858204917Sdes filelen = strlen(file); 1859204917Sdes 1860248619Sdes /* Count the number of escaped characters in the input string. */ 1861248619Sdes cesc = isesc = 0; 1862248619Sdes for (i = 0; i < filelen; i++) { 1863248619Sdes if (!isesc && file[i] == '\\' && i + 1 < filelen){ 1864248619Sdes isesc = 1; 1865248619Sdes cesc++; 1866248619Sdes } else 1867248619Sdes isesc = 0; 1868248619Sdes } 1869248619Sdes 1870248619Sdes if (tmplen > (filelen - cesc)) { 1871248619Sdes tmp2 = tmp + filelen - cesc; 1872262566Sdes len = strlen(tmp2); 1873204917Sdes /* quote argument on way out */ 1874255767Sdes for (i = 0; i < len; i += clen) { 1875255767Sdes if ((clen = mblen(tmp2 + i, len - i)) < 0 || 1876255767Sdes (size_t)clen > sizeof(ins) - 2) 1877255767Sdes fatal("invalid multibyte character"); 1878204917Sdes ins[0] = '\\'; 1879255767Sdes memcpy(ins + 1, tmp2 + i, clen); 1880255767Sdes ins[clen + 1] = '\0'; 1881204917Sdes switch (tmp2[i]) { 1882204917Sdes case '\'': 1883204917Sdes case '"': 1884204917Sdes case '\\': 1885204917Sdes case '\t': 1886221420Sdes case '[': 1887204917Sdes case ' ': 1888248619Sdes case '#': 1889248619Sdes case '*': 1890204917Sdes if (quote == '\0' || tmp2[i] == quote) { 1891204917Sdes if (el_insertstr(el, ins) == -1) 1892204917Sdes fatal("el_insertstr " 1893204917Sdes "failed."); 1894204917Sdes break; 1895204917Sdes } 1896204917Sdes /* FALLTHROUGH */ 1897204917Sdes default: 1898204917Sdes if (el_insertstr(el, ins + 1) == -1) 1899204917Sdes fatal("el_insertstr failed."); 1900204917Sdes break; 1901204917Sdes } 1902204917Sdes } 1903204917Sdes } 1904204917Sdes 1905204917Sdes lf = el_line(el); 1906204917Sdes if (g.gl_matchc == 1) { 1907204917Sdes i = 0; 1908204917Sdes if (!terminated) 1909204917Sdes ins[i++] = quote; 1910204917Sdes if (*(lf->cursor - 1) != '/' && 1911204917Sdes (lastarg || *(lf->cursor) != ' ')) 1912204917Sdes ins[i++] = ' '; 1913204917Sdes ins[i] = '\0'; 1914204917Sdes if (i > 0 && el_insertstr(el, ins) == -1) 1915204917Sdes fatal("el_insertstr failed."); 1916204917Sdes } 1917255767Sdes free(tmp); 1918204917Sdes 1919204917Sdes out: 1920204917Sdes globfree(&g); 1921204917Sdes return g.gl_matchc; 1922204917Sdes} 1923204917Sdes 1924204917Sdes/* tab-completion hook function, called via libedit */ 1925204917Sdesstatic unsigned char 1926204917Sdescomplete(EditLine *el, int ch) 1927204917Sdes{ 1928262566Sdes char **argv, *line, quote; 1929255767Sdes int argc, carg; 1930255767Sdes u_int cursor, len, terminated, ret = CC_ERROR; 1931204917Sdes const LineInfo *lf; 1932204917Sdes struct complete_ctx *complete_ctx; 1933204917Sdes 1934204917Sdes lf = el_line(el); 1935204917Sdes if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0) 1936204917Sdes fatal("%s: el_get failed", __func__); 1937204917Sdes 1938204917Sdes /* Figure out which argument the cursor points to */ 1939204917Sdes cursor = lf->cursor - lf->buffer; 1940204917Sdes line = (char *)xmalloc(cursor + 1); 1941204917Sdes memcpy(line, lf->buffer, cursor); 1942204917Sdes line[cursor] = '\0'; 1943204917Sdes argv = makeargv(line, &carg, 1, "e, &terminated); 1944255767Sdes free(line); 1945204917Sdes 1946204917Sdes /* Get all the arguments on the line */ 1947204917Sdes len = lf->lastchar - lf->buffer; 1948204917Sdes line = (char *)xmalloc(len + 1); 1949204917Sdes memcpy(line, lf->buffer, len); 1950204917Sdes line[len] = '\0'; 1951204917Sdes argv = makeargv(line, &argc, 1, NULL, NULL); 1952204917Sdes 1953204917Sdes /* Ensure cursor is at EOL or a argument boundary */ 1954204917Sdes if (line[cursor] != ' ' && line[cursor] != '\0' && 1955204917Sdes line[cursor] != '\n') { 1956255767Sdes free(line); 1957204917Sdes return ret; 1958204917Sdes } 1959204917Sdes 1960204917Sdes if (carg == 0) { 1961204917Sdes /* Show all available commands */ 1962204917Sdes complete_cmd_parse(el, NULL, argc == carg, '\0', 1); 1963204917Sdes ret = CC_REDISPLAY; 1964204917Sdes } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') { 1965204917Sdes /* Handle the command parsing */ 1966204917Sdes if (complete_cmd_parse(el, argv[0], argc == carg, 1967262566Sdes quote, terminated) != 0) 1968204917Sdes ret = CC_REDISPLAY; 1969204917Sdes } else if (carg >= 1) { 1970204917Sdes /* Handle file parsing */ 1971204917Sdes int remote = complete_is_remote(argv[0]); 1972204917Sdes char *filematch = NULL; 1973204917Sdes 1974204917Sdes if (carg > 1 && line[cursor-1] != ' ') 1975204917Sdes filematch = argv[carg - 1]; 1976204917Sdes 1977204917Sdes if (remote != 0 && 1978204917Sdes complete_match(el, complete_ctx->conn, 1979204917Sdes *complete_ctx->remote_pathp, filematch, 1980262566Sdes remote, carg == argc, quote, terminated) != 0) 1981204917Sdes ret = CC_REDISPLAY; 1982204917Sdes } 1983204917Sdes 1984262566Sdes free(line); 1985204917Sdes return ret; 1986204917Sdes} 1987204917Sdes#endif /* USE_LIBEDIT */ 1988204917Sdes 1989126274Sdesint 1990204917Sdesinteractive_loop(struct sftp_conn *conn, char *file1, char *file2) 1991126274Sdes{ 1992204917Sdes char *remote_path; 1993126274Sdes char *dir = NULL; 1994126274Sdes char cmd[2048]; 1995149749Sdes int err, interactive; 1996146998Sdes EditLine *el = NULL; 1997146998Sdes#ifdef USE_LIBEDIT 1998146998Sdes History *hl = NULL; 1999146998Sdes HistEvent hev; 2000146998Sdes extern char *__progname; 2001204917Sdes struct complete_ctx complete_ctx; 2002126274Sdes 2003146998Sdes if (!batchmode && isatty(STDIN_FILENO)) { 2004146998Sdes if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL) 2005146998Sdes fatal("Couldn't initialise editline"); 2006146998Sdes if ((hl = history_init()) == NULL) 2007146998Sdes fatal("Couldn't initialise editline history"); 2008146998Sdes history(hl, &hev, H_SETSIZE, 100); 2009146998Sdes el_set(el, EL_HIST, history, hl); 2010146998Sdes 2011146998Sdes el_set(el, EL_PROMPT, prompt); 2012146998Sdes el_set(el, EL_EDITOR, "emacs"); 2013146998Sdes el_set(el, EL_TERMINAL, NULL); 2014146998Sdes el_set(el, EL_SIGNAL, 1); 2015146998Sdes el_source(el, NULL); 2016204917Sdes 2017204917Sdes /* Tab Completion */ 2018262566Sdes el_set(el, EL_ADDFN, "ftp-complete", 2019221420Sdes "Context sensitive argument completion", complete); 2020204917Sdes complete_ctx.conn = conn; 2021204917Sdes complete_ctx.remote_pathp = &remote_path; 2022204917Sdes el_set(el, EL_CLIENTDATA, (void*)&complete_ctx); 2023204917Sdes el_set(el, EL_BIND, "^I", "ftp-complete", NULL); 2024262566Sdes /* enable ctrl-left-arrow and ctrl-right-arrow */ 2025262566Sdes el_set(el, EL_BIND, "\\e[1;5C", "em-next-word", NULL); 2026262566Sdes el_set(el, EL_BIND, "\\e[5C", "em-next-word", NULL); 2027262566Sdes el_set(el, EL_BIND, "\\e[1;5D", "ed-prev-word", NULL); 2028262566Sdes el_set(el, EL_BIND, "\\e\\e[D", "ed-prev-word", NULL); 2029262566Sdes /* make ^w match ksh behaviour */ 2030262566Sdes el_set(el, EL_BIND, "^w", "ed-delete-prev-word", NULL); 2031146998Sdes } 2032146998Sdes#endif /* USE_LIBEDIT */ 2033146998Sdes 2034204917Sdes remote_path = do_realpath(conn, "."); 2035204917Sdes if (remote_path == NULL) 2036126274Sdes fatal("Need cwd"); 2037126274Sdes 2038126274Sdes if (file1 != NULL) { 2039126274Sdes dir = xstrdup(file1); 2040204917Sdes dir = make_absolute(dir, remote_path); 2041126274Sdes 2042126274Sdes if (remote_is_dir(conn, dir) && file2 == NULL) { 2043255767Sdes if (!quiet) 2044255767Sdes printf("Changing to: %s\n", dir); 2045126274Sdes snprintf(cmd, sizeof cmd, "cd \"%s\"", dir); 2046204917Sdes if (parse_dispatch_command(conn, cmd, 2047204917Sdes &remote_path, 1) != 0) { 2048255767Sdes free(dir); 2049255767Sdes free(remote_path); 2050255767Sdes free(conn); 2051126274Sdes return (-1); 2052146998Sdes } 2053126274Sdes } else { 2054248619Sdes /* XXX this is wrong wrt quoting */ 2055255767Sdes snprintf(cmd, sizeof cmd, "get%s %s%s%s", 2056255767Sdes global_aflag ? " -a" : "", dir, 2057255767Sdes file2 == NULL ? "" : " ", 2058255767Sdes file2 == NULL ? "" : file2); 2059204917Sdes err = parse_dispatch_command(conn, cmd, 2060204917Sdes &remote_path, 1); 2061255767Sdes free(dir); 2062255767Sdes free(remote_path); 2063255767Sdes free(conn); 2064126274Sdes return (err); 2065126274Sdes } 2066255767Sdes free(dir); 2067126274Sdes } 2068126274Sdes 2069149749Sdes setlinebuf(stdout); 2070149749Sdes setlinebuf(infile); 2071126274Sdes 2072149749Sdes interactive = !batchmode && isatty(STDIN_FILENO); 2073126274Sdes err = 0; 2074126274Sdes for (;;) { 2075126274Sdes char *cp; 2076126274Sdes 2077137015Sdes signal(SIGINT, SIG_IGN); 2078137015Sdes 2079146998Sdes if (el == NULL) { 2080149749Sdes if (interactive) 2081149749Sdes printf("sftp> "); 2082146998Sdes if (fgets(cmd, sizeof(cmd), infile) == NULL) { 2083149749Sdes if (interactive) 2084149749Sdes printf("\n"); 2085146998Sdes break; 2086146998Sdes } 2087149749Sdes if (!interactive) { /* Echo command */ 2088149749Sdes printf("sftp> %s", cmd); 2089149749Sdes if (strlen(cmd) > 0 && 2090149749Sdes cmd[strlen(cmd) - 1] != '\n') 2091149749Sdes printf("\n"); 2092149749Sdes } 2093146998Sdes } else { 2094146998Sdes#ifdef USE_LIBEDIT 2095146998Sdes const char *line; 2096146998Sdes int count = 0; 2097126274Sdes 2098204917Sdes if ((line = el_gets(el, &count)) == NULL || 2099204917Sdes count <= 0) { 2100149749Sdes printf("\n"); 2101149749Sdes break; 2102149749Sdes } 2103146998Sdes history(hl, &hev, H_ENTER, line); 2104146998Sdes if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) { 2105146998Sdes fprintf(stderr, "Error: input line too long\n"); 2106146998Sdes continue; 2107146998Sdes } 2108146998Sdes#endif /* USE_LIBEDIT */ 2109126274Sdes } 2110126274Sdes 2111126274Sdes cp = strrchr(cmd, '\n'); 2112126274Sdes if (cp) 2113126274Sdes *cp = '\0'; 2114126274Sdes 2115137015Sdes /* Handle user interrupts gracefully during commands */ 2116137015Sdes interrupted = 0; 2117137015Sdes signal(SIGINT, cmd_interrupt); 2118137015Sdes 2119204917Sdes err = parse_dispatch_command(conn, cmd, &remote_path, 2120204917Sdes batchmode); 2121126274Sdes if (err != 0) 2122126274Sdes break; 2123126274Sdes } 2124255767Sdes free(remote_path); 2125255767Sdes free(conn); 2126126274Sdes 2127149749Sdes#ifdef USE_LIBEDIT 2128149749Sdes if (el != NULL) 2129149749Sdes el_end(el); 2130149749Sdes#endif /* USE_LIBEDIT */ 2131149749Sdes 2132126274Sdes /* err == 1 signifies normal "quit" exit */ 2133126274Sdes return (err >= 0 ? 0 : -1); 2134126274Sdes} 2135126274Sdes 2136126274Sdesstatic void 2137124208Sdesconnect_to_server(char *path, char **args, int *in, int *out) 2138124208Sdes{ 213976259Sgreen int c_in, c_out; 214099060Sdes 214176259Sgreen#ifdef USE_PIPES 214276259Sgreen int pin[2], pout[2]; 214399060Sdes 214476259Sgreen if ((pipe(pin) == -1) || (pipe(pout) == -1)) 214576259Sgreen fatal("pipe: %s", strerror(errno)); 214676259Sgreen *in = pin[0]; 214776259Sgreen *out = pout[1]; 214876259Sgreen c_in = pout[0]; 214976259Sgreen c_out = pin[1]; 215076259Sgreen#else /* USE_PIPES */ 215176259Sgreen int inout[2]; 215299060Sdes 215376259Sgreen if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1) 215476259Sgreen fatal("socketpair: %s", strerror(errno)); 215576259Sgreen *in = *out = inout[0]; 215676259Sgreen c_in = c_out = inout[1]; 215776259Sgreen#endif /* USE_PIPES */ 215876259Sgreen 2159124208Sdes if ((sshpid = fork()) == -1) 216076259Sgreen fatal("fork: %s", strerror(errno)); 2161124208Sdes else if (sshpid == 0) { 216276259Sgreen if ((dup2(c_in, STDIN_FILENO) == -1) || 216376259Sgreen (dup2(c_out, STDOUT_FILENO) == -1)) { 216476259Sgreen fprintf(stderr, "dup2: %s\n", strerror(errno)); 2165137015Sdes _exit(1); 216676259Sgreen } 216776259Sgreen close(*in); 216876259Sgreen close(*out); 216976259Sgreen close(c_in); 217076259Sgreen close(c_out); 2171137015Sdes 2172137015Sdes /* 2173137015Sdes * The underlying ssh is in the same process group, so we must 2174137015Sdes * ignore SIGINT if we want to gracefully abort commands, 2175137015Sdes * otherwise the signal will make it to the ssh process and 2176204917Sdes * kill it too. Contrawise, since sftp sends SIGTERMs to the 2177204917Sdes * underlying ssh, it must *not* ignore that signal. 2178137015Sdes */ 2179137015Sdes signal(SIGINT, SIG_IGN); 2180204917Sdes signal(SIGTERM, SIG_DFL); 2181137015Sdes execvp(path, args); 218292555Sdes fprintf(stderr, "exec: %s: %s\n", path, strerror(errno)); 2183137015Sdes _exit(1); 218476259Sgreen } 218576259Sgreen 2186124208Sdes signal(SIGTERM, killchild); 2187124208Sdes signal(SIGINT, killchild); 2188124208Sdes signal(SIGHUP, killchild); 218976259Sgreen close(c_in); 219076259Sgreen close(c_out); 219176259Sgreen} 219276259Sgreen 219392555Sdesstatic void 219476259Sgreenusage(void) 219576259Sgreen{ 219692555Sdes extern char *__progname; 219798675Sdes 219892555Sdes fprintf(stderr, 2199262566Sdes "usage: %s [-1246aCfpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n" 2200204917Sdes " [-D sftp_server_path] [-F ssh_config] " 2201221420Sdes "[-i identity_file] [-l limit]\n" 2202204917Sdes " [-o ssh_option] [-P port] [-R num_requests] " 2203204917Sdes "[-S program]\n" 2204204917Sdes " [-s subsystem | sftp_server] host\n" 2205192595Sdes " %s [user@]host[:file ...]\n" 2206192595Sdes " %s [user@]host[:dir[/]]\n" 2207204917Sdes " %s -b batchfile [user@]host\n", 2208204917Sdes __progname, __progname, __progname, __progname); 220976259Sgreen exit(1); 221076259Sgreen} 221176259Sgreen 221276259Sgreenint 221376259Sgreenmain(int argc, char **argv) 221476259Sgreen{ 2215113908Sdes int in, out, ch, err; 2216204917Sdes char *host = NULL, *userhost, *cp, *file2 = NULL; 221792555Sdes int debug_level = 0, sshver = 2; 221892555Sdes char *file1 = NULL, *sftp_server = NULL; 221992555Sdes char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL; 2220221420Sdes const char *errstr; 222192555Sdes LogLevel ll = SYSLOG_LEVEL_INFO; 222292555Sdes arglist args; 222376259Sgreen extern int optind; 222476259Sgreen extern char *optarg; 2225204917Sdes struct sftp_conn *conn; 2226204917Sdes size_t copy_buffer_len = DEFAULT_COPY_BUFLEN; 2227204917Sdes size_t num_requests = DEFAULT_NUM_REQUESTS; 2228221420Sdes long long limit_kbps = 0; 222976259Sgreen 2230157016Sdes /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ 2231157016Sdes sanitise_stdfd(); 2232255767Sdes setlocale(LC_CTYPE, ""); 2233157016Sdes 2234124208Sdes __progname = ssh_get_progname(argv[0]); 2235157016Sdes memset(&args, '\0', sizeof(args)); 223692555Sdes args.list = NULL; 2237162852Sdes addargs(&args, "%s", ssh_program); 223892555Sdes addargs(&args, "-oForwardX11 no"); 223992555Sdes addargs(&args, "-oForwardAgent no"); 2240157016Sdes addargs(&args, "-oPermitLocalCommand no"); 224192555Sdes addargs(&args, "-oClearAllForwardings yes"); 2242126274Sdes 224392555Sdes ll = SYSLOG_LEVEL_INFO; 2244126274Sdes infile = stdin; 224576259Sgreen 2246204917Sdes while ((ch = getopt(argc, argv, 2247262566Sdes "1246afhpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) { 224876259Sgreen switch (ch) { 2249204917Sdes /* Passed through to ssh(1) */ 2250204917Sdes case '4': 2251204917Sdes case '6': 225276259Sgreen case 'C': 2253204917Sdes addargs(&args, "-%c", ch); 225476259Sgreen break; 2255204917Sdes /* Passed through to ssh(1) with argument */ 2256204917Sdes case 'F': 2257204917Sdes case 'c': 2258204917Sdes case 'i': 2259204917Sdes case 'o': 2260204917Sdes addargs(&args, "-%c", ch); 2261204917Sdes addargs(&args, "%s", optarg); 2262204917Sdes break; 2263204917Sdes case 'q': 2264255767Sdes ll = SYSLOG_LEVEL_ERROR; 2265255767Sdes quiet = 1; 2266204917Sdes showprogress = 0; 2267204917Sdes addargs(&args, "-%c", ch); 2268204917Sdes break; 2269204917Sdes case 'P': 2270204917Sdes addargs(&args, "-oPort %s", optarg); 2271204917Sdes break; 227276259Sgreen case 'v': 227392555Sdes if (debug_level < 3) { 227492555Sdes addargs(&args, "-v"); 227592555Sdes ll = SYSLOG_LEVEL_DEBUG1 + debug_level; 227692555Sdes } 227792555Sdes debug_level++; 227876259Sgreen break; 227976259Sgreen case '1': 228092555Sdes sshver = 1; 228176259Sgreen if (sftp_server == NULL) 228276259Sgreen sftp_server = _PATH_SFTP_SERVER; 228376259Sgreen break; 2284204917Sdes case '2': 2285204917Sdes sshver = 2; 228676259Sgreen break; 2287255767Sdes case 'a': 2288255767Sdes global_aflag = 1; 2289255767Sdes break; 2290204917Sdes case 'B': 2291204917Sdes copy_buffer_len = strtol(optarg, &cp, 10); 2292204917Sdes if (copy_buffer_len == 0 || *cp != '\0') 2293204917Sdes fatal("Invalid buffer size \"%s\"", optarg); 229476259Sgreen break; 229576259Sgreen case 'b': 2296126274Sdes if (batchmode) 2297126274Sdes fatal("Batch file already specified."); 2298126274Sdes 2299126274Sdes /* Allow "-" as stdin */ 2300137015Sdes if (strcmp(optarg, "-") != 0 && 2301149749Sdes (infile = fopen(optarg, "r")) == NULL) 2302126274Sdes fatal("%s (%s).", strerror(errno), optarg); 2303113908Sdes showprogress = 0; 2304255767Sdes quiet = batchmode = 1; 2305146998Sdes addargs(&args, "-obatchmode yes"); 230676259Sgreen break; 2307262566Sdes case 'f': 2308262566Sdes global_fflag = 1; 2309262566Sdes break; 2310204917Sdes case 'p': 2311204917Sdes global_pflag = 1; 2312204917Sdes break; 2313204917Sdes case 'D': 231492555Sdes sftp_direct = optarg; 231592555Sdes break; 2316221420Sdes case 'l': 2317221420Sdes limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024, 2318221420Sdes &errstr); 2319221420Sdes if (errstr != NULL) 2320221420Sdes usage(); 2321221420Sdes limit_kbps *= 1024; /* kbps */ 2322221420Sdes break; 2323204917Sdes case 'r': 2324204917Sdes global_rflag = 1; 232592555Sdes break; 232692555Sdes case 'R': 232792555Sdes num_requests = strtol(optarg, &cp, 10); 232892555Sdes if (num_requests == 0 || *cp != '\0') 232998675Sdes fatal("Invalid number of requests \"%s\"", 233092555Sdes optarg); 233192555Sdes break; 2332204917Sdes case 's': 2333204917Sdes sftp_server = optarg; 2334204917Sdes break; 2335204917Sdes case 'S': 2336204917Sdes ssh_program = optarg; 2337204917Sdes replacearg(&args, 0, "%s", ssh_program); 2338204917Sdes break; 233976259Sgreen case 'h': 234076259Sgreen default: 234176259Sgreen usage(); 234276259Sgreen } 234376259Sgreen } 234476259Sgreen 2345128456Sdes if (!isatty(STDERR_FILENO)) 2346128456Sdes showprogress = 0; 2347128456Sdes 234898675Sdes log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1); 234998675Sdes 235092555Sdes if (sftp_direct == NULL) { 235192555Sdes if (optind == argc || argc > (optind + 2)) 235292555Sdes usage(); 235376259Sgreen 235492555Sdes userhost = xstrdup(argv[optind]); 235592555Sdes file2 = argv[optind+1]; 235676259Sgreen 2357113908Sdes if ((host = strrchr(userhost, '@')) == NULL) 235892555Sdes host = userhost; 235992555Sdes else { 236092555Sdes *host++ = '\0'; 236192555Sdes if (!userhost[0]) { 236292555Sdes fprintf(stderr, "Missing username\n"); 236392555Sdes usage(); 236492555Sdes } 2365204917Sdes addargs(&args, "-l"); 2366204917Sdes addargs(&args, "%s", userhost); 236792555Sdes } 236892555Sdes 2369126274Sdes if ((cp = colon(host)) != NULL) { 2370126274Sdes *cp++ = '\0'; 2371126274Sdes file1 = cp; 2372126274Sdes } 2373126274Sdes 237492555Sdes host = cleanhostname(host); 237592555Sdes if (!*host) { 237692555Sdes fprintf(stderr, "Missing hostname\n"); 237776259Sgreen usage(); 237876259Sgreen } 237976259Sgreen 238092555Sdes addargs(&args, "-oProtocol %d", sshver); 238176259Sgreen 238292555Sdes /* no subsystem if the server-spec contains a '/' */ 238392555Sdes if (sftp_server == NULL || strchr(sftp_server, '/') == NULL) 238492555Sdes addargs(&args, "-s"); 238576259Sgreen 2386204917Sdes addargs(&args, "--"); 238792555Sdes addargs(&args, "%s", host); 238898675Sdes addargs(&args, "%s", (sftp_server != NULL ? 238992555Sdes sftp_server : "sftp")); 239076259Sgreen 2391124208Sdes connect_to_server(ssh_program, args.list, &in, &out); 239292555Sdes } else { 239392555Sdes args.list = NULL; 239492555Sdes addargs(&args, "sftp-server"); 239576259Sgreen 2396124208Sdes connect_to_server(sftp_direct, args.list, &in, &out); 239792555Sdes } 2398157016Sdes freeargs(&args); 239976259Sgreen 2400221420Sdes conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps); 2401204917Sdes if (conn == NULL) 2402204917Sdes fatal("Couldn't initialise connection to server"); 240376259Sgreen 2404255767Sdes if (!quiet) { 2405204917Sdes if (sftp_direct == NULL) 2406204917Sdes fprintf(stderr, "Connected to %s.\n", host); 2407204917Sdes else 2408204917Sdes fprintf(stderr, "Attached to %s.\n", sftp_direct); 2409204917Sdes } 2410204917Sdes 2411204917Sdes err = interactive_loop(conn, file1, file2); 2412204917Sdes 241398937Sdes#if !defined(USE_PIPES) 2414149749Sdes shutdown(in, SHUT_RDWR); 2415149749Sdes shutdown(out, SHUT_RDWR); 241698937Sdes#endif 241798937Sdes 241876259Sgreen close(in); 241976259Sgreen close(out); 2420126274Sdes if (batchmode) 242176259Sgreen fclose(infile); 242276259Sgreen 242398675Sdes while (waitpid(sshpid, NULL, 0) == -1) 242498675Sdes if (errno != EINTR) 242598675Sdes fatal("Couldn't wait for ssh process: %s", 242698675Sdes strerror(errno)); 242776259Sgreen 2428113908Sdes exit(err == 0 ? 0 : 1); 242976259Sgreen} 2430