sftp.c revision 192595
1192595Sdes/* $OpenBSD: sftp.c,v 1.107 2009/02/02 11:15:14 dtucker Exp $ */ 276259Sgreen/* 3126274Sdes * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> 476259Sgreen * 5126274Sdes * Permission to use, copy, modify, and distribute this software for any 6126274Sdes * purpose with or without fee is hereby granted, provided that the above 7126274Sdes * copyright notice and this permission notice appear in all copies. 876259Sgreen * 9126274Sdes * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10126274Sdes * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11126274Sdes * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12126274Sdes * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13126274Sdes * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14126274Sdes * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15126274Sdes * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 1676259Sgreen */ 1776259Sgreen 1876259Sgreen#include "includes.h" 1976259Sgreen 20162852Sdes#include <sys/types.h> 21162852Sdes#include <sys/ioctl.h> 22162852Sdes#ifdef HAVE_SYS_STAT_H 23162852Sdes# include <sys/stat.h> 24162852Sdes#endif 25162852Sdes#include <sys/param.h> 26162852Sdes#include <sys/socket.h> 27162852Sdes#include <sys/wait.h> 28181111Sdes#ifdef HAVE_SYS_STATVFS_H 29181111Sdes#include <sys/statvfs.h> 30181111Sdes#endif 3176259Sgreen 32181111Sdes#include <ctype.h> 33162852Sdes#include <errno.h> 34162852Sdes 35162852Sdes#ifdef HAVE_PATHS_H 36162852Sdes# include <paths.h> 37162852Sdes#endif 38146998Sdes#ifdef USE_LIBEDIT 39146998Sdes#include <histedit.h> 40146998Sdes#else 41146998Sdestypedef void EditLine; 42146998Sdes#endif 43162852Sdes#include <signal.h> 44162852Sdes#include <stdlib.h> 45162852Sdes#include <stdio.h> 46162852Sdes#include <string.h> 47162852Sdes#include <unistd.h> 48162852Sdes#include <stdarg.h> 49146998Sdes 50181111Sdes#ifdef HAVE_UTIL_H 51181111Sdes# include <util.h> 52181111Sdes#endif 53181111Sdes 54181111Sdes#ifdef HAVE_LIBUTIL_H 55181111Sdes# include <libutil.h> 56181111Sdes#endif 57181111Sdes 5876259Sgreen#include "xmalloc.h" 5976259Sgreen#include "log.h" 6076259Sgreen#include "pathnames.h" 6192555Sdes#include "misc.h" 6276259Sgreen 6376259Sgreen#include "sftp.h" 64162852Sdes#include "buffer.h" 6576259Sgreen#include "sftp-common.h" 6676259Sgreen#include "sftp-client.h" 6776259Sgreen 68126274Sdes/* File to read commands from */ 69126274SdesFILE* infile; 70126274Sdes 71126274Sdes/* Are we in batchfile mode? */ 72126274Sdesint batchmode = 0; 73126274Sdes 74126274Sdes/* Size of buffer used when copying files */ 75126274Sdessize_t copy_buffer_len = 32768; 76126274Sdes 77126274Sdes/* Number of concurrent outstanding requests */ 78181111Sdessize_t num_requests = 64; 79126274Sdes 80126274Sdes/* PID of ssh transport process */ 81126274Sdesstatic pid_t sshpid = -1; 82126274Sdes 83126274Sdes/* This is set to 0 if the progressmeter is not desired. */ 84128456Sdesint showprogress = 1; 85126274Sdes 86137015Sdes/* SIGINT received during command processing */ 87137015Sdesvolatile sig_atomic_t interrupted = 0; 88137015Sdes 89137015Sdes/* I wish qsort() took a separate ctx for the comparison function...*/ 90137015Sdesint sort_flag; 91137015Sdes 92126274Sdesint remote_glob(struct sftp_conn *, const char *, int, 93126274Sdes int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */ 94126274Sdes 9598937Sdesextern char *__progname; 9698937Sdes 97126274Sdes/* Separators for interactive commands */ 98126274Sdes#define WHITESPACE " \t\r\n" 9976259Sgreen 100137015Sdes/* ls flags */ 101137015Sdes#define LS_LONG_VIEW 0x01 /* Full view ala ls -l */ 102137015Sdes#define LS_SHORT_VIEW 0x02 /* Single row view ala ls -1 */ 103137015Sdes#define LS_NUMERIC_VIEW 0x04 /* Long view with numeric uid/gid */ 104137015Sdes#define LS_NAME_SORT 0x08 /* Sort by name (default) */ 105137015Sdes#define LS_TIME_SORT 0x10 /* Sort by mtime */ 106137015Sdes#define LS_SIZE_SORT 0x20 /* Sort by file size */ 107137015Sdes#define LS_REVERSE_SORT 0x40 /* Reverse sort order */ 108137015Sdes#define LS_SHOW_ALL 0x80 /* Don't skip filenames starting with '.' */ 109113908Sdes 110137015Sdes#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW) 111137015Sdes#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT) 112137015Sdes 113126274Sdes/* Commands for interactive mode */ 114126274Sdes#define I_CHDIR 1 115126274Sdes#define I_CHGRP 2 116126274Sdes#define I_CHMOD 3 117126274Sdes#define I_CHOWN 4 118181111Sdes#define I_DF 24 119126274Sdes#define I_GET 5 120126274Sdes#define I_HELP 6 121126274Sdes#define I_LCHDIR 7 122126274Sdes#define I_LLS 8 123126274Sdes#define I_LMKDIR 9 124126274Sdes#define I_LPWD 10 125126274Sdes#define I_LS 11 126126274Sdes#define I_LUMASK 12 127126274Sdes#define I_MKDIR 13 128126274Sdes#define I_PUT 14 129126274Sdes#define I_PWD 15 130126274Sdes#define I_QUIT 16 131126274Sdes#define I_RENAME 17 132126274Sdes#define I_RM 18 133126274Sdes#define I_RMDIR 19 134126274Sdes#define I_SHELL 20 135126274Sdes#define I_SYMLINK 21 136126274Sdes#define I_VERSION 22 137126274Sdes#define I_PROGRESS 23 138126274Sdes 139126274Sdesstruct CMD { 140126274Sdes const char *c; 141126274Sdes const int n; 142126274Sdes}; 143126274Sdes 144126274Sdesstatic const struct CMD cmds[] = { 145126274Sdes { "bye", I_QUIT }, 146126274Sdes { "cd", I_CHDIR }, 147126274Sdes { "chdir", I_CHDIR }, 148126274Sdes { "chgrp", I_CHGRP }, 149126274Sdes { "chmod", I_CHMOD }, 150126274Sdes { "chown", I_CHOWN }, 151181111Sdes { "df", I_DF }, 152126274Sdes { "dir", I_LS }, 153126274Sdes { "exit", I_QUIT }, 154126274Sdes { "get", I_GET }, 155126274Sdes { "mget", I_GET }, 156126274Sdes { "help", I_HELP }, 157126274Sdes { "lcd", I_LCHDIR }, 158126274Sdes { "lchdir", I_LCHDIR }, 159126274Sdes { "lls", I_LLS }, 160126274Sdes { "lmkdir", I_LMKDIR }, 161126274Sdes { "ln", I_SYMLINK }, 162126274Sdes { "lpwd", I_LPWD }, 163126274Sdes { "ls", I_LS }, 164126274Sdes { "lumask", I_LUMASK }, 165126274Sdes { "mkdir", I_MKDIR }, 166126274Sdes { "progress", I_PROGRESS }, 167126274Sdes { "put", I_PUT }, 168126274Sdes { "mput", I_PUT }, 169126274Sdes { "pwd", I_PWD }, 170126274Sdes { "quit", I_QUIT }, 171126274Sdes { "rename", I_RENAME }, 172126274Sdes { "rm", I_RM }, 173126274Sdes { "rmdir", I_RMDIR }, 174126274Sdes { "symlink", I_SYMLINK }, 175126274Sdes { "version", I_VERSION }, 176126274Sdes { "!", I_SHELL }, 177126274Sdes { "?", I_HELP }, 178126274Sdes { NULL, -1} 179126274Sdes}; 180126274Sdes 181126274Sdesint interactive_loop(int fd_in, int fd_out, char *file1, char *file2); 182126274Sdes 183181111Sdes/* ARGSUSED */ 18492555Sdesstatic void 185137015Sdeskillchild(int signo) 186137015Sdes{ 187146998Sdes if (sshpid > 1) { 188137015Sdes kill(sshpid, SIGTERM); 189146998Sdes waitpid(sshpid, NULL, 0); 190146998Sdes } 191137015Sdes 192137015Sdes _exit(1); 193137015Sdes} 194137015Sdes 195181111Sdes/* ARGSUSED */ 196137015Sdesstatic void 197137015Sdescmd_interrupt(int signo) 198137015Sdes{ 199137015Sdes const char msg[] = "\rInterrupt \n"; 200146998Sdes int olderrno = errno; 201137015Sdes 202137015Sdes write(STDERR_FILENO, msg, sizeof(msg) - 1); 203137015Sdes interrupted = 1; 204146998Sdes errno = olderrno; 205137015Sdes} 206137015Sdes 207137015Sdesstatic void 208126274Sdeshelp(void) 209126274Sdes{ 210192595Sdes printf("Available commands:\n" 211192595Sdes "bye Quit sftp\n" 212192595Sdes "cd path Change remote directory to 'path'\n" 213192595Sdes "chgrp grp path Change group of file 'path' to 'grp'\n" 214192595Sdes "chmod mode path Change permissions of file 'path' to 'mode'\n" 215192595Sdes "chown own path Change owner of file 'path' to 'own'\n" 216192595Sdes "df [-hi] [path] Display statistics for current directory or\n" 217192595Sdes " filesystem containing 'path'\n" 218192595Sdes "exit Quit sftp\n" 219192595Sdes "get [-P] remote-path [local-path] Download file\n" 220192595Sdes "help Display this help text\n" 221192595Sdes "lcd path Change local directory to 'path'\n" 222192595Sdes "lls [ls-options [path]] Display local directory listing\n" 223192595Sdes "lmkdir path Create local directory\n" 224192595Sdes "ln oldpath newpath Symlink remote file\n" 225192595Sdes "lpwd Print local working directory\n" 226192595Sdes "ls [-1aflnrSt] [path] Display remote directory listing\n" 227192595Sdes "lumask umask Set local umask to 'umask'\n" 228192595Sdes "mkdir path Create remote directory\n" 229192595Sdes "progress Toggle display of progress meter\n" 230192595Sdes "put [-P] local-path [remote-path] Upload file\n" 231192595Sdes "pwd Display remote working directory\n" 232192595Sdes "quit Quit sftp\n" 233192595Sdes "rename oldpath newpath Rename remote file\n" 234192595Sdes "rm path Delete remote file\n" 235192595Sdes "rmdir path Remove remote directory\n" 236192595Sdes "symlink oldpath newpath Symlink remote file\n" 237192595Sdes "version Show SFTP version\n" 238192595Sdes "!command Execute 'command' in local shell\n" 239192595Sdes "! Escape to local shell\n" 240192595Sdes "? Synonym for help\n"); 241126274Sdes} 242126274Sdes 243126274Sdesstatic void 244126274Sdeslocal_do_shell(const char *args) 245126274Sdes{ 246126274Sdes int status; 247126274Sdes char *shell; 248126274Sdes pid_t pid; 249126274Sdes 250126274Sdes if (!*args) 251126274Sdes args = NULL; 252126274Sdes 253126274Sdes if ((shell = getenv("SHELL")) == NULL) 254126274Sdes shell = _PATH_BSHELL; 255126274Sdes 256126274Sdes if ((pid = fork()) == -1) 257126274Sdes fatal("Couldn't fork: %s", strerror(errno)); 258126274Sdes 259126274Sdes if (pid == 0) { 260126274Sdes /* XXX: child has pipe fds to ssh subproc open - issue? */ 261126274Sdes if (args) { 262126274Sdes debug3("Executing %s -c \"%s\"", shell, args); 263126274Sdes execl(shell, shell, "-c", args, (char *)NULL); 264126274Sdes } else { 265126274Sdes debug3("Executing %s", shell); 266126274Sdes execl(shell, shell, (char *)NULL); 267126274Sdes } 268126274Sdes fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell, 269126274Sdes strerror(errno)); 270126274Sdes _exit(1); 271126274Sdes } 272126274Sdes while (waitpid(pid, &status, 0) == -1) 273126274Sdes if (errno != EINTR) 274126274Sdes fatal("Couldn't wait for child: %s", strerror(errno)); 275126274Sdes if (!WIFEXITED(status)) 276162852Sdes error("Shell exited abnormally"); 277126274Sdes else if (WEXITSTATUS(status)) 278126274Sdes error("Shell exited with status %d", WEXITSTATUS(status)); 279126274Sdes} 280126274Sdes 281126274Sdesstatic void 282126274Sdeslocal_do_ls(const char *args) 283126274Sdes{ 284126274Sdes if (!args || !*args) 285126274Sdes local_do_shell(_PATH_LS); 286126274Sdes else { 287126274Sdes int len = strlen(_PATH_LS " ") + strlen(args) + 1; 288126274Sdes char *buf = xmalloc(len); 289126274Sdes 290126274Sdes /* XXX: quoting - rip quoting code from ftp? */ 291126274Sdes snprintf(buf, len, _PATH_LS " %s", args); 292126274Sdes local_do_shell(buf); 293126274Sdes xfree(buf); 294126274Sdes } 295126274Sdes} 296126274Sdes 297126274Sdes/* Strip one path (usually the pwd) from the start of another */ 298126274Sdesstatic char * 299126274Sdespath_strip(char *path, char *strip) 300126274Sdes{ 301126274Sdes size_t len; 302126274Sdes 303126274Sdes if (strip == NULL) 304126274Sdes return (xstrdup(path)); 305126274Sdes 306126274Sdes len = strlen(strip); 307146998Sdes if (strncmp(path, strip, len) == 0) { 308126274Sdes if (strip[len - 1] != '/' && path[len] == '/') 309126274Sdes len++; 310126274Sdes return (xstrdup(path + len)); 311126274Sdes } 312126274Sdes 313126274Sdes return (xstrdup(path)); 314126274Sdes} 315126274Sdes 316126274Sdesstatic char * 317126274Sdespath_append(char *p1, char *p2) 318126274Sdes{ 319126274Sdes char *ret; 320181111Sdes size_t len = strlen(p1) + strlen(p2) + 2; 321126274Sdes 322126274Sdes ret = xmalloc(len); 323126274Sdes strlcpy(ret, p1, len); 324181111Sdes if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/') 325126274Sdes strlcat(ret, "/", len); 326126274Sdes strlcat(ret, p2, len); 327126274Sdes 328126274Sdes return(ret); 329126274Sdes} 330126274Sdes 331126274Sdesstatic char * 332126274Sdesmake_absolute(char *p, char *pwd) 333126274Sdes{ 334137015Sdes char *abs_str; 335126274Sdes 336126274Sdes /* Derelativise */ 337126274Sdes if (p && p[0] != '/') { 338137015Sdes abs_str = path_append(pwd, p); 339126274Sdes xfree(p); 340137015Sdes return(abs_str); 341126274Sdes } else 342126274Sdes return(p); 343126274Sdes} 344126274Sdes 345126274Sdesstatic int 346126274Sdesinfer_path(const char *p, char **ifp) 347126274Sdes{ 348126274Sdes char *cp; 349126274Sdes 350126274Sdes cp = strrchr(p, '/'); 351126274Sdes if (cp == NULL) { 352126274Sdes *ifp = xstrdup(p); 353126274Sdes return(0); 354126274Sdes } 355126274Sdes 356126274Sdes if (!cp[1]) { 357126274Sdes error("Invalid path"); 358126274Sdes return(-1); 359126274Sdes } 360126274Sdes 361126274Sdes *ifp = xstrdup(cp + 1); 362126274Sdes return(0); 363126274Sdes} 364126274Sdes 365126274Sdesstatic int 366181111Sdesparse_getput_flags(const char *cmd, char **argv, int argc, int *pflag) 367126274Sdes{ 368181111Sdes extern int opterr, optind, optopt, optreset; 369181111Sdes int ch; 370126274Sdes 371181111Sdes optind = optreset = 1; 372181111Sdes opterr = 0; 373181111Sdes 374181111Sdes *pflag = 0; 375181111Sdes while ((ch = getopt(argc, argv, "Pp")) != -1) { 376181111Sdes switch (ch) { 377126274Sdes case 'p': 378126274Sdes case 'P': 379126274Sdes *pflag = 1; 380126274Sdes break; 381126274Sdes default: 382181111Sdes error("%s: Invalid flag -%c", cmd, optopt); 383181111Sdes return -1; 384126274Sdes } 385126274Sdes } 386126274Sdes 387181111Sdes return optind; 388126274Sdes} 389126274Sdes 390126274Sdesstatic int 391181111Sdesparse_ls_flags(char **argv, int argc, int *lflag) 392126274Sdes{ 393181111Sdes extern int opterr, optind, optopt, optreset; 394181111Sdes int ch; 395126274Sdes 396181111Sdes optind = optreset = 1; 397181111Sdes opterr = 0; 398181111Sdes 399137015Sdes *lflag = LS_NAME_SORT; 400181111Sdes while ((ch = getopt(argc, argv, "1Saflnrt")) != -1) { 401181111Sdes switch (ch) { 402181111Sdes case '1': 403181111Sdes *lflag &= ~VIEW_FLAGS; 404181111Sdes *lflag |= LS_SHORT_VIEW; 405181111Sdes break; 406181111Sdes case 'S': 407181111Sdes *lflag &= ~SORT_FLAGS; 408181111Sdes *lflag |= LS_SIZE_SORT; 409181111Sdes break; 410181111Sdes case 'a': 411181111Sdes *lflag |= LS_SHOW_ALL; 412181111Sdes break; 413181111Sdes case 'f': 414181111Sdes *lflag &= ~SORT_FLAGS; 415181111Sdes break; 416181111Sdes case 'l': 417181111Sdes *lflag &= ~VIEW_FLAGS; 418181111Sdes *lflag |= LS_LONG_VIEW; 419181111Sdes break; 420181111Sdes case 'n': 421181111Sdes *lflag &= ~VIEW_FLAGS; 422181111Sdes *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW; 423181111Sdes break; 424181111Sdes case 'r': 425181111Sdes *lflag |= LS_REVERSE_SORT; 426181111Sdes break; 427181111Sdes case 't': 428181111Sdes *lflag &= ~SORT_FLAGS; 429181111Sdes *lflag |= LS_TIME_SORT; 430181111Sdes break; 431181111Sdes default: 432181111Sdes error("ls: Invalid flag -%c", optopt); 433181111Sdes return -1; 434126274Sdes } 435126274Sdes } 436126274Sdes 437181111Sdes return optind; 438126274Sdes} 439126274Sdes 440126274Sdesstatic int 441181111Sdesparse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag) 442126274Sdes{ 443181111Sdes extern int opterr, optind, optopt, optreset; 444181111Sdes int ch; 445126274Sdes 446181111Sdes optind = optreset = 1; 447181111Sdes opterr = 0; 448126274Sdes 449181111Sdes *hflag = *iflag = 0; 450181111Sdes while ((ch = getopt(argc, argv, "hi")) != -1) { 451181111Sdes switch (ch) { 452181111Sdes case 'h': 453181111Sdes *hflag = 1; 454181111Sdes break; 455181111Sdes case 'i': 456181111Sdes *iflag = 1; 457181111Sdes break; 458181111Sdes default: 459181111Sdes error("%s: Invalid flag -%c", cmd, optopt); 460181111Sdes return -1; 461126274Sdes } 462126274Sdes } 463126274Sdes 464181111Sdes return optind; 465126274Sdes} 466126274Sdes 467126274Sdesstatic int 468126274Sdesis_dir(char *path) 469126274Sdes{ 470126274Sdes struct stat sb; 471126274Sdes 472126274Sdes /* XXX: report errors? */ 473126274Sdes if (stat(path, &sb) == -1) 474126274Sdes return(0); 475126274Sdes 476162852Sdes return(S_ISDIR(sb.st_mode)); 477126274Sdes} 478126274Sdes 479126274Sdesstatic int 480126274Sdesremote_is_dir(struct sftp_conn *conn, char *path) 481126274Sdes{ 482126274Sdes Attrib *a; 483126274Sdes 484126274Sdes /* XXX: report errors? */ 485126274Sdes if ((a = do_stat(conn, path, 1)) == NULL) 486126274Sdes return(0); 487126274Sdes if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) 488126274Sdes return(0); 489162852Sdes return(S_ISDIR(a->perm)); 490126274Sdes} 491126274Sdes 492126274Sdesstatic int 493126274Sdesprocess_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag) 494126274Sdes{ 495126274Sdes char *abs_src = NULL; 496126274Sdes char *abs_dst = NULL; 497126274Sdes char *tmp; 498126274Sdes glob_t g; 499126274Sdes int err = 0; 500126274Sdes int i; 501126274Sdes 502126274Sdes abs_src = xstrdup(src); 503126274Sdes abs_src = make_absolute(abs_src, pwd); 504126274Sdes 505126274Sdes memset(&g, 0, sizeof(g)); 506126274Sdes debug3("Looking up %s", abs_src); 507126274Sdes if (remote_glob(conn, abs_src, 0, NULL, &g)) { 508126274Sdes error("File \"%s\" not found.", abs_src); 509126274Sdes err = -1; 510126274Sdes goto out; 511126274Sdes } 512126274Sdes 513126274Sdes /* If multiple matches, dst must be a directory or unspecified */ 514126274Sdes if (g.gl_matchc > 1 && dst && !is_dir(dst)) { 515126274Sdes error("Multiple files match, but \"%s\" is not a directory", 516126274Sdes dst); 517126274Sdes err = -1; 518126274Sdes goto out; 519126274Sdes } 520126274Sdes 521137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 522126274Sdes if (infer_path(g.gl_pathv[i], &tmp)) { 523126274Sdes err = -1; 524126274Sdes goto out; 525126274Sdes } 526126274Sdes 527126274Sdes if (g.gl_matchc == 1 && dst) { 528126274Sdes /* If directory specified, append filename */ 529162852Sdes xfree(tmp); 530126274Sdes if (is_dir(dst)) { 531126274Sdes if (infer_path(g.gl_pathv[0], &tmp)) { 532126274Sdes err = 1; 533126274Sdes goto out; 534126274Sdes } 535126274Sdes abs_dst = path_append(dst, tmp); 536126274Sdes xfree(tmp); 537126274Sdes } else 538126274Sdes abs_dst = xstrdup(dst); 539126274Sdes } else if (dst) { 540126274Sdes abs_dst = path_append(dst, tmp); 541126274Sdes xfree(tmp); 542126274Sdes } else 543126274Sdes abs_dst = tmp; 544126274Sdes 545126274Sdes printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst); 546126274Sdes if (do_download(conn, g.gl_pathv[i], abs_dst, pflag) == -1) 547126274Sdes err = -1; 548126274Sdes xfree(abs_dst); 549126274Sdes abs_dst = NULL; 550126274Sdes } 551126274Sdes 552126274Sdesout: 553126274Sdes xfree(abs_src); 554126274Sdes globfree(&g); 555126274Sdes return(err); 556126274Sdes} 557126274Sdes 558126274Sdesstatic int 559126274Sdesprocess_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag) 560126274Sdes{ 561126274Sdes char *tmp_dst = NULL; 562126274Sdes char *abs_dst = NULL; 563126274Sdes char *tmp; 564126274Sdes glob_t g; 565126274Sdes int err = 0; 566126274Sdes int i; 567181111Sdes struct stat sb; 568126274Sdes 569126274Sdes if (dst) { 570126274Sdes tmp_dst = xstrdup(dst); 571126274Sdes tmp_dst = make_absolute(tmp_dst, pwd); 572126274Sdes } 573126274Sdes 574126274Sdes memset(&g, 0, sizeof(g)); 575126274Sdes debug3("Looking up %s", src); 576181111Sdes if (glob(src, GLOB_NOCHECK, NULL, &g)) { 577126274Sdes error("File \"%s\" not found.", src); 578126274Sdes err = -1; 579126274Sdes goto out; 580126274Sdes } 581126274Sdes 582126274Sdes /* If multiple matches, dst may be directory or unspecified */ 583126274Sdes if (g.gl_matchc > 1 && tmp_dst && !remote_is_dir(conn, tmp_dst)) { 584126274Sdes error("Multiple files match, but \"%s\" is not a directory", 585126274Sdes tmp_dst); 586126274Sdes err = -1; 587126274Sdes goto out; 588126274Sdes } 589126274Sdes 590137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 591181111Sdes if (stat(g.gl_pathv[i], &sb) == -1) { 592181111Sdes err = -1; 593181111Sdes error("stat %s: %s", g.gl_pathv[i], strerror(errno)); 594181111Sdes continue; 595181111Sdes } 596181111Sdes 597181111Sdes if (!S_ISREG(sb.st_mode)) { 598126274Sdes error("skipping non-regular file %s", 599126274Sdes g.gl_pathv[i]); 600126274Sdes continue; 601126274Sdes } 602126274Sdes if (infer_path(g.gl_pathv[i], &tmp)) { 603126274Sdes err = -1; 604126274Sdes goto out; 605126274Sdes } 606126274Sdes 607126274Sdes if (g.gl_matchc == 1 && tmp_dst) { 608126274Sdes /* If directory specified, append filename */ 609126274Sdes if (remote_is_dir(conn, tmp_dst)) { 610126274Sdes if (infer_path(g.gl_pathv[0], &tmp)) { 611126274Sdes err = 1; 612126274Sdes goto out; 613126274Sdes } 614126274Sdes abs_dst = path_append(tmp_dst, tmp); 615126274Sdes xfree(tmp); 616126274Sdes } else 617126274Sdes abs_dst = xstrdup(tmp_dst); 618126274Sdes 619126274Sdes } else if (tmp_dst) { 620126274Sdes abs_dst = path_append(tmp_dst, tmp); 621126274Sdes xfree(tmp); 622126274Sdes } else 623126274Sdes abs_dst = make_absolute(tmp, pwd); 624126274Sdes 625126274Sdes printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst); 626126274Sdes if (do_upload(conn, g.gl_pathv[i], abs_dst, pflag) == -1) 627126274Sdes err = -1; 628126274Sdes } 629126274Sdes 630126274Sdesout: 631126274Sdes if (abs_dst) 632126274Sdes xfree(abs_dst); 633126274Sdes if (tmp_dst) 634126274Sdes xfree(tmp_dst); 635126274Sdes globfree(&g); 636126274Sdes return(err); 637126274Sdes} 638126274Sdes 639126274Sdesstatic int 640126274Sdessdirent_comp(const void *aa, const void *bb) 641126274Sdes{ 642126274Sdes SFTP_DIRENT *a = *(SFTP_DIRENT **)aa; 643126274Sdes SFTP_DIRENT *b = *(SFTP_DIRENT **)bb; 644137015Sdes int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1; 645126274Sdes 646137015Sdes#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1)) 647137015Sdes if (sort_flag & LS_NAME_SORT) 648137015Sdes return (rmul * strcmp(a->filename, b->filename)); 649137015Sdes else if (sort_flag & LS_TIME_SORT) 650137015Sdes return (rmul * NCMP(a->a.mtime, b->a.mtime)); 651137015Sdes else if (sort_flag & LS_SIZE_SORT) 652137015Sdes return (rmul * NCMP(a->a.size, b->a.size)); 653137015Sdes 654137015Sdes fatal("Unknown ls sort type"); 655126274Sdes} 656126274Sdes 657126274Sdes/* sftp ls.1 replacement for directories */ 658126274Sdesstatic int 659126274Sdesdo_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag) 660126274Sdes{ 661149749Sdes int n; 662149749Sdes u_int c = 1, colspace = 0, columns = 1; 663126274Sdes SFTP_DIRENT **d; 664126274Sdes 665126274Sdes if ((n = do_readdir(conn, path, &d)) != 0) 666126274Sdes return (n); 667126274Sdes 668137015Sdes if (!(lflag & LS_SHORT_VIEW)) { 669149749Sdes u_int m = 0, width = 80; 670126274Sdes struct winsize ws; 671126274Sdes char *tmp; 672126274Sdes 673126274Sdes /* Count entries for sort and find longest filename */ 674137015Sdes for (n = 0; d[n] != NULL; n++) { 675137015Sdes if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL)) 676137015Sdes m = MAX(m, strlen(d[n]->filename)); 677137015Sdes } 678126274Sdes 679126274Sdes /* Add any subpath that also needs to be counted */ 680126274Sdes tmp = path_strip(path, strip_path); 681126274Sdes m += strlen(tmp); 682126274Sdes xfree(tmp); 683126274Sdes 684126274Sdes if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1) 685126274Sdes width = ws.ws_col; 686126274Sdes 687126274Sdes columns = width / (m + 2); 688126274Sdes columns = MAX(columns, 1); 689126274Sdes colspace = width / columns; 690126274Sdes colspace = MIN(colspace, width); 691126274Sdes } 692126274Sdes 693137015Sdes if (lflag & SORT_FLAGS) { 694157016Sdes for (n = 0; d[n] != NULL; n++) 695157016Sdes ; /* count entries */ 696137015Sdes sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT); 697137015Sdes qsort(d, n, sizeof(*d), sdirent_comp); 698137015Sdes } 699126274Sdes 700137015Sdes for (n = 0; d[n] != NULL && !interrupted; n++) { 701126274Sdes char *tmp, *fname; 702126274Sdes 703137015Sdes if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL)) 704137015Sdes continue; 705137015Sdes 706126274Sdes tmp = path_append(path, d[n]->filename); 707126274Sdes fname = path_strip(tmp, strip_path); 708126274Sdes xfree(tmp); 709126274Sdes 710137015Sdes if (lflag & LS_LONG_VIEW) { 711137015Sdes if (lflag & LS_NUMERIC_VIEW) { 712137015Sdes char *lname; 713137015Sdes struct stat sb; 714126274Sdes 715137015Sdes memset(&sb, 0, sizeof(sb)); 716137015Sdes attrib_to_stat(&d[n]->a, &sb); 717137015Sdes lname = ls_file(fname, &sb, 1); 718137015Sdes printf("%s\n", lname); 719137015Sdes xfree(lname); 720137015Sdes } else 721137015Sdes printf("%s\n", d[n]->longname); 722126274Sdes } else { 723126274Sdes printf("%-*s", colspace, fname); 724126274Sdes if (c >= columns) { 725126274Sdes printf("\n"); 726126274Sdes c = 1; 727126274Sdes } else 728126274Sdes c++; 729126274Sdes } 730126274Sdes 731126274Sdes xfree(fname); 732126274Sdes } 733126274Sdes 734137015Sdes if (!(lflag & LS_LONG_VIEW) && (c != 1)) 735126274Sdes printf("\n"); 736126274Sdes 737126274Sdes free_sftp_dirents(d); 738126274Sdes return (0); 739126274Sdes} 740126274Sdes 741126274Sdes/* sftp ls.1 replacement which handles path globs */ 742126274Sdesstatic int 743126274Sdesdo_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path, 744126274Sdes int lflag) 745126274Sdes{ 746126274Sdes glob_t g; 747149749Sdes u_int i, c = 1, colspace = 0, columns = 1; 748146998Sdes Attrib *a = NULL; 749126274Sdes 750126274Sdes memset(&g, 0, sizeof(g)); 751126274Sdes 752126274Sdes if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE, 753146998Sdes NULL, &g) || (g.gl_pathc && !g.gl_matchc)) { 754146998Sdes if (g.gl_pathc) 755146998Sdes globfree(&g); 756126274Sdes error("Can't ls: \"%s\" not found", path); 757126274Sdes return (-1); 758126274Sdes } 759126274Sdes 760137015Sdes if (interrupted) 761137015Sdes goto out; 762137015Sdes 763126274Sdes /* 764146998Sdes * If the glob returns a single match and it is a directory, 765146998Sdes * then just list its contents. 766126274Sdes */ 767146998Sdes if (g.gl_matchc == 1) { 768146998Sdes if ((a = do_lstat(conn, g.gl_pathv[0], 1)) == NULL) { 769126274Sdes globfree(&g); 770126274Sdes return (-1); 771126274Sdes } 772126274Sdes if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) && 773126274Sdes S_ISDIR(a->perm)) { 774146998Sdes int err; 775146998Sdes 776146998Sdes err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag); 777126274Sdes globfree(&g); 778146998Sdes return (err); 779126274Sdes } 780126274Sdes } 781126274Sdes 782137015Sdes if (!(lflag & LS_SHORT_VIEW)) { 783149749Sdes u_int m = 0, width = 80; 784126274Sdes struct winsize ws; 785126274Sdes 786126274Sdes /* Count entries for sort and find longest filename */ 787126274Sdes for (i = 0; g.gl_pathv[i]; i++) 788126274Sdes m = MAX(m, strlen(g.gl_pathv[i])); 789126274Sdes 790126274Sdes if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1) 791126274Sdes width = ws.ws_col; 792126274Sdes 793126274Sdes columns = width / (m + 2); 794126274Sdes columns = MAX(columns, 1); 795126274Sdes colspace = width / columns; 796126274Sdes } 797126274Sdes 798146998Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++, a = NULL) { 799126274Sdes char *fname; 800126274Sdes 801126274Sdes fname = path_strip(g.gl_pathv[i], strip_path); 802126274Sdes 803137015Sdes if (lflag & LS_LONG_VIEW) { 804126274Sdes char *lname; 805126274Sdes struct stat sb; 806126274Sdes 807126274Sdes /* 808126274Sdes * XXX: this is slow - 1 roundtrip per path 809126274Sdes * A solution to this is to fork glob() and 810126274Sdes * build a sftp specific version which keeps the 811126274Sdes * attribs (which currently get thrown away) 812126274Sdes * that the server returns as well as the filenames. 813126274Sdes */ 814126274Sdes memset(&sb, 0, sizeof(sb)); 815146998Sdes if (a == NULL) 816146998Sdes a = do_lstat(conn, g.gl_pathv[i], 1); 817126274Sdes if (a != NULL) 818126274Sdes attrib_to_stat(a, &sb); 819126274Sdes lname = ls_file(fname, &sb, 1); 820126274Sdes printf("%s\n", lname); 821126274Sdes xfree(lname); 822126274Sdes } else { 823126274Sdes printf("%-*s", colspace, fname); 824126274Sdes if (c >= columns) { 825126274Sdes printf("\n"); 826126274Sdes c = 1; 827126274Sdes } else 828126274Sdes c++; 829126274Sdes } 830126274Sdes xfree(fname); 831126274Sdes } 832126274Sdes 833137015Sdes if (!(lflag & LS_LONG_VIEW) && (c != 1)) 834126274Sdes printf("\n"); 835126274Sdes 836137015Sdes out: 837126274Sdes if (g.gl_pathc) 838126274Sdes globfree(&g); 839126274Sdes 840126274Sdes return (0); 841126274Sdes} 842126274Sdes 843126274Sdesstatic int 844181111Sdesdo_df(struct sftp_conn *conn, char *path, int hflag, int iflag) 845181111Sdes{ 846181111Sdes struct sftp_statvfs st; 847181111Sdes char s_used[FMT_SCALED_STRSIZE]; 848181111Sdes char s_avail[FMT_SCALED_STRSIZE]; 849181111Sdes char s_root[FMT_SCALED_STRSIZE]; 850181111Sdes char s_total[FMT_SCALED_STRSIZE]; 851181111Sdes 852181111Sdes if (do_statvfs(conn, path, &st, 1) == -1) 853181111Sdes return -1; 854181111Sdes if (iflag) { 855181111Sdes printf(" Inodes Used Avail " 856181111Sdes "(root) %%Capacity\n"); 857181111Sdes printf("%11llu %11llu %11llu %11llu %3llu%%\n", 858181111Sdes (unsigned long long)st.f_files, 859181111Sdes (unsigned long long)(st.f_files - st.f_ffree), 860181111Sdes (unsigned long long)st.f_favail, 861181111Sdes (unsigned long long)st.f_ffree, 862181111Sdes (unsigned long long)(100 * (st.f_files - st.f_ffree) / 863181111Sdes st.f_files)); 864181111Sdes } else if (hflag) { 865181111Sdes strlcpy(s_used, "error", sizeof(s_used)); 866181111Sdes strlcpy(s_avail, "error", sizeof(s_avail)); 867181111Sdes strlcpy(s_root, "error", sizeof(s_root)); 868181111Sdes strlcpy(s_total, "error", sizeof(s_total)); 869181111Sdes fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used); 870181111Sdes fmt_scaled(st.f_bavail * st.f_frsize, s_avail); 871181111Sdes fmt_scaled(st.f_bfree * st.f_frsize, s_root); 872181111Sdes fmt_scaled(st.f_blocks * st.f_frsize, s_total); 873181111Sdes printf(" Size Used Avail (root) %%Capacity\n"); 874181111Sdes printf("%7sB %7sB %7sB %7sB %3llu%%\n", 875181111Sdes s_total, s_used, s_avail, s_root, 876181111Sdes (unsigned long long)(100 * (st.f_blocks - st.f_bfree) / 877181111Sdes st.f_blocks)); 878181111Sdes } else { 879181111Sdes printf(" Size Used Avail " 880181111Sdes "(root) %%Capacity\n"); 881181111Sdes printf("%12llu %12llu %12llu %12llu %3llu%%\n", 882181111Sdes (unsigned long long)(st.f_frsize * st.f_blocks / 1024), 883181111Sdes (unsigned long long)(st.f_frsize * 884181111Sdes (st.f_blocks - st.f_bfree) / 1024), 885181111Sdes (unsigned long long)(st.f_frsize * st.f_bavail / 1024), 886181111Sdes (unsigned long long)(st.f_frsize * st.f_bfree / 1024), 887181111Sdes (unsigned long long)(100 * (st.f_blocks - st.f_bfree) / 888181111Sdes st.f_blocks)); 889181111Sdes } 890181111Sdes return 0; 891181111Sdes} 892181111Sdes 893181111Sdes/* 894181111Sdes * Undo escaping of glob sequences in place. Used to undo extra escaping 895181111Sdes * applied in makeargv() when the string is destined for a function that 896181111Sdes * does not glob it. 897181111Sdes */ 898181111Sdesstatic void 899181111Sdesundo_glob_escape(char *s) 900181111Sdes{ 901181111Sdes size_t i, j; 902181111Sdes 903181111Sdes for (i = j = 0;;) { 904181111Sdes if (s[i] == '\0') { 905181111Sdes s[j] = '\0'; 906181111Sdes return; 907181111Sdes } 908181111Sdes if (s[i] != '\\') { 909181111Sdes s[j++] = s[i++]; 910181111Sdes continue; 911181111Sdes } 912181111Sdes /* s[i] == '\\' */ 913181111Sdes ++i; 914181111Sdes switch (s[i]) { 915181111Sdes case '?': 916181111Sdes case '[': 917181111Sdes case '*': 918181111Sdes case '\\': 919181111Sdes s[j++] = s[i++]; 920181111Sdes break; 921181111Sdes case '\0': 922181111Sdes s[j++] = '\\'; 923181111Sdes s[j] = '\0'; 924181111Sdes return; 925181111Sdes default: 926181111Sdes s[j++] = '\\'; 927181111Sdes s[j++] = s[i++]; 928181111Sdes break; 929181111Sdes } 930181111Sdes } 931181111Sdes} 932181111Sdes 933181111Sdes/* 934181111Sdes * Split a string into an argument vector using sh(1)-style quoting, 935181111Sdes * comment and escaping rules, but with some tweaks to handle glob(3) 936181111Sdes * wildcards. 937181111Sdes * Returns NULL on error or a NULL-terminated array of arguments. 938181111Sdes */ 939181111Sdes#define MAXARGS 128 940181111Sdes#define MAXARGLEN 8192 941181111Sdesstatic char ** 942181111Sdesmakeargv(const char *arg, int *argcp) 943181111Sdes{ 944181111Sdes int argc, quot; 945181111Sdes size_t i, j; 946181111Sdes static char argvs[MAXARGLEN]; 947181111Sdes static char *argv[MAXARGS + 1]; 948181111Sdes enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q; 949181111Sdes 950181111Sdes *argcp = argc = 0; 951181111Sdes if (strlen(arg) > sizeof(argvs) - 1) { 952181111Sdes args_too_longs: 953181111Sdes error("string too long"); 954181111Sdes return NULL; 955181111Sdes } 956181111Sdes state = MA_START; 957181111Sdes i = j = 0; 958181111Sdes for (;;) { 959181111Sdes if (isspace(arg[i])) { 960181111Sdes if (state == MA_UNQUOTED) { 961181111Sdes /* Terminate current argument */ 962181111Sdes argvs[j++] = '\0'; 963181111Sdes argc++; 964181111Sdes state = MA_START; 965181111Sdes } else if (state != MA_START) 966181111Sdes argvs[j++] = arg[i]; 967181111Sdes } else if (arg[i] == '"' || arg[i] == '\'') { 968181111Sdes q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE; 969181111Sdes if (state == MA_START) { 970181111Sdes argv[argc] = argvs + j; 971181111Sdes state = q; 972181111Sdes } else if (state == MA_UNQUOTED) 973181111Sdes state = q; 974181111Sdes else if (state == q) 975181111Sdes state = MA_UNQUOTED; 976181111Sdes else 977181111Sdes argvs[j++] = arg[i]; 978181111Sdes } else if (arg[i] == '\\') { 979181111Sdes if (state == MA_SQUOTE || state == MA_DQUOTE) { 980181111Sdes quot = state == MA_SQUOTE ? '\'' : '"'; 981181111Sdes /* Unescape quote we are in */ 982181111Sdes /* XXX support \n and friends? */ 983181111Sdes if (arg[i + 1] == quot) { 984181111Sdes i++; 985181111Sdes argvs[j++] = arg[i]; 986181111Sdes } else if (arg[i + 1] == '?' || 987181111Sdes arg[i + 1] == '[' || arg[i + 1] == '*') { 988181111Sdes /* 989181111Sdes * Special case for sftp: append 990181111Sdes * double-escaped glob sequence - 991181111Sdes * glob will undo one level of 992181111Sdes * escaping. NB. string can grow here. 993181111Sdes */ 994181111Sdes if (j >= sizeof(argvs) - 5) 995181111Sdes goto args_too_longs; 996181111Sdes argvs[j++] = '\\'; 997181111Sdes argvs[j++] = arg[i++]; 998181111Sdes argvs[j++] = '\\'; 999181111Sdes argvs[j++] = arg[i]; 1000181111Sdes } else { 1001181111Sdes argvs[j++] = arg[i++]; 1002181111Sdes argvs[j++] = arg[i]; 1003181111Sdes } 1004181111Sdes } else { 1005181111Sdes if (state == MA_START) { 1006181111Sdes argv[argc] = argvs + j; 1007181111Sdes state = MA_UNQUOTED; 1008181111Sdes } 1009181111Sdes if (arg[i + 1] == '?' || arg[i + 1] == '[' || 1010181111Sdes arg[i + 1] == '*' || arg[i + 1] == '\\') { 1011181111Sdes /* 1012181111Sdes * Special case for sftp: append 1013181111Sdes * escaped glob sequence - 1014181111Sdes * glob will undo one level of 1015181111Sdes * escaping. 1016181111Sdes */ 1017181111Sdes argvs[j++] = arg[i++]; 1018181111Sdes argvs[j++] = arg[i]; 1019181111Sdes } else { 1020181111Sdes /* Unescape everything */ 1021181111Sdes /* XXX support \n and friends? */ 1022181111Sdes i++; 1023181111Sdes argvs[j++] = arg[i]; 1024181111Sdes } 1025181111Sdes } 1026181111Sdes } else if (arg[i] == '#') { 1027181111Sdes if (state == MA_SQUOTE || state == MA_DQUOTE) 1028181111Sdes argvs[j++] = arg[i]; 1029181111Sdes else 1030181111Sdes goto string_done; 1031181111Sdes } else if (arg[i] == '\0') { 1032181111Sdes if (state == MA_SQUOTE || state == MA_DQUOTE) { 1033181111Sdes error("Unterminated quoted argument"); 1034181111Sdes return NULL; 1035181111Sdes } 1036181111Sdes string_done: 1037181111Sdes if (state == MA_UNQUOTED) { 1038181111Sdes argvs[j++] = '\0'; 1039181111Sdes argc++; 1040181111Sdes } 1041181111Sdes break; 1042181111Sdes } else { 1043181111Sdes if (state == MA_START) { 1044181111Sdes argv[argc] = argvs + j; 1045181111Sdes state = MA_UNQUOTED; 1046181111Sdes } 1047181111Sdes if ((state == MA_SQUOTE || state == MA_DQUOTE) && 1048181111Sdes (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) { 1049181111Sdes /* 1050181111Sdes * Special case for sftp: escape quoted 1051181111Sdes * glob(3) wildcards. NB. string can grow 1052181111Sdes * here. 1053181111Sdes */ 1054181111Sdes if (j >= sizeof(argvs) - 3) 1055181111Sdes goto args_too_longs; 1056181111Sdes argvs[j++] = '\\'; 1057181111Sdes argvs[j++] = arg[i]; 1058181111Sdes } else 1059181111Sdes argvs[j++] = arg[i]; 1060181111Sdes } 1061181111Sdes i++; 1062181111Sdes } 1063181111Sdes *argcp = argc; 1064181111Sdes return argv; 1065181111Sdes} 1066181111Sdes 1067181111Sdesstatic int 1068181111Sdesparse_args(const char **cpp, int *pflag, int *lflag, int *iflag, int *hflag, 1069126274Sdes unsigned long *n_arg, char **path1, char **path2) 1070126274Sdes{ 1071126274Sdes const char *cmd, *cp = *cpp; 1072181111Sdes char *cp2, **argv; 1073126274Sdes int base = 0; 1074126274Sdes long l; 1075181111Sdes int i, cmdnum, optidx, argc; 1076126274Sdes 1077126274Sdes /* Skip leading whitespace */ 1078126274Sdes cp = cp + strspn(cp, WHITESPACE); 1079126274Sdes 1080126274Sdes /* Ignore blank lines and lines which begin with comment '#' char */ 1081126274Sdes if (*cp == '\0' || *cp == '#') 1082126274Sdes return (0); 1083126274Sdes 1084126274Sdes /* Check for leading '-' (disable error processing) */ 1085126274Sdes *iflag = 0; 1086126274Sdes if (*cp == '-') { 1087126274Sdes *iflag = 1; 1088126274Sdes cp++; 1089126274Sdes } 1090126274Sdes 1091181111Sdes if ((argv = makeargv(cp, &argc)) == NULL) 1092181111Sdes return -1; 1093181111Sdes 1094126274Sdes /* Figure out which command we have */ 1095181111Sdes for (i = 0; cmds[i].c != NULL; i++) { 1096181111Sdes if (strcasecmp(cmds[i].c, argv[0]) == 0) 1097126274Sdes break; 1098126274Sdes } 1099126274Sdes cmdnum = cmds[i].n; 1100126274Sdes cmd = cmds[i].c; 1101126274Sdes 1102126274Sdes /* Special case */ 1103126274Sdes if (*cp == '!') { 1104126274Sdes cp++; 1105126274Sdes cmdnum = I_SHELL; 1106126274Sdes } else if (cmdnum == -1) { 1107126274Sdes error("Invalid command."); 1108181111Sdes return -1; 1109126274Sdes } 1110126274Sdes 1111126274Sdes /* Get arguments and parse flags */ 1112181111Sdes *lflag = *pflag = *hflag = *n_arg = 0; 1113126274Sdes *path1 = *path2 = NULL; 1114181111Sdes optidx = 1; 1115126274Sdes switch (cmdnum) { 1116126274Sdes case I_GET: 1117126274Sdes case I_PUT: 1118181111Sdes if ((optidx = parse_getput_flags(cmd, argv, argc, pflag)) == -1) 1119181111Sdes return -1; 1120126274Sdes /* Get first pathname (mandatory) */ 1121181111Sdes if (argc - optidx < 1) { 1122126274Sdes error("You must specify at least one path after a " 1123126274Sdes "%s command.", cmd); 1124181111Sdes return -1; 1125126274Sdes } 1126181111Sdes *path1 = xstrdup(argv[optidx]); 1127181111Sdes /* Get second pathname (optional) */ 1128181111Sdes if (argc - optidx > 1) { 1129181111Sdes *path2 = xstrdup(argv[optidx + 1]); 1130181111Sdes /* Destination is not globbed */ 1131181111Sdes undo_glob_escape(*path2); 1132181111Sdes } 1133126274Sdes break; 1134126274Sdes case I_RENAME: 1135126274Sdes case I_SYMLINK: 1136181111Sdes if (argc - optidx < 2) { 1137126274Sdes error("You must specify two paths after a %s " 1138126274Sdes "command.", cmd); 1139181111Sdes return -1; 1140126274Sdes } 1141181111Sdes *path1 = xstrdup(argv[optidx]); 1142181111Sdes *path2 = xstrdup(argv[optidx + 1]); 1143181111Sdes /* Paths are not globbed */ 1144181111Sdes undo_glob_escape(*path1); 1145181111Sdes undo_glob_escape(*path2); 1146126274Sdes break; 1147126274Sdes case I_RM: 1148126274Sdes case I_MKDIR: 1149126274Sdes case I_RMDIR: 1150126274Sdes case I_CHDIR: 1151126274Sdes case I_LCHDIR: 1152126274Sdes case I_LMKDIR: 1153126274Sdes /* Get pathname (mandatory) */ 1154181111Sdes if (argc - optidx < 1) { 1155126274Sdes error("You must specify a path after a %s command.", 1156126274Sdes cmd); 1157181111Sdes return -1; 1158126274Sdes } 1159181111Sdes *path1 = xstrdup(argv[optidx]); 1160181111Sdes /* Only "rm" globs */ 1161181111Sdes if (cmdnum != I_RM) 1162181111Sdes undo_glob_escape(*path1); 1163126274Sdes break; 1164181111Sdes case I_DF: 1165181111Sdes if ((optidx = parse_df_flags(cmd, argv, argc, hflag, 1166181111Sdes iflag)) == -1) 1167181111Sdes return -1; 1168181111Sdes /* Default to current directory if no path specified */ 1169181111Sdes if (argc - optidx < 1) 1170181111Sdes *path1 = NULL; 1171181111Sdes else { 1172181111Sdes *path1 = xstrdup(argv[optidx]); 1173181111Sdes undo_glob_escape(*path1); 1174181111Sdes } 1175181111Sdes break; 1176126274Sdes case I_LS: 1177181111Sdes if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1) 1178126274Sdes return(-1); 1179126274Sdes /* Path is optional */ 1180181111Sdes if (argc - optidx > 0) 1181181111Sdes *path1 = xstrdup(argv[optidx]); 1182126274Sdes break; 1183126274Sdes case I_LLS: 1184181111Sdes /* Skip ls command and following whitespace */ 1185181111Sdes cp = cp + strlen(cmd) + strspn(cp, WHITESPACE); 1186126274Sdes case I_SHELL: 1187126274Sdes /* Uses the rest of the line */ 1188126274Sdes break; 1189126274Sdes case I_LUMASK: 1190126274Sdes case I_CHMOD: 1191126274Sdes base = 8; 1192126274Sdes case I_CHOWN: 1193126274Sdes case I_CHGRP: 1194126274Sdes /* Get numeric arg (mandatory) */ 1195181111Sdes if (argc - optidx < 1) 1196181111Sdes goto need_num_arg; 1197164146Sdes errno = 0; 1198181111Sdes l = strtol(argv[optidx], &cp2, base); 1199181111Sdes if (cp2 == argv[optidx] || *cp2 != '\0' || 1200181111Sdes ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) || 1201181111Sdes l < 0) { 1202181111Sdes need_num_arg: 1203126274Sdes error("You must supply a numeric argument " 1204126274Sdes "to the %s command.", cmd); 1205181111Sdes return -1; 1206126274Sdes } 1207126274Sdes *n_arg = l; 1208181111Sdes if (cmdnum == I_LUMASK) 1209126274Sdes break; 1210126274Sdes /* Get pathname (mandatory) */ 1211181111Sdes if (argc - optidx < 2) { 1212126274Sdes error("You must specify a path after a %s command.", 1213126274Sdes cmd); 1214181111Sdes return -1; 1215126274Sdes } 1216181111Sdes *path1 = xstrdup(argv[optidx + 1]); 1217126274Sdes break; 1218126274Sdes case I_QUIT: 1219126274Sdes case I_PWD: 1220126274Sdes case I_LPWD: 1221126274Sdes case I_HELP: 1222126274Sdes case I_VERSION: 1223126274Sdes case I_PROGRESS: 1224126274Sdes break; 1225126274Sdes default: 1226126274Sdes fatal("Command not implemented"); 1227126274Sdes } 1228126274Sdes 1229126274Sdes *cpp = cp; 1230126274Sdes return(cmdnum); 1231126274Sdes} 1232126274Sdes 1233126274Sdesstatic int 1234126274Sdesparse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, 1235126274Sdes int err_abort) 1236126274Sdes{ 1237126274Sdes char *path1, *path2, *tmp; 1238192595Sdes int pflag = 0, lflag = 0, iflag = 0, hflag = 0, cmdnum, i; 1239192595Sdes unsigned long n_arg = 0; 1240126274Sdes Attrib a, *aa; 1241126274Sdes char path_buf[MAXPATHLEN]; 1242126274Sdes int err = 0; 1243126274Sdes glob_t g; 1244126274Sdes 1245126274Sdes path1 = path2 = NULL; 1246181111Sdes cmdnum = parse_args(&cmd, &pflag, &lflag, &iflag, &hflag, &n_arg, 1247126274Sdes &path1, &path2); 1248126274Sdes 1249126274Sdes if (iflag != 0) 1250126274Sdes err_abort = 0; 1251126274Sdes 1252126274Sdes memset(&g, 0, sizeof(g)); 1253126274Sdes 1254126274Sdes /* Perform command */ 1255126274Sdes switch (cmdnum) { 1256126274Sdes case 0: 1257126274Sdes /* Blank line */ 1258126274Sdes break; 1259126274Sdes case -1: 1260126274Sdes /* Unrecognized command */ 1261126274Sdes err = -1; 1262126274Sdes break; 1263126274Sdes case I_GET: 1264126274Sdes err = process_get(conn, path1, path2, *pwd, pflag); 1265126274Sdes break; 1266126274Sdes case I_PUT: 1267126274Sdes err = process_put(conn, path1, path2, *pwd, pflag); 1268126274Sdes break; 1269126274Sdes case I_RENAME: 1270126274Sdes path1 = make_absolute(path1, *pwd); 1271126274Sdes path2 = make_absolute(path2, *pwd); 1272126274Sdes err = do_rename(conn, path1, path2); 1273126274Sdes break; 1274126274Sdes case I_SYMLINK: 1275126274Sdes path2 = make_absolute(path2, *pwd); 1276126274Sdes err = do_symlink(conn, path1, path2); 1277126274Sdes break; 1278126274Sdes case I_RM: 1279126274Sdes path1 = make_absolute(path1, *pwd); 1280126274Sdes remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); 1281137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 1282126274Sdes printf("Removing %s\n", g.gl_pathv[i]); 1283126274Sdes err = do_rm(conn, g.gl_pathv[i]); 1284126274Sdes if (err != 0 && err_abort) 1285126274Sdes break; 1286126274Sdes } 1287126274Sdes break; 1288126274Sdes case I_MKDIR: 1289126274Sdes path1 = make_absolute(path1, *pwd); 1290126274Sdes attrib_clear(&a); 1291126274Sdes a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; 1292126274Sdes a.perm = 0777; 1293126274Sdes err = do_mkdir(conn, path1, &a); 1294126274Sdes break; 1295126274Sdes case I_RMDIR: 1296126274Sdes path1 = make_absolute(path1, *pwd); 1297126274Sdes err = do_rmdir(conn, path1); 1298126274Sdes break; 1299126274Sdes case I_CHDIR: 1300126274Sdes path1 = make_absolute(path1, *pwd); 1301126274Sdes if ((tmp = do_realpath(conn, path1)) == NULL) { 1302126274Sdes err = 1; 1303126274Sdes break; 1304126274Sdes } 1305126274Sdes if ((aa = do_stat(conn, tmp, 0)) == NULL) { 1306126274Sdes xfree(tmp); 1307126274Sdes err = 1; 1308126274Sdes break; 1309126274Sdes } 1310126274Sdes if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) { 1311126274Sdes error("Can't change directory: Can't check target"); 1312126274Sdes xfree(tmp); 1313126274Sdes err = 1; 1314126274Sdes break; 1315126274Sdes } 1316126274Sdes if (!S_ISDIR(aa->perm)) { 1317126274Sdes error("Can't change directory: \"%s\" is not " 1318126274Sdes "a directory", tmp); 1319126274Sdes xfree(tmp); 1320126274Sdes err = 1; 1321126274Sdes break; 1322126274Sdes } 1323126274Sdes xfree(*pwd); 1324126274Sdes *pwd = tmp; 1325126274Sdes break; 1326126274Sdes case I_LS: 1327126274Sdes if (!path1) { 1328126274Sdes do_globbed_ls(conn, *pwd, *pwd, lflag); 1329126274Sdes break; 1330126274Sdes } 1331126274Sdes 1332126274Sdes /* Strip pwd off beginning of non-absolute paths */ 1333126274Sdes tmp = NULL; 1334126274Sdes if (*path1 != '/') 1335126274Sdes tmp = *pwd; 1336126274Sdes 1337126274Sdes path1 = make_absolute(path1, *pwd); 1338126274Sdes err = do_globbed_ls(conn, path1, tmp, lflag); 1339126274Sdes break; 1340181111Sdes case I_DF: 1341181111Sdes /* Default to current directory if no path specified */ 1342181111Sdes if (path1 == NULL) 1343181111Sdes path1 = xstrdup(*pwd); 1344181111Sdes path1 = make_absolute(path1, *pwd); 1345181111Sdes err = do_df(conn, path1, hflag, iflag); 1346181111Sdes break; 1347126274Sdes case I_LCHDIR: 1348126274Sdes if (chdir(path1) == -1) { 1349126274Sdes error("Couldn't change local directory to " 1350126274Sdes "\"%s\": %s", path1, strerror(errno)); 1351126274Sdes err = 1; 1352126274Sdes } 1353126274Sdes break; 1354126274Sdes case I_LMKDIR: 1355126274Sdes if (mkdir(path1, 0777) == -1) { 1356126274Sdes error("Couldn't create local directory " 1357126274Sdes "\"%s\": %s", path1, strerror(errno)); 1358126274Sdes err = 1; 1359126274Sdes } 1360126274Sdes break; 1361126274Sdes case I_LLS: 1362126274Sdes local_do_ls(cmd); 1363126274Sdes break; 1364126274Sdes case I_SHELL: 1365126274Sdes local_do_shell(cmd); 1366126274Sdes break; 1367126274Sdes case I_LUMASK: 1368126274Sdes umask(n_arg); 1369126274Sdes printf("Local umask: %03lo\n", n_arg); 1370126274Sdes break; 1371126274Sdes case I_CHMOD: 1372126274Sdes path1 = make_absolute(path1, *pwd); 1373126274Sdes attrib_clear(&a); 1374126274Sdes a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; 1375126274Sdes a.perm = n_arg; 1376126274Sdes remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); 1377137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 1378126274Sdes printf("Changing mode on %s\n", g.gl_pathv[i]); 1379126274Sdes err = do_setstat(conn, g.gl_pathv[i], &a); 1380126274Sdes if (err != 0 && err_abort) 1381126274Sdes break; 1382126274Sdes } 1383126274Sdes break; 1384126274Sdes case I_CHOWN: 1385126274Sdes case I_CHGRP: 1386126274Sdes path1 = make_absolute(path1, *pwd); 1387126274Sdes remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); 1388137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 1389126274Sdes if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) { 1390192595Sdes if (err_abort) { 1391192595Sdes err = -1; 1392126274Sdes break; 1393192595Sdes } else 1394126274Sdes continue; 1395126274Sdes } 1396126274Sdes if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) { 1397126274Sdes error("Can't get current ownership of " 1398126274Sdes "remote file \"%s\"", g.gl_pathv[i]); 1399192595Sdes if (err_abort) { 1400192595Sdes err = -1; 1401126274Sdes break; 1402192595Sdes } else 1403126274Sdes continue; 1404126274Sdes } 1405126274Sdes aa->flags &= SSH2_FILEXFER_ATTR_UIDGID; 1406126274Sdes if (cmdnum == I_CHOWN) { 1407126274Sdes printf("Changing owner on %s\n", g.gl_pathv[i]); 1408126274Sdes aa->uid = n_arg; 1409126274Sdes } else { 1410126274Sdes printf("Changing group on %s\n", g.gl_pathv[i]); 1411126274Sdes aa->gid = n_arg; 1412126274Sdes } 1413126274Sdes err = do_setstat(conn, g.gl_pathv[i], aa); 1414126274Sdes if (err != 0 && err_abort) 1415126274Sdes break; 1416126274Sdes } 1417126274Sdes break; 1418126274Sdes case I_PWD: 1419126274Sdes printf("Remote working directory: %s\n", *pwd); 1420126274Sdes break; 1421126274Sdes case I_LPWD: 1422126274Sdes if (!getcwd(path_buf, sizeof(path_buf))) { 1423126274Sdes error("Couldn't get local cwd: %s", strerror(errno)); 1424126274Sdes err = -1; 1425126274Sdes break; 1426126274Sdes } 1427126274Sdes printf("Local working directory: %s\n", path_buf); 1428126274Sdes break; 1429126274Sdes case I_QUIT: 1430126274Sdes /* Processed below */ 1431126274Sdes break; 1432126274Sdes case I_HELP: 1433126274Sdes help(); 1434126274Sdes break; 1435126274Sdes case I_VERSION: 1436126274Sdes printf("SFTP protocol version %u\n", sftp_proto_version(conn)); 1437126274Sdes break; 1438126274Sdes case I_PROGRESS: 1439126274Sdes showprogress = !showprogress; 1440126274Sdes if (showprogress) 1441126274Sdes printf("Progress meter enabled\n"); 1442126274Sdes else 1443126274Sdes printf("Progress meter disabled\n"); 1444126274Sdes break; 1445126274Sdes default: 1446126274Sdes fatal("%d is not implemented", cmdnum); 1447126274Sdes } 1448126274Sdes 1449126274Sdes if (g.gl_pathc) 1450126274Sdes globfree(&g); 1451126274Sdes if (path1) 1452126274Sdes xfree(path1); 1453126274Sdes if (path2) 1454126274Sdes xfree(path2); 1455126274Sdes 1456126274Sdes /* If an unignored error occurs in batch mode we should abort. */ 1457126274Sdes if (err_abort && err != 0) 1458126274Sdes return (-1); 1459126274Sdes else if (cmdnum == I_QUIT) 1460126274Sdes return (1); 1461126274Sdes 1462126274Sdes return (0); 1463126274Sdes} 1464126274Sdes 1465146998Sdes#ifdef USE_LIBEDIT 1466146998Sdesstatic char * 1467146998Sdesprompt(EditLine *el) 1468146998Sdes{ 1469146998Sdes return ("sftp> "); 1470146998Sdes} 1471146998Sdes#endif 1472146998Sdes 1473126274Sdesint 1474126274Sdesinteractive_loop(int fd_in, int fd_out, char *file1, char *file2) 1475126274Sdes{ 1476126274Sdes char *pwd; 1477126274Sdes char *dir = NULL; 1478126274Sdes char cmd[2048]; 1479126274Sdes struct sftp_conn *conn; 1480149749Sdes int err, interactive; 1481146998Sdes EditLine *el = NULL; 1482146998Sdes#ifdef USE_LIBEDIT 1483146998Sdes History *hl = NULL; 1484146998Sdes HistEvent hev; 1485146998Sdes extern char *__progname; 1486126274Sdes 1487146998Sdes if (!batchmode && isatty(STDIN_FILENO)) { 1488146998Sdes if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL) 1489146998Sdes fatal("Couldn't initialise editline"); 1490146998Sdes if ((hl = history_init()) == NULL) 1491146998Sdes fatal("Couldn't initialise editline history"); 1492146998Sdes history(hl, &hev, H_SETSIZE, 100); 1493146998Sdes el_set(el, EL_HIST, history, hl); 1494146998Sdes 1495146998Sdes el_set(el, EL_PROMPT, prompt); 1496146998Sdes el_set(el, EL_EDITOR, "emacs"); 1497146998Sdes el_set(el, EL_TERMINAL, NULL); 1498146998Sdes el_set(el, EL_SIGNAL, 1); 1499146998Sdes el_source(el, NULL); 1500146998Sdes } 1501146998Sdes#endif /* USE_LIBEDIT */ 1502146998Sdes 1503126274Sdes conn = do_init(fd_in, fd_out, copy_buffer_len, num_requests); 1504126274Sdes if (conn == NULL) 1505126274Sdes fatal("Couldn't initialise connection to server"); 1506126274Sdes 1507126274Sdes pwd = do_realpath(conn, "."); 1508126274Sdes if (pwd == NULL) 1509126274Sdes fatal("Need cwd"); 1510126274Sdes 1511126274Sdes if (file1 != NULL) { 1512126274Sdes dir = xstrdup(file1); 1513126274Sdes dir = make_absolute(dir, pwd); 1514126274Sdes 1515126274Sdes if (remote_is_dir(conn, dir) && file2 == NULL) { 1516126274Sdes printf("Changing to: %s\n", dir); 1517126274Sdes snprintf(cmd, sizeof cmd, "cd \"%s\"", dir); 1518146998Sdes if (parse_dispatch_command(conn, cmd, &pwd, 1) != 0) { 1519146998Sdes xfree(dir); 1520146998Sdes xfree(pwd); 1521162852Sdes xfree(conn); 1522126274Sdes return (-1); 1523146998Sdes } 1524126274Sdes } else { 1525126274Sdes if (file2 == NULL) 1526126274Sdes snprintf(cmd, sizeof cmd, "get %s", dir); 1527126274Sdes else 1528126274Sdes snprintf(cmd, sizeof cmd, "get %s %s", dir, 1529126274Sdes file2); 1530126274Sdes 1531126274Sdes err = parse_dispatch_command(conn, cmd, &pwd, 1); 1532126274Sdes xfree(dir); 1533126274Sdes xfree(pwd); 1534162852Sdes xfree(conn); 1535126274Sdes return (err); 1536126274Sdes } 1537126274Sdes xfree(dir); 1538126274Sdes } 1539126274Sdes 1540149749Sdes#if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF) 1541126274Sdes setvbuf(stdout, NULL, _IOLBF, 0); 1542126274Sdes setvbuf(infile, NULL, _IOLBF, 0); 1543126274Sdes#else 1544149749Sdes setlinebuf(stdout); 1545149749Sdes setlinebuf(infile); 1546126274Sdes#endif 1547126274Sdes 1548149749Sdes interactive = !batchmode && isatty(STDIN_FILENO); 1549126274Sdes err = 0; 1550126274Sdes for (;;) { 1551126274Sdes char *cp; 1552126274Sdes 1553137015Sdes signal(SIGINT, SIG_IGN); 1554137015Sdes 1555146998Sdes if (el == NULL) { 1556149749Sdes if (interactive) 1557149749Sdes printf("sftp> "); 1558146998Sdes if (fgets(cmd, sizeof(cmd), infile) == NULL) { 1559149749Sdes if (interactive) 1560149749Sdes printf("\n"); 1561146998Sdes break; 1562146998Sdes } 1563149749Sdes if (!interactive) { /* Echo command */ 1564149749Sdes printf("sftp> %s", cmd); 1565149749Sdes if (strlen(cmd) > 0 && 1566149749Sdes cmd[strlen(cmd) - 1] != '\n') 1567149749Sdes printf("\n"); 1568149749Sdes } 1569146998Sdes } else { 1570146998Sdes#ifdef USE_LIBEDIT 1571146998Sdes const char *line; 1572146998Sdes int count = 0; 1573126274Sdes 1574149749Sdes if ((line = el_gets(el, &count)) == NULL || count <= 0) { 1575149749Sdes printf("\n"); 1576149749Sdes break; 1577149749Sdes } 1578146998Sdes history(hl, &hev, H_ENTER, line); 1579146998Sdes if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) { 1580146998Sdes fprintf(stderr, "Error: input line too long\n"); 1581146998Sdes continue; 1582146998Sdes } 1583146998Sdes#endif /* USE_LIBEDIT */ 1584126274Sdes } 1585126274Sdes 1586126274Sdes cp = strrchr(cmd, '\n'); 1587126274Sdes if (cp) 1588126274Sdes *cp = '\0'; 1589126274Sdes 1590137015Sdes /* Handle user interrupts gracefully during commands */ 1591137015Sdes interrupted = 0; 1592137015Sdes signal(SIGINT, cmd_interrupt); 1593137015Sdes 1594126274Sdes err = parse_dispatch_command(conn, cmd, &pwd, batchmode); 1595126274Sdes if (err != 0) 1596126274Sdes break; 1597126274Sdes } 1598126274Sdes xfree(pwd); 1599162852Sdes xfree(conn); 1600126274Sdes 1601149749Sdes#ifdef USE_LIBEDIT 1602149749Sdes if (el != NULL) 1603149749Sdes el_end(el); 1604149749Sdes#endif /* USE_LIBEDIT */ 1605149749Sdes 1606126274Sdes /* err == 1 signifies normal "quit" exit */ 1607126274Sdes return (err >= 0 ? 0 : -1); 1608126274Sdes} 1609126274Sdes 1610126274Sdesstatic void 1611124208Sdesconnect_to_server(char *path, char **args, int *in, int *out) 1612124208Sdes{ 161376259Sgreen int c_in, c_out; 161499060Sdes 161576259Sgreen#ifdef USE_PIPES 161676259Sgreen int pin[2], pout[2]; 161799060Sdes 161876259Sgreen if ((pipe(pin) == -1) || (pipe(pout) == -1)) 161976259Sgreen fatal("pipe: %s", strerror(errno)); 162076259Sgreen *in = pin[0]; 162176259Sgreen *out = pout[1]; 162276259Sgreen c_in = pout[0]; 162376259Sgreen c_out = pin[1]; 162476259Sgreen#else /* USE_PIPES */ 162576259Sgreen int inout[2]; 162699060Sdes 162776259Sgreen if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1) 162876259Sgreen fatal("socketpair: %s", strerror(errno)); 162976259Sgreen *in = *out = inout[0]; 163076259Sgreen c_in = c_out = inout[1]; 163176259Sgreen#endif /* USE_PIPES */ 163276259Sgreen 1633124208Sdes if ((sshpid = fork()) == -1) 163476259Sgreen fatal("fork: %s", strerror(errno)); 1635124208Sdes else if (sshpid == 0) { 163676259Sgreen if ((dup2(c_in, STDIN_FILENO) == -1) || 163776259Sgreen (dup2(c_out, STDOUT_FILENO) == -1)) { 163876259Sgreen fprintf(stderr, "dup2: %s\n", strerror(errno)); 1639137015Sdes _exit(1); 164076259Sgreen } 164176259Sgreen close(*in); 164276259Sgreen close(*out); 164376259Sgreen close(c_in); 164476259Sgreen close(c_out); 1645137015Sdes 1646137015Sdes /* 1647137015Sdes * The underlying ssh is in the same process group, so we must 1648137015Sdes * ignore SIGINT if we want to gracefully abort commands, 1649137015Sdes * otherwise the signal will make it to the ssh process and 1650137015Sdes * kill it too 1651137015Sdes */ 1652137015Sdes signal(SIGINT, SIG_IGN); 1653137015Sdes execvp(path, args); 165492555Sdes fprintf(stderr, "exec: %s: %s\n", path, strerror(errno)); 1655137015Sdes _exit(1); 165676259Sgreen } 165776259Sgreen 1658124208Sdes signal(SIGTERM, killchild); 1659124208Sdes signal(SIGINT, killchild); 1660124208Sdes signal(SIGHUP, killchild); 166176259Sgreen close(c_in); 166276259Sgreen close(c_out); 166376259Sgreen} 166476259Sgreen 166592555Sdesstatic void 166676259Sgreenusage(void) 166776259Sgreen{ 166892555Sdes extern char *__progname; 166998675Sdes 167092555Sdes fprintf(stderr, 1671126274Sdes "usage: %s [-1Cv] [-B buffer_size] [-b batchfile] [-F ssh_config]\n" 1672126274Sdes " [-o ssh_option] [-P sftp_server_path] [-R num_requests]\n" 1673126274Sdes " [-S program] [-s subsystem | sftp_server] host\n" 1674192595Sdes " %s [user@]host[:file ...]\n" 1675192595Sdes " %s [user@]host[:dir[/]]\n" 1676126274Sdes " %s -b batchfile [user@]host\n", __progname, __progname, __progname, __progname); 167776259Sgreen exit(1); 167876259Sgreen} 167976259Sgreen 168076259Sgreenint 168176259Sgreenmain(int argc, char **argv) 168276259Sgreen{ 1683113908Sdes int in, out, ch, err; 1684137015Sdes char *host, *userhost, *cp, *file2 = NULL; 168592555Sdes int debug_level = 0, sshver = 2; 168692555Sdes char *file1 = NULL, *sftp_server = NULL; 168792555Sdes char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL; 168892555Sdes LogLevel ll = SYSLOG_LEVEL_INFO; 168992555Sdes arglist args; 169076259Sgreen extern int optind; 169176259Sgreen extern char *optarg; 169276259Sgreen 1693157016Sdes /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ 1694157016Sdes sanitise_stdfd(); 1695157016Sdes 1696124208Sdes __progname = ssh_get_progname(argv[0]); 1697157016Sdes memset(&args, '\0', sizeof(args)); 169892555Sdes args.list = NULL; 1699162852Sdes addargs(&args, "%s", ssh_program); 170092555Sdes addargs(&args, "-oForwardX11 no"); 170192555Sdes addargs(&args, "-oForwardAgent no"); 1702157016Sdes addargs(&args, "-oPermitLocalCommand no"); 170392555Sdes addargs(&args, "-oClearAllForwardings yes"); 1704126274Sdes 170592555Sdes ll = SYSLOG_LEVEL_INFO; 1706126274Sdes infile = stdin; 170776259Sgreen 170892555Sdes while ((ch = getopt(argc, argv, "1hvCo:s:S:b:B:F:P:R:")) != -1) { 170976259Sgreen switch (ch) { 171076259Sgreen case 'C': 171192555Sdes addargs(&args, "-C"); 171276259Sgreen break; 171376259Sgreen case 'v': 171492555Sdes if (debug_level < 3) { 171592555Sdes addargs(&args, "-v"); 171692555Sdes ll = SYSLOG_LEVEL_DEBUG1 + debug_level; 171792555Sdes } 171892555Sdes debug_level++; 171976259Sgreen break; 172092555Sdes case 'F': 172176259Sgreen case 'o': 172292555Sdes addargs(&args, "-%c%s", ch, optarg); 172376259Sgreen break; 172476259Sgreen case '1': 172592555Sdes sshver = 1; 172676259Sgreen if (sftp_server == NULL) 172776259Sgreen sftp_server = _PATH_SFTP_SERVER; 172876259Sgreen break; 172976259Sgreen case 's': 173076259Sgreen sftp_server = optarg; 173176259Sgreen break; 173276259Sgreen case 'S': 173376259Sgreen ssh_program = optarg; 1734157016Sdes replacearg(&args, 0, "%s", ssh_program); 173576259Sgreen break; 173676259Sgreen case 'b': 1737126274Sdes if (batchmode) 1738126274Sdes fatal("Batch file already specified."); 1739126274Sdes 1740126274Sdes /* Allow "-" as stdin */ 1741137015Sdes if (strcmp(optarg, "-") != 0 && 1742149749Sdes (infile = fopen(optarg, "r")) == NULL) 1743126274Sdes fatal("%s (%s).", strerror(errno), optarg); 1744113908Sdes showprogress = 0; 1745126274Sdes batchmode = 1; 1746146998Sdes addargs(&args, "-obatchmode yes"); 174776259Sgreen break; 174892555Sdes case 'P': 174992555Sdes sftp_direct = optarg; 175092555Sdes break; 175192555Sdes case 'B': 175292555Sdes copy_buffer_len = strtol(optarg, &cp, 10); 175392555Sdes if (copy_buffer_len == 0 || *cp != '\0') 175492555Sdes fatal("Invalid buffer size \"%s\"", optarg); 175592555Sdes break; 175692555Sdes case 'R': 175792555Sdes num_requests = strtol(optarg, &cp, 10); 175892555Sdes if (num_requests == 0 || *cp != '\0') 175998675Sdes fatal("Invalid number of requests \"%s\"", 176092555Sdes optarg); 176192555Sdes break; 176276259Sgreen case 'h': 176376259Sgreen default: 176476259Sgreen usage(); 176576259Sgreen } 176676259Sgreen } 176776259Sgreen 1768128456Sdes if (!isatty(STDERR_FILENO)) 1769128456Sdes showprogress = 0; 1770128456Sdes 177198675Sdes log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1); 177298675Sdes 177392555Sdes if (sftp_direct == NULL) { 177492555Sdes if (optind == argc || argc > (optind + 2)) 177592555Sdes usage(); 177676259Sgreen 177792555Sdes userhost = xstrdup(argv[optind]); 177892555Sdes file2 = argv[optind+1]; 177976259Sgreen 1780113908Sdes if ((host = strrchr(userhost, '@')) == NULL) 178192555Sdes host = userhost; 178292555Sdes else { 178392555Sdes *host++ = '\0'; 178492555Sdes if (!userhost[0]) { 178592555Sdes fprintf(stderr, "Missing username\n"); 178692555Sdes usage(); 178792555Sdes } 1788181111Sdes addargs(&args, "-l%s", userhost); 178992555Sdes } 179092555Sdes 1791126274Sdes if ((cp = colon(host)) != NULL) { 1792126274Sdes *cp++ = '\0'; 1793126274Sdes file1 = cp; 1794126274Sdes } 1795126274Sdes 179692555Sdes host = cleanhostname(host); 179792555Sdes if (!*host) { 179892555Sdes fprintf(stderr, "Missing hostname\n"); 179976259Sgreen usage(); 180076259Sgreen } 180176259Sgreen 180292555Sdes addargs(&args, "-oProtocol %d", sshver); 180376259Sgreen 180492555Sdes /* no subsystem if the server-spec contains a '/' */ 180592555Sdes if (sftp_server == NULL || strchr(sftp_server, '/') == NULL) 180692555Sdes addargs(&args, "-s"); 180776259Sgreen 180892555Sdes addargs(&args, "%s", host); 180998675Sdes addargs(&args, "%s", (sftp_server != NULL ? 181092555Sdes sftp_server : "sftp")); 181176259Sgreen 1812126274Sdes if (!batchmode) 1813126274Sdes fprintf(stderr, "Connecting to %s...\n", host); 1814124208Sdes connect_to_server(ssh_program, args.list, &in, &out); 181592555Sdes } else { 181692555Sdes args.list = NULL; 181792555Sdes addargs(&args, "sftp-server"); 181876259Sgreen 1819126274Sdes if (!batchmode) 1820126274Sdes fprintf(stderr, "Attaching to %s...\n", sftp_direct); 1821124208Sdes connect_to_server(sftp_direct, args.list, &in, &out); 182292555Sdes } 1823157016Sdes freeargs(&args); 182476259Sgreen 1825113908Sdes err = interactive_loop(in, out, file1, file2); 182676259Sgreen 182798937Sdes#if !defined(USE_PIPES) 1828149749Sdes shutdown(in, SHUT_RDWR); 1829149749Sdes shutdown(out, SHUT_RDWR); 183098937Sdes#endif 183198937Sdes 183276259Sgreen close(in); 183376259Sgreen close(out); 1834126274Sdes if (batchmode) 183576259Sgreen fclose(infile); 183676259Sgreen 183798675Sdes while (waitpid(sshpid, NULL, 0) == -1) 183898675Sdes if (errno != EINTR) 183998675Sdes fatal("Couldn't wait for ssh process: %s", 184098675Sdes strerror(errno)); 184176259Sgreen 1842113908Sdes exit(err == 0 ? 0 : 1); 184376259Sgreen} 1844