1323124Sdes/* $OpenBSD: sftp.c,v 1.175 2016/07/22 03:47:36 djm 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> 52323124Sdes#include <stdarg.h> 53162852Sdes#include <stdlib.h> 54162852Sdes#include <stdio.h> 55162852Sdes#include <string.h> 56162852Sdes#include <unistd.h> 57162852Sdes#include <stdarg.h> 58146998Sdes 59181111Sdes#ifdef HAVE_UTIL_H 60181111Sdes# include <util.h> 61181111Sdes#endif 62181111Sdes 6376259Sgreen#include "xmalloc.h" 6476259Sgreen#include "log.h" 6576259Sgreen#include "pathnames.h" 6692555Sdes#include "misc.h" 67323124Sdes#include "utf8.h" 6876259Sgreen 6976259Sgreen#include "sftp.h" 70295367Sdes#include "ssherr.h" 71295367Sdes#include "sshbuf.h" 7276259Sgreen#include "sftp-common.h" 7376259Sgreen#include "sftp-client.h" 7476259Sgreen 75204917Sdes#define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */ 76294693Sdes#define DEFAULT_NUM_REQUESTS 64 /* # concurrent outstanding requests */ 77204917Sdes 78126274Sdes/* File to read commands from */ 79126274SdesFILE* infile; 80126274Sdes 81126274Sdes/* Are we in batchfile mode? */ 82126274Sdesint batchmode = 0; 83126274Sdes 84126274Sdes/* PID of ssh transport process */ 85126274Sdesstatic pid_t sshpid = -1; 86126274Sdes 87255767Sdes/* Suppress diagnositic messages */ 88255767Sdesint quiet = 0; 89255767Sdes 90126274Sdes/* This is set to 0 if the progressmeter is not desired. */ 91128456Sdesint showprogress = 1; 92126274Sdes 93204917Sdes/* When this option is set, we always recursively download/upload directories */ 94204917Sdesint global_rflag = 0; 95204917Sdes 96295367Sdes/* When this option is set, we resume download or upload if possible */ 97255767Sdesint global_aflag = 0; 98255767Sdes 99204917Sdes/* When this option is set, the file transfers will always preserve times */ 100204917Sdesint global_pflag = 0; 101204917Sdes 102262566Sdes/* When this option is set, transfers will have fsync() called on each file */ 103262566Sdesint global_fflag = 0; 104262566Sdes 105137015Sdes/* SIGINT received during command processing */ 106137015Sdesvolatile sig_atomic_t interrupted = 0; 107137015Sdes 108137015Sdes/* I wish qsort() took a separate ctx for the comparison function...*/ 109137015Sdesint sort_flag; 110137015Sdes 111204917Sdes/* Context used for commandline completion */ 112204917Sdesstruct complete_ctx { 113204917Sdes struct sftp_conn *conn; 114204917Sdes char **remote_pathp; 115204917Sdes}; 116204917Sdes 117126274Sdesint remote_glob(struct sftp_conn *, const char *, int, 118126274Sdes int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */ 119126274Sdes 12098937Sdesextern char *__progname; 12198937Sdes 122126274Sdes/* Separators for interactive commands */ 123126274Sdes#define WHITESPACE " \t\r\n" 12476259Sgreen 125137015Sdes/* ls flags */ 126204917Sdes#define LS_LONG_VIEW 0x0001 /* Full view ala ls -l */ 127204917Sdes#define LS_SHORT_VIEW 0x0002 /* Single row view ala ls -1 */ 128204917Sdes#define LS_NUMERIC_VIEW 0x0004 /* Long view with numeric uid/gid */ 129204917Sdes#define LS_NAME_SORT 0x0008 /* Sort by name (default) */ 130204917Sdes#define LS_TIME_SORT 0x0010 /* Sort by mtime */ 131204917Sdes#define LS_SIZE_SORT 0x0020 /* Sort by file size */ 132204917Sdes#define LS_REVERSE_SORT 0x0040 /* Reverse sort order */ 133204917Sdes#define LS_SHOW_ALL 0x0080 /* Don't skip filenames starting with '.' */ 134204917Sdes#define LS_SI_UNITS 0x0100 /* Display sizes as K, M, G, etc. */ 135113908Sdes 136204917Sdes#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS) 137137015Sdes#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT) 138137015Sdes 139126274Sdes/* Commands for interactive mode */ 140262566Sdesenum sftp_command { 141262566Sdes I_CHDIR = 1, 142262566Sdes I_CHGRP, 143262566Sdes I_CHMOD, 144262566Sdes I_CHOWN, 145262566Sdes I_DF, 146262566Sdes I_GET, 147262566Sdes I_HELP, 148262566Sdes I_LCHDIR, 149262566Sdes I_LINK, 150262566Sdes I_LLS, 151262566Sdes I_LMKDIR, 152262566Sdes I_LPWD, 153262566Sdes I_LS, 154262566Sdes I_LUMASK, 155262566Sdes I_MKDIR, 156262566Sdes I_PUT, 157262566Sdes I_PWD, 158262566Sdes I_QUIT, 159295367Sdes I_REGET, 160262566Sdes I_RENAME, 161295367Sdes I_REPUT, 162262566Sdes I_RM, 163262566Sdes I_RMDIR, 164262566Sdes I_SHELL, 165262566Sdes I_SYMLINK, 166262566Sdes I_VERSION, 167262566Sdes I_PROGRESS, 168262566Sdes}; 169126274Sdes 170126274Sdesstruct CMD { 171126274Sdes const char *c; 172126274Sdes const int n; 173204917Sdes const int t; 174126274Sdes}; 175126274Sdes 176204917Sdes/* Type of completion */ 177204917Sdes#define NOARGS 0 178204917Sdes#define REMOTE 1 179204917Sdes#define LOCAL 2 180204917Sdes 181126274Sdesstatic const struct CMD cmds[] = { 182204917Sdes { "bye", I_QUIT, NOARGS }, 183204917Sdes { "cd", I_CHDIR, REMOTE }, 184204917Sdes { "chdir", I_CHDIR, REMOTE }, 185204917Sdes { "chgrp", I_CHGRP, REMOTE }, 186204917Sdes { "chmod", I_CHMOD, REMOTE }, 187204917Sdes { "chown", I_CHOWN, REMOTE }, 188204917Sdes { "df", I_DF, REMOTE }, 189204917Sdes { "dir", I_LS, REMOTE }, 190204917Sdes { "exit", I_QUIT, NOARGS }, 191204917Sdes { "get", I_GET, REMOTE }, 192204917Sdes { "help", I_HELP, NOARGS }, 193204917Sdes { "lcd", I_LCHDIR, LOCAL }, 194204917Sdes { "lchdir", I_LCHDIR, LOCAL }, 195204917Sdes { "lls", I_LLS, LOCAL }, 196204917Sdes { "lmkdir", I_LMKDIR, LOCAL }, 197221420Sdes { "ln", I_LINK, REMOTE }, 198204917Sdes { "lpwd", I_LPWD, LOCAL }, 199204917Sdes { "ls", I_LS, REMOTE }, 200204917Sdes { "lumask", I_LUMASK, NOARGS }, 201204917Sdes { "mkdir", I_MKDIR, REMOTE }, 202215116Sdes { "mget", I_GET, REMOTE }, 203215116Sdes { "mput", I_PUT, LOCAL }, 204204917Sdes { "progress", I_PROGRESS, NOARGS }, 205204917Sdes { "put", I_PUT, LOCAL }, 206204917Sdes { "pwd", I_PWD, REMOTE }, 207204917Sdes { "quit", I_QUIT, NOARGS }, 208255767Sdes { "reget", I_REGET, REMOTE }, 209204917Sdes { "rename", I_RENAME, REMOTE }, 210295367Sdes { "reput", I_REPUT, LOCAL }, 211204917Sdes { "rm", I_RM, REMOTE }, 212204917Sdes { "rmdir", I_RMDIR, REMOTE }, 213204917Sdes { "symlink", I_SYMLINK, REMOTE }, 214204917Sdes { "version", I_VERSION, NOARGS }, 215204917Sdes { "!", I_SHELL, NOARGS }, 216204917Sdes { "?", I_HELP, NOARGS }, 217204917Sdes { NULL, -1, -1 } 218126274Sdes}; 219126274Sdes 220204917Sdesint interactive_loop(struct sftp_conn *, char *file1, char *file2); 221126274Sdes 222181111Sdes/* ARGSUSED */ 22392555Sdesstatic void 224137015Sdeskillchild(int signo) 225137015Sdes{ 226146998Sdes if (sshpid > 1) { 227137015Sdes kill(sshpid, SIGTERM); 228146998Sdes waitpid(sshpid, NULL, 0); 229146998Sdes } 230137015Sdes 231137015Sdes _exit(1); 232137015Sdes} 233137015Sdes 234181111Sdes/* ARGSUSED */ 235137015Sdesstatic void 236137015Sdescmd_interrupt(int signo) 237137015Sdes{ 238137015Sdes const char msg[] = "\rInterrupt \n"; 239146998Sdes int olderrno = errno; 240137015Sdes 241255767Sdes (void)write(STDERR_FILENO, msg, sizeof(msg) - 1); 242137015Sdes interrupted = 1; 243146998Sdes errno = olderrno; 244137015Sdes} 245137015Sdes 246137015Sdesstatic void 247126274Sdeshelp(void) 248126274Sdes{ 249192595Sdes printf("Available commands:\n" 250192595Sdes "bye Quit sftp\n" 251192595Sdes "cd path Change remote directory to 'path'\n" 252192595Sdes "chgrp grp path Change group of file 'path' to 'grp'\n" 253192595Sdes "chmod mode path Change permissions of file 'path' to 'mode'\n" 254192595Sdes "chown own path Change owner of file 'path' to 'own'\n" 255192595Sdes "df [-hi] [path] Display statistics for current directory or\n" 256192595Sdes " filesystem containing 'path'\n" 257192595Sdes "exit Quit sftp\n" 258295367Sdes "get [-afPpRr] remote [local] Download file\n" 259295367Sdes "reget [-fPpRr] remote [local] Resume download file\n" 260295367Sdes "reput [-fPpRr] [local] remote Resume upload file\n" 261192595Sdes "help Display this help text\n" 262192595Sdes "lcd path Change local directory to 'path'\n" 263192595Sdes "lls [ls-options [path]] Display local directory listing\n" 264192595Sdes "lmkdir path Create local directory\n" 265221420Sdes "ln [-s] oldpath newpath Link remote file (-s for symlink)\n" 266192595Sdes "lpwd Print local working directory\n" 267204917Sdes "ls [-1afhlnrSt] [path] Display remote directory listing\n" 268192595Sdes "lumask umask Set local umask to 'umask'\n" 269192595Sdes "mkdir path Create remote directory\n" 270192595Sdes "progress Toggle display of progress meter\n" 271295367Sdes "put [-afPpRr] local [remote] Upload file\n" 272192595Sdes "pwd Display remote working directory\n" 273192595Sdes "quit Quit sftp\n" 274192595Sdes "rename oldpath newpath Rename remote file\n" 275192595Sdes "rm path Delete remote file\n" 276192595Sdes "rmdir path Remove remote directory\n" 277192595Sdes "symlink oldpath newpath Symlink remote file\n" 278192595Sdes "version Show SFTP version\n" 279192595Sdes "!command Execute 'command' in local shell\n" 280192595Sdes "! Escape to local shell\n" 281192595Sdes "? Synonym for help\n"); 282126274Sdes} 283126274Sdes 284126274Sdesstatic void 285126274Sdeslocal_do_shell(const char *args) 286126274Sdes{ 287126274Sdes int status; 288126274Sdes char *shell; 289126274Sdes pid_t pid; 290126274Sdes 291126274Sdes if (!*args) 292126274Sdes args = NULL; 293126274Sdes 294221420Sdes if ((shell = getenv("SHELL")) == NULL || *shell == '\0') 295126274Sdes shell = _PATH_BSHELL; 296126274Sdes 297126274Sdes if ((pid = fork()) == -1) 298126274Sdes fatal("Couldn't fork: %s", strerror(errno)); 299126274Sdes 300126274Sdes if (pid == 0) { 301126274Sdes /* XXX: child has pipe fds to ssh subproc open - issue? */ 302126274Sdes if (args) { 303126274Sdes debug3("Executing %s -c \"%s\"", shell, args); 304126274Sdes execl(shell, shell, "-c", args, (char *)NULL); 305126274Sdes } else { 306126274Sdes debug3("Executing %s", shell); 307126274Sdes execl(shell, shell, (char *)NULL); 308126274Sdes } 309126274Sdes fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell, 310126274Sdes strerror(errno)); 311126274Sdes _exit(1); 312126274Sdes } 313126274Sdes while (waitpid(pid, &status, 0) == -1) 314126274Sdes if (errno != EINTR) 315126274Sdes fatal("Couldn't wait for child: %s", strerror(errno)); 316126274Sdes if (!WIFEXITED(status)) 317162852Sdes error("Shell exited abnormally"); 318126274Sdes else if (WEXITSTATUS(status)) 319126274Sdes error("Shell exited with status %d", WEXITSTATUS(status)); 320126274Sdes} 321126274Sdes 322126274Sdesstatic void 323126274Sdeslocal_do_ls(const char *args) 324126274Sdes{ 325126274Sdes if (!args || !*args) 326126274Sdes local_do_shell(_PATH_LS); 327126274Sdes else { 328126274Sdes int len = strlen(_PATH_LS " ") + strlen(args) + 1; 329126274Sdes char *buf = xmalloc(len); 330126274Sdes 331126274Sdes /* XXX: quoting - rip quoting code from ftp? */ 332126274Sdes snprintf(buf, len, _PATH_LS " %s", args); 333126274Sdes local_do_shell(buf); 334255767Sdes free(buf); 335126274Sdes } 336126274Sdes} 337126274Sdes 338126274Sdes/* Strip one path (usually the pwd) from the start of another */ 339126274Sdesstatic char * 340323124Sdespath_strip(const char *path, const char *strip) 341126274Sdes{ 342126274Sdes size_t len; 343126274Sdes 344126274Sdes if (strip == NULL) 345126274Sdes return (xstrdup(path)); 346126274Sdes 347126274Sdes len = strlen(strip); 348146998Sdes if (strncmp(path, strip, len) == 0) { 349126274Sdes if (strip[len - 1] != '/' && path[len] == '/') 350126274Sdes len++; 351126274Sdes return (xstrdup(path + len)); 352126274Sdes } 353126274Sdes 354126274Sdes return (xstrdup(path)); 355126274Sdes} 356126274Sdes 357126274Sdesstatic char * 358323124Sdesmake_absolute(char *p, const char *pwd) 359126274Sdes{ 360137015Sdes char *abs_str; 361126274Sdes 362126274Sdes /* Derelativise */ 363126274Sdes if (p && p[0] != '/') { 364137015Sdes abs_str = path_append(pwd, p); 365255767Sdes free(p); 366137015Sdes return(abs_str); 367126274Sdes } else 368126274Sdes return(p); 369126274Sdes} 370126274Sdes 371126274Sdesstatic int 372255767Sdesparse_getput_flags(const char *cmd, char **argv, int argc, 373262566Sdes int *aflag, int *fflag, int *pflag, int *rflag) 374126274Sdes{ 375181111Sdes extern int opterr, optind, optopt, optreset; 376181111Sdes int ch; 377126274Sdes 378181111Sdes optind = optreset = 1; 379181111Sdes opterr = 0; 380181111Sdes 381262566Sdes *aflag = *fflag = *rflag = *pflag = 0; 382262566Sdes while ((ch = getopt(argc, argv, "afPpRr")) != -1) { 383181111Sdes switch (ch) { 384255767Sdes case 'a': 385255767Sdes *aflag = 1; 386255767Sdes break; 387262566Sdes case 'f': 388262566Sdes *fflag = 1; 389262566Sdes break; 390126274Sdes case 'p': 391126274Sdes case 'P': 392126274Sdes *pflag = 1; 393126274Sdes break; 394204917Sdes case 'r': 395204917Sdes case 'R': 396204917Sdes *rflag = 1; 397204917Sdes break; 398126274Sdes default: 399181111Sdes error("%s: Invalid flag -%c", cmd, optopt); 400181111Sdes return -1; 401126274Sdes } 402126274Sdes } 403126274Sdes 404181111Sdes return optind; 405126274Sdes} 406126274Sdes 407126274Sdesstatic int 408221420Sdesparse_link_flags(const char *cmd, char **argv, int argc, int *sflag) 409221420Sdes{ 410221420Sdes extern int opterr, optind, optopt, optreset; 411221420Sdes int ch; 412221420Sdes 413221420Sdes optind = optreset = 1; 414221420Sdes opterr = 0; 415221420Sdes 416221420Sdes *sflag = 0; 417221420Sdes while ((ch = getopt(argc, argv, "s")) != -1) { 418221420Sdes switch (ch) { 419221420Sdes case 's': 420221420Sdes *sflag = 1; 421221420Sdes break; 422221420Sdes default: 423221420Sdes error("%s: Invalid flag -%c", cmd, optopt); 424221420Sdes return -1; 425221420Sdes } 426221420Sdes } 427221420Sdes 428221420Sdes return optind; 429221420Sdes} 430221420Sdes 431221420Sdesstatic int 432262566Sdesparse_rename_flags(const char *cmd, char **argv, int argc, int *lflag) 433262566Sdes{ 434262566Sdes extern int opterr, optind, optopt, optreset; 435262566Sdes int ch; 436262566Sdes 437262566Sdes optind = optreset = 1; 438262566Sdes opterr = 0; 439262566Sdes 440262566Sdes *lflag = 0; 441262566Sdes while ((ch = getopt(argc, argv, "l")) != -1) { 442262566Sdes switch (ch) { 443262566Sdes case 'l': 444262566Sdes *lflag = 1; 445262566Sdes break; 446262566Sdes default: 447262566Sdes error("%s: Invalid flag -%c", cmd, optopt); 448262566Sdes return -1; 449262566Sdes } 450262566Sdes } 451262566Sdes 452262566Sdes return optind; 453262566Sdes} 454262566Sdes 455262566Sdesstatic int 456181111Sdesparse_ls_flags(char **argv, int argc, int *lflag) 457126274Sdes{ 458181111Sdes extern int opterr, optind, optopt, optreset; 459181111Sdes int ch; 460126274Sdes 461181111Sdes optind = optreset = 1; 462181111Sdes opterr = 0; 463181111Sdes 464137015Sdes *lflag = LS_NAME_SORT; 465204917Sdes while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) { 466181111Sdes switch (ch) { 467181111Sdes case '1': 468181111Sdes *lflag &= ~VIEW_FLAGS; 469181111Sdes *lflag |= LS_SHORT_VIEW; 470181111Sdes break; 471181111Sdes case 'S': 472181111Sdes *lflag &= ~SORT_FLAGS; 473181111Sdes *lflag |= LS_SIZE_SORT; 474181111Sdes break; 475181111Sdes case 'a': 476181111Sdes *lflag |= LS_SHOW_ALL; 477181111Sdes break; 478181111Sdes case 'f': 479181111Sdes *lflag &= ~SORT_FLAGS; 480181111Sdes break; 481204917Sdes case 'h': 482204917Sdes *lflag |= LS_SI_UNITS; 483204917Sdes break; 484181111Sdes case 'l': 485204917Sdes *lflag &= ~LS_SHORT_VIEW; 486181111Sdes *lflag |= LS_LONG_VIEW; 487181111Sdes break; 488181111Sdes case 'n': 489204917Sdes *lflag &= ~LS_SHORT_VIEW; 490181111Sdes *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW; 491181111Sdes break; 492181111Sdes case 'r': 493181111Sdes *lflag |= LS_REVERSE_SORT; 494181111Sdes break; 495181111Sdes case 't': 496181111Sdes *lflag &= ~SORT_FLAGS; 497181111Sdes *lflag |= LS_TIME_SORT; 498181111Sdes break; 499181111Sdes default: 500181111Sdes error("ls: Invalid flag -%c", optopt); 501181111Sdes return -1; 502126274Sdes } 503126274Sdes } 504126274Sdes 505181111Sdes return optind; 506126274Sdes} 507126274Sdes 508126274Sdesstatic int 509181111Sdesparse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag) 510126274Sdes{ 511181111Sdes extern int opterr, optind, optopt, optreset; 512181111Sdes int ch; 513126274Sdes 514181111Sdes optind = optreset = 1; 515181111Sdes opterr = 0; 516126274Sdes 517181111Sdes *hflag = *iflag = 0; 518181111Sdes while ((ch = getopt(argc, argv, "hi")) != -1) { 519181111Sdes switch (ch) { 520181111Sdes case 'h': 521181111Sdes *hflag = 1; 522181111Sdes break; 523181111Sdes case 'i': 524181111Sdes *iflag = 1; 525181111Sdes break; 526181111Sdes default: 527181111Sdes error("%s: Invalid flag -%c", cmd, optopt); 528181111Sdes return -1; 529126274Sdes } 530126274Sdes } 531126274Sdes 532181111Sdes return optind; 533126274Sdes} 534126274Sdes 535126274Sdesstatic int 536262566Sdesparse_no_flags(const char *cmd, char **argv, int argc) 537262566Sdes{ 538262566Sdes extern int opterr, optind, optopt, optreset; 539262566Sdes int ch; 540262566Sdes 541262566Sdes optind = optreset = 1; 542262566Sdes opterr = 0; 543262566Sdes 544262566Sdes while ((ch = getopt(argc, argv, "")) != -1) { 545262566Sdes switch (ch) { 546262566Sdes default: 547262566Sdes error("%s: Invalid flag -%c", cmd, optopt); 548262566Sdes return -1; 549262566Sdes } 550262566Sdes } 551262566Sdes 552262566Sdes return optind; 553262566Sdes} 554262566Sdes 555262566Sdesstatic int 556323124Sdesis_dir(const char *path) 557126274Sdes{ 558126274Sdes struct stat sb; 559126274Sdes 560126274Sdes /* XXX: report errors? */ 561126274Sdes if (stat(path, &sb) == -1) 562126274Sdes return(0); 563126274Sdes 564162852Sdes return(S_ISDIR(sb.st_mode)); 565126274Sdes} 566126274Sdes 567126274Sdesstatic int 568323124Sdesremote_is_dir(struct sftp_conn *conn, const char *path) 569126274Sdes{ 570126274Sdes Attrib *a; 571126274Sdes 572126274Sdes /* XXX: report errors? */ 573126274Sdes if ((a = do_stat(conn, path, 1)) == NULL) 574126274Sdes return(0); 575126274Sdes if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) 576126274Sdes return(0); 577162852Sdes return(S_ISDIR(a->perm)); 578126274Sdes} 579126274Sdes 580204917Sdes/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */ 581126274Sdesstatic int 582323124Sdespathname_is_dir(const char *pathname) 583126274Sdes{ 584204917Sdes size_t l = strlen(pathname); 585204917Sdes 586204917Sdes return l > 0 && pathname[l - 1] == '/'; 587204917Sdes} 588204917Sdes 589204917Sdesstatic int 590323124Sdesprocess_get(struct sftp_conn *conn, const char *src, const char *dst, 591323124Sdes const char *pwd, int pflag, int rflag, int resume, int fflag) 592204917Sdes{ 593126274Sdes char *abs_src = NULL; 594126274Sdes char *abs_dst = NULL; 595126274Sdes glob_t g; 596204917Sdes char *filename, *tmp=NULL; 597295367Sdes int i, r, err = 0; 598126274Sdes 599126274Sdes abs_src = xstrdup(src); 600126274Sdes abs_src = make_absolute(abs_src, pwd); 601204917Sdes memset(&g, 0, sizeof(g)); 602126274Sdes 603126274Sdes debug3("Looking up %s", abs_src); 604295367Sdes if ((r = remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) != 0) { 605295367Sdes if (r == GLOB_NOSPACE) { 606295367Sdes error("Too many matches for \"%s\".", abs_src); 607295367Sdes } else { 608295367Sdes error("File \"%s\" not found.", abs_src); 609295367Sdes } 610126274Sdes err = -1; 611126274Sdes goto out; 612126274Sdes } 613126274Sdes 614204917Sdes /* 615204917Sdes * If multiple matches then dst must be a directory or 616204917Sdes * unspecified. 617204917Sdes */ 618204917Sdes if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) { 619204917Sdes error("Multiple source paths, but destination " 620204917Sdes "\"%s\" is not a directory", dst); 621126274Sdes err = -1; 622126274Sdes goto out; 623126274Sdes } 624126274Sdes 625137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 626204917Sdes tmp = xstrdup(g.gl_pathv[i]); 627204917Sdes if ((filename = basename(tmp)) == NULL) { 628204917Sdes error("basename %s: %s", tmp, strerror(errno)); 629255767Sdes free(tmp); 630126274Sdes err = -1; 631126274Sdes goto out; 632126274Sdes } 633126274Sdes 634126274Sdes if (g.gl_matchc == 1 && dst) { 635126274Sdes if (is_dir(dst)) { 636204917Sdes abs_dst = path_append(dst, filename); 637204917Sdes } else { 638126274Sdes abs_dst = xstrdup(dst); 639204917Sdes } 640126274Sdes } else if (dst) { 641204917Sdes abs_dst = path_append(dst, filename); 642204917Sdes } else { 643204917Sdes abs_dst = xstrdup(filename); 644204917Sdes } 645255767Sdes free(tmp); 646126274Sdes 647255767Sdes resume |= global_aflag; 648255767Sdes if (!quiet && resume) 649323124Sdes mprintf("Resuming %s to %s\n", 650323124Sdes g.gl_pathv[i], abs_dst); 651255767Sdes else if (!quiet && !resume) 652323124Sdes mprintf("Fetching %s to %s\n", 653323124Sdes g.gl_pathv[i], abs_dst); 654204917Sdes if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) { 655255767Sdes if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL, 656262566Sdes pflag || global_pflag, 1, resume, 657262566Sdes fflag || global_fflag) == -1) 658204917Sdes err = -1; 659204917Sdes } else { 660204917Sdes if (do_download(conn, g.gl_pathv[i], abs_dst, NULL, 661262566Sdes pflag || global_pflag, resume, 662262566Sdes fflag || global_fflag) == -1) 663204917Sdes err = -1; 664204917Sdes } 665255767Sdes free(abs_dst); 666126274Sdes abs_dst = NULL; 667126274Sdes } 668126274Sdes 669126274Sdesout: 670255767Sdes free(abs_src); 671126274Sdes globfree(&g); 672126274Sdes return(err); 673126274Sdes} 674126274Sdes 675126274Sdesstatic int 676323124Sdesprocess_put(struct sftp_conn *conn, const char *src, const char *dst, 677323124Sdes const char *pwd, int pflag, int rflag, int resume, int fflag) 678126274Sdes{ 679126274Sdes char *tmp_dst = NULL; 680126274Sdes char *abs_dst = NULL; 681204917Sdes char *tmp = NULL, *filename = NULL; 682126274Sdes glob_t g; 683126274Sdes int err = 0; 684204917Sdes int i, dst_is_dir = 1; 685181111Sdes struct stat sb; 686126274Sdes 687126274Sdes if (dst) { 688126274Sdes tmp_dst = xstrdup(dst); 689126274Sdes tmp_dst = make_absolute(tmp_dst, pwd); 690126274Sdes } 691126274Sdes 692126274Sdes memset(&g, 0, sizeof(g)); 693126274Sdes debug3("Looking up %s", src); 694204917Sdes if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) { 695126274Sdes error("File \"%s\" not found.", src); 696126274Sdes err = -1; 697126274Sdes goto out; 698126274Sdes } 699126274Sdes 700204917Sdes /* If we aren't fetching to pwd then stash this status for later */ 701204917Sdes if (tmp_dst != NULL) 702204917Sdes dst_is_dir = remote_is_dir(conn, tmp_dst); 703204917Sdes 704126274Sdes /* If multiple matches, dst may be directory or unspecified */ 705204917Sdes if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) { 706204917Sdes error("Multiple paths match, but destination " 707204917Sdes "\"%s\" is not a directory", tmp_dst); 708126274Sdes err = -1; 709126274Sdes goto out; 710126274Sdes } 711126274Sdes 712137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 713181111Sdes if (stat(g.gl_pathv[i], &sb) == -1) { 714181111Sdes err = -1; 715181111Sdes error("stat %s: %s", g.gl_pathv[i], strerror(errno)); 716181111Sdes continue; 717181111Sdes } 718262566Sdes 719204917Sdes tmp = xstrdup(g.gl_pathv[i]); 720204917Sdes if ((filename = basename(tmp)) == NULL) { 721204917Sdes error("basename %s: %s", tmp, strerror(errno)); 722255767Sdes free(tmp); 723126274Sdes err = -1; 724126274Sdes goto out; 725126274Sdes } 726126274Sdes 727126274Sdes if (g.gl_matchc == 1 && tmp_dst) { 728126274Sdes /* If directory specified, append filename */ 729204917Sdes if (dst_is_dir) 730204917Sdes abs_dst = path_append(tmp_dst, filename); 731204917Sdes else 732126274Sdes abs_dst = xstrdup(tmp_dst); 733126274Sdes } else if (tmp_dst) { 734204917Sdes abs_dst = path_append(tmp_dst, filename); 735204917Sdes } else { 736204917Sdes abs_dst = make_absolute(xstrdup(filename), pwd); 737204917Sdes } 738255767Sdes free(tmp); 739126274Sdes 740295367Sdes resume |= global_aflag; 741295367Sdes if (!quiet && resume) 742323124Sdes mprintf("Resuming upload of %s to %s\n", 743323124Sdes g.gl_pathv[i], abs_dst); 744295367Sdes else if (!quiet && !resume) 745323124Sdes mprintf("Uploading %s to %s\n", 746323124Sdes g.gl_pathv[i], abs_dst); 747204917Sdes if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) { 748204917Sdes if (upload_dir(conn, g.gl_pathv[i], abs_dst, 749295367Sdes pflag || global_pflag, 1, resume, 750262566Sdes fflag || global_fflag) == -1) 751204917Sdes err = -1; 752204917Sdes } else { 753204917Sdes if (do_upload(conn, g.gl_pathv[i], abs_dst, 754295367Sdes pflag || global_pflag, resume, 755262566Sdes fflag || global_fflag) == -1) 756204917Sdes err = -1; 757204917Sdes } 758126274Sdes } 759126274Sdes 760126274Sdesout: 761255767Sdes free(abs_dst); 762255767Sdes free(tmp_dst); 763126274Sdes globfree(&g); 764126274Sdes return(err); 765126274Sdes} 766126274Sdes 767126274Sdesstatic int 768126274Sdessdirent_comp(const void *aa, const void *bb) 769126274Sdes{ 770126274Sdes SFTP_DIRENT *a = *(SFTP_DIRENT **)aa; 771126274Sdes SFTP_DIRENT *b = *(SFTP_DIRENT **)bb; 772137015Sdes int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1; 773126274Sdes 774137015Sdes#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1)) 775137015Sdes if (sort_flag & LS_NAME_SORT) 776137015Sdes return (rmul * strcmp(a->filename, b->filename)); 777137015Sdes else if (sort_flag & LS_TIME_SORT) 778137015Sdes return (rmul * NCMP(a->a.mtime, b->a.mtime)); 779137015Sdes else if (sort_flag & LS_SIZE_SORT) 780137015Sdes return (rmul * NCMP(a->a.size, b->a.size)); 781137015Sdes 782137015Sdes fatal("Unknown ls sort type"); 783126274Sdes} 784126274Sdes 785126274Sdes/* sftp ls.1 replacement for directories */ 786126274Sdesstatic int 787323124Sdesdo_ls_dir(struct sftp_conn *conn, const char *path, 788323124Sdes const char *strip_path, int lflag) 789126274Sdes{ 790149749Sdes int n; 791149749Sdes u_int c = 1, colspace = 0, columns = 1; 792126274Sdes SFTP_DIRENT **d; 793126274Sdes 794126274Sdes if ((n = do_readdir(conn, path, &d)) != 0) 795126274Sdes return (n); 796126274Sdes 797137015Sdes if (!(lflag & LS_SHORT_VIEW)) { 798149749Sdes u_int m = 0, width = 80; 799126274Sdes struct winsize ws; 800126274Sdes char *tmp; 801126274Sdes 802126274Sdes /* Count entries for sort and find longest filename */ 803137015Sdes for (n = 0; d[n] != NULL; n++) { 804137015Sdes if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL)) 805137015Sdes m = MAX(m, strlen(d[n]->filename)); 806137015Sdes } 807126274Sdes 808126274Sdes /* Add any subpath that also needs to be counted */ 809126274Sdes tmp = path_strip(path, strip_path); 810126274Sdes m += strlen(tmp); 811255767Sdes free(tmp); 812126274Sdes 813126274Sdes if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1) 814126274Sdes width = ws.ws_col; 815126274Sdes 816126274Sdes columns = width / (m + 2); 817126274Sdes columns = MAX(columns, 1); 818126274Sdes colspace = width / columns; 819126274Sdes colspace = MIN(colspace, width); 820126274Sdes } 821126274Sdes 822137015Sdes if (lflag & SORT_FLAGS) { 823157016Sdes for (n = 0; d[n] != NULL; n++) 824157016Sdes ; /* count entries */ 825137015Sdes sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT); 826137015Sdes qsort(d, n, sizeof(*d), sdirent_comp); 827137015Sdes } 828126274Sdes 829137015Sdes for (n = 0; d[n] != NULL && !interrupted; n++) { 830126274Sdes char *tmp, *fname; 831126274Sdes 832137015Sdes if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL)) 833137015Sdes continue; 834137015Sdes 835126274Sdes tmp = path_append(path, d[n]->filename); 836126274Sdes fname = path_strip(tmp, strip_path); 837255767Sdes free(tmp); 838126274Sdes 839137015Sdes if (lflag & LS_LONG_VIEW) { 840204917Sdes if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) { 841137015Sdes char *lname; 842137015Sdes struct stat sb; 843126274Sdes 844137015Sdes memset(&sb, 0, sizeof(sb)); 845137015Sdes attrib_to_stat(&d[n]->a, &sb); 846204917Sdes lname = ls_file(fname, &sb, 1, 847204917Sdes (lflag & LS_SI_UNITS)); 848323124Sdes mprintf("%s\n", lname); 849255767Sdes free(lname); 850137015Sdes } else 851323124Sdes mprintf("%s\n", d[n]->longname); 852126274Sdes } else { 853323124Sdes mprintf("%-*s", colspace, fname); 854126274Sdes if (c >= columns) { 855126274Sdes printf("\n"); 856126274Sdes c = 1; 857126274Sdes } else 858126274Sdes c++; 859126274Sdes } 860126274Sdes 861255767Sdes free(fname); 862126274Sdes } 863126274Sdes 864137015Sdes if (!(lflag & LS_LONG_VIEW) && (c != 1)) 865126274Sdes printf("\n"); 866126274Sdes 867126274Sdes free_sftp_dirents(d); 868126274Sdes return (0); 869126274Sdes} 870126274Sdes 871126274Sdes/* sftp ls.1 replacement which handles path globs */ 872126274Sdesstatic int 873323124Sdesdo_globbed_ls(struct sftp_conn *conn, const char *path, 874323124Sdes const char *strip_path, int lflag) 875126274Sdes{ 876221420Sdes char *fname, *lname; 877126274Sdes glob_t g; 878295367Sdes int err, r; 879221420Sdes struct winsize ws; 880221420Sdes u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80; 881126274Sdes 882126274Sdes memset(&g, 0, sizeof(g)); 883126274Sdes 884295367Sdes if ((r = remote_glob(conn, path, 885240075Sdes GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT, 886295367Sdes NULL, &g)) != 0 || 887221420Sdes (g.gl_pathc && !g.gl_matchc)) { 888146998Sdes if (g.gl_pathc) 889146998Sdes globfree(&g); 890295367Sdes if (r == GLOB_NOSPACE) { 891295367Sdes error("Can't ls: Too many matches for \"%s\"", path); 892295367Sdes } else { 893295367Sdes error("Can't ls: \"%s\" not found", path); 894295367Sdes } 895221420Sdes return -1; 896126274Sdes } 897126274Sdes 898137015Sdes if (interrupted) 899137015Sdes goto out; 900137015Sdes 901126274Sdes /* 902146998Sdes * If the glob returns a single match and it is a directory, 903146998Sdes * then just list its contents. 904126274Sdes */ 905221420Sdes if (g.gl_matchc == 1 && g.gl_statv[0] != NULL && 906221420Sdes S_ISDIR(g.gl_statv[0]->st_mode)) { 907221420Sdes err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag); 908221420Sdes globfree(&g); 909221420Sdes return err; 910126274Sdes } 911126274Sdes 912221420Sdes if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1) 913221420Sdes width = ws.ws_col; 914221420Sdes 915137015Sdes if (!(lflag & LS_SHORT_VIEW)) { 916126274Sdes /* Count entries for sort and find longest filename */ 917126274Sdes for (i = 0; g.gl_pathv[i]; i++) 918126274Sdes m = MAX(m, strlen(g.gl_pathv[i])); 919126274Sdes 920126274Sdes columns = width / (m + 2); 921126274Sdes columns = MAX(columns, 1); 922126274Sdes colspace = width / columns; 923126274Sdes } 924126274Sdes 925240075Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 926126274Sdes fname = path_strip(g.gl_pathv[i], strip_path); 927137015Sdes if (lflag & LS_LONG_VIEW) { 928221420Sdes if (g.gl_statv[i] == NULL) { 929221420Sdes error("no stat information for %s", fname); 930221420Sdes continue; 931221420Sdes } 932221420Sdes lname = ls_file(fname, g.gl_statv[i], 1, 933221420Sdes (lflag & LS_SI_UNITS)); 934323124Sdes mprintf("%s\n", lname); 935255767Sdes free(lname); 936126274Sdes } else { 937323124Sdes mprintf("%-*s", colspace, fname); 938126274Sdes if (c >= columns) { 939126274Sdes printf("\n"); 940126274Sdes c = 1; 941126274Sdes } else 942126274Sdes c++; 943126274Sdes } 944255767Sdes free(fname); 945126274Sdes } 946126274Sdes 947137015Sdes if (!(lflag & LS_LONG_VIEW) && (c != 1)) 948126274Sdes printf("\n"); 949126274Sdes 950137015Sdes out: 951126274Sdes if (g.gl_pathc) 952126274Sdes globfree(&g); 953126274Sdes 954221420Sdes return 0; 955126274Sdes} 956126274Sdes 957126274Sdesstatic int 958323124Sdesdo_df(struct sftp_conn *conn, const char *path, int hflag, int iflag) 959181111Sdes{ 960181111Sdes struct sftp_statvfs st; 961181111Sdes char s_used[FMT_SCALED_STRSIZE]; 962181111Sdes char s_avail[FMT_SCALED_STRSIZE]; 963181111Sdes char s_root[FMT_SCALED_STRSIZE]; 964181111Sdes char s_total[FMT_SCALED_STRSIZE]; 965204917Sdes unsigned long long ffree; 966181111Sdes 967181111Sdes if (do_statvfs(conn, path, &st, 1) == -1) 968181111Sdes return -1; 969181111Sdes if (iflag) { 970204917Sdes ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0; 971181111Sdes printf(" Inodes Used Avail " 972181111Sdes "(root) %%Capacity\n"); 973181111Sdes printf("%11llu %11llu %11llu %11llu %3llu%%\n", 974181111Sdes (unsigned long long)st.f_files, 975181111Sdes (unsigned long long)(st.f_files - st.f_ffree), 976181111Sdes (unsigned long long)st.f_favail, 977204917Sdes (unsigned long long)st.f_ffree, ffree); 978181111Sdes } else if (hflag) { 979181111Sdes strlcpy(s_used, "error", sizeof(s_used)); 980181111Sdes strlcpy(s_avail, "error", sizeof(s_avail)); 981181111Sdes strlcpy(s_root, "error", sizeof(s_root)); 982181111Sdes strlcpy(s_total, "error", sizeof(s_total)); 983181111Sdes fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used); 984181111Sdes fmt_scaled(st.f_bavail * st.f_frsize, s_avail); 985181111Sdes fmt_scaled(st.f_bfree * st.f_frsize, s_root); 986181111Sdes fmt_scaled(st.f_blocks * st.f_frsize, s_total); 987181111Sdes printf(" Size Used Avail (root) %%Capacity\n"); 988181111Sdes printf("%7sB %7sB %7sB %7sB %3llu%%\n", 989181111Sdes s_total, s_used, s_avail, s_root, 990181111Sdes (unsigned long long)(100 * (st.f_blocks - st.f_bfree) / 991181111Sdes st.f_blocks)); 992181111Sdes } else { 993181111Sdes printf(" Size Used Avail " 994181111Sdes "(root) %%Capacity\n"); 995181111Sdes printf("%12llu %12llu %12llu %12llu %3llu%%\n", 996181111Sdes (unsigned long long)(st.f_frsize * st.f_blocks / 1024), 997181111Sdes (unsigned long long)(st.f_frsize * 998181111Sdes (st.f_blocks - st.f_bfree) / 1024), 999181111Sdes (unsigned long long)(st.f_frsize * st.f_bavail / 1024), 1000181111Sdes (unsigned long long)(st.f_frsize * st.f_bfree / 1024), 1001181111Sdes (unsigned long long)(100 * (st.f_blocks - st.f_bfree) / 1002181111Sdes st.f_blocks)); 1003181111Sdes } 1004181111Sdes return 0; 1005181111Sdes} 1006181111Sdes 1007181111Sdes/* 1008181111Sdes * Undo escaping of glob sequences in place. Used to undo extra escaping 1009181111Sdes * applied in makeargv() when the string is destined for a function that 1010181111Sdes * does not glob it. 1011181111Sdes */ 1012181111Sdesstatic void 1013181111Sdesundo_glob_escape(char *s) 1014181111Sdes{ 1015181111Sdes size_t i, j; 1016181111Sdes 1017181111Sdes for (i = j = 0;;) { 1018181111Sdes if (s[i] == '\0') { 1019181111Sdes s[j] = '\0'; 1020181111Sdes return; 1021181111Sdes } 1022181111Sdes if (s[i] != '\\') { 1023181111Sdes s[j++] = s[i++]; 1024181111Sdes continue; 1025181111Sdes } 1026181111Sdes /* s[i] == '\\' */ 1027181111Sdes ++i; 1028181111Sdes switch (s[i]) { 1029181111Sdes case '?': 1030181111Sdes case '[': 1031181111Sdes case '*': 1032181111Sdes case '\\': 1033181111Sdes s[j++] = s[i++]; 1034181111Sdes break; 1035181111Sdes case '\0': 1036181111Sdes s[j++] = '\\'; 1037181111Sdes s[j] = '\0'; 1038181111Sdes return; 1039181111Sdes default: 1040181111Sdes s[j++] = '\\'; 1041181111Sdes s[j++] = s[i++]; 1042181111Sdes break; 1043181111Sdes } 1044181111Sdes } 1045181111Sdes} 1046181111Sdes 1047181111Sdes/* 1048181111Sdes * Split a string into an argument vector using sh(1)-style quoting, 1049181111Sdes * comment and escaping rules, but with some tweaks to handle glob(3) 1050181111Sdes * wildcards. 1051204917Sdes * The "sloppy" flag allows for recovery from missing terminating quote, for 1052204917Sdes * use in parsing incomplete commandlines during tab autocompletion. 1053204917Sdes * 1054181111Sdes * Returns NULL on error or a NULL-terminated array of arguments. 1055204917Sdes * 1056204917Sdes * If "lastquote" is not NULL, the quoting character used for the last 1057204917Sdes * argument is placed in *lastquote ("\0", "'" or "\""). 1058262566Sdes * 1059204917Sdes * If "terminated" is not NULL, *terminated will be set to 1 when the 1060204917Sdes * last argument's quote has been properly terminated or 0 otherwise. 1061204917Sdes * This parameter is only of use if "sloppy" is set. 1062181111Sdes */ 1063181111Sdes#define MAXARGS 128 1064181111Sdes#define MAXARGLEN 8192 1065181111Sdesstatic char ** 1066204917Sdesmakeargv(const char *arg, int *argcp, int sloppy, char *lastquote, 1067204917Sdes u_int *terminated) 1068181111Sdes{ 1069181111Sdes int argc, quot; 1070181111Sdes size_t i, j; 1071181111Sdes static char argvs[MAXARGLEN]; 1072181111Sdes static char *argv[MAXARGS + 1]; 1073181111Sdes enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q; 1074181111Sdes 1075181111Sdes *argcp = argc = 0; 1076181111Sdes if (strlen(arg) > sizeof(argvs) - 1) { 1077181111Sdes args_too_longs: 1078181111Sdes error("string too long"); 1079181111Sdes return NULL; 1080181111Sdes } 1081204917Sdes if (terminated != NULL) 1082204917Sdes *terminated = 1; 1083204917Sdes if (lastquote != NULL) 1084204917Sdes *lastquote = '\0'; 1085181111Sdes state = MA_START; 1086181111Sdes i = j = 0; 1087181111Sdes for (;;) { 1088248619Sdes if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){ 1089248619Sdes error("Too many arguments."); 1090248619Sdes return NULL; 1091248619Sdes } 1092262566Sdes if (isspace((unsigned char)arg[i])) { 1093181111Sdes if (state == MA_UNQUOTED) { 1094181111Sdes /* Terminate current argument */ 1095181111Sdes argvs[j++] = '\0'; 1096181111Sdes argc++; 1097181111Sdes state = MA_START; 1098181111Sdes } else if (state != MA_START) 1099181111Sdes argvs[j++] = arg[i]; 1100181111Sdes } else if (arg[i] == '"' || arg[i] == '\'') { 1101181111Sdes q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE; 1102181111Sdes if (state == MA_START) { 1103181111Sdes argv[argc] = argvs + j; 1104181111Sdes state = q; 1105204917Sdes if (lastquote != NULL) 1106204917Sdes *lastquote = arg[i]; 1107262566Sdes } else if (state == MA_UNQUOTED) 1108181111Sdes state = q; 1109181111Sdes else if (state == q) 1110181111Sdes state = MA_UNQUOTED; 1111181111Sdes else 1112181111Sdes argvs[j++] = arg[i]; 1113181111Sdes } else if (arg[i] == '\\') { 1114181111Sdes if (state == MA_SQUOTE || state == MA_DQUOTE) { 1115181111Sdes quot = state == MA_SQUOTE ? '\'' : '"'; 1116181111Sdes /* Unescape quote we are in */ 1117181111Sdes /* XXX support \n and friends? */ 1118181111Sdes if (arg[i + 1] == quot) { 1119181111Sdes i++; 1120181111Sdes argvs[j++] = arg[i]; 1121181111Sdes } else if (arg[i + 1] == '?' || 1122181111Sdes arg[i + 1] == '[' || arg[i + 1] == '*') { 1123181111Sdes /* 1124181111Sdes * Special case for sftp: append 1125181111Sdes * double-escaped glob sequence - 1126181111Sdes * glob will undo one level of 1127181111Sdes * escaping. NB. string can grow here. 1128181111Sdes */ 1129181111Sdes if (j >= sizeof(argvs) - 5) 1130181111Sdes goto args_too_longs; 1131181111Sdes argvs[j++] = '\\'; 1132181111Sdes argvs[j++] = arg[i++]; 1133181111Sdes argvs[j++] = '\\'; 1134181111Sdes argvs[j++] = arg[i]; 1135181111Sdes } else { 1136181111Sdes argvs[j++] = arg[i++]; 1137181111Sdes argvs[j++] = arg[i]; 1138181111Sdes } 1139181111Sdes } else { 1140181111Sdes if (state == MA_START) { 1141181111Sdes argv[argc] = argvs + j; 1142181111Sdes state = MA_UNQUOTED; 1143204917Sdes if (lastquote != NULL) 1144204917Sdes *lastquote = '\0'; 1145181111Sdes } 1146181111Sdes if (arg[i + 1] == '?' || arg[i + 1] == '[' || 1147181111Sdes arg[i + 1] == '*' || arg[i + 1] == '\\') { 1148181111Sdes /* 1149181111Sdes * Special case for sftp: append 1150181111Sdes * escaped glob sequence - 1151181111Sdes * glob will undo one level of 1152181111Sdes * escaping. 1153181111Sdes */ 1154181111Sdes argvs[j++] = arg[i++]; 1155181111Sdes argvs[j++] = arg[i]; 1156181111Sdes } else { 1157181111Sdes /* Unescape everything */ 1158181111Sdes /* XXX support \n and friends? */ 1159181111Sdes i++; 1160181111Sdes argvs[j++] = arg[i]; 1161181111Sdes } 1162181111Sdes } 1163181111Sdes } else if (arg[i] == '#') { 1164181111Sdes if (state == MA_SQUOTE || state == MA_DQUOTE) 1165181111Sdes argvs[j++] = arg[i]; 1166181111Sdes else 1167181111Sdes goto string_done; 1168181111Sdes } else if (arg[i] == '\0') { 1169181111Sdes if (state == MA_SQUOTE || state == MA_DQUOTE) { 1170204917Sdes if (sloppy) { 1171204917Sdes state = MA_UNQUOTED; 1172204917Sdes if (terminated != NULL) 1173204917Sdes *terminated = 0; 1174204917Sdes goto string_done; 1175204917Sdes } 1176181111Sdes error("Unterminated quoted argument"); 1177181111Sdes return NULL; 1178181111Sdes } 1179181111Sdes string_done: 1180181111Sdes if (state == MA_UNQUOTED) { 1181181111Sdes argvs[j++] = '\0'; 1182181111Sdes argc++; 1183181111Sdes } 1184181111Sdes break; 1185181111Sdes } else { 1186181111Sdes if (state == MA_START) { 1187181111Sdes argv[argc] = argvs + j; 1188181111Sdes state = MA_UNQUOTED; 1189204917Sdes if (lastquote != NULL) 1190204917Sdes *lastquote = '\0'; 1191181111Sdes } 1192181111Sdes if ((state == MA_SQUOTE || state == MA_DQUOTE) && 1193181111Sdes (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) { 1194181111Sdes /* 1195181111Sdes * Special case for sftp: escape quoted 1196181111Sdes * glob(3) wildcards. NB. string can grow 1197181111Sdes * here. 1198181111Sdes */ 1199181111Sdes if (j >= sizeof(argvs) - 3) 1200181111Sdes goto args_too_longs; 1201181111Sdes argvs[j++] = '\\'; 1202181111Sdes argvs[j++] = arg[i]; 1203181111Sdes } else 1204181111Sdes argvs[j++] = arg[i]; 1205181111Sdes } 1206181111Sdes i++; 1207181111Sdes } 1208181111Sdes *argcp = argc; 1209181111Sdes return argv; 1210181111Sdes} 1211181111Sdes 1212181111Sdesstatic int 1213295367Sdesparse_args(const char **cpp, int *ignore_errors, int *aflag, 1214323124Sdes int *fflag, int *hflag, int *iflag, int *lflag, int *pflag, 1215295367Sdes int *rflag, int *sflag, 1216262566Sdes unsigned long *n_arg, char **path1, char **path2) 1217126274Sdes{ 1218126274Sdes const char *cmd, *cp = *cpp; 1219181111Sdes char *cp2, **argv; 1220126274Sdes int base = 0; 1221126274Sdes long l; 1222181111Sdes int i, cmdnum, optidx, argc; 1223126274Sdes 1224126274Sdes /* Skip leading whitespace */ 1225126274Sdes cp = cp + strspn(cp, WHITESPACE); 1226126274Sdes 1227126274Sdes /* Check for leading '-' (disable error processing) */ 1228262566Sdes *ignore_errors = 0; 1229126274Sdes if (*cp == '-') { 1230262566Sdes *ignore_errors = 1; 1231126274Sdes cp++; 1232204917Sdes cp = cp + strspn(cp, WHITESPACE); 1233126274Sdes } 1234126274Sdes 1235204917Sdes /* Ignore blank lines and lines which begin with comment '#' char */ 1236204917Sdes if (*cp == '\0' || *cp == '#') 1237204917Sdes return (0); 1238204917Sdes 1239204917Sdes if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL) 1240181111Sdes return -1; 1241181111Sdes 1242126274Sdes /* Figure out which command we have */ 1243181111Sdes for (i = 0; cmds[i].c != NULL; i++) { 1244248619Sdes if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0) 1245126274Sdes break; 1246126274Sdes } 1247126274Sdes cmdnum = cmds[i].n; 1248126274Sdes cmd = cmds[i].c; 1249126274Sdes 1250126274Sdes /* Special case */ 1251126274Sdes if (*cp == '!') { 1252126274Sdes cp++; 1253126274Sdes cmdnum = I_SHELL; 1254126274Sdes } else if (cmdnum == -1) { 1255126274Sdes error("Invalid command."); 1256181111Sdes return -1; 1257126274Sdes } 1258126274Sdes 1259126274Sdes /* Get arguments and parse flags */ 1260262566Sdes *aflag = *fflag = *hflag = *iflag = *lflag = *pflag = 0; 1261262566Sdes *rflag = *sflag = 0; 1262126274Sdes *path1 = *path2 = NULL; 1263181111Sdes optidx = 1; 1264126274Sdes switch (cmdnum) { 1265126274Sdes case I_GET: 1266255767Sdes case I_REGET: 1267295367Sdes case I_REPUT: 1268126274Sdes case I_PUT: 1269221420Sdes if ((optidx = parse_getput_flags(cmd, argv, argc, 1270262566Sdes aflag, fflag, pflag, rflag)) == -1) 1271181111Sdes return -1; 1272126274Sdes /* Get first pathname (mandatory) */ 1273181111Sdes if (argc - optidx < 1) { 1274126274Sdes error("You must specify at least one path after a " 1275126274Sdes "%s command.", cmd); 1276181111Sdes return -1; 1277126274Sdes } 1278181111Sdes *path1 = xstrdup(argv[optidx]); 1279181111Sdes /* Get second pathname (optional) */ 1280181111Sdes if (argc - optidx > 1) { 1281181111Sdes *path2 = xstrdup(argv[optidx + 1]); 1282181111Sdes /* Destination is not globbed */ 1283181111Sdes undo_glob_escape(*path2); 1284181111Sdes } 1285126274Sdes break; 1286221420Sdes case I_LINK: 1287221420Sdes if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1) 1288221420Sdes return -1; 1289262566Sdes goto parse_two_paths; 1290262566Sdes case I_RENAME: 1291262566Sdes if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1) 1292262566Sdes return -1; 1293262566Sdes goto parse_two_paths; 1294221420Sdes case I_SYMLINK: 1295262566Sdes if ((optidx = parse_no_flags(cmd, argv, argc)) == -1) 1296262566Sdes return -1; 1297262566Sdes parse_two_paths: 1298181111Sdes if (argc - optidx < 2) { 1299126274Sdes error("You must specify two paths after a %s " 1300126274Sdes "command.", cmd); 1301181111Sdes return -1; 1302126274Sdes } 1303181111Sdes *path1 = xstrdup(argv[optidx]); 1304181111Sdes *path2 = xstrdup(argv[optidx + 1]); 1305181111Sdes /* Paths are not globbed */ 1306181111Sdes undo_glob_escape(*path1); 1307181111Sdes undo_glob_escape(*path2); 1308126274Sdes break; 1309126274Sdes case I_RM: 1310126274Sdes case I_MKDIR: 1311126274Sdes case I_RMDIR: 1312126274Sdes case I_CHDIR: 1313126274Sdes case I_LCHDIR: 1314126274Sdes case I_LMKDIR: 1315262566Sdes if ((optidx = parse_no_flags(cmd, argv, argc)) == -1) 1316262566Sdes return -1; 1317126274Sdes /* Get pathname (mandatory) */ 1318181111Sdes if (argc - optidx < 1) { 1319126274Sdes error("You must specify a path after a %s command.", 1320126274Sdes cmd); 1321181111Sdes return -1; 1322126274Sdes } 1323181111Sdes *path1 = xstrdup(argv[optidx]); 1324181111Sdes /* Only "rm" globs */ 1325181111Sdes if (cmdnum != I_RM) 1326181111Sdes undo_glob_escape(*path1); 1327126274Sdes break; 1328181111Sdes case I_DF: 1329181111Sdes if ((optidx = parse_df_flags(cmd, argv, argc, hflag, 1330181111Sdes iflag)) == -1) 1331181111Sdes return -1; 1332181111Sdes /* Default to current directory if no path specified */ 1333181111Sdes if (argc - optidx < 1) 1334181111Sdes *path1 = NULL; 1335181111Sdes else { 1336181111Sdes *path1 = xstrdup(argv[optidx]); 1337181111Sdes undo_glob_escape(*path1); 1338181111Sdes } 1339181111Sdes break; 1340126274Sdes case I_LS: 1341181111Sdes if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1) 1342126274Sdes return(-1); 1343126274Sdes /* Path is optional */ 1344181111Sdes if (argc - optidx > 0) 1345181111Sdes *path1 = xstrdup(argv[optidx]); 1346126274Sdes break; 1347126274Sdes case I_LLS: 1348181111Sdes /* Skip ls command and following whitespace */ 1349181111Sdes cp = cp + strlen(cmd) + strspn(cp, WHITESPACE); 1350126274Sdes case I_SHELL: 1351126274Sdes /* Uses the rest of the line */ 1352126274Sdes break; 1353126274Sdes case I_LUMASK: 1354126274Sdes case I_CHMOD: 1355126274Sdes base = 8; 1356126274Sdes case I_CHOWN: 1357126274Sdes case I_CHGRP: 1358262566Sdes if ((optidx = parse_no_flags(cmd, argv, argc)) == -1) 1359262566Sdes return -1; 1360126274Sdes /* Get numeric arg (mandatory) */ 1361181111Sdes if (argc - optidx < 1) 1362181111Sdes goto need_num_arg; 1363164146Sdes errno = 0; 1364181111Sdes l = strtol(argv[optidx], &cp2, base); 1365181111Sdes if (cp2 == argv[optidx] || *cp2 != '\0' || 1366181111Sdes ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) || 1367181111Sdes l < 0) { 1368181111Sdes need_num_arg: 1369126274Sdes error("You must supply a numeric argument " 1370126274Sdes "to the %s command.", cmd); 1371181111Sdes return -1; 1372126274Sdes } 1373126274Sdes *n_arg = l; 1374181111Sdes if (cmdnum == I_LUMASK) 1375126274Sdes break; 1376126274Sdes /* Get pathname (mandatory) */ 1377181111Sdes if (argc - optidx < 2) { 1378126274Sdes error("You must specify a path after a %s command.", 1379126274Sdes cmd); 1380181111Sdes return -1; 1381126274Sdes } 1382181111Sdes *path1 = xstrdup(argv[optidx + 1]); 1383126274Sdes break; 1384126274Sdes case I_QUIT: 1385126274Sdes case I_PWD: 1386126274Sdes case I_LPWD: 1387126274Sdes case I_HELP: 1388126274Sdes case I_VERSION: 1389126274Sdes case I_PROGRESS: 1390262566Sdes if ((optidx = parse_no_flags(cmd, argv, argc)) == -1) 1391262566Sdes return -1; 1392126274Sdes break; 1393126274Sdes default: 1394126274Sdes fatal("Command not implemented"); 1395126274Sdes } 1396126274Sdes 1397126274Sdes *cpp = cp; 1398126274Sdes return(cmdnum); 1399126274Sdes} 1400126274Sdes 1401126274Sdesstatic int 1402126274Sdesparse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, 1403126274Sdes int err_abort) 1404126274Sdes{ 1405126274Sdes char *path1, *path2, *tmp; 1406323124Sdes int ignore_errors = 0, aflag = 0, fflag = 0, hflag = 0, 1407295367Sdes iflag = 0; 1408262566Sdes int lflag = 0, pflag = 0, rflag = 0, sflag = 0; 1409221420Sdes int cmdnum, i; 1410192595Sdes unsigned long n_arg = 0; 1411126274Sdes Attrib a, *aa; 1412295367Sdes char path_buf[PATH_MAX]; 1413126274Sdes int err = 0; 1414126274Sdes glob_t g; 1415126274Sdes 1416126274Sdes path1 = path2 = NULL; 1417262566Sdes cmdnum = parse_args(&cmd, &ignore_errors, &aflag, &fflag, &hflag, 1418262566Sdes &iflag, &lflag, &pflag, &rflag, &sflag, &n_arg, &path1, &path2); 1419262566Sdes if (ignore_errors != 0) 1420126274Sdes err_abort = 0; 1421126274Sdes 1422126274Sdes memset(&g, 0, sizeof(g)); 1423126274Sdes 1424126274Sdes /* Perform command */ 1425126274Sdes switch (cmdnum) { 1426126274Sdes case 0: 1427126274Sdes /* Blank line */ 1428126274Sdes break; 1429126274Sdes case -1: 1430126274Sdes /* Unrecognized command */ 1431126274Sdes err = -1; 1432126274Sdes break; 1433255767Sdes case I_REGET: 1434255767Sdes aflag = 1; 1435255767Sdes /* FALLTHROUGH */ 1436126274Sdes case I_GET: 1437255767Sdes err = process_get(conn, path1, path2, *pwd, pflag, 1438262566Sdes rflag, aflag, fflag); 1439126274Sdes break; 1440295367Sdes case I_REPUT: 1441295367Sdes aflag = 1; 1442295367Sdes /* FALLTHROUGH */ 1443126274Sdes case I_PUT: 1444262566Sdes err = process_put(conn, path1, path2, *pwd, pflag, 1445295367Sdes rflag, aflag, fflag); 1446126274Sdes break; 1447126274Sdes case I_RENAME: 1448126274Sdes path1 = make_absolute(path1, *pwd); 1449126274Sdes path2 = make_absolute(path2, *pwd); 1450262566Sdes err = do_rename(conn, path1, path2, lflag); 1451126274Sdes break; 1452126274Sdes case I_SYMLINK: 1453221420Sdes sflag = 1; 1454221420Sdes case I_LINK: 1455262566Sdes if (!sflag) 1456262566Sdes path1 = make_absolute(path1, *pwd); 1457126274Sdes path2 = make_absolute(path2, *pwd); 1458221420Sdes err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2); 1459126274Sdes break; 1460126274Sdes case I_RM: 1461126274Sdes path1 = make_absolute(path1, *pwd); 1462126274Sdes remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); 1463137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 1464255767Sdes if (!quiet) 1465323124Sdes mprintf("Removing %s\n", g.gl_pathv[i]); 1466126274Sdes err = do_rm(conn, g.gl_pathv[i]); 1467126274Sdes if (err != 0 && err_abort) 1468126274Sdes break; 1469126274Sdes } 1470126274Sdes break; 1471126274Sdes case I_MKDIR: 1472126274Sdes path1 = make_absolute(path1, *pwd); 1473126274Sdes attrib_clear(&a); 1474126274Sdes a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; 1475126274Sdes a.perm = 0777; 1476204917Sdes err = do_mkdir(conn, path1, &a, 1); 1477126274Sdes break; 1478126274Sdes case I_RMDIR: 1479126274Sdes path1 = make_absolute(path1, *pwd); 1480126274Sdes err = do_rmdir(conn, path1); 1481126274Sdes break; 1482126274Sdes case I_CHDIR: 1483126274Sdes path1 = make_absolute(path1, *pwd); 1484126274Sdes if ((tmp = do_realpath(conn, path1)) == NULL) { 1485126274Sdes err = 1; 1486126274Sdes break; 1487126274Sdes } 1488126274Sdes if ((aa = do_stat(conn, tmp, 0)) == NULL) { 1489255767Sdes free(tmp); 1490126274Sdes err = 1; 1491126274Sdes break; 1492126274Sdes } 1493126274Sdes if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) { 1494126274Sdes error("Can't change directory: Can't check target"); 1495255767Sdes free(tmp); 1496126274Sdes err = 1; 1497126274Sdes break; 1498126274Sdes } 1499126274Sdes if (!S_ISDIR(aa->perm)) { 1500126274Sdes error("Can't change directory: \"%s\" is not " 1501126274Sdes "a directory", tmp); 1502255767Sdes free(tmp); 1503126274Sdes err = 1; 1504126274Sdes break; 1505126274Sdes } 1506255767Sdes free(*pwd); 1507126274Sdes *pwd = tmp; 1508126274Sdes break; 1509126274Sdes case I_LS: 1510126274Sdes if (!path1) { 1511215116Sdes do_ls_dir(conn, *pwd, *pwd, lflag); 1512126274Sdes break; 1513126274Sdes } 1514126274Sdes 1515126274Sdes /* Strip pwd off beginning of non-absolute paths */ 1516126274Sdes tmp = NULL; 1517126274Sdes if (*path1 != '/') 1518126274Sdes tmp = *pwd; 1519126274Sdes 1520126274Sdes path1 = make_absolute(path1, *pwd); 1521126274Sdes err = do_globbed_ls(conn, path1, tmp, lflag); 1522126274Sdes break; 1523181111Sdes case I_DF: 1524181111Sdes /* Default to current directory if no path specified */ 1525181111Sdes if (path1 == NULL) 1526181111Sdes path1 = xstrdup(*pwd); 1527181111Sdes path1 = make_absolute(path1, *pwd); 1528181111Sdes err = do_df(conn, path1, hflag, iflag); 1529181111Sdes break; 1530126274Sdes case I_LCHDIR: 1531295367Sdes tmp = tilde_expand_filename(path1, getuid()); 1532295367Sdes free(path1); 1533295367Sdes path1 = tmp; 1534126274Sdes if (chdir(path1) == -1) { 1535126274Sdes error("Couldn't change local directory to " 1536126274Sdes "\"%s\": %s", path1, strerror(errno)); 1537126274Sdes err = 1; 1538126274Sdes } 1539126274Sdes break; 1540126274Sdes case I_LMKDIR: 1541126274Sdes if (mkdir(path1, 0777) == -1) { 1542126274Sdes error("Couldn't create local directory " 1543126274Sdes "\"%s\": %s", path1, strerror(errno)); 1544126274Sdes err = 1; 1545126274Sdes } 1546126274Sdes break; 1547126274Sdes case I_LLS: 1548126274Sdes local_do_ls(cmd); 1549126274Sdes break; 1550126274Sdes case I_SHELL: 1551126274Sdes local_do_shell(cmd); 1552126274Sdes break; 1553126274Sdes case I_LUMASK: 1554126274Sdes umask(n_arg); 1555126274Sdes printf("Local umask: %03lo\n", n_arg); 1556126274Sdes break; 1557126274Sdes case I_CHMOD: 1558126274Sdes path1 = make_absolute(path1, *pwd); 1559126274Sdes attrib_clear(&a); 1560126274Sdes a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; 1561126274Sdes a.perm = n_arg; 1562126274Sdes remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); 1563137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 1564255767Sdes if (!quiet) 1565323124Sdes mprintf("Changing mode on %s\n", 1566323124Sdes g.gl_pathv[i]); 1567126274Sdes err = do_setstat(conn, g.gl_pathv[i], &a); 1568126274Sdes if (err != 0 && err_abort) 1569126274Sdes break; 1570126274Sdes } 1571126274Sdes break; 1572126274Sdes case I_CHOWN: 1573126274Sdes case I_CHGRP: 1574126274Sdes path1 = make_absolute(path1, *pwd); 1575126274Sdes remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); 1576137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 1577126274Sdes if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) { 1578192595Sdes if (err_abort) { 1579192595Sdes err = -1; 1580126274Sdes break; 1581192595Sdes } else 1582126274Sdes continue; 1583126274Sdes } 1584126274Sdes if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) { 1585126274Sdes error("Can't get current ownership of " 1586126274Sdes "remote file \"%s\"", g.gl_pathv[i]); 1587192595Sdes if (err_abort) { 1588192595Sdes err = -1; 1589126274Sdes break; 1590192595Sdes } else 1591126274Sdes continue; 1592126274Sdes } 1593126274Sdes aa->flags &= SSH2_FILEXFER_ATTR_UIDGID; 1594126274Sdes if (cmdnum == I_CHOWN) { 1595255767Sdes if (!quiet) 1596323124Sdes mprintf("Changing owner on %s\n", 1597255767Sdes g.gl_pathv[i]); 1598126274Sdes aa->uid = n_arg; 1599126274Sdes } else { 1600255767Sdes if (!quiet) 1601323124Sdes mprintf("Changing group on %s\n", 1602255767Sdes g.gl_pathv[i]); 1603126274Sdes aa->gid = n_arg; 1604126274Sdes } 1605126274Sdes err = do_setstat(conn, g.gl_pathv[i], aa); 1606126274Sdes if (err != 0 && err_abort) 1607126274Sdes break; 1608126274Sdes } 1609126274Sdes break; 1610126274Sdes case I_PWD: 1611323124Sdes mprintf("Remote working directory: %s\n", *pwd); 1612126274Sdes break; 1613126274Sdes case I_LPWD: 1614126274Sdes if (!getcwd(path_buf, sizeof(path_buf))) { 1615126274Sdes error("Couldn't get local cwd: %s", strerror(errno)); 1616126274Sdes err = -1; 1617126274Sdes break; 1618126274Sdes } 1619323124Sdes mprintf("Local working directory: %s\n", path_buf); 1620126274Sdes break; 1621126274Sdes case I_QUIT: 1622126274Sdes /* Processed below */ 1623126274Sdes break; 1624126274Sdes case I_HELP: 1625126274Sdes help(); 1626126274Sdes break; 1627126274Sdes case I_VERSION: 1628126274Sdes printf("SFTP protocol version %u\n", sftp_proto_version(conn)); 1629126274Sdes break; 1630126274Sdes case I_PROGRESS: 1631126274Sdes showprogress = !showprogress; 1632126274Sdes if (showprogress) 1633126274Sdes printf("Progress meter enabled\n"); 1634126274Sdes else 1635126274Sdes printf("Progress meter disabled\n"); 1636126274Sdes break; 1637126274Sdes default: 1638126274Sdes fatal("%d is not implemented", cmdnum); 1639126274Sdes } 1640126274Sdes 1641126274Sdes if (g.gl_pathc) 1642126274Sdes globfree(&g); 1643255767Sdes free(path1); 1644255767Sdes free(path2); 1645126274Sdes 1646126274Sdes /* If an unignored error occurs in batch mode we should abort. */ 1647126274Sdes if (err_abort && err != 0) 1648126274Sdes return (-1); 1649126274Sdes else if (cmdnum == I_QUIT) 1650126274Sdes return (1); 1651126274Sdes 1652126274Sdes return (0); 1653126274Sdes} 1654126274Sdes 1655146998Sdes#ifdef USE_LIBEDIT 1656146998Sdesstatic char * 1657146998Sdesprompt(EditLine *el) 1658146998Sdes{ 1659146998Sdes return ("sftp> "); 1660146998Sdes} 1661146998Sdes 1662204917Sdes/* Display entries in 'list' after skipping the first 'len' chars */ 1663204917Sdesstatic void 1664204917Sdescomplete_display(char **list, u_int len) 1665204917Sdes{ 1666204917Sdes u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen; 1667204917Sdes struct winsize ws; 1668204917Sdes char *tmp; 1669204917Sdes 1670204917Sdes /* Count entries for sort and find longest */ 1671262566Sdes for (y = 0; list[y]; y++) 1672204917Sdes m = MAX(m, strlen(list[y])); 1673204917Sdes 1674204917Sdes if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1) 1675204917Sdes width = ws.ws_col; 1676204917Sdes 1677204917Sdes m = m > len ? m - len : 0; 1678204917Sdes columns = width / (m + 2); 1679204917Sdes columns = MAX(columns, 1); 1680204917Sdes colspace = width / columns; 1681204917Sdes colspace = MIN(colspace, width); 1682204917Sdes 1683204917Sdes printf("\n"); 1684204917Sdes m = 1; 1685204917Sdes for (y = 0; list[y]; y++) { 1686204917Sdes llen = strlen(list[y]); 1687204917Sdes tmp = llen > len ? list[y] + len : ""; 1688323124Sdes mprintf("%-*s", colspace, tmp); 1689204917Sdes if (m >= columns) { 1690204917Sdes printf("\n"); 1691204917Sdes m = 1; 1692204917Sdes } else 1693204917Sdes m++; 1694204917Sdes } 1695204917Sdes printf("\n"); 1696204917Sdes} 1697204917Sdes 1698204917Sdes/* 1699204917Sdes * Given a "list" of words that begin with a common prefix of "word", 1700204917Sdes * attempt to find an autocompletion to extends "word" by the next 1701204917Sdes * characters common to all entries in "list". 1702204917Sdes */ 1703204917Sdesstatic char * 1704204917Sdescomplete_ambiguous(const char *word, char **list, size_t count) 1705204917Sdes{ 1706204917Sdes if (word == NULL) 1707204917Sdes return NULL; 1708204917Sdes 1709204917Sdes if (count > 0) { 1710204917Sdes u_int y, matchlen = strlen(list[0]); 1711204917Sdes 1712204917Sdes /* Find length of common stem */ 1713204917Sdes for (y = 1; list[y]; y++) { 1714204917Sdes u_int x; 1715204917Sdes 1716262566Sdes for (x = 0; x < matchlen; x++) 1717262566Sdes if (list[0][x] != list[y][x]) 1718204917Sdes break; 1719204917Sdes 1720204917Sdes matchlen = x; 1721204917Sdes } 1722204917Sdes 1723204917Sdes if (matchlen > strlen(word)) { 1724204917Sdes char *tmp = xstrdup(list[0]); 1725204917Sdes 1726204917Sdes tmp[matchlen] = '\0'; 1727204917Sdes return tmp; 1728204917Sdes } 1729262566Sdes } 1730204917Sdes 1731204917Sdes return xstrdup(word); 1732204917Sdes} 1733204917Sdes 1734204917Sdes/* Autocomplete a sftp command */ 1735204917Sdesstatic int 1736204917Sdescomplete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote, 1737204917Sdes int terminated) 1738204917Sdes{ 1739204917Sdes u_int y, count = 0, cmdlen, tmplen; 1740204917Sdes char *tmp, **list, argterm[3]; 1741204917Sdes const LineInfo *lf; 1742204917Sdes 1743204917Sdes list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *)); 1744204917Sdes 1745204917Sdes /* No command specified: display all available commands */ 1746204917Sdes if (cmd == NULL) { 1747204917Sdes for (y = 0; cmds[y].c; y++) 1748204917Sdes list[count++] = xstrdup(cmds[y].c); 1749262566Sdes 1750204917Sdes list[count] = NULL; 1751204917Sdes complete_display(list, 0); 1752204917Sdes 1753262566Sdes for (y = 0; list[y] != NULL; y++) 1754262566Sdes free(list[y]); 1755255767Sdes free(list); 1756204917Sdes return count; 1757204917Sdes } 1758204917Sdes 1759204917Sdes /* Prepare subset of commands that start with "cmd" */ 1760204917Sdes cmdlen = strlen(cmd); 1761204917Sdes for (y = 0; cmds[y].c; y++) { 1762262566Sdes if (!strncasecmp(cmd, cmds[y].c, cmdlen)) 1763204917Sdes list[count++] = xstrdup(cmds[y].c); 1764204917Sdes } 1765204917Sdes list[count] = NULL; 1766204917Sdes 1767240075Sdes if (count == 0) { 1768255767Sdes free(list); 1769204917Sdes return 0; 1770240075Sdes } 1771204917Sdes 1772204917Sdes /* Complete ambigious command */ 1773204917Sdes tmp = complete_ambiguous(cmd, list, count); 1774204917Sdes if (count > 1) 1775204917Sdes complete_display(list, 0); 1776204917Sdes 1777262566Sdes for (y = 0; list[y]; y++) 1778262566Sdes free(list[y]); 1779255767Sdes free(list); 1780204917Sdes 1781204917Sdes if (tmp != NULL) { 1782204917Sdes tmplen = strlen(tmp); 1783204917Sdes cmdlen = strlen(cmd); 1784204917Sdes /* If cmd may be extended then do so */ 1785204917Sdes if (tmplen > cmdlen) 1786204917Sdes if (el_insertstr(el, tmp + cmdlen) == -1) 1787204917Sdes fatal("el_insertstr failed."); 1788204917Sdes lf = el_line(el); 1789204917Sdes /* Terminate argument cleanly */ 1790204917Sdes if (count == 1) { 1791204917Sdes y = 0; 1792204917Sdes if (!terminated) 1793204917Sdes argterm[y++] = quote; 1794204917Sdes if (lastarg || *(lf->cursor) != ' ') 1795204917Sdes argterm[y++] = ' '; 1796204917Sdes argterm[y] = '\0'; 1797204917Sdes if (y > 0 && el_insertstr(el, argterm) == -1) 1798204917Sdes fatal("el_insertstr failed."); 1799204917Sdes } 1800255767Sdes free(tmp); 1801204917Sdes } 1802204917Sdes 1803204917Sdes return count; 1804204917Sdes} 1805204917Sdes 1806204917Sdes/* 1807204917Sdes * Determine whether a particular sftp command's arguments (if any) 1808204917Sdes * represent local or remote files. 1809204917Sdes */ 1810204917Sdesstatic int 1811204917Sdescomplete_is_remote(char *cmd) { 1812204917Sdes int i; 1813204917Sdes 1814204917Sdes if (cmd == NULL) 1815204917Sdes return -1; 1816204917Sdes 1817204917Sdes for (i = 0; cmds[i].c; i++) { 1818262566Sdes if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c))) 1819204917Sdes return cmds[i].t; 1820204917Sdes } 1821204917Sdes 1822204917Sdes return -1; 1823204917Sdes} 1824204917Sdes 1825204917Sdes/* Autocomplete a filename "file" */ 1826204917Sdesstatic int 1827204917Sdescomplete_match(EditLine *el, struct sftp_conn *conn, char *remote_path, 1828204917Sdes char *file, int remote, int lastarg, char quote, int terminated) 1829204917Sdes{ 1830204917Sdes glob_t g; 1831255767Sdes char *tmp, *tmp2, ins[8]; 1832248619Sdes u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs; 1833255767Sdes int clen; 1834204917Sdes const LineInfo *lf; 1835262566Sdes 1836204917Sdes /* Glob from "file" location */ 1837204917Sdes if (file == NULL) 1838204917Sdes tmp = xstrdup("*"); 1839204917Sdes else 1840204917Sdes xasprintf(&tmp, "%s*", file); 1841204917Sdes 1842248619Sdes /* Check if the path is absolute. */ 1843248619Sdes isabs = tmp[0] == '/'; 1844248619Sdes 1845204917Sdes memset(&g, 0, sizeof(g)); 1846204917Sdes if (remote != LOCAL) { 1847204917Sdes tmp = make_absolute(tmp, remote_path); 1848204917Sdes remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g); 1849262566Sdes } else 1850204917Sdes glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g); 1851262566Sdes 1852204917Sdes /* Determine length of pwd so we can trim completion display */ 1853204917Sdes for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) { 1854204917Sdes /* Terminate counting on first unescaped glob metacharacter */ 1855204917Sdes if (tmp[tmplen] == '*' || tmp[tmplen] == '?') { 1856204917Sdes if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0') 1857204917Sdes hadglob = 1; 1858204917Sdes break; 1859204917Sdes } 1860204917Sdes if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0') 1861204917Sdes tmplen++; 1862204917Sdes if (tmp[tmplen] == '/') 1863204917Sdes pwdlen = tmplen + 1; /* track last seen '/' */ 1864204917Sdes } 1865255767Sdes free(tmp); 1866295367Sdes tmp = NULL; 1867204917Sdes 1868262566Sdes if (g.gl_matchc == 0) 1869204917Sdes goto out; 1870204917Sdes 1871204917Sdes if (g.gl_matchc > 1) 1872204917Sdes complete_display(g.gl_pathv, pwdlen); 1873204917Sdes 1874204917Sdes /* Don't try to extend globs */ 1875204917Sdes if (file == NULL || hadglob) 1876204917Sdes goto out; 1877204917Sdes 1878204917Sdes tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc); 1879248619Sdes tmp = path_strip(tmp2, isabs ? NULL : remote_path); 1880255767Sdes free(tmp2); 1881204917Sdes 1882204917Sdes if (tmp == NULL) 1883204917Sdes goto out; 1884204917Sdes 1885204917Sdes tmplen = strlen(tmp); 1886204917Sdes filelen = strlen(file); 1887204917Sdes 1888248619Sdes /* Count the number of escaped characters in the input string. */ 1889248619Sdes cesc = isesc = 0; 1890248619Sdes for (i = 0; i < filelen; i++) { 1891248619Sdes if (!isesc && file[i] == '\\' && i + 1 < filelen){ 1892248619Sdes isesc = 1; 1893248619Sdes cesc++; 1894248619Sdes } else 1895248619Sdes isesc = 0; 1896248619Sdes } 1897248619Sdes 1898248619Sdes if (tmplen > (filelen - cesc)) { 1899248619Sdes tmp2 = tmp + filelen - cesc; 1900262566Sdes len = strlen(tmp2); 1901204917Sdes /* quote argument on way out */ 1902255767Sdes for (i = 0; i < len; i += clen) { 1903255767Sdes if ((clen = mblen(tmp2 + i, len - i)) < 0 || 1904255767Sdes (size_t)clen > sizeof(ins) - 2) 1905255767Sdes fatal("invalid multibyte character"); 1906204917Sdes ins[0] = '\\'; 1907255767Sdes memcpy(ins + 1, tmp2 + i, clen); 1908255767Sdes ins[clen + 1] = '\0'; 1909204917Sdes switch (tmp2[i]) { 1910204917Sdes case '\'': 1911204917Sdes case '"': 1912204917Sdes case '\\': 1913204917Sdes case '\t': 1914221420Sdes case '[': 1915204917Sdes case ' ': 1916248619Sdes case '#': 1917248619Sdes case '*': 1918204917Sdes if (quote == '\0' || tmp2[i] == quote) { 1919204917Sdes if (el_insertstr(el, ins) == -1) 1920204917Sdes fatal("el_insertstr " 1921204917Sdes "failed."); 1922204917Sdes break; 1923204917Sdes } 1924204917Sdes /* FALLTHROUGH */ 1925204917Sdes default: 1926204917Sdes if (el_insertstr(el, ins + 1) == -1) 1927204917Sdes fatal("el_insertstr failed."); 1928204917Sdes break; 1929204917Sdes } 1930204917Sdes } 1931204917Sdes } 1932204917Sdes 1933204917Sdes lf = el_line(el); 1934204917Sdes if (g.gl_matchc == 1) { 1935204917Sdes i = 0; 1936295367Sdes if (!terminated && quote != '\0') 1937204917Sdes ins[i++] = quote; 1938204917Sdes if (*(lf->cursor - 1) != '/' && 1939204917Sdes (lastarg || *(lf->cursor) != ' ')) 1940204917Sdes ins[i++] = ' '; 1941204917Sdes ins[i] = '\0'; 1942204917Sdes if (i > 0 && el_insertstr(el, ins) == -1) 1943204917Sdes fatal("el_insertstr failed."); 1944204917Sdes } 1945255767Sdes free(tmp); 1946204917Sdes 1947204917Sdes out: 1948204917Sdes globfree(&g); 1949204917Sdes return g.gl_matchc; 1950204917Sdes} 1951204917Sdes 1952204917Sdes/* tab-completion hook function, called via libedit */ 1953204917Sdesstatic unsigned char 1954204917Sdescomplete(EditLine *el, int ch) 1955204917Sdes{ 1956262566Sdes char **argv, *line, quote; 1957255767Sdes int argc, carg; 1958255767Sdes u_int cursor, len, terminated, ret = CC_ERROR; 1959204917Sdes const LineInfo *lf; 1960204917Sdes struct complete_ctx *complete_ctx; 1961204917Sdes 1962204917Sdes lf = el_line(el); 1963204917Sdes if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0) 1964204917Sdes fatal("%s: el_get failed", __func__); 1965204917Sdes 1966204917Sdes /* Figure out which argument the cursor points to */ 1967204917Sdes cursor = lf->cursor - lf->buffer; 1968295367Sdes line = xmalloc(cursor + 1); 1969204917Sdes memcpy(line, lf->buffer, cursor); 1970204917Sdes line[cursor] = '\0'; 1971204917Sdes argv = makeargv(line, &carg, 1, "e, &terminated); 1972255767Sdes free(line); 1973204917Sdes 1974204917Sdes /* Get all the arguments on the line */ 1975204917Sdes len = lf->lastchar - lf->buffer; 1976295367Sdes line = xmalloc(len + 1); 1977204917Sdes memcpy(line, lf->buffer, len); 1978204917Sdes line[len] = '\0'; 1979204917Sdes argv = makeargv(line, &argc, 1, NULL, NULL); 1980204917Sdes 1981204917Sdes /* Ensure cursor is at EOL or a argument boundary */ 1982204917Sdes if (line[cursor] != ' ' && line[cursor] != '\0' && 1983204917Sdes line[cursor] != '\n') { 1984255767Sdes free(line); 1985204917Sdes return ret; 1986204917Sdes } 1987204917Sdes 1988204917Sdes if (carg == 0) { 1989204917Sdes /* Show all available commands */ 1990204917Sdes complete_cmd_parse(el, NULL, argc == carg, '\0', 1); 1991204917Sdes ret = CC_REDISPLAY; 1992204917Sdes } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') { 1993204917Sdes /* Handle the command parsing */ 1994204917Sdes if (complete_cmd_parse(el, argv[0], argc == carg, 1995262566Sdes quote, terminated) != 0) 1996204917Sdes ret = CC_REDISPLAY; 1997204917Sdes } else if (carg >= 1) { 1998204917Sdes /* Handle file parsing */ 1999204917Sdes int remote = complete_is_remote(argv[0]); 2000204917Sdes char *filematch = NULL; 2001204917Sdes 2002204917Sdes if (carg > 1 && line[cursor-1] != ' ') 2003204917Sdes filematch = argv[carg - 1]; 2004204917Sdes 2005204917Sdes if (remote != 0 && 2006204917Sdes complete_match(el, complete_ctx->conn, 2007204917Sdes *complete_ctx->remote_pathp, filematch, 2008262566Sdes remote, carg == argc, quote, terminated) != 0) 2009204917Sdes ret = CC_REDISPLAY; 2010204917Sdes } 2011204917Sdes 2012262566Sdes free(line); 2013204917Sdes return ret; 2014204917Sdes} 2015204917Sdes#endif /* USE_LIBEDIT */ 2016204917Sdes 2017126274Sdesint 2018204917Sdesinteractive_loop(struct sftp_conn *conn, char *file1, char *file2) 2019126274Sdes{ 2020204917Sdes char *remote_path; 2021126274Sdes char *dir = NULL; 2022126274Sdes char cmd[2048]; 2023149749Sdes int err, interactive; 2024146998Sdes EditLine *el = NULL; 2025146998Sdes#ifdef USE_LIBEDIT 2026146998Sdes History *hl = NULL; 2027146998Sdes HistEvent hev; 2028146998Sdes extern char *__progname; 2029204917Sdes struct complete_ctx complete_ctx; 2030126274Sdes 2031146998Sdes if (!batchmode && isatty(STDIN_FILENO)) { 2032146998Sdes if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL) 2033146998Sdes fatal("Couldn't initialise editline"); 2034146998Sdes if ((hl = history_init()) == NULL) 2035146998Sdes fatal("Couldn't initialise editline history"); 2036146998Sdes history(hl, &hev, H_SETSIZE, 100); 2037146998Sdes el_set(el, EL_HIST, history, hl); 2038146998Sdes 2039146998Sdes el_set(el, EL_PROMPT, prompt); 2040146998Sdes el_set(el, EL_EDITOR, "emacs"); 2041146998Sdes el_set(el, EL_TERMINAL, NULL); 2042146998Sdes el_set(el, EL_SIGNAL, 1); 2043146998Sdes el_source(el, NULL); 2044204917Sdes 2045204917Sdes /* Tab Completion */ 2046262566Sdes el_set(el, EL_ADDFN, "ftp-complete", 2047221420Sdes "Context sensitive argument completion", complete); 2048204917Sdes complete_ctx.conn = conn; 2049204917Sdes complete_ctx.remote_pathp = &remote_path; 2050204917Sdes el_set(el, EL_CLIENTDATA, (void*)&complete_ctx); 2051204917Sdes el_set(el, EL_BIND, "^I", "ftp-complete", NULL); 2052262566Sdes /* enable ctrl-left-arrow and ctrl-right-arrow */ 2053262566Sdes el_set(el, EL_BIND, "\\e[1;5C", "em-next-word", NULL); 2054262566Sdes el_set(el, EL_BIND, "\\e[5C", "em-next-word", NULL); 2055262566Sdes el_set(el, EL_BIND, "\\e[1;5D", "ed-prev-word", NULL); 2056262566Sdes el_set(el, EL_BIND, "\\e\\e[D", "ed-prev-word", NULL); 2057262566Sdes /* make ^w match ksh behaviour */ 2058262566Sdes el_set(el, EL_BIND, "^w", "ed-delete-prev-word", NULL); 2059146998Sdes } 2060146998Sdes#endif /* USE_LIBEDIT */ 2061146998Sdes 2062204917Sdes remote_path = do_realpath(conn, "."); 2063204917Sdes if (remote_path == NULL) 2064126274Sdes fatal("Need cwd"); 2065126274Sdes 2066126274Sdes if (file1 != NULL) { 2067126274Sdes dir = xstrdup(file1); 2068204917Sdes dir = make_absolute(dir, remote_path); 2069126274Sdes 2070126274Sdes if (remote_is_dir(conn, dir) && file2 == NULL) { 2071255767Sdes if (!quiet) 2072323124Sdes mprintf("Changing to: %s\n", dir); 2073126274Sdes snprintf(cmd, sizeof cmd, "cd \"%s\"", dir); 2074204917Sdes if (parse_dispatch_command(conn, cmd, 2075204917Sdes &remote_path, 1) != 0) { 2076255767Sdes free(dir); 2077255767Sdes free(remote_path); 2078255767Sdes free(conn); 2079126274Sdes return (-1); 2080146998Sdes } 2081126274Sdes } else { 2082248619Sdes /* XXX this is wrong wrt quoting */ 2083255767Sdes snprintf(cmd, sizeof cmd, "get%s %s%s%s", 2084255767Sdes global_aflag ? " -a" : "", dir, 2085255767Sdes file2 == NULL ? "" : " ", 2086255767Sdes file2 == NULL ? "" : file2); 2087204917Sdes err = parse_dispatch_command(conn, cmd, 2088204917Sdes &remote_path, 1); 2089255767Sdes free(dir); 2090255767Sdes free(remote_path); 2091255767Sdes free(conn); 2092126274Sdes return (err); 2093126274Sdes } 2094255767Sdes free(dir); 2095126274Sdes } 2096126274Sdes 2097295367Sdes setvbuf(stdout, NULL, _IOLBF, 0); 2098295367Sdes setvbuf(infile, NULL, _IOLBF, 0); 2099126274Sdes 2100149749Sdes interactive = !batchmode && isatty(STDIN_FILENO); 2101126274Sdes err = 0; 2102126274Sdes for (;;) { 2103126274Sdes char *cp; 2104126274Sdes 2105137015Sdes signal(SIGINT, SIG_IGN); 2106137015Sdes 2107146998Sdes if (el == NULL) { 2108149749Sdes if (interactive) 2109149749Sdes printf("sftp> "); 2110146998Sdes if (fgets(cmd, sizeof(cmd), infile) == NULL) { 2111149749Sdes if (interactive) 2112149749Sdes printf("\n"); 2113146998Sdes break; 2114146998Sdes } 2115149749Sdes if (!interactive) { /* Echo command */ 2116323124Sdes mprintf("sftp> %s", cmd); 2117149749Sdes if (strlen(cmd) > 0 && 2118149749Sdes cmd[strlen(cmd) - 1] != '\n') 2119149749Sdes printf("\n"); 2120149749Sdes } 2121146998Sdes } else { 2122146998Sdes#ifdef USE_LIBEDIT 2123146998Sdes const char *line; 2124146998Sdes int count = 0; 2125126274Sdes 2126204917Sdes if ((line = el_gets(el, &count)) == NULL || 2127204917Sdes count <= 0) { 2128149749Sdes printf("\n"); 2129149749Sdes break; 2130149749Sdes } 2131146998Sdes history(hl, &hev, H_ENTER, line); 2132146998Sdes if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) { 2133146998Sdes fprintf(stderr, "Error: input line too long\n"); 2134146998Sdes continue; 2135146998Sdes } 2136146998Sdes#endif /* USE_LIBEDIT */ 2137126274Sdes } 2138126274Sdes 2139126274Sdes cp = strrchr(cmd, '\n'); 2140126274Sdes if (cp) 2141126274Sdes *cp = '\0'; 2142126274Sdes 2143137015Sdes /* Handle user interrupts gracefully during commands */ 2144137015Sdes interrupted = 0; 2145137015Sdes signal(SIGINT, cmd_interrupt); 2146137015Sdes 2147204917Sdes err = parse_dispatch_command(conn, cmd, &remote_path, 2148204917Sdes batchmode); 2149126274Sdes if (err != 0) 2150126274Sdes break; 2151126274Sdes } 2152255767Sdes free(remote_path); 2153255767Sdes free(conn); 2154126274Sdes 2155149749Sdes#ifdef USE_LIBEDIT 2156149749Sdes if (el != NULL) 2157149749Sdes el_end(el); 2158149749Sdes#endif /* USE_LIBEDIT */ 2159149749Sdes 2160126274Sdes /* err == 1 signifies normal "quit" exit */ 2161126274Sdes return (err >= 0 ? 0 : -1); 2162126274Sdes} 2163126274Sdes 2164126274Sdesstatic void 2165124208Sdesconnect_to_server(char *path, char **args, int *in, int *out) 2166124208Sdes{ 216776259Sgreen int c_in, c_out; 216899060Sdes 216976259Sgreen#ifdef USE_PIPES 217076259Sgreen int pin[2], pout[2]; 217199060Sdes 217276259Sgreen if ((pipe(pin) == -1) || (pipe(pout) == -1)) 217376259Sgreen fatal("pipe: %s", strerror(errno)); 217476259Sgreen *in = pin[0]; 217576259Sgreen *out = pout[1]; 217676259Sgreen c_in = pout[0]; 217776259Sgreen c_out = pin[1]; 217876259Sgreen#else /* USE_PIPES */ 217976259Sgreen int inout[2]; 218099060Sdes 218176259Sgreen if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1) 218276259Sgreen fatal("socketpair: %s", strerror(errno)); 218376259Sgreen *in = *out = inout[0]; 218476259Sgreen c_in = c_out = inout[1]; 218576259Sgreen#endif /* USE_PIPES */ 218676259Sgreen 2187124208Sdes if ((sshpid = fork()) == -1) 218876259Sgreen fatal("fork: %s", strerror(errno)); 2189124208Sdes else if (sshpid == 0) { 219076259Sgreen if ((dup2(c_in, STDIN_FILENO) == -1) || 219176259Sgreen (dup2(c_out, STDOUT_FILENO) == -1)) { 219276259Sgreen fprintf(stderr, "dup2: %s\n", strerror(errno)); 2193137015Sdes _exit(1); 219476259Sgreen } 219576259Sgreen close(*in); 219676259Sgreen close(*out); 219776259Sgreen close(c_in); 219876259Sgreen close(c_out); 2199137015Sdes 2200137015Sdes /* 2201137015Sdes * The underlying ssh is in the same process group, so we must 2202137015Sdes * ignore SIGINT if we want to gracefully abort commands, 2203137015Sdes * otherwise the signal will make it to the ssh process and 2204204917Sdes * kill it too. Contrawise, since sftp sends SIGTERMs to the 2205204917Sdes * underlying ssh, it must *not* ignore that signal. 2206137015Sdes */ 2207137015Sdes signal(SIGINT, SIG_IGN); 2208204917Sdes signal(SIGTERM, SIG_DFL); 2209137015Sdes execvp(path, args); 221092555Sdes fprintf(stderr, "exec: %s: %s\n", path, strerror(errno)); 2211137015Sdes _exit(1); 221276259Sgreen } 221376259Sgreen 2214124208Sdes signal(SIGTERM, killchild); 2215124208Sdes signal(SIGINT, killchild); 2216124208Sdes signal(SIGHUP, killchild); 221776259Sgreen close(c_in); 221876259Sgreen close(c_out); 221976259Sgreen} 222076259Sgreen 222192555Sdesstatic void 222276259Sgreenusage(void) 222376259Sgreen{ 222492555Sdes extern char *__progname; 222598675Sdes 222692555Sdes fprintf(stderr, 2227262566Sdes "usage: %s [-1246aCfpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n" 2228204917Sdes " [-D sftp_server_path] [-F ssh_config] " 2229221420Sdes "[-i identity_file] [-l limit]\n" 2230204917Sdes " [-o ssh_option] [-P port] [-R num_requests] " 2231204917Sdes "[-S program]\n" 2232204917Sdes " [-s subsystem | sftp_server] host\n" 2233192595Sdes " %s [user@]host[:file ...]\n" 2234192595Sdes " %s [user@]host[:dir[/]]\n" 2235204917Sdes " %s -b batchfile [user@]host\n", 2236204917Sdes __progname, __progname, __progname, __progname); 223776259Sgreen exit(1); 223876259Sgreen} 223976259Sgreen 224076259Sgreenint 224176259Sgreenmain(int argc, char **argv) 224276259Sgreen{ 2243113908Sdes int in, out, ch, err; 2244204917Sdes char *host = NULL, *userhost, *cp, *file2 = NULL; 224592555Sdes int debug_level = 0, sshver = 2; 224692555Sdes char *file1 = NULL, *sftp_server = NULL; 224792555Sdes char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL; 2248221420Sdes const char *errstr; 224992555Sdes LogLevel ll = SYSLOG_LEVEL_INFO; 225092555Sdes arglist args; 225176259Sgreen extern int optind; 225276259Sgreen extern char *optarg; 2253204917Sdes struct sftp_conn *conn; 2254204917Sdes size_t copy_buffer_len = DEFAULT_COPY_BUFLEN; 2255204917Sdes size_t num_requests = DEFAULT_NUM_REQUESTS; 2256221420Sdes long long limit_kbps = 0; 225776259Sgreen 2258296781Sdes ssh_malloc_init(); /* must be called before any mallocs */ 2259157016Sdes /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ 2260157016Sdes sanitise_stdfd(); 2261255767Sdes setlocale(LC_CTYPE, ""); 2262157016Sdes 2263124208Sdes __progname = ssh_get_progname(argv[0]); 2264157016Sdes memset(&args, '\0', sizeof(args)); 226592555Sdes args.list = NULL; 2266162852Sdes addargs(&args, "%s", ssh_program); 226792555Sdes addargs(&args, "-oForwardX11 no"); 226892555Sdes addargs(&args, "-oForwardAgent no"); 2269157016Sdes addargs(&args, "-oPermitLocalCommand no"); 227092555Sdes addargs(&args, "-oClearAllForwardings yes"); 2271126274Sdes 227292555Sdes ll = SYSLOG_LEVEL_INFO; 2273126274Sdes infile = stdin; 227476259Sgreen 2275204917Sdes while ((ch = getopt(argc, argv, 2276262566Sdes "1246afhpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) { 227776259Sgreen switch (ch) { 2278204917Sdes /* Passed through to ssh(1) */ 2279204917Sdes case '4': 2280204917Sdes case '6': 228176259Sgreen case 'C': 2282204917Sdes addargs(&args, "-%c", ch); 228376259Sgreen break; 2284204917Sdes /* Passed through to ssh(1) with argument */ 2285204917Sdes case 'F': 2286204917Sdes case 'c': 2287204917Sdes case 'i': 2288204917Sdes case 'o': 2289204917Sdes addargs(&args, "-%c", ch); 2290204917Sdes addargs(&args, "%s", optarg); 2291204917Sdes break; 2292204917Sdes case 'q': 2293255767Sdes ll = SYSLOG_LEVEL_ERROR; 2294255767Sdes quiet = 1; 2295204917Sdes showprogress = 0; 2296204917Sdes addargs(&args, "-%c", ch); 2297204917Sdes break; 2298204917Sdes case 'P': 2299204917Sdes addargs(&args, "-oPort %s", optarg); 2300204917Sdes break; 230176259Sgreen case 'v': 230292555Sdes if (debug_level < 3) { 230392555Sdes addargs(&args, "-v"); 230492555Sdes ll = SYSLOG_LEVEL_DEBUG1 + debug_level; 230592555Sdes } 230692555Sdes debug_level++; 230776259Sgreen break; 230876259Sgreen case '1': 230992555Sdes sshver = 1; 231076259Sgreen if (sftp_server == NULL) 231176259Sgreen sftp_server = _PATH_SFTP_SERVER; 231276259Sgreen break; 2313204917Sdes case '2': 2314204917Sdes sshver = 2; 231576259Sgreen break; 2316255767Sdes case 'a': 2317255767Sdes global_aflag = 1; 2318255767Sdes break; 2319204917Sdes case 'B': 2320204917Sdes copy_buffer_len = strtol(optarg, &cp, 10); 2321204917Sdes if (copy_buffer_len == 0 || *cp != '\0') 2322204917Sdes fatal("Invalid buffer size \"%s\"", optarg); 232376259Sgreen break; 232476259Sgreen case 'b': 2325126274Sdes if (batchmode) 2326126274Sdes fatal("Batch file already specified."); 2327126274Sdes 2328126274Sdes /* Allow "-" as stdin */ 2329137015Sdes if (strcmp(optarg, "-") != 0 && 2330149749Sdes (infile = fopen(optarg, "r")) == NULL) 2331126274Sdes fatal("%s (%s).", strerror(errno), optarg); 2332113908Sdes showprogress = 0; 2333255767Sdes quiet = batchmode = 1; 2334146998Sdes addargs(&args, "-obatchmode yes"); 233576259Sgreen break; 2336262566Sdes case 'f': 2337262566Sdes global_fflag = 1; 2338262566Sdes break; 2339204917Sdes case 'p': 2340204917Sdes global_pflag = 1; 2341204917Sdes break; 2342204917Sdes case 'D': 234392555Sdes sftp_direct = optarg; 234492555Sdes break; 2345221420Sdes case 'l': 2346221420Sdes limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024, 2347221420Sdes &errstr); 2348221420Sdes if (errstr != NULL) 2349221420Sdes usage(); 2350221420Sdes limit_kbps *= 1024; /* kbps */ 2351221420Sdes break; 2352204917Sdes case 'r': 2353204917Sdes global_rflag = 1; 235492555Sdes break; 235592555Sdes case 'R': 235692555Sdes num_requests = strtol(optarg, &cp, 10); 235792555Sdes if (num_requests == 0 || *cp != '\0') 235898675Sdes fatal("Invalid number of requests \"%s\"", 235992555Sdes optarg); 236092555Sdes break; 2361204917Sdes case 's': 2362204917Sdes sftp_server = optarg; 2363204917Sdes break; 2364204917Sdes case 'S': 2365204917Sdes ssh_program = optarg; 2366204917Sdes replacearg(&args, 0, "%s", ssh_program); 2367204917Sdes break; 236876259Sgreen case 'h': 236976259Sgreen default: 237076259Sgreen usage(); 237176259Sgreen } 237276259Sgreen } 237376259Sgreen 2374128456Sdes if (!isatty(STDERR_FILENO)) 2375128456Sdes showprogress = 0; 2376128456Sdes 237798675Sdes log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1); 237898675Sdes 237992555Sdes if (sftp_direct == NULL) { 238092555Sdes if (optind == argc || argc > (optind + 2)) 238192555Sdes usage(); 238276259Sgreen 238392555Sdes userhost = xstrdup(argv[optind]); 238492555Sdes file2 = argv[optind+1]; 238576259Sgreen 2386113908Sdes if ((host = strrchr(userhost, '@')) == NULL) 238792555Sdes host = userhost; 238892555Sdes else { 238992555Sdes *host++ = '\0'; 239092555Sdes if (!userhost[0]) { 239192555Sdes fprintf(stderr, "Missing username\n"); 239292555Sdes usage(); 239392555Sdes } 2394204917Sdes addargs(&args, "-l"); 2395204917Sdes addargs(&args, "%s", userhost); 239692555Sdes } 239792555Sdes 2398126274Sdes if ((cp = colon(host)) != NULL) { 2399126274Sdes *cp++ = '\0'; 2400126274Sdes file1 = cp; 2401126274Sdes } 2402126274Sdes 240392555Sdes host = cleanhostname(host); 240492555Sdes if (!*host) { 240592555Sdes fprintf(stderr, "Missing hostname\n"); 240676259Sgreen usage(); 240776259Sgreen } 240876259Sgreen 240992555Sdes addargs(&args, "-oProtocol %d", sshver); 241076259Sgreen 241192555Sdes /* no subsystem if the server-spec contains a '/' */ 241292555Sdes if (sftp_server == NULL || strchr(sftp_server, '/') == NULL) 241392555Sdes addargs(&args, "-s"); 241476259Sgreen 2415204917Sdes addargs(&args, "--"); 241692555Sdes addargs(&args, "%s", host); 241798675Sdes addargs(&args, "%s", (sftp_server != NULL ? 241892555Sdes sftp_server : "sftp")); 241976259Sgreen 2420124208Sdes connect_to_server(ssh_program, args.list, &in, &out); 242192555Sdes } else { 242292555Sdes args.list = NULL; 242392555Sdes addargs(&args, "sftp-server"); 242476259Sgreen 2425124208Sdes connect_to_server(sftp_direct, args.list, &in, &out); 242692555Sdes } 2427157016Sdes freeargs(&args); 242876259Sgreen 2429221420Sdes conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps); 2430204917Sdes if (conn == NULL) 2431204917Sdes fatal("Couldn't initialise connection to server"); 243276259Sgreen 2433255767Sdes if (!quiet) { 2434204917Sdes if (sftp_direct == NULL) 2435204917Sdes fprintf(stderr, "Connected to %s.\n", host); 2436204917Sdes else 2437204917Sdes fprintf(stderr, "Attached to %s.\n", sftp_direct); 2438204917Sdes } 2439204917Sdes 2440204917Sdes err = interactive_loop(conn, file1, file2); 2441204917Sdes 244298937Sdes#if !defined(USE_PIPES) 2443149749Sdes shutdown(in, SHUT_RDWR); 2444149749Sdes shutdown(out, SHUT_RDWR); 244598937Sdes#endif 244698937Sdes 244776259Sgreen close(in); 244876259Sgreen close(out); 2449126274Sdes if (batchmode) 245076259Sgreen fclose(infile); 245176259Sgreen 245298675Sdes while (waitpid(sshpid, NULL, 0) == -1) 245398675Sdes if (errno != EINTR) 245498675Sdes fatal("Couldn't wait for ssh process: %s", 245598675Sdes strerror(errno)); 245676259Sgreen 2457113908Sdes exit(err == 0 ? 0 : 1); 245876259Sgreen} 2459