sftp.c revision 162852
1162852Sdes/* $OpenBSD: sftp.c,v 1.92 2006/09/19 05:52:23 otto 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> 2876259Sgreen 29162852Sdes#include <errno.h> 30162852Sdes 31162852Sdes#ifdef HAVE_PATHS_H 32162852Sdes# include <paths.h> 33162852Sdes#endif 34146998Sdes#ifdef USE_LIBEDIT 35146998Sdes#include <histedit.h> 36146998Sdes#else 37146998Sdestypedef void EditLine; 38146998Sdes#endif 39162852Sdes#include <signal.h> 40162852Sdes#include <stdlib.h> 41162852Sdes#include <stdio.h> 42162852Sdes#include <string.h> 43162852Sdes#include <unistd.h> 44162852Sdes#include <stdarg.h> 45146998Sdes 4676259Sgreen#include "xmalloc.h" 4776259Sgreen#include "log.h" 4876259Sgreen#include "pathnames.h" 4992555Sdes#include "misc.h" 5076259Sgreen 5176259Sgreen#include "sftp.h" 52162852Sdes#include "buffer.h" 5376259Sgreen#include "sftp-common.h" 5476259Sgreen#include "sftp-client.h" 5576259Sgreen 56126274Sdes/* File to read commands from */ 57126274SdesFILE* infile; 58126274Sdes 59126274Sdes/* Are we in batchfile mode? */ 60126274Sdesint batchmode = 0; 61126274Sdes 62126274Sdes/* Size of buffer used when copying files */ 63126274Sdessize_t copy_buffer_len = 32768; 64126274Sdes 65126274Sdes/* Number of concurrent outstanding requests */ 66126274Sdessize_t num_requests = 16; 67126274Sdes 68126274Sdes/* PID of ssh transport process */ 69126274Sdesstatic pid_t sshpid = -1; 70126274Sdes 71126274Sdes/* This is set to 0 if the progressmeter is not desired. */ 72128456Sdesint showprogress = 1; 73126274Sdes 74137015Sdes/* SIGINT received during command processing */ 75137015Sdesvolatile sig_atomic_t interrupted = 0; 76137015Sdes 77137015Sdes/* I wish qsort() took a separate ctx for the comparison function...*/ 78137015Sdesint sort_flag; 79137015Sdes 80126274Sdesint remote_glob(struct sftp_conn *, const char *, int, 81126274Sdes int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */ 82126274Sdes 8398937Sdesextern char *__progname; 8498937Sdes 85126274Sdes/* Separators for interactive commands */ 86126274Sdes#define WHITESPACE " \t\r\n" 8776259Sgreen 88137015Sdes/* ls flags */ 89137015Sdes#define LS_LONG_VIEW 0x01 /* Full view ala ls -l */ 90137015Sdes#define LS_SHORT_VIEW 0x02 /* Single row view ala ls -1 */ 91137015Sdes#define LS_NUMERIC_VIEW 0x04 /* Long view with numeric uid/gid */ 92137015Sdes#define LS_NAME_SORT 0x08 /* Sort by name (default) */ 93137015Sdes#define LS_TIME_SORT 0x10 /* Sort by mtime */ 94137015Sdes#define LS_SIZE_SORT 0x20 /* Sort by file size */ 95137015Sdes#define LS_REVERSE_SORT 0x40 /* Reverse sort order */ 96137015Sdes#define LS_SHOW_ALL 0x80 /* Don't skip filenames starting with '.' */ 97113908Sdes 98137015Sdes#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW) 99137015Sdes#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT) 100137015Sdes 101126274Sdes/* Commands for interactive mode */ 102126274Sdes#define I_CHDIR 1 103126274Sdes#define I_CHGRP 2 104126274Sdes#define I_CHMOD 3 105126274Sdes#define I_CHOWN 4 106126274Sdes#define I_GET 5 107126274Sdes#define I_HELP 6 108126274Sdes#define I_LCHDIR 7 109126274Sdes#define I_LLS 8 110126274Sdes#define I_LMKDIR 9 111126274Sdes#define I_LPWD 10 112126274Sdes#define I_LS 11 113126274Sdes#define I_LUMASK 12 114126274Sdes#define I_MKDIR 13 115126274Sdes#define I_PUT 14 116126274Sdes#define I_PWD 15 117126274Sdes#define I_QUIT 16 118126274Sdes#define I_RENAME 17 119126274Sdes#define I_RM 18 120126274Sdes#define I_RMDIR 19 121126274Sdes#define I_SHELL 20 122126274Sdes#define I_SYMLINK 21 123126274Sdes#define I_VERSION 22 124126274Sdes#define I_PROGRESS 23 125126274Sdes 126126274Sdesstruct CMD { 127126274Sdes const char *c; 128126274Sdes const int n; 129126274Sdes}; 130126274Sdes 131126274Sdesstatic const struct CMD cmds[] = { 132126274Sdes { "bye", I_QUIT }, 133126274Sdes { "cd", I_CHDIR }, 134126274Sdes { "chdir", I_CHDIR }, 135126274Sdes { "chgrp", I_CHGRP }, 136126274Sdes { "chmod", I_CHMOD }, 137126274Sdes { "chown", I_CHOWN }, 138126274Sdes { "dir", I_LS }, 139126274Sdes { "exit", I_QUIT }, 140126274Sdes { "get", I_GET }, 141126274Sdes { "mget", I_GET }, 142126274Sdes { "help", I_HELP }, 143126274Sdes { "lcd", I_LCHDIR }, 144126274Sdes { "lchdir", I_LCHDIR }, 145126274Sdes { "lls", I_LLS }, 146126274Sdes { "lmkdir", I_LMKDIR }, 147126274Sdes { "ln", I_SYMLINK }, 148126274Sdes { "lpwd", I_LPWD }, 149126274Sdes { "ls", I_LS }, 150126274Sdes { "lumask", I_LUMASK }, 151126274Sdes { "mkdir", I_MKDIR }, 152126274Sdes { "progress", I_PROGRESS }, 153126274Sdes { "put", I_PUT }, 154126274Sdes { "mput", I_PUT }, 155126274Sdes { "pwd", I_PWD }, 156126274Sdes { "quit", I_QUIT }, 157126274Sdes { "rename", I_RENAME }, 158126274Sdes { "rm", I_RM }, 159126274Sdes { "rmdir", I_RMDIR }, 160126274Sdes { "symlink", I_SYMLINK }, 161126274Sdes { "version", I_VERSION }, 162126274Sdes { "!", I_SHELL }, 163126274Sdes { "?", I_HELP }, 164126274Sdes { NULL, -1} 165126274Sdes}; 166126274Sdes 167126274Sdesint interactive_loop(int fd_in, int fd_out, char *file1, char *file2); 168126274Sdes 16992555Sdesstatic void 170137015Sdeskillchild(int signo) 171137015Sdes{ 172146998Sdes if (sshpid > 1) { 173137015Sdes kill(sshpid, SIGTERM); 174146998Sdes waitpid(sshpid, NULL, 0); 175146998Sdes } 176137015Sdes 177137015Sdes _exit(1); 178137015Sdes} 179137015Sdes 180137015Sdesstatic void 181137015Sdescmd_interrupt(int signo) 182137015Sdes{ 183137015Sdes const char msg[] = "\rInterrupt \n"; 184146998Sdes int olderrno = errno; 185137015Sdes 186137015Sdes write(STDERR_FILENO, msg, sizeof(msg) - 1); 187137015Sdes interrupted = 1; 188146998Sdes errno = olderrno; 189137015Sdes} 190137015Sdes 191137015Sdesstatic void 192126274Sdeshelp(void) 193126274Sdes{ 194126274Sdes printf("Available commands:\n"); 195126274Sdes printf("cd path Change remote directory to 'path'\n"); 196126274Sdes printf("lcd path Change local directory to 'path'\n"); 197126274Sdes printf("chgrp grp path Change group of file 'path' to 'grp'\n"); 198126274Sdes printf("chmod mode path Change permissions of file 'path' to 'mode'\n"); 199126274Sdes printf("chown own path Change owner of file 'path' to 'own'\n"); 200126274Sdes printf("help Display this help text\n"); 201126274Sdes printf("get remote-path [local-path] Download file\n"); 202126274Sdes printf("lls [ls-options [path]] Display local directory listing\n"); 203126274Sdes printf("ln oldpath newpath Symlink remote file\n"); 204126274Sdes printf("lmkdir path Create local directory\n"); 205126274Sdes printf("lpwd Print local working directory\n"); 206126274Sdes printf("ls [path] Display remote directory listing\n"); 207126274Sdes printf("lumask umask Set local umask to 'umask'\n"); 208126274Sdes printf("mkdir path Create remote directory\n"); 209126274Sdes printf("progress Toggle display of progress meter\n"); 210126274Sdes printf("put local-path [remote-path] Upload file\n"); 211126274Sdes printf("pwd Display remote working directory\n"); 212126274Sdes printf("exit Quit sftp\n"); 213126274Sdes printf("quit Quit sftp\n"); 214126274Sdes printf("rename oldpath newpath Rename remote file\n"); 215126274Sdes printf("rmdir path Remove remote directory\n"); 216126274Sdes printf("rm path Delete remote file\n"); 217126274Sdes printf("symlink oldpath newpath Symlink remote file\n"); 218126274Sdes printf("version Show SFTP version\n"); 219126274Sdes printf("!command Execute 'command' in local shell\n"); 220126274Sdes printf("! Escape to local shell\n"); 221126274Sdes printf("? Synonym for help\n"); 222126274Sdes} 223126274Sdes 224126274Sdesstatic void 225126274Sdeslocal_do_shell(const char *args) 226126274Sdes{ 227126274Sdes int status; 228126274Sdes char *shell; 229126274Sdes pid_t pid; 230126274Sdes 231126274Sdes if (!*args) 232126274Sdes args = NULL; 233126274Sdes 234126274Sdes if ((shell = getenv("SHELL")) == NULL) 235126274Sdes shell = _PATH_BSHELL; 236126274Sdes 237126274Sdes if ((pid = fork()) == -1) 238126274Sdes fatal("Couldn't fork: %s", strerror(errno)); 239126274Sdes 240126274Sdes if (pid == 0) { 241126274Sdes /* XXX: child has pipe fds to ssh subproc open - issue? */ 242126274Sdes if (args) { 243126274Sdes debug3("Executing %s -c \"%s\"", shell, args); 244126274Sdes execl(shell, shell, "-c", args, (char *)NULL); 245126274Sdes } else { 246126274Sdes debug3("Executing %s", shell); 247126274Sdes execl(shell, shell, (char *)NULL); 248126274Sdes } 249126274Sdes fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell, 250126274Sdes strerror(errno)); 251126274Sdes _exit(1); 252126274Sdes } 253126274Sdes while (waitpid(pid, &status, 0) == -1) 254126274Sdes if (errno != EINTR) 255126274Sdes fatal("Couldn't wait for child: %s", strerror(errno)); 256126274Sdes if (!WIFEXITED(status)) 257162852Sdes error("Shell exited abnormally"); 258126274Sdes else if (WEXITSTATUS(status)) 259126274Sdes error("Shell exited with status %d", WEXITSTATUS(status)); 260126274Sdes} 261126274Sdes 262126274Sdesstatic void 263126274Sdeslocal_do_ls(const char *args) 264126274Sdes{ 265126274Sdes if (!args || !*args) 266126274Sdes local_do_shell(_PATH_LS); 267126274Sdes else { 268126274Sdes int len = strlen(_PATH_LS " ") + strlen(args) + 1; 269126274Sdes char *buf = xmalloc(len); 270126274Sdes 271126274Sdes /* XXX: quoting - rip quoting code from ftp? */ 272126274Sdes snprintf(buf, len, _PATH_LS " %s", args); 273126274Sdes local_do_shell(buf); 274126274Sdes xfree(buf); 275126274Sdes } 276126274Sdes} 277126274Sdes 278126274Sdes/* Strip one path (usually the pwd) from the start of another */ 279126274Sdesstatic char * 280126274Sdespath_strip(char *path, char *strip) 281126274Sdes{ 282126274Sdes size_t len; 283126274Sdes 284126274Sdes if (strip == NULL) 285126274Sdes return (xstrdup(path)); 286126274Sdes 287126274Sdes len = strlen(strip); 288146998Sdes if (strncmp(path, strip, len) == 0) { 289126274Sdes if (strip[len - 1] != '/' && path[len] == '/') 290126274Sdes len++; 291126274Sdes return (xstrdup(path + len)); 292126274Sdes } 293126274Sdes 294126274Sdes return (xstrdup(path)); 295126274Sdes} 296126274Sdes 297126274Sdesstatic char * 298126274Sdespath_append(char *p1, char *p2) 299126274Sdes{ 300126274Sdes char *ret; 301126274Sdes int len = strlen(p1) + strlen(p2) + 2; 302126274Sdes 303126274Sdes ret = xmalloc(len); 304126274Sdes strlcpy(ret, p1, len); 305126274Sdes if (p1[strlen(p1) - 1] != '/') 306126274Sdes strlcat(ret, "/", len); 307126274Sdes strlcat(ret, p2, len); 308126274Sdes 309126274Sdes return(ret); 310126274Sdes} 311126274Sdes 312126274Sdesstatic char * 313126274Sdesmake_absolute(char *p, char *pwd) 314126274Sdes{ 315137015Sdes char *abs_str; 316126274Sdes 317126274Sdes /* Derelativise */ 318126274Sdes if (p && p[0] != '/') { 319137015Sdes abs_str = path_append(pwd, p); 320126274Sdes xfree(p); 321137015Sdes return(abs_str); 322126274Sdes } else 323126274Sdes return(p); 324126274Sdes} 325126274Sdes 326126274Sdesstatic int 327126274Sdesinfer_path(const char *p, char **ifp) 328126274Sdes{ 329126274Sdes char *cp; 330126274Sdes 331126274Sdes cp = strrchr(p, '/'); 332126274Sdes if (cp == NULL) { 333126274Sdes *ifp = xstrdup(p); 334126274Sdes return(0); 335126274Sdes } 336126274Sdes 337126274Sdes if (!cp[1]) { 338126274Sdes error("Invalid path"); 339126274Sdes return(-1); 340126274Sdes } 341126274Sdes 342126274Sdes *ifp = xstrdup(cp + 1); 343126274Sdes return(0); 344126274Sdes} 345126274Sdes 346126274Sdesstatic int 347126274Sdesparse_getput_flags(const char **cpp, int *pflag) 348126274Sdes{ 349126274Sdes const char *cp = *cpp; 350126274Sdes 351126274Sdes /* Check for flags */ 352126274Sdes if (cp[0] == '-' && cp[1] && strchr(WHITESPACE, cp[2])) { 353126274Sdes switch (cp[1]) { 354126274Sdes case 'p': 355126274Sdes case 'P': 356126274Sdes *pflag = 1; 357126274Sdes break; 358126274Sdes default: 359126274Sdes error("Invalid flag -%c", cp[1]); 360126274Sdes return(-1); 361126274Sdes } 362126274Sdes cp += 2; 363126274Sdes *cpp = cp + strspn(cp, WHITESPACE); 364126274Sdes } 365126274Sdes 366126274Sdes return(0); 367126274Sdes} 368126274Sdes 369126274Sdesstatic int 370126274Sdesparse_ls_flags(const char **cpp, int *lflag) 371126274Sdes{ 372126274Sdes const char *cp = *cpp; 373126274Sdes 374137015Sdes /* Defaults */ 375137015Sdes *lflag = LS_NAME_SORT; 376137015Sdes 377126274Sdes /* Check for flags */ 378126274Sdes if (cp++[0] == '-') { 379147001Sdes for (; strchr(WHITESPACE, *cp) == NULL; cp++) { 380126274Sdes switch (*cp) { 381126274Sdes case 'l': 382137015Sdes *lflag &= ~VIEW_FLAGS; 383137015Sdes *lflag |= LS_LONG_VIEW; 384126274Sdes break; 385126274Sdes case '1': 386137015Sdes *lflag &= ~VIEW_FLAGS; 387137015Sdes *lflag |= LS_SHORT_VIEW; 388126274Sdes break; 389137015Sdes case 'n': 390137015Sdes *lflag &= ~VIEW_FLAGS; 391137015Sdes *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW; 392137015Sdes break; 393137015Sdes case 'S': 394137015Sdes *lflag &= ~SORT_FLAGS; 395137015Sdes *lflag |= LS_SIZE_SORT; 396137015Sdes break; 397137015Sdes case 't': 398137015Sdes *lflag &= ~SORT_FLAGS; 399137015Sdes *lflag |= LS_TIME_SORT; 400137015Sdes break; 401137015Sdes case 'r': 402137015Sdes *lflag |= LS_REVERSE_SORT; 403137015Sdes break; 404137015Sdes case 'f': 405137015Sdes *lflag &= ~SORT_FLAGS; 406137015Sdes break; 407137015Sdes case 'a': 408137015Sdes *lflag |= LS_SHOW_ALL; 409137015Sdes break; 410126274Sdes default: 411126274Sdes error("Invalid flag -%c", *cp); 412126274Sdes return(-1); 413126274Sdes } 414126274Sdes } 415126274Sdes *cpp = cp + strspn(cp, WHITESPACE); 416126274Sdes } 417126274Sdes 418126274Sdes return(0); 419126274Sdes} 420126274Sdes 421126274Sdesstatic int 422126274Sdesget_pathname(const char **cpp, char **path) 423126274Sdes{ 424126274Sdes const char *cp = *cpp, *end; 425126274Sdes char quot; 426149749Sdes u_int i, j; 427126274Sdes 428126274Sdes cp += strspn(cp, WHITESPACE); 429126274Sdes if (!*cp) { 430126274Sdes *cpp = cp; 431126274Sdes *path = NULL; 432126274Sdes return (0); 433126274Sdes } 434126274Sdes 435126274Sdes *path = xmalloc(strlen(cp) + 1); 436126274Sdes 437126274Sdes /* Check for quoted filenames */ 438126274Sdes if (*cp == '\"' || *cp == '\'') { 439126274Sdes quot = *cp++; 440126274Sdes 441126274Sdes /* Search for terminating quote, unescape some chars */ 442126274Sdes for (i = j = 0; i <= strlen(cp); i++) { 443126274Sdes if (cp[i] == quot) { /* Found quote */ 444126274Sdes i++; 445126274Sdes (*path)[j] = '\0'; 446126274Sdes break; 447126274Sdes } 448126274Sdes if (cp[i] == '\0') { /* End of string */ 449126274Sdes error("Unterminated quote"); 450126274Sdes goto fail; 451126274Sdes } 452126274Sdes if (cp[i] == '\\') { /* Escaped characters */ 453126274Sdes i++; 454126274Sdes if (cp[i] != '\'' && cp[i] != '\"' && 455126274Sdes cp[i] != '\\') { 456137015Sdes error("Bad escaped character '\\%c'", 457126274Sdes cp[i]); 458126274Sdes goto fail; 459126274Sdes } 460126274Sdes } 461126274Sdes (*path)[j++] = cp[i]; 462126274Sdes } 463126274Sdes 464126274Sdes if (j == 0) { 465126274Sdes error("Empty quotes"); 466126274Sdes goto fail; 467126274Sdes } 468126274Sdes *cpp = cp + i + strspn(cp + i, WHITESPACE); 469126274Sdes } else { 470126274Sdes /* Read to end of filename */ 471126274Sdes end = strpbrk(cp, WHITESPACE); 472126274Sdes if (end == NULL) 473126274Sdes end = strchr(cp, '\0'); 474126274Sdes *cpp = end + strspn(end, WHITESPACE); 475126274Sdes 476126274Sdes memcpy(*path, cp, end - cp); 477126274Sdes (*path)[end - cp] = '\0'; 478126274Sdes } 479126274Sdes return (0); 480126274Sdes 481126274Sdes fail: 482126274Sdes xfree(*path); 483126274Sdes *path = NULL; 484126274Sdes return (-1); 485126274Sdes} 486126274Sdes 487126274Sdesstatic int 488126274Sdesis_dir(char *path) 489126274Sdes{ 490126274Sdes struct stat sb; 491126274Sdes 492126274Sdes /* XXX: report errors? */ 493126274Sdes if (stat(path, &sb) == -1) 494126274Sdes return(0); 495126274Sdes 496162852Sdes return(S_ISDIR(sb.st_mode)); 497126274Sdes} 498126274Sdes 499126274Sdesstatic int 500126274Sdesis_reg(char *path) 501126274Sdes{ 502126274Sdes struct stat sb; 503126274Sdes 504126274Sdes if (stat(path, &sb) == -1) 505126274Sdes fatal("stat %s: %s", path, strerror(errno)); 506126274Sdes 507126274Sdes return(S_ISREG(sb.st_mode)); 508126274Sdes} 509126274Sdes 510126274Sdesstatic int 511126274Sdesremote_is_dir(struct sftp_conn *conn, char *path) 512126274Sdes{ 513126274Sdes Attrib *a; 514126274Sdes 515126274Sdes /* XXX: report errors? */ 516126274Sdes if ((a = do_stat(conn, path, 1)) == NULL) 517126274Sdes return(0); 518126274Sdes if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) 519126274Sdes return(0); 520162852Sdes return(S_ISDIR(a->perm)); 521126274Sdes} 522126274Sdes 523126274Sdesstatic int 524126274Sdesprocess_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag) 525126274Sdes{ 526126274Sdes char *abs_src = NULL; 527126274Sdes char *abs_dst = NULL; 528126274Sdes char *tmp; 529126274Sdes glob_t g; 530126274Sdes int err = 0; 531126274Sdes int i; 532126274Sdes 533126274Sdes abs_src = xstrdup(src); 534126274Sdes abs_src = make_absolute(abs_src, pwd); 535126274Sdes 536126274Sdes memset(&g, 0, sizeof(g)); 537126274Sdes debug3("Looking up %s", abs_src); 538126274Sdes if (remote_glob(conn, abs_src, 0, NULL, &g)) { 539126274Sdes error("File \"%s\" not found.", abs_src); 540126274Sdes err = -1; 541126274Sdes goto out; 542126274Sdes } 543126274Sdes 544126274Sdes /* If multiple matches, dst must be a directory or unspecified */ 545126274Sdes if (g.gl_matchc > 1 && dst && !is_dir(dst)) { 546126274Sdes error("Multiple files match, but \"%s\" is not a directory", 547126274Sdes dst); 548126274Sdes err = -1; 549126274Sdes goto out; 550126274Sdes } 551126274Sdes 552137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 553126274Sdes if (infer_path(g.gl_pathv[i], &tmp)) { 554126274Sdes err = -1; 555126274Sdes goto out; 556126274Sdes } 557126274Sdes 558126274Sdes if (g.gl_matchc == 1 && dst) { 559126274Sdes /* If directory specified, append filename */ 560162852Sdes xfree(tmp); 561126274Sdes if (is_dir(dst)) { 562126274Sdes if (infer_path(g.gl_pathv[0], &tmp)) { 563126274Sdes err = 1; 564126274Sdes goto out; 565126274Sdes } 566126274Sdes abs_dst = path_append(dst, tmp); 567126274Sdes xfree(tmp); 568126274Sdes } else 569126274Sdes abs_dst = xstrdup(dst); 570126274Sdes } else if (dst) { 571126274Sdes abs_dst = path_append(dst, tmp); 572126274Sdes xfree(tmp); 573126274Sdes } else 574126274Sdes abs_dst = tmp; 575126274Sdes 576126274Sdes printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst); 577126274Sdes if (do_download(conn, g.gl_pathv[i], abs_dst, pflag) == -1) 578126274Sdes err = -1; 579126274Sdes xfree(abs_dst); 580126274Sdes abs_dst = NULL; 581126274Sdes } 582126274Sdes 583126274Sdesout: 584126274Sdes xfree(abs_src); 585126274Sdes globfree(&g); 586126274Sdes return(err); 587126274Sdes} 588126274Sdes 589126274Sdesstatic int 590126274Sdesprocess_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag) 591126274Sdes{ 592126274Sdes char *tmp_dst = NULL; 593126274Sdes char *abs_dst = NULL; 594126274Sdes char *tmp; 595126274Sdes glob_t g; 596126274Sdes int err = 0; 597126274Sdes int i; 598126274Sdes 599126274Sdes if (dst) { 600126274Sdes tmp_dst = xstrdup(dst); 601126274Sdes tmp_dst = make_absolute(tmp_dst, pwd); 602126274Sdes } 603126274Sdes 604126274Sdes memset(&g, 0, sizeof(g)); 605126274Sdes debug3("Looking up %s", src); 606126274Sdes if (glob(src, 0, NULL, &g)) { 607126274Sdes error("File \"%s\" not found.", src); 608126274Sdes err = -1; 609126274Sdes goto out; 610126274Sdes } 611126274Sdes 612126274Sdes /* If multiple matches, dst may be directory or unspecified */ 613126274Sdes if (g.gl_matchc > 1 && tmp_dst && !remote_is_dir(conn, tmp_dst)) { 614126274Sdes error("Multiple files match, but \"%s\" is not a directory", 615126274Sdes tmp_dst); 616126274Sdes err = -1; 617126274Sdes goto out; 618126274Sdes } 619126274Sdes 620137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 621126274Sdes if (!is_reg(g.gl_pathv[i])) { 622126274Sdes error("skipping non-regular file %s", 623126274Sdes g.gl_pathv[i]); 624126274Sdes continue; 625126274Sdes } 626126274Sdes if (infer_path(g.gl_pathv[i], &tmp)) { 627126274Sdes err = -1; 628126274Sdes goto out; 629126274Sdes } 630126274Sdes 631126274Sdes if (g.gl_matchc == 1 && tmp_dst) { 632126274Sdes /* If directory specified, append filename */ 633126274Sdes if (remote_is_dir(conn, tmp_dst)) { 634126274Sdes if (infer_path(g.gl_pathv[0], &tmp)) { 635126274Sdes err = 1; 636126274Sdes goto out; 637126274Sdes } 638126274Sdes abs_dst = path_append(tmp_dst, tmp); 639126274Sdes xfree(tmp); 640126274Sdes } else 641126274Sdes abs_dst = xstrdup(tmp_dst); 642126274Sdes 643126274Sdes } else if (tmp_dst) { 644126274Sdes abs_dst = path_append(tmp_dst, tmp); 645126274Sdes xfree(tmp); 646126274Sdes } else 647126274Sdes abs_dst = make_absolute(tmp, pwd); 648126274Sdes 649126274Sdes printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst); 650126274Sdes if (do_upload(conn, g.gl_pathv[i], abs_dst, pflag) == -1) 651126274Sdes err = -1; 652126274Sdes } 653126274Sdes 654126274Sdesout: 655126274Sdes if (abs_dst) 656126274Sdes xfree(abs_dst); 657126274Sdes if (tmp_dst) 658126274Sdes xfree(tmp_dst); 659126274Sdes globfree(&g); 660126274Sdes return(err); 661126274Sdes} 662126274Sdes 663126274Sdesstatic int 664126274Sdessdirent_comp(const void *aa, const void *bb) 665126274Sdes{ 666126274Sdes SFTP_DIRENT *a = *(SFTP_DIRENT **)aa; 667126274Sdes SFTP_DIRENT *b = *(SFTP_DIRENT **)bb; 668137015Sdes int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1; 669126274Sdes 670137015Sdes#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1)) 671137015Sdes if (sort_flag & LS_NAME_SORT) 672137015Sdes return (rmul * strcmp(a->filename, b->filename)); 673137015Sdes else if (sort_flag & LS_TIME_SORT) 674137015Sdes return (rmul * NCMP(a->a.mtime, b->a.mtime)); 675137015Sdes else if (sort_flag & LS_SIZE_SORT) 676137015Sdes return (rmul * NCMP(a->a.size, b->a.size)); 677137015Sdes 678137015Sdes fatal("Unknown ls sort type"); 679126274Sdes} 680126274Sdes 681126274Sdes/* sftp ls.1 replacement for directories */ 682126274Sdesstatic int 683126274Sdesdo_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag) 684126274Sdes{ 685149749Sdes int n; 686149749Sdes u_int c = 1, colspace = 0, columns = 1; 687126274Sdes SFTP_DIRENT **d; 688126274Sdes 689126274Sdes if ((n = do_readdir(conn, path, &d)) != 0) 690126274Sdes return (n); 691126274Sdes 692137015Sdes if (!(lflag & LS_SHORT_VIEW)) { 693149749Sdes u_int m = 0, width = 80; 694126274Sdes struct winsize ws; 695126274Sdes char *tmp; 696126274Sdes 697126274Sdes /* Count entries for sort and find longest filename */ 698137015Sdes for (n = 0; d[n] != NULL; n++) { 699137015Sdes if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL)) 700137015Sdes m = MAX(m, strlen(d[n]->filename)); 701137015Sdes } 702126274Sdes 703126274Sdes /* Add any subpath that also needs to be counted */ 704126274Sdes tmp = path_strip(path, strip_path); 705126274Sdes m += strlen(tmp); 706126274Sdes xfree(tmp); 707126274Sdes 708126274Sdes if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1) 709126274Sdes width = ws.ws_col; 710126274Sdes 711126274Sdes columns = width / (m + 2); 712126274Sdes columns = MAX(columns, 1); 713126274Sdes colspace = width / columns; 714126274Sdes colspace = MIN(colspace, width); 715126274Sdes } 716126274Sdes 717137015Sdes if (lflag & SORT_FLAGS) { 718157016Sdes for (n = 0; d[n] != NULL; n++) 719157016Sdes ; /* count entries */ 720137015Sdes sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT); 721137015Sdes qsort(d, n, sizeof(*d), sdirent_comp); 722137015Sdes } 723126274Sdes 724137015Sdes for (n = 0; d[n] != NULL && !interrupted; n++) { 725126274Sdes char *tmp, *fname; 726126274Sdes 727137015Sdes if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL)) 728137015Sdes continue; 729137015Sdes 730126274Sdes tmp = path_append(path, d[n]->filename); 731126274Sdes fname = path_strip(tmp, strip_path); 732126274Sdes xfree(tmp); 733126274Sdes 734137015Sdes if (lflag & LS_LONG_VIEW) { 735137015Sdes if (lflag & LS_NUMERIC_VIEW) { 736137015Sdes char *lname; 737137015Sdes struct stat sb; 738126274Sdes 739137015Sdes memset(&sb, 0, sizeof(sb)); 740137015Sdes attrib_to_stat(&d[n]->a, &sb); 741137015Sdes lname = ls_file(fname, &sb, 1); 742137015Sdes printf("%s\n", lname); 743137015Sdes xfree(lname); 744137015Sdes } else 745137015Sdes printf("%s\n", d[n]->longname); 746126274Sdes } else { 747126274Sdes printf("%-*s", colspace, fname); 748126274Sdes if (c >= columns) { 749126274Sdes printf("\n"); 750126274Sdes c = 1; 751126274Sdes } else 752126274Sdes c++; 753126274Sdes } 754126274Sdes 755126274Sdes xfree(fname); 756126274Sdes } 757126274Sdes 758137015Sdes if (!(lflag & LS_LONG_VIEW) && (c != 1)) 759126274Sdes printf("\n"); 760126274Sdes 761126274Sdes free_sftp_dirents(d); 762126274Sdes return (0); 763126274Sdes} 764126274Sdes 765126274Sdes/* sftp ls.1 replacement which handles path globs */ 766126274Sdesstatic int 767126274Sdesdo_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path, 768126274Sdes int lflag) 769126274Sdes{ 770126274Sdes glob_t g; 771149749Sdes u_int i, c = 1, colspace = 0, columns = 1; 772146998Sdes Attrib *a = NULL; 773126274Sdes 774126274Sdes memset(&g, 0, sizeof(g)); 775126274Sdes 776126274Sdes if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE, 777146998Sdes NULL, &g) || (g.gl_pathc && !g.gl_matchc)) { 778146998Sdes if (g.gl_pathc) 779146998Sdes globfree(&g); 780126274Sdes error("Can't ls: \"%s\" not found", path); 781126274Sdes return (-1); 782126274Sdes } 783126274Sdes 784137015Sdes if (interrupted) 785137015Sdes goto out; 786137015Sdes 787126274Sdes /* 788146998Sdes * If the glob returns a single match and it is a directory, 789146998Sdes * then just list its contents. 790126274Sdes */ 791146998Sdes if (g.gl_matchc == 1) { 792146998Sdes if ((a = do_lstat(conn, g.gl_pathv[0], 1)) == NULL) { 793126274Sdes globfree(&g); 794126274Sdes return (-1); 795126274Sdes } 796126274Sdes if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) && 797126274Sdes S_ISDIR(a->perm)) { 798146998Sdes int err; 799146998Sdes 800146998Sdes err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag); 801126274Sdes globfree(&g); 802146998Sdes return (err); 803126274Sdes } 804126274Sdes } 805126274Sdes 806137015Sdes if (!(lflag & LS_SHORT_VIEW)) { 807149749Sdes u_int m = 0, width = 80; 808126274Sdes struct winsize ws; 809126274Sdes 810126274Sdes /* Count entries for sort and find longest filename */ 811126274Sdes for (i = 0; g.gl_pathv[i]; i++) 812126274Sdes m = MAX(m, strlen(g.gl_pathv[i])); 813126274Sdes 814126274Sdes if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1) 815126274Sdes width = ws.ws_col; 816126274Sdes 817126274Sdes columns = width / (m + 2); 818126274Sdes columns = MAX(columns, 1); 819126274Sdes colspace = width / columns; 820126274Sdes } 821126274Sdes 822146998Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++, a = NULL) { 823126274Sdes char *fname; 824126274Sdes 825126274Sdes fname = path_strip(g.gl_pathv[i], strip_path); 826126274Sdes 827137015Sdes if (lflag & LS_LONG_VIEW) { 828126274Sdes char *lname; 829126274Sdes struct stat sb; 830126274Sdes 831126274Sdes /* 832126274Sdes * XXX: this is slow - 1 roundtrip per path 833126274Sdes * A solution to this is to fork glob() and 834126274Sdes * build a sftp specific version which keeps the 835126274Sdes * attribs (which currently get thrown away) 836126274Sdes * that the server returns as well as the filenames. 837126274Sdes */ 838126274Sdes memset(&sb, 0, sizeof(sb)); 839146998Sdes if (a == NULL) 840146998Sdes a = do_lstat(conn, g.gl_pathv[i], 1); 841126274Sdes if (a != NULL) 842126274Sdes attrib_to_stat(a, &sb); 843126274Sdes lname = ls_file(fname, &sb, 1); 844126274Sdes printf("%s\n", lname); 845126274Sdes xfree(lname); 846126274Sdes } else { 847126274Sdes printf("%-*s", colspace, fname); 848126274Sdes if (c >= columns) { 849126274Sdes printf("\n"); 850126274Sdes c = 1; 851126274Sdes } else 852126274Sdes c++; 853126274Sdes } 854126274Sdes xfree(fname); 855126274Sdes } 856126274Sdes 857137015Sdes if (!(lflag & LS_LONG_VIEW) && (c != 1)) 858126274Sdes printf("\n"); 859126274Sdes 860137015Sdes out: 861126274Sdes if (g.gl_pathc) 862126274Sdes globfree(&g); 863126274Sdes 864126274Sdes return (0); 865126274Sdes} 866126274Sdes 867126274Sdesstatic int 868126274Sdesparse_args(const char **cpp, int *pflag, int *lflag, int *iflag, 869126274Sdes unsigned long *n_arg, char **path1, char **path2) 870126274Sdes{ 871126274Sdes const char *cmd, *cp = *cpp; 872126274Sdes char *cp2; 873126274Sdes int base = 0; 874126274Sdes long l; 875126274Sdes int i, cmdnum; 876126274Sdes 877126274Sdes /* Skip leading whitespace */ 878126274Sdes cp = cp + strspn(cp, WHITESPACE); 879126274Sdes 880126274Sdes /* Ignore blank lines and lines which begin with comment '#' char */ 881126274Sdes if (*cp == '\0' || *cp == '#') 882126274Sdes return (0); 883126274Sdes 884126274Sdes /* Check for leading '-' (disable error processing) */ 885126274Sdes *iflag = 0; 886126274Sdes if (*cp == '-') { 887126274Sdes *iflag = 1; 888126274Sdes cp++; 889126274Sdes } 890126274Sdes 891126274Sdes /* Figure out which command we have */ 892126274Sdes for (i = 0; cmds[i].c; i++) { 893126274Sdes int cmdlen = strlen(cmds[i].c); 894126274Sdes 895126274Sdes /* Check for command followed by whitespace */ 896126274Sdes if (!strncasecmp(cp, cmds[i].c, cmdlen) && 897126274Sdes strchr(WHITESPACE, cp[cmdlen])) { 898126274Sdes cp += cmdlen; 899126274Sdes cp = cp + strspn(cp, WHITESPACE); 900126274Sdes break; 901126274Sdes } 902126274Sdes } 903126274Sdes cmdnum = cmds[i].n; 904126274Sdes cmd = cmds[i].c; 905126274Sdes 906126274Sdes /* Special case */ 907126274Sdes if (*cp == '!') { 908126274Sdes cp++; 909126274Sdes cmdnum = I_SHELL; 910126274Sdes } else if (cmdnum == -1) { 911126274Sdes error("Invalid command."); 912126274Sdes return (-1); 913126274Sdes } 914126274Sdes 915126274Sdes /* Get arguments and parse flags */ 916126274Sdes *lflag = *pflag = *n_arg = 0; 917126274Sdes *path1 = *path2 = NULL; 918126274Sdes switch (cmdnum) { 919126274Sdes case I_GET: 920126274Sdes case I_PUT: 921126274Sdes if (parse_getput_flags(&cp, pflag)) 922126274Sdes return(-1); 923126274Sdes /* Get first pathname (mandatory) */ 924126274Sdes if (get_pathname(&cp, path1)) 925126274Sdes return(-1); 926126274Sdes if (*path1 == NULL) { 927126274Sdes error("You must specify at least one path after a " 928126274Sdes "%s command.", cmd); 929126274Sdes return(-1); 930126274Sdes } 931126274Sdes /* Try to get second pathname (optional) */ 932126274Sdes if (get_pathname(&cp, path2)) 933126274Sdes return(-1); 934126274Sdes break; 935126274Sdes case I_RENAME: 936126274Sdes case I_SYMLINK: 937126274Sdes if (get_pathname(&cp, path1)) 938126274Sdes return(-1); 939126274Sdes if (get_pathname(&cp, path2)) 940126274Sdes return(-1); 941126274Sdes if (!*path1 || !*path2) { 942126274Sdes error("You must specify two paths after a %s " 943126274Sdes "command.", cmd); 944126274Sdes return(-1); 945126274Sdes } 946126274Sdes break; 947126274Sdes case I_RM: 948126274Sdes case I_MKDIR: 949126274Sdes case I_RMDIR: 950126274Sdes case I_CHDIR: 951126274Sdes case I_LCHDIR: 952126274Sdes case I_LMKDIR: 953126274Sdes /* Get pathname (mandatory) */ 954126274Sdes if (get_pathname(&cp, path1)) 955126274Sdes return(-1); 956126274Sdes if (*path1 == NULL) { 957126274Sdes error("You must specify a path after a %s command.", 958126274Sdes cmd); 959126274Sdes return(-1); 960126274Sdes } 961126274Sdes break; 962126274Sdes case I_LS: 963126274Sdes if (parse_ls_flags(&cp, lflag)) 964126274Sdes return(-1); 965126274Sdes /* Path is optional */ 966126274Sdes if (get_pathname(&cp, path1)) 967126274Sdes return(-1); 968126274Sdes break; 969126274Sdes case I_LLS: 970126274Sdes case I_SHELL: 971126274Sdes /* Uses the rest of the line */ 972126274Sdes break; 973126274Sdes case I_LUMASK: 974126274Sdes base = 8; 975126274Sdes case I_CHMOD: 976126274Sdes base = 8; 977126274Sdes case I_CHOWN: 978126274Sdes case I_CHGRP: 979126274Sdes /* Get numeric arg (mandatory) */ 980126274Sdes l = strtol(cp, &cp2, base); 981126274Sdes if (cp2 == cp || ((l == LONG_MIN || l == LONG_MAX) && 982126274Sdes errno == ERANGE) || l < 0) { 983126274Sdes error("You must supply a numeric argument " 984126274Sdes "to the %s command.", cmd); 985126274Sdes return(-1); 986126274Sdes } 987126274Sdes cp = cp2; 988126274Sdes *n_arg = l; 989126274Sdes if (cmdnum == I_LUMASK && strchr(WHITESPACE, *cp)) 990126274Sdes break; 991126274Sdes if (cmdnum == I_LUMASK || !strchr(WHITESPACE, *cp)) { 992126274Sdes error("You must supply a numeric argument " 993126274Sdes "to the %s command.", cmd); 994126274Sdes return(-1); 995126274Sdes } 996126274Sdes cp += strspn(cp, WHITESPACE); 997126274Sdes 998126274Sdes /* Get pathname (mandatory) */ 999126274Sdes if (get_pathname(&cp, path1)) 1000126274Sdes return(-1); 1001126274Sdes if (*path1 == NULL) { 1002126274Sdes error("You must specify a path after a %s command.", 1003126274Sdes cmd); 1004126274Sdes return(-1); 1005126274Sdes } 1006126274Sdes break; 1007126274Sdes case I_QUIT: 1008126274Sdes case I_PWD: 1009126274Sdes case I_LPWD: 1010126274Sdes case I_HELP: 1011126274Sdes case I_VERSION: 1012126274Sdes case I_PROGRESS: 1013126274Sdes break; 1014126274Sdes default: 1015126274Sdes fatal("Command not implemented"); 1016126274Sdes } 1017126274Sdes 1018126274Sdes *cpp = cp; 1019126274Sdes return(cmdnum); 1020126274Sdes} 1021126274Sdes 1022126274Sdesstatic int 1023126274Sdesparse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, 1024126274Sdes int err_abort) 1025126274Sdes{ 1026126274Sdes char *path1, *path2, *tmp; 1027126274Sdes int pflag, lflag, iflag, cmdnum, i; 1028126274Sdes unsigned long n_arg; 1029126274Sdes Attrib a, *aa; 1030126274Sdes char path_buf[MAXPATHLEN]; 1031126274Sdes int err = 0; 1032126274Sdes glob_t g; 1033126274Sdes 1034126274Sdes path1 = path2 = NULL; 1035126274Sdes cmdnum = parse_args(&cmd, &pflag, &lflag, &iflag, &n_arg, 1036126274Sdes &path1, &path2); 1037126274Sdes 1038126274Sdes if (iflag != 0) 1039126274Sdes err_abort = 0; 1040126274Sdes 1041126274Sdes memset(&g, 0, sizeof(g)); 1042126274Sdes 1043126274Sdes /* Perform command */ 1044126274Sdes switch (cmdnum) { 1045126274Sdes case 0: 1046126274Sdes /* Blank line */ 1047126274Sdes break; 1048126274Sdes case -1: 1049126274Sdes /* Unrecognized command */ 1050126274Sdes err = -1; 1051126274Sdes break; 1052126274Sdes case I_GET: 1053126274Sdes err = process_get(conn, path1, path2, *pwd, pflag); 1054126274Sdes break; 1055126274Sdes case I_PUT: 1056126274Sdes err = process_put(conn, path1, path2, *pwd, pflag); 1057126274Sdes break; 1058126274Sdes case I_RENAME: 1059126274Sdes path1 = make_absolute(path1, *pwd); 1060126274Sdes path2 = make_absolute(path2, *pwd); 1061126274Sdes err = do_rename(conn, path1, path2); 1062126274Sdes break; 1063126274Sdes case I_SYMLINK: 1064126274Sdes path2 = make_absolute(path2, *pwd); 1065126274Sdes err = do_symlink(conn, path1, path2); 1066126274Sdes break; 1067126274Sdes case I_RM: 1068126274Sdes path1 = make_absolute(path1, *pwd); 1069126274Sdes remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); 1070137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 1071126274Sdes printf("Removing %s\n", g.gl_pathv[i]); 1072126274Sdes err = do_rm(conn, g.gl_pathv[i]); 1073126274Sdes if (err != 0 && err_abort) 1074126274Sdes break; 1075126274Sdes } 1076126274Sdes break; 1077126274Sdes case I_MKDIR: 1078126274Sdes path1 = make_absolute(path1, *pwd); 1079126274Sdes attrib_clear(&a); 1080126274Sdes a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; 1081126274Sdes a.perm = 0777; 1082126274Sdes err = do_mkdir(conn, path1, &a); 1083126274Sdes break; 1084126274Sdes case I_RMDIR: 1085126274Sdes path1 = make_absolute(path1, *pwd); 1086126274Sdes err = do_rmdir(conn, path1); 1087126274Sdes break; 1088126274Sdes case I_CHDIR: 1089126274Sdes path1 = make_absolute(path1, *pwd); 1090126274Sdes if ((tmp = do_realpath(conn, path1)) == NULL) { 1091126274Sdes err = 1; 1092126274Sdes break; 1093126274Sdes } 1094126274Sdes if ((aa = do_stat(conn, tmp, 0)) == NULL) { 1095126274Sdes xfree(tmp); 1096126274Sdes err = 1; 1097126274Sdes break; 1098126274Sdes } 1099126274Sdes if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) { 1100126274Sdes error("Can't change directory: Can't check target"); 1101126274Sdes xfree(tmp); 1102126274Sdes err = 1; 1103126274Sdes break; 1104126274Sdes } 1105126274Sdes if (!S_ISDIR(aa->perm)) { 1106126274Sdes error("Can't change directory: \"%s\" is not " 1107126274Sdes "a directory", tmp); 1108126274Sdes xfree(tmp); 1109126274Sdes err = 1; 1110126274Sdes break; 1111126274Sdes } 1112126274Sdes xfree(*pwd); 1113126274Sdes *pwd = tmp; 1114126274Sdes break; 1115126274Sdes case I_LS: 1116126274Sdes if (!path1) { 1117126274Sdes do_globbed_ls(conn, *pwd, *pwd, lflag); 1118126274Sdes break; 1119126274Sdes } 1120126274Sdes 1121126274Sdes /* Strip pwd off beginning of non-absolute paths */ 1122126274Sdes tmp = NULL; 1123126274Sdes if (*path1 != '/') 1124126274Sdes tmp = *pwd; 1125126274Sdes 1126126274Sdes path1 = make_absolute(path1, *pwd); 1127126274Sdes err = do_globbed_ls(conn, path1, tmp, lflag); 1128126274Sdes break; 1129126274Sdes case I_LCHDIR: 1130126274Sdes if (chdir(path1) == -1) { 1131126274Sdes error("Couldn't change local directory to " 1132126274Sdes "\"%s\": %s", path1, strerror(errno)); 1133126274Sdes err = 1; 1134126274Sdes } 1135126274Sdes break; 1136126274Sdes case I_LMKDIR: 1137126274Sdes if (mkdir(path1, 0777) == -1) { 1138126274Sdes error("Couldn't create local directory " 1139126274Sdes "\"%s\": %s", path1, strerror(errno)); 1140126274Sdes err = 1; 1141126274Sdes } 1142126274Sdes break; 1143126274Sdes case I_LLS: 1144126274Sdes local_do_ls(cmd); 1145126274Sdes break; 1146126274Sdes case I_SHELL: 1147126274Sdes local_do_shell(cmd); 1148126274Sdes break; 1149126274Sdes case I_LUMASK: 1150126274Sdes umask(n_arg); 1151126274Sdes printf("Local umask: %03lo\n", n_arg); 1152126274Sdes break; 1153126274Sdes case I_CHMOD: 1154126274Sdes path1 = make_absolute(path1, *pwd); 1155126274Sdes attrib_clear(&a); 1156126274Sdes a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; 1157126274Sdes a.perm = n_arg; 1158126274Sdes remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); 1159137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 1160126274Sdes printf("Changing mode on %s\n", g.gl_pathv[i]); 1161126274Sdes err = do_setstat(conn, g.gl_pathv[i], &a); 1162126274Sdes if (err != 0 && err_abort) 1163126274Sdes break; 1164126274Sdes } 1165126274Sdes break; 1166126274Sdes case I_CHOWN: 1167126274Sdes case I_CHGRP: 1168126274Sdes path1 = make_absolute(path1, *pwd); 1169126274Sdes remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); 1170137015Sdes for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 1171126274Sdes if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) { 1172126274Sdes if (err != 0 && err_abort) 1173126274Sdes break; 1174126274Sdes else 1175126274Sdes continue; 1176126274Sdes } 1177126274Sdes if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) { 1178126274Sdes error("Can't get current ownership of " 1179126274Sdes "remote file \"%s\"", g.gl_pathv[i]); 1180126274Sdes if (err != 0 && err_abort) 1181126274Sdes break; 1182126274Sdes else 1183126274Sdes continue; 1184126274Sdes } 1185126274Sdes aa->flags &= SSH2_FILEXFER_ATTR_UIDGID; 1186126274Sdes if (cmdnum == I_CHOWN) { 1187126274Sdes printf("Changing owner on %s\n", g.gl_pathv[i]); 1188126274Sdes aa->uid = n_arg; 1189126274Sdes } else { 1190126274Sdes printf("Changing group on %s\n", g.gl_pathv[i]); 1191126274Sdes aa->gid = n_arg; 1192126274Sdes } 1193126274Sdes err = do_setstat(conn, g.gl_pathv[i], aa); 1194126274Sdes if (err != 0 && err_abort) 1195126274Sdes break; 1196126274Sdes } 1197126274Sdes break; 1198126274Sdes case I_PWD: 1199126274Sdes printf("Remote working directory: %s\n", *pwd); 1200126274Sdes break; 1201126274Sdes case I_LPWD: 1202126274Sdes if (!getcwd(path_buf, sizeof(path_buf))) { 1203126274Sdes error("Couldn't get local cwd: %s", strerror(errno)); 1204126274Sdes err = -1; 1205126274Sdes break; 1206126274Sdes } 1207126274Sdes printf("Local working directory: %s\n", path_buf); 1208126274Sdes break; 1209126274Sdes case I_QUIT: 1210126274Sdes /* Processed below */ 1211126274Sdes break; 1212126274Sdes case I_HELP: 1213126274Sdes help(); 1214126274Sdes break; 1215126274Sdes case I_VERSION: 1216126274Sdes printf("SFTP protocol version %u\n", sftp_proto_version(conn)); 1217126274Sdes break; 1218126274Sdes case I_PROGRESS: 1219126274Sdes showprogress = !showprogress; 1220126274Sdes if (showprogress) 1221126274Sdes printf("Progress meter enabled\n"); 1222126274Sdes else 1223126274Sdes printf("Progress meter disabled\n"); 1224126274Sdes break; 1225126274Sdes default: 1226126274Sdes fatal("%d is not implemented", cmdnum); 1227126274Sdes } 1228126274Sdes 1229126274Sdes if (g.gl_pathc) 1230126274Sdes globfree(&g); 1231126274Sdes if (path1) 1232126274Sdes xfree(path1); 1233126274Sdes if (path2) 1234126274Sdes xfree(path2); 1235126274Sdes 1236126274Sdes /* If an unignored error occurs in batch mode we should abort. */ 1237126274Sdes if (err_abort && err != 0) 1238126274Sdes return (-1); 1239126274Sdes else if (cmdnum == I_QUIT) 1240126274Sdes return (1); 1241126274Sdes 1242126274Sdes return (0); 1243126274Sdes} 1244126274Sdes 1245146998Sdes#ifdef USE_LIBEDIT 1246146998Sdesstatic char * 1247146998Sdesprompt(EditLine *el) 1248146998Sdes{ 1249146998Sdes return ("sftp> "); 1250146998Sdes} 1251146998Sdes#endif 1252146998Sdes 1253126274Sdesint 1254126274Sdesinteractive_loop(int fd_in, int fd_out, char *file1, char *file2) 1255126274Sdes{ 1256126274Sdes char *pwd; 1257126274Sdes char *dir = NULL; 1258126274Sdes char cmd[2048]; 1259126274Sdes struct sftp_conn *conn; 1260149749Sdes int err, interactive; 1261146998Sdes EditLine *el = NULL; 1262146998Sdes#ifdef USE_LIBEDIT 1263146998Sdes History *hl = NULL; 1264146998Sdes HistEvent hev; 1265146998Sdes extern char *__progname; 1266126274Sdes 1267146998Sdes if (!batchmode && isatty(STDIN_FILENO)) { 1268146998Sdes if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL) 1269146998Sdes fatal("Couldn't initialise editline"); 1270146998Sdes if ((hl = history_init()) == NULL) 1271146998Sdes fatal("Couldn't initialise editline history"); 1272146998Sdes history(hl, &hev, H_SETSIZE, 100); 1273146998Sdes el_set(el, EL_HIST, history, hl); 1274146998Sdes 1275146998Sdes el_set(el, EL_PROMPT, prompt); 1276146998Sdes el_set(el, EL_EDITOR, "emacs"); 1277146998Sdes el_set(el, EL_TERMINAL, NULL); 1278146998Sdes el_set(el, EL_SIGNAL, 1); 1279146998Sdes el_source(el, NULL); 1280146998Sdes } 1281146998Sdes#endif /* USE_LIBEDIT */ 1282146998Sdes 1283126274Sdes conn = do_init(fd_in, fd_out, copy_buffer_len, num_requests); 1284126274Sdes if (conn == NULL) 1285126274Sdes fatal("Couldn't initialise connection to server"); 1286126274Sdes 1287126274Sdes pwd = do_realpath(conn, "."); 1288126274Sdes if (pwd == NULL) 1289126274Sdes fatal("Need cwd"); 1290126274Sdes 1291126274Sdes if (file1 != NULL) { 1292126274Sdes dir = xstrdup(file1); 1293126274Sdes dir = make_absolute(dir, pwd); 1294126274Sdes 1295126274Sdes if (remote_is_dir(conn, dir) && file2 == NULL) { 1296126274Sdes printf("Changing to: %s\n", dir); 1297126274Sdes snprintf(cmd, sizeof cmd, "cd \"%s\"", dir); 1298146998Sdes if (parse_dispatch_command(conn, cmd, &pwd, 1) != 0) { 1299146998Sdes xfree(dir); 1300146998Sdes xfree(pwd); 1301162852Sdes xfree(conn); 1302126274Sdes return (-1); 1303146998Sdes } 1304126274Sdes } else { 1305126274Sdes if (file2 == NULL) 1306126274Sdes snprintf(cmd, sizeof cmd, "get %s", dir); 1307126274Sdes else 1308126274Sdes snprintf(cmd, sizeof cmd, "get %s %s", dir, 1309126274Sdes file2); 1310126274Sdes 1311126274Sdes err = parse_dispatch_command(conn, cmd, &pwd, 1); 1312126274Sdes xfree(dir); 1313126274Sdes xfree(pwd); 1314162852Sdes xfree(conn); 1315126274Sdes return (err); 1316126274Sdes } 1317126274Sdes xfree(dir); 1318126274Sdes } 1319126274Sdes 1320149749Sdes#if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF) 1321126274Sdes setvbuf(stdout, NULL, _IOLBF, 0); 1322126274Sdes setvbuf(infile, NULL, _IOLBF, 0); 1323126274Sdes#else 1324149749Sdes setlinebuf(stdout); 1325149749Sdes setlinebuf(infile); 1326126274Sdes#endif 1327126274Sdes 1328149749Sdes interactive = !batchmode && isatty(STDIN_FILENO); 1329126274Sdes err = 0; 1330126274Sdes for (;;) { 1331126274Sdes char *cp; 1332126274Sdes 1333137015Sdes signal(SIGINT, SIG_IGN); 1334137015Sdes 1335146998Sdes if (el == NULL) { 1336149749Sdes if (interactive) 1337149749Sdes printf("sftp> "); 1338146998Sdes if (fgets(cmd, sizeof(cmd), infile) == NULL) { 1339149749Sdes if (interactive) 1340149749Sdes printf("\n"); 1341146998Sdes break; 1342146998Sdes } 1343149749Sdes if (!interactive) { /* Echo command */ 1344149749Sdes printf("sftp> %s", cmd); 1345149749Sdes if (strlen(cmd) > 0 && 1346149749Sdes cmd[strlen(cmd) - 1] != '\n') 1347149749Sdes printf("\n"); 1348149749Sdes } 1349146998Sdes } else { 1350146998Sdes#ifdef USE_LIBEDIT 1351146998Sdes const char *line; 1352146998Sdes int count = 0; 1353126274Sdes 1354149749Sdes if ((line = el_gets(el, &count)) == NULL || count <= 0) { 1355149749Sdes printf("\n"); 1356149749Sdes break; 1357149749Sdes } 1358146998Sdes history(hl, &hev, H_ENTER, line); 1359146998Sdes if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) { 1360146998Sdes fprintf(stderr, "Error: input line too long\n"); 1361146998Sdes continue; 1362146998Sdes } 1363146998Sdes#endif /* USE_LIBEDIT */ 1364126274Sdes } 1365126274Sdes 1366126274Sdes cp = strrchr(cmd, '\n'); 1367126274Sdes if (cp) 1368126274Sdes *cp = '\0'; 1369126274Sdes 1370137015Sdes /* Handle user interrupts gracefully during commands */ 1371137015Sdes interrupted = 0; 1372137015Sdes signal(SIGINT, cmd_interrupt); 1373137015Sdes 1374126274Sdes err = parse_dispatch_command(conn, cmd, &pwd, batchmode); 1375126274Sdes if (err != 0) 1376126274Sdes break; 1377126274Sdes } 1378126274Sdes xfree(pwd); 1379162852Sdes xfree(conn); 1380126274Sdes 1381149749Sdes#ifdef USE_LIBEDIT 1382149749Sdes if (el != NULL) 1383149749Sdes el_end(el); 1384149749Sdes#endif /* USE_LIBEDIT */ 1385149749Sdes 1386126274Sdes /* err == 1 signifies normal "quit" exit */ 1387126274Sdes return (err >= 0 ? 0 : -1); 1388126274Sdes} 1389126274Sdes 1390126274Sdesstatic void 1391124208Sdesconnect_to_server(char *path, char **args, int *in, int *out) 1392124208Sdes{ 139376259Sgreen int c_in, c_out; 139499060Sdes 139576259Sgreen#ifdef USE_PIPES 139676259Sgreen int pin[2], pout[2]; 139799060Sdes 139876259Sgreen if ((pipe(pin) == -1) || (pipe(pout) == -1)) 139976259Sgreen fatal("pipe: %s", strerror(errno)); 140076259Sgreen *in = pin[0]; 140176259Sgreen *out = pout[1]; 140276259Sgreen c_in = pout[0]; 140376259Sgreen c_out = pin[1]; 140476259Sgreen#else /* USE_PIPES */ 140576259Sgreen int inout[2]; 140699060Sdes 140776259Sgreen if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1) 140876259Sgreen fatal("socketpair: %s", strerror(errno)); 140976259Sgreen *in = *out = inout[0]; 141076259Sgreen c_in = c_out = inout[1]; 141176259Sgreen#endif /* USE_PIPES */ 141276259Sgreen 1413124208Sdes if ((sshpid = fork()) == -1) 141476259Sgreen fatal("fork: %s", strerror(errno)); 1415124208Sdes else if (sshpid == 0) { 141676259Sgreen if ((dup2(c_in, STDIN_FILENO) == -1) || 141776259Sgreen (dup2(c_out, STDOUT_FILENO) == -1)) { 141876259Sgreen fprintf(stderr, "dup2: %s\n", strerror(errno)); 1419137015Sdes _exit(1); 142076259Sgreen } 142176259Sgreen close(*in); 142276259Sgreen close(*out); 142376259Sgreen close(c_in); 142476259Sgreen close(c_out); 1425137015Sdes 1426137015Sdes /* 1427137015Sdes * The underlying ssh is in the same process group, so we must 1428137015Sdes * ignore SIGINT if we want to gracefully abort commands, 1429137015Sdes * otherwise the signal will make it to the ssh process and 1430137015Sdes * kill it too 1431137015Sdes */ 1432137015Sdes signal(SIGINT, SIG_IGN); 1433137015Sdes execvp(path, args); 143492555Sdes fprintf(stderr, "exec: %s: %s\n", path, strerror(errno)); 1435137015Sdes _exit(1); 143676259Sgreen } 143776259Sgreen 1438124208Sdes signal(SIGTERM, killchild); 1439124208Sdes signal(SIGINT, killchild); 1440124208Sdes signal(SIGHUP, killchild); 144176259Sgreen close(c_in); 144276259Sgreen close(c_out); 144376259Sgreen} 144476259Sgreen 144592555Sdesstatic void 144676259Sgreenusage(void) 144776259Sgreen{ 144892555Sdes extern char *__progname; 144998675Sdes 145092555Sdes fprintf(stderr, 1451126274Sdes "usage: %s [-1Cv] [-B buffer_size] [-b batchfile] [-F ssh_config]\n" 1452126274Sdes " [-o ssh_option] [-P sftp_server_path] [-R num_requests]\n" 1453126274Sdes " [-S program] [-s subsystem | sftp_server] host\n" 1454126274Sdes " %s [[user@]host[:file [file]]]\n" 1455126274Sdes " %s [[user@]host[:dir[/]]]\n" 1456126274Sdes " %s -b batchfile [user@]host\n", __progname, __progname, __progname, __progname); 145776259Sgreen exit(1); 145876259Sgreen} 145976259Sgreen 146076259Sgreenint 146176259Sgreenmain(int argc, char **argv) 146276259Sgreen{ 1463113908Sdes int in, out, ch, err; 1464137015Sdes char *host, *userhost, *cp, *file2 = NULL; 146592555Sdes int debug_level = 0, sshver = 2; 146692555Sdes char *file1 = NULL, *sftp_server = NULL; 146792555Sdes char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL; 146892555Sdes LogLevel ll = SYSLOG_LEVEL_INFO; 146992555Sdes arglist args; 147076259Sgreen extern int optind; 147176259Sgreen extern char *optarg; 147276259Sgreen 1473157016Sdes /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ 1474157016Sdes sanitise_stdfd(); 1475157016Sdes 1476124208Sdes __progname = ssh_get_progname(argv[0]); 1477157016Sdes memset(&args, '\0', sizeof(args)); 147892555Sdes args.list = NULL; 1479162852Sdes addargs(&args, "%s", ssh_program); 148092555Sdes addargs(&args, "-oForwardX11 no"); 148192555Sdes addargs(&args, "-oForwardAgent no"); 1482157016Sdes addargs(&args, "-oPermitLocalCommand no"); 148392555Sdes addargs(&args, "-oClearAllForwardings yes"); 1484126274Sdes 148592555Sdes ll = SYSLOG_LEVEL_INFO; 1486126274Sdes infile = stdin; 148776259Sgreen 148892555Sdes while ((ch = getopt(argc, argv, "1hvCo:s:S:b:B:F:P:R:")) != -1) { 148976259Sgreen switch (ch) { 149076259Sgreen case 'C': 149192555Sdes addargs(&args, "-C"); 149276259Sgreen break; 149376259Sgreen case 'v': 149492555Sdes if (debug_level < 3) { 149592555Sdes addargs(&args, "-v"); 149692555Sdes ll = SYSLOG_LEVEL_DEBUG1 + debug_level; 149792555Sdes } 149892555Sdes debug_level++; 149976259Sgreen break; 150092555Sdes case 'F': 150176259Sgreen case 'o': 150292555Sdes addargs(&args, "-%c%s", ch, optarg); 150376259Sgreen break; 150476259Sgreen case '1': 150592555Sdes sshver = 1; 150676259Sgreen if (sftp_server == NULL) 150776259Sgreen sftp_server = _PATH_SFTP_SERVER; 150876259Sgreen break; 150976259Sgreen case 's': 151076259Sgreen sftp_server = optarg; 151176259Sgreen break; 151276259Sgreen case 'S': 151376259Sgreen ssh_program = optarg; 1514157016Sdes replacearg(&args, 0, "%s", ssh_program); 151576259Sgreen break; 151676259Sgreen case 'b': 1517126274Sdes if (batchmode) 1518126274Sdes fatal("Batch file already specified."); 1519126274Sdes 1520126274Sdes /* Allow "-" as stdin */ 1521137015Sdes if (strcmp(optarg, "-") != 0 && 1522149749Sdes (infile = fopen(optarg, "r")) == NULL) 1523126274Sdes fatal("%s (%s).", strerror(errno), optarg); 1524113908Sdes showprogress = 0; 1525126274Sdes batchmode = 1; 1526146998Sdes addargs(&args, "-obatchmode yes"); 152776259Sgreen break; 152892555Sdes case 'P': 152992555Sdes sftp_direct = optarg; 153092555Sdes break; 153192555Sdes case 'B': 153292555Sdes copy_buffer_len = strtol(optarg, &cp, 10); 153392555Sdes if (copy_buffer_len == 0 || *cp != '\0') 153492555Sdes fatal("Invalid buffer size \"%s\"", optarg); 153592555Sdes break; 153692555Sdes case 'R': 153792555Sdes num_requests = strtol(optarg, &cp, 10); 153892555Sdes if (num_requests == 0 || *cp != '\0') 153998675Sdes fatal("Invalid number of requests \"%s\"", 154092555Sdes optarg); 154192555Sdes break; 154276259Sgreen case 'h': 154376259Sgreen default: 154476259Sgreen usage(); 154576259Sgreen } 154676259Sgreen } 154776259Sgreen 1548128456Sdes if (!isatty(STDERR_FILENO)) 1549128456Sdes showprogress = 0; 1550128456Sdes 155198675Sdes log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1); 155298675Sdes 155392555Sdes if (sftp_direct == NULL) { 155492555Sdes if (optind == argc || argc > (optind + 2)) 155592555Sdes usage(); 155676259Sgreen 155792555Sdes userhost = xstrdup(argv[optind]); 155892555Sdes file2 = argv[optind+1]; 155976259Sgreen 1560113908Sdes if ((host = strrchr(userhost, '@')) == NULL) 156192555Sdes host = userhost; 156292555Sdes else { 156392555Sdes *host++ = '\0'; 156492555Sdes if (!userhost[0]) { 156592555Sdes fprintf(stderr, "Missing username\n"); 156692555Sdes usage(); 156792555Sdes } 156892555Sdes addargs(&args, "-l%s",userhost); 156992555Sdes } 157092555Sdes 1571126274Sdes if ((cp = colon(host)) != NULL) { 1572126274Sdes *cp++ = '\0'; 1573126274Sdes file1 = cp; 1574126274Sdes } 1575126274Sdes 157692555Sdes host = cleanhostname(host); 157792555Sdes if (!*host) { 157892555Sdes fprintf(stderr, "Missing hostname\n"); 157976259Sgreen usage(); 158076259Sgreen } 158176259Sgreen 158292555Sdes addargs(&args, "-oProtocol %d", sshver); 158376259Sgreen 158492555Sdes /* no subsystem if the server-spec contains a '/' */ 158592555Sdes if (sftp_server == NULL || strchr(sftp_server, '/') == NULL) 158692555Sdes addargs(&args, "-s"); 158776259Sgreen 158892555Sdes addargs(&args, "%s", host); 158998675Sdes addargs(&args, "%s", (sftp_server != NULL ? 159092555Sdes sftp_server : "sftp")); 159176259Sgreen 1592126274Sdes if (!batchmode) 1593126274Sdes fprintf(stderr, "Connecting to %s...\n", host); 1594124208Sdes connect_to_server(ssh_program, args.list, &in, &out); 159592555Sdes } else { 159692555Sdes args.list = NULL; 159792555Sdes addargs(&args, "sftp-server"); 159876259Sgreen 1599126274Sdes if (!batchmode) 1600126274Sdes fprintf(stderr, "Attaching to %s...\n", sftp_direct); 1601124208Sdes connect_to_server(sftp_direct, args.list, &in, &out); 160292555Sdes } 1603157016Sdes freeargs(&args); 160476259Sgreen 1605113908Sdes err = interactive_loop(in, out, file1, file2); 160676259Sgreen 160798937Sdes#if !defined(USE_PIPES) 1608149749Sdes shutdown(in, SHUT_RDWR); 1609149749Sdes shutdown(out, SHUT_RDWR); 161098937Sdes#endif 161198937Sdes 161276259Sgreen close(in); 161376259Sgreen close(out); 1614126274Sdes if (batchmode) 161576259Sgreen fclose(infile); 161676259Sgreen 161798675Sdes while (waitpid(sshpid, NULL, 0) == -1) 161898675Sdes if (errno != EINTR) 161998675Sdes fatal("Couldn't wait for ssh process: %s", 162098675Sdes strerror(errno)); 162176259Sgreen 1622113908Sdes exit(err == 0 ? 0 : 1); 162376259Sgreen} 1624