17c478bd9Sstevel@tonic-gate /* 2*90685d2cSjp161948 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> 37c478bd9Sstevel@tonic-gate * 4*90685d2cSjp161948 * Permission to use, copy, modify, and distribute this software for any 5*90685d2cSjp161948 * purpose with or without fee is hereby granted, provided that the above 6*90685d2cSjp161948 * copyright notice and this permission notice appear in all copies. 77c478bd9Sstevel@tonic-gate * 8*90685d2cSjp161948 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9*90685d2cSjp161948 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10*90685d2cSjp161948 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11*90685d2cSjp161948 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12*90685d2cSjp161948 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13*90685d2cSjp161948 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14*90685d2cSjp161948 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 157c478bd9Sstevel@tonic-gate */ 167c478bd9Sstevel@tonic-gate 17*90685d2cSjp161948 /* $OpenBSD: sftp.c,v 1.96 2007/01/03 04:09:15 stevesk Exp $ */ 187c478bd9Sstevel@tonic-gate 197c478bd9Sstevel@tonic-gate #pragma ident "%Z%%M% %I% %E% SMI" 207c478bd9Sstevel@tonic-gate 21*90685d2cSjp161948 #include "includes.h" 227c478bd9Sstevel@tonic-gate 23*90685d2cSjp161948 #include <sys/types.h> 24*90685d2cSjp161948 #include <sys/ioctl.h> 25*90685d2cSjp161948 #ifdef HAVE_SYS_STAT_H 26*90685d2cSjp161948 # include <sys/stat.h> 27*90685d2cSjp161948 #endif 28*90685d2cSjp161948 #include <sys/param.h> 29*90685d2cSjp161948 #include <sys/socket.h> 30*90685d2cSjp161948 #include <sys/wait.h> 31*90685d2cSjp161948 32*90685d2cSjp161948 #include <errno.h> 33*90685d2cSjp161948 34*90685d2cSjp161948 #ifdef HAVE_PATHS_H 35*90685d2cSjp161948 # include <paths.h> 36*90685d2cSjp161948 #endif 37*90685d2cSjp161948 #ifdef USE_LIBEDIT 38*90685d2cSjp161948 #include <histedit.h> 39*90685d2cSjp161948 #else 40*90685d2cSjp161948 typedef void EditLine; 41*90685d2cSjp161948 #endif 42*90685d2cSjp161948 #include <signal.h> 43*90685d2cSjp161948 #include <stdlib.h> 44*90685d2cSjp161948 #include <stdio.h> 45*90685d2cSjp161948 #include <string.h> 46*90685d2cSjp161948 #include <unistd.h> 47*90685d2cSjp161948 #include <stdarg.h> 48*90685d2cSjp161948 497c478bd9Sstevel@tonic-gate #include "xmalloc.h" 507c478bd9Sstevel@tonic-gate #include "log.h" 517c478bd9Sstevel@tonic-gate #include "pathnames.h" 527c478bd9Sstevel@tonic-gate #include "misc.h" 537c478bd9Sstevel@tonic-gate 547c478bd9Sstevel@tonic-gate #include "sftp.h" 55*90685d2cSjp161948 #include "buffer.h" 567c478bd9Sstevel@tonic-gate #include "sftp-common.h" 577c478bd9Sstevel@tonic-gate #include "sftp-client.h" 587c478bd9Sstevel@tonic-gate 597c478bd9Sstevel@tonic-gate #ifdef HAVE___PROGNAME 607c478bd9Sstevel@tonic-gate extern char *__progname; 617c478bd9Sstevel@tonic-gate #else 627c478bd9Sstevel@tonic-gate char *__progname; 637c478bd9Sstevel@tonic-gate #endif 647c478bd9Sstevel@tonic-gate 65*90685d2cSjp161948 66*90685d2cSjp161948 /* File to read commands from */ 677c478bd9Sstevel@tonic-gate FILE* infile; 68*90685d2cSjp161948 69*90685d2cSjp161948 /* Are we in batchfile mode? */ 70*90685d2cSjp161948 int batchmode = 0; 71*90685d2cSjp161948 72*90685d2cSjp161948 /* Size of buffer used when copying files */ 737c478bd9Sstevel@tonic-gate size_t copy_buffer_len = 32768; 74*90685d2cSjp161948 75*90685d2cSjp161948 /* Number of concurrent outstanding requests */ 767c478bd9Sstevel@tonic-gate size_t num_requests = 16; 777c478bd9Sstevel@tonic-gate 78*90685d2cSjp161948 /* PID of ssh transport process */ 79*90685d2cSjp161948 static pid_t sshpid = -1; 80*90685d2cSjp161948 81*90685d2cSjp161948 /* This is set to 0 if the progressmeter is not desired. */ 82*90685d2cSjp161948 int showprogress = 1; 83*90685d2cSjp161948 84*90685d2cSjp161948 /* SIGINT received during command processing */ 85*90685d2cSjp161948 volatile sig_atomic_t interrupted = 0; 86*90685d2cSjp161948 87*90685d2cSjp161948 /* I wish qsort() took a separate ctx for the comparison function...*/ 88*90685d2cSjp161948 int sort_flag; 89*90685d2cSjp161948 90*90685d2cSjp161948 int remote_glob(struct sftp_conn *, const char *, int, 91*90685d2cSjp161948 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */ 92*90685d2cSjp161948 93*90685d2cSjp161948 /* Separators for interactive commands */ 94*90685d2cSjp161948 #define WHITESPACE " \t\r\n" 95*90685d2cSjp161948 96*90685d2cSjp161948 /* ls flags */ 97*90685d2cSjp161948 #define LS_LONG_VIEW 0x01 /* Full view ala ls -l */ 98*90685d2cSjp161948 #define LS_SHORT_VIEW 0x02 /* Single row view ala ls -1 */ 99*90685d2cSjp161948 #define LS_NUMERIC_VIEW 0x04 /* Long view with numeric uid/gid */ 100*90685d2cSjp161948 #define LS_NAME_SORT 0x08 /* Sort by name (default) */ 101*90685d2cSjp161948 #define LS_TIME_SORT 0x10 /* Sort by mtime */ 102*90685d2cSjp161948 #define LS_SIZE_SORT 0x20 /* Sort by file size */ 103*90685d2cSjp161948 #define LS_REVERSE_SORT 0x40 /* Reverse sort order */ 104*90685d2cSjp161948 #define LS_SHOW_ALL 0x80 /* Don't skip filenames starting with '.' */ 105*90685d2cSjp161948 106*90685d2cSjp161948 #define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW) 107*90685d2cSjp161948 #define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT) 108*90685d2cSjp161948 109*90685d2cSjp161948 /* Commands for interactive mode */ 110*90685d2cSjp161948 #define I_CHDIR 1 111*90685d2cSjp161948 #define I_CHGRP 2 112*90685d2cSjp161948 #define I_CHMOD 3 113*90685d2cSjp161948 #define I_CHOWN 4 114*90685d2cSjp161948 #define I_GET 5 115*90685d2cSjp161948 #define I_HELP 6 116*90685d2cSjp161948 #define I_LCHDIR 7 117*90685d2cSjp161948 #define I_LLS 8 118*90685d2cSjp161948 #define I_LMKDIR 9 119*90685d2cSjp161948 #define I_LPWD 10 120*90685d2cSjp161948 #define I_LS 11 121*90685d2cSjp161948 #define I_LUMASK 12 122*90685d2cSjp161948 #define I_MKDIR 13 123*90685d2cSjp161948 #define I_PUT 14 124*90685d2cSjp161948 #define I_PWD 15 125*90685d2cSjp161948 #define I_QUIT 16 126*90685d2cSjp161948 #define I_RENAME 17 127*90685d2cSjp161948 #define I_RM 18 128*90685d2cSjp161948 #define I_RMDIR 19 129*90685d2cSjp161948 #define I_SHELL 20 130*90685d2cSjp161948 #define I_SYMLINK 21 131*90685d2cSjp161948 #define I_VERSION 22 132*90685d2cSjp161948 #define I_PROGRESS 23 133*90685d2cSjp161948 134*90685d2cSjp161948 struct CMD { 135*90685d2cSjp161948 const char *c; 136*90685d2cSjp161948 const int n; 137*90685d2cSjp161948 }; 138*90685d2cSjp161948 139*90685d2cSjp161948 static const struct CMD cmds[] = { 140*90685d2cSjp161948 { "bye", I_QUIT }, 141*90685d2cSjp161948 { "cd", I_CHDIR }, 142*90685d2cSjp161948 { "chdir", I_CHDIR }, 143*90685d2cSjp161948 { "chgrp", I_CHGRP }, 144*90685d2cSjp161948 { "chmod", I_CHMOD }, 145*90685d2cSjp161948 { "chown", I_CHOWN }, 146*90685d2cSjp161948 { "dir", I_LS }, 147*90685d2cSjp161948 { "exit", I_QUIT }, 148*90685d2cSjp161948 { "get", I_GET }, 149*90685d2cSjp161948 { "mget", I_GET }, 150*90685d2cSjp161948 { "help", I_HELP }, 151*90685d2cSjp161948 { "lcd", I_LCHDIR }, 152*90685d2cSjp161948 { "lchdir", I_LCHDIR }, 153*90685d2cSjp161948 { "lls", I_LLS }, 154*90685d2cSjp161948 { "lmkdir", I_LMKDIR }, 155*90685d2cSjp161948 { "ln", I_SYMLINK }, 156*90685d2cSjp161948 { "lpwd", I_LPWD }, 157*90685d2cSjp161948 { "ls", I_LS }, 158*90685d2cSjp161948 { "lumask", I_LUMASK }, 159*90685d2cSjp161948 { "mkdir", I_MKDIR }, 160*90685d2cSjp161948 { "progress", I_PROGRESS }, 161*90685d2cSjp161948 { "put", I_PUT }, 162*90685d2cSjp161948 { "mput", I_PUT }, 163*90685d2cSjp161948 { "pwd", I_PWD }, 164*90685d2cSjp161948 { "quit", I_QUIT }, 165*90685d2cSjp161948 { "rename", I_RENAME }, 166*90685d2cSjp161948 { "rm", I_RM }, 167*90685d2cSjp161948 { "rmdir", I_RMDIR }, 168*90685d2cSjp161948 { "symlink", I_SYMLINK }, 169*90685d2cSjp161948 { "version", I_VERSION }, 170*90685d2cSjp161948 { "!", I_SHELL }, 171*90685d2cSjp161948 { "?", I_HELP }, 172*90685d2cSjp161948 { NULL, -1} 173*90685d2cSjp161948 }; 174*90685d2cSjp161948 175*90685d2cSjp161948 int interactive_loop(int fd_in, int fd_out, char *file1, char *file2); 176*90685d2cSjp161948 177*90685d2cSjp161948 /* ARGSUSED */ 1787c478bd9Sstevel@tonic-gate static void 179*90685d2cSjp161948 killchild(int signo) 180*90685d2cSjp161948 { 181*90685d2cSjp161948 if (sshpid > 1) { 182*90685d2cSjp161948 kill(sshpid, SIGTERM); 183*90685d2cSjp161948 waitpid(sshpid, NULL, 0); 184*90685d2cSjp161948 } 185*90685d2cSjp161948 186*90685d2cSjp161948 _exit(1); 187*90685d2cSjp161948 } 188*90685d2cSjp161948 189*90685d2cSjp161948 /* ARGSUSED */ 190*90685d2cSjp161948 static void 191*90685d2cSjp161948 cmd_interrupt(int signo) 192*90685d2cSjp161948 { 193*90685d2cSjp161948 const char msg[] = "\rInterrupt \n"; 194*90685d2cSjp161948 int olderrno = errno; 195*90685d2cSjp161948 196*90685d2cSjp161948 write(STDERR_FILENO, msg, sizeof(msg) - 1); 197*90685d2cSjp161948 interrupted = 1; 198*90685d2cSjp161948 errno = olderrno; 199*90685d2cSjp161948 } 200*90685d2cSjp161948 201*90685d2cSjp161948 static void 202*90685d2cSjp161948 help(void) 203*90685d2cSjp161948 { 204*90685d2cSjp161948 printf(gettext("Available commands:\n" 205*90685d2cSjp161948 "cd path Change remote directory to 'path'\n" 206*90685d2cSjp161948 "lcd path Change local directory to 'path'\n" 207*90685d2cSjp161948 "chgrp grp path Change group of file 'path' to 'grp'\n" 208*90685d2cSjp161948 "chmod mode path Change permissions of file 'path' to 'mode'\n" 209*90685d2cSjp161948 "chown own path Change owner of file 'path' to 'own'\n" 210*90685d2cSjp161948 "help Display this help text\n" 211*90685d2cSjp161948 "get remote-path [local-path] Download file\n" 212*90685d2cSjp161948 "lls [ls-options [path]] Display local directory listing\n" 213*90685d2cSjp161948 "ln oldpath newpath Symlink remote file\n" 214*90685d2cSjp161948 "lmkdir path Create local directory\n" 215*90685d2cSjp161948 "lpwd Print local working directory\n" 216*90685d2cSjp161948 "ls [path] Display remote directory listing\n" 217*90685d2cSjp161948 "lumask umask Set local umask to 'umask'\n" 218*90685d2cSjp161948 "mkdir path Create remote directory\n" 219*90685d2cSjp161948 "progress Toggle display of progress meter\n" 220*90685d2cSjp161948 "put local-path [remote-path] Upload file\n" 221*90685d2cSjp161948 "pwd Display remote working directory\n" 222*90685d2cSjp161948 "exit Quit sftp\n" 223*90685d2cSjp161948 "quit Quit sftp\n" 224*90685d2cSjp161948 "rename oldpath newpath Rename remote file\n" 225*90685d2cSjp161948 "rmdir path Remove remote directory\n" 226*90685d2cSjp161948 "rm path Delete remote file\n" 227*90685d2cSjp161948 "symlink oldpath newpath Symlink remote file\n" 228*90685d2cSjp161948 "version Show SFTP version\n" 229*90685d2cSjp161948 "!command Execute 'command' in local shell\n" 230*90685d2cSjp161948 "! Escape to local shell\n" 231*90685d2cSjp161948 "? Synonym for help\n")); 232*90685d2cSjp161948 } 233*90685d2cSjp161948 234*90685d2cSjp161948 static void 235*90685d2cSjp161948 local_do_shell(const char *args) 236*90685d2cSjp161948 { 237*90685d2cSjp161948 int status; 238*90685d2cSjp161948 char *shell; 239*90685d2cSjp161948 pid_t pid; 240*90685d2cSjp161948 241*90685d2cSjp161948 if (!*args) 242*90685d2cSjp161948 args = NULL; 243*90685d2cSjp161948 244*90685d2cSjp161948 if ((shell = getenv("SHELL")) == NULL) 245*90685d2cSjp161948 shell = _PATH_BSHELL; 246*90685d2cSjp161948 247*90685d2cSjp161948 if ((pid = fork()) == -1) 248*90685d2cSjp161948 fatal("Couldn't fork: %s", strerror(errno)); 249*90685d2cSjp161948 250*90685d2cSjp161948 if (pid == 0) { 251*90685d2cSjp161948 /* XXX: child has pipe fds to ssh subproc open - issue? */ 252*90685d2cSjp161948 if (args) { 253*90685d2cSjp161948 debug3("Executing %s -c \"%s\"", shell, args); 254*90685d2cSjp161948 execl(shell, shell, "-c", args, (char *)NULL); 255*90685d2cSjp161948 } else { 256*90685d2cSjp161948 debug3("Executing %s", shell); 257*90685d2cSjp161948 execl(shell, shell, (char *)NULL); 258*90685d2cSjp161948 } 259*90685d2cSjp161948 fprintf(stderr, gettext("Couldn't execute \"%s\": %s\n"), shell, 260*90685d2cSjp161948 strerror(errno)); 261*90685d2cSjp161948 _exit(1); 262*90685d2cSjp161948 } 263*90685d2cSjp161948 while (waitpid(pid, &status, 0) == -1) 264*90685d2cSjp161948 if (errno != EINTR) 265*90685d2cSjp161948 fatal("Couldn't wait for child: %s", strerror(errno)); 266*90685d2cSjp161948 if (!WIFEXITED(status)) 267*90685d2cSjp161948 error("Shell exited abnormally"); 268*90685d2cSjp161948 else if (WEXITSTATUS(status)) 269*90685d2cSjp161948 error("Shell exited with status %d", WEXITSTATUS(status)); 270*90685d2cSjp161948 } 271*90685d2cSjp161948 272*90685d2cSjp161948 static void 273*90685d2cSjp161948 local_do_ls(const char *args) 274*90685d2cSjp161948 { 275*90685d2cSjp161948 if (!args || !*args) 276*90685d2cSjp161948 local_do_shell(_PATH_LS); 277*90685d2cSjp161948 else { 278*90685d2cSjp161948 int len = strlen(_PATH_LS " ") + strlen(args) + 1; 279*90685d2cSjp161948 char *buf = xmalloc(len); 280*90685d2cSjp161948 281*90685d2cSjp161948 /* XXX: quoting - rip quoting code from ftp? */ 282*90685d2cSjp161948 snprintf(buf, len, _PATH_LS " %s", args); 283*90685d2cSjp161948 local_do_shell(buf); 284*90685d2cSjp161948 xfree(buf); 285*90685d2cSjp161948 } 286*90685d2cSjp161948 } 287*90685d2cSjp161948 288*90685d2cSjp161948 /* Strip one path (usually the pwd) from the start of another */ 289*90685d2cSjp161948 static char * 290*90685d2cSjp161948 path_strip(char *path, char *strip) 291*90685d2cSjp161948 { 292*90685d2cSjp161948 size_t len; 293*90685d2cSjp161948 294*90685d2cSjp161948 if (strip == NULL) 295*90685d2cSjp161948 return (xstrdup(path)); 296*90685d2cSjp161948 297*90685d2cSjp161948 len = strlen(strip); 298*90685d2cSjp161948 if (strncmp(path, strip, len) == 0) { 299*90685d2cSjp161948 if (strip[len - 1] != '/' && path[len] == '/') 300*90685d2cSjp161948 len++; 301*90685d2cSjp161948 return (xstrdup(path + len)); 302*90685d2cSjp161948 } 303*90685d2cSjp161948 304*90685d2cSjp161948 return (xstrdup(path)); 305*90685d2cSjp161948 } 306*90685d2cSjp161948 307*90685d2cSjp161948 static char * 308*90685d2cSjp161948 path_append(char *p1, char *p2) 309*90685d2cSjp161948 { 310*90685d2cSjp161948 char *ret; 311*90685d2cSjp161948 size_t len = strlen(p1) + strlen(p2) + 2; 312*90685d2cSjp161948 313*90685d2cSjp161948 ret = xmalloc(len); 314*90685d2cSjp161948 strlcpy(ret, p1, len); 315*90685d2cSjp161948 if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/') 316*90685d2cSjp161948 strlcat(ret, "/", len); 317*90685d2cSjp161948 strlcat(ret, p2, len); 318*90685d2cSjp161948 319*90685d2cSjp161948 return(ret); 320*90685d2cSjp161948 } 321*90685d2cSjp161948 322*90685d2cSjp161948 static char * 323*90685d2cSjp161948 make_absolute(char *p, char *pwd) 324*90685d2cSjp161948 { 325*90685d2cSjp161948 char *abs_str; 326*90685d2cSjp161948 327*90685d2cSjp161948 /* Derelativise */ 328*90685d2cSjp161948 if (p && p[0] != '/') { 329*90685d2cSjp161948 abs_str = path_append(pwd, p); 330*90685d2cSjp161948 xfree(p); 331*90685d2cSjp161948 return(abs_str); 332*90685d2cSjp161948 } else 333*90685d2cSjp161948 return(p); 334*90685d2cSjp161948 } 335*90685d2cSjp161948 336*90685d2cSjp161948 static int 337*90685d2cSjp161948 infer_path(const char *p, char **ifp) 338*90685d2cSjp161948 { 339*90685d2cSjp161948 char *cp; 340*90685d2cSjp161948 341*90685d2cSjp161948 cp = strrchr(p, '/'); 342*90685d2cSjp161948 if (cp == NULL) { 343*90685d2cSjp161948 *ifp = xstrdup(p); 344*90685d2cSjp161948 return(0); 345*90685d2cSjp161948 } 346*90685d2cSjp161948 347*90685d2cSjp161948 if (!cp[1]) { 348*90685d2cSjp161948 error("Invalid path"); 349*90685d2cSjp161948 return(-1); 350*90685d2cSjp161948 } 351*90685d2cSjp161948 352*90685d2cSjp161948 *ifp = xstrdup(cp + 1); 353*90685d2cSjp161948 return(0); 354*90685d2cSjp161948 } 355*90685d2cSjp161948 356*90685d2cSjp161948 static int 357*90685d2cSjp161948 parse_getput_flags(const char **cpp, int *pflag) 358*90685d2cSjp161948 { 359*90685d2cSjp161948 const char *cp = *cpp; 360*90685d2cSjp161948 361*90685d2cSjp161948 /* Check for flags */ 362*90685d2cSjp161948 if (cp[0] == '-' && cp[1] && strchr(WHITESPACE, cp[2])) { 363*90685d2cSjp161948 switch (cp[1]) { 364*90685d2cSjp161948 case 'p': 365*90685d2cSjp161948 case 'P': 366*90685d2cSjp161948 *pflag = 1; 367*90685d2cSjp161948 break; 368*90685d2cSjp161948 default: 369*90685d2cSjp161948 error("Invalid flag -%c", cp[1]); 370*90685d2cSjp161948 return(-1); 371*90685d2cSjp161948 } 372*90685d2cSjp161948 cp += 2; 373*90685d2cSjp161948 *cpp = cp + strspn(cp, WHITESPACE); 374*90685d2cSjp161948 } 375*90685d2cSjp161948 376*90685d2cSjp161948 return(0); 377*90685d2cSjp161948 } 378*90685d2cSjp161948 379*90685d2cSjp161948 static int 380*90685d2cSjp161948 parse_ls_flags(const char **cpp, int *lflag) 381*90685d2cSjp161948 { 382*90685d2cSjp161948 const char *cp = *cpp; 383*90685d2cSjp161948 384*90685d2cSjp161948 /* Defaults */ 385*90685d2cSjp161948 *lflag = LS_NAME_SORT; 386*90685d2cSjp161948 387*90685d2cSjp161948 /* Check for flags */ 388*90685d2cSjp161948 if (cp++[0] == '-') { 389*90685d2cSjp161948 for (; strchr(WHITESPACE, *cp) == NULL; cp++) { 390*90685d2cSjp161948 switch (*cp) { 391*90685d2cSjp161948 case 'l': 392*90685d2cSjp161948 *lflag &= ~VIEW_FLAGS; 393*90685d2cSjp161948 *lflag |= LS_LONG_VIEW; 394*90685d2cSjp161948 break; 395*90685d2cSjp161948 case '1': 396*90685d2cSjp161948 *lflag &= ~VIEW_FLAGS; 397*90685d2cSjp161948 *lflag |= LS_SHORT_VIEW; 398*90685d2cSjp161948 break; 399*90685d2cSjp161948 case 'n': 400*90685d2cSjp161948 *lflag &= ~VIEW_FLAGS; 401*90685d2cSjp161948 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW; 402*90685d2cSjp161948 break; 403*90685d2cSjp161948 case 'S': 404*90685d2cSjp161948 *lflag &= ~SORT_FLAGS; 405*90685d2cSjp161948 *lflag |= LS_SIZE_SORT; 406*90685d2cSjp161948 break; 407*90685d2cSjp161948 case 't': 408*90685d2cSjp161948 *lflag &= ~SORT_FLAGS; 409*90685d2cSjp161948 *lflag |= LS_TIME_SORT; 410*90685d2cSjp161948 break; 411*90685d2cSjp161948 case 'r': 412*90685d2cSjp161948 *lflag |= LS_REVERSE_SORT; 413*90685d2cSjp161948 break; 414*90685d2cSjp161948 case 'f': 415*90685d2cSjp161948 *lflag &= ~SORT_FLAGS; 416*90685d2cSjp161948 break; 417*90685d2cSjp161948 case 'a': 418*90685d2cSjp161948 *lflag |= LS_SHOW_ALL; 419*90685d2cSjp161948 break; 420*90685d2cSjp161948 default: 421*90685d2cSjp161948 error("Invalid flag -%c", *cp); 422*90685d2cSjp161948 return(-1); 423*90685d2cSjp161948 } 424*90685d2cSjp161948 } 425*90685d2cSjp161948 *cpp = cp + strspn(cp, WHITESPACE); 426*90685d2cSjp161948 } 427*90685d2cSjp161948 428*90685d2cSjp161948 return(0); 429*90685d2cSjp161948 } 430*90685d2cSjp161948 431*90685d2cSjp161948 static int 432*90685d2cSjp161948 get_pathname(const char **cpp, char **path) 433*90685d2cSjp161948 { 434*90685d2cSjp161948 const char *cp = *cpp, *end; 435*90685d2cSjp161948 char quot; 436*90685d2cSjp161948 u_int i, j; 437*90685d2cSjp161948 438*90685d2cSjp161948 cp += strspn(cp, WHITESPACE); 439*90685d2cSjp161948 if (!*cp) { 440*90685d2cSjp161948 *cpp = cp; 441*90685d2cSjp161948 *path = NULL; 442*90685d2cSjp161948 return (0); 443*90685d2cSjp161948 } 444*90685d2cSjp161948 445*90685d2cSjp161948 *path = xmalloc(strlen(cp) + 1); 446*90685d2cSjp161948 447*90685d2cSjp161948 /* Check for quoted filenames */ 448*90685d2cSjp161948 if (*cp == '\"' || *cp == '\'') { 449*90685d2cSjp161948 quot = *cp++; 450*90685d2cSjp161948 451*90685d2cSjp161948 /* Search for terminating quote, unescape some chars */ 452*90685d2cSjp161948 for (i = j = 0; i <= strlen(cp); i++) { 453*90685d2cSjp161948 if (cp[i] == quot) { /* Found quote */ 454*90685d2cSjp161948 i++; 455*90685d2cSjp161948 (*path)[j] = '\0'; 456*90685d2cSjp161948 break; 457*90685d2cSjp161948 } 458*90685d2cSjp161948 if (cp[i] == '\0') { /* End of string */ 459*90685d2cSjp161948 error("Unterminated quote"); 460*90685d2cSjp161948 goto fail; 461*90685d2cSjp161948 } 462*90685d2cSjp161948 if (cp[i] == '\\') { /* Escaped characters */ 463*90685d2cSjp161948 i++; 464*90685d2cSjp161948 if (cp[i] != '\'' && cp[i] != '\"' && 465*90685d2cSjp161948 cp[i] != '\\') { 466*90685d2cSjp161948 error("Bad escaped character '\\%c'", 467*90685d2cSjp161948 cp[i]); 468*90685d2cSjp161948 goto fail; 469*90685d2cSjp161948 } 470*90685d2cSjp161948 } 471*90685d2cSjp161948 (*path)[j++] = cp[i]; 472*90685d2cSjp161948 } 473*90685d2cSjp161948 474*90685d2cSjp161948 if (j == 0) { 475*90685d2cSjp161948 error("Empty quotes"); 476*90685d2cSjp161948 goto fail; 477*90685d2cSjp161948 } 478*90685d2cSjp161948 *cpp = cp + i + strspn(cp + i, WHITESPACE); 479*90685d2cSjp161948 } else { 480*90685d2cSjp161948 /* Read to end of filename */ 481*90685d2cSjp161948 end = strpbrk(cp, WHITESPACE); 482*90685d2cSjp161948 if (end == NULL) 483*90685d2cSjp161948 end = strchr(cp, '\0'); 484*90685d2cSjp161948 *cpp = end + strspn(end, WHITESPACE); 485*90685d2cSjp161948 486*90685d2cSjp161948 memcpy(*path, cp, end - cp); 487*90685d2cSjp161948 (*path)[end - cp] = '\0'; 488*90685d2cSjp161948 } 489*90685d2cSjp161948 return (0); 490*90685d2cSjp161948 491*90685d2cSjp161948 fail: 492*90685d2cSjp161948 xfree(*path); 493*90685d2cSjp161948 *path = NULL; 494*90685d2cSjp161948 return (-1); 495*90685d2cSjp161948 } 496*90685d2cSjp161948 497*90685d2cSjp161948 static int 498*90685d2cSjp161948 is_dir(char *path) 499*90685d2cSjp161948 { 500*90685d2cSjp161948 struct stat sb; 501*90685d2cSjp161948 502*90685d2cSjp161948 /* XXX: report errors? */ 503*90685d2cSjp161948 if (stat(path, &sb) == -1) 504*90685d2cSjp161948 return(0); 505*90685d2cSjp161948 506*90685d2cSjp161948 return(S_ISDIR(sb.st_mode)); 507*90685d2cSjp161948 } 508*90685d2cSjp161948 509*90685d2cSjp161948 static int 510*90685d2cSjp161948 is_reg(char *path) 511*90685d2cSjp161948 { 512*90685d2cSjp161948 struct stat sb; 513*90685d2cSjp161948 514*90685d2cSjp161948 if (stat(path, &sb) == -1) 515*90685d2cSjp161948 fatal("stat %s: %s", path, strerror(errno)); 516*90685d2cSjp161948 517*90685d2cSjp161948 return(S_ISREG(sb.st_mode)); 518*90685d2cSjp161948 } 519*90685d2cSjp161948 520*90685d2cSjp161948 static int 521*90685d2cSjp161948 remote_is_dir(struct sftp_conn *conn, char *path) 522*90685d2cSjp161948 { 523*90685d2cSjp161948 Attrib *a; 524*90685d2cSjp161948 525*90685d2cSjp161948 /* XXX: report errors? */ 526*90685d2cSjp161948 if ((a = do_stat(conn, path, 1)) == NULL) 527*90685d2cSjp161948 return(0); 528*90685d2cSjp161948 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) 529*90685d2cSjp161948 return(0); 530*90685d2cSjp161948 return(S_ISDIR(a->perm)); 531*90685d2cSjp161948 } 532*90685d2cSjp161948 533*90685d2cSjp161948 static int 534*90685d2cSjp161948 process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag) 535*90685d2cSjp161948 { 536*90685d2cSjp161948 char *abs_src = NULL; 537*90685d2cSjp161948 char *abs_dst = NULL; 538*90685d2cSjp161948 char *tmp; 539*90685d2cSjp161948 glob_t g; 540*90685d2cSjp161948 int err = 0; 541*90685d2cSjp161948 int i; 542*90685d2cSjp161948 543*90685d2cSjp161948 abs_src = xstrdup(src); 544*90685d2cSjp161948 abs_src = make_absolute(abs_src, pwd); 545*90685d2cSjp161948 546*90685d2cSjp161948 memset(&g, 0, sizeof(g)); 547*90685d2cSjp161948 debug3("Looking up %s", abs_src); 548*90685d2cSjp161948 if (remote_glob(conn, abs_src, 0, NULL, &g)) { 549*90685d2cSjp161948 error("File \"%s\" not found.", abs_src); 550*90685d2cSjp161948 err = -1; 551*90685d2cSjp161948 goto out; 552*90685d2cSjp161948 } 553*90685d2cSjp161948 554*90685d2cSjp161948 /* If multiple matches, dst must be a directory or unspecified */ 555*90685d2cSjp161948 if (g.gl_matchc > 1 && dst && !is_dir(dst)) { 556*90685d2cSjp161948 error("Multiple files match, but \"%s\" is not a directory", 557*90685d2cSjp161948 dst); 558*90685d2cSjp161948 err = -1; 559*90685d2cSjp161948 goto out; 560*90685d2cSjp161948 } 561*90685d2cSjp161948 562*90685d2cSjp161948 for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 563*90685d2cSjp161948 if (infer_path(g.gl_pathv[i], &tmp)) { 564*90685d2cSjp161948 err = -1; 565*90685d2cSjp161948 goto out; 566*90685d2cSjp161948 } 567*90685d2cSjp161948 568*90685d2cSjp161948 if (g.gl_matchc == 1 && dst) { 569*90685d2cSjp161948 /* If directory specified, append filename */ 570*90685d2cSjp161948 xfree(tmp); 571*90685d2cSjp161948 if (is_dir(dst)) { 572*90685d2cSjp161948 if (infer_path(g.gl_pathv[0], &tmp)) { 573*90685d2cSjp161948 err = 1; 574*90685d2cSjp161948 goto out; 575*90685d2cSjp161948 } 576*90685d2cSjp161948 abs_dst = path_append(dst, tmp); 577*90685d2cSjp161948 xfree(tmp); 578*90685d2cSjp161948 } else 579*90685d2cSjp161948 abs_dst = xstrdup(dst); 580*90685d2cSjp161948 } else if (dst) { 581*90685d2cSjp161948 abs_dst = path_append(dst, tmp); 582*90685d2cSjp161948 xfree(tmp); 583*90685d2cSjp161948 } else 584*90685d2cSjp161948 abs_dst = tmp; 585*90685d2cSjp161948 586*90685d2cSjp161948 printf(gettext("Fetching %s to %s\n"), g.gl_pathv[i], abs_dst); 587*90685d2cSjp161948 if (do_download(conn, g.gl_pathv[i], abs_dst, pflag) == -1) 588*90685d2cSjp161948 err = -1; 589*90685d2cSjp161948 xfree(abs_dst); 590*90685d2cSjp161948 abs_dst = NULL; 591*90685d2cSjp161948 } 592*90685d2cSjp161948 593*90685d2cSjp161948 out: 594*90685d2cSjp161948 xfree(abs_src); 595*90685d2cSjp161948 globfree(&g); 596*90685d2cSjp161948 return(err); 597*90685d2cSjp161948 } 598*90685d2cSjp161948 599*90685d2cSjp161948 static int 600*90685d2cSjp161948 process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag) 601*90685d2cSjp161948 { 602*90685d2cSjp161948 char *tmp_dst = NULL; 603*90685d2cSjp161948 char *abs_dst = NULL; 604*90685d2cSjp161948 char *tmp; 605*90685d2cSjp161948 glob_t g; 606*90685d2cSjp161948 int err = 0; 607*90685d2cSjp161948 int i; 608*90685d2cSjp161948 609*90685d2cSjp161948 if (dst) { 610*90685d2cSjp161948 tmp_dst = xstrdup(dst); 611*90685d2cSjp161948 tmp_dst = make_absolute(tmp_dst, pwd); 612*90685d2cSjp161948 } 613*90685d2cSjp161948 614*90685d2cSjp161948 memset(&g, 0, sizeof(g)); 615*90685d2cSjp161948 debug3("Looking up %s", src); 616*90685d2cSjp161948 if (glob(src, 0, NULL, &g)) { 617*90685d2cSjp161948 error("File \"%s\" not found.", src); 618*90685d2cSjp161948 err = -1; 619*90685d2cSjp161948 goto out; 620*90685d2cSjp161948 } 621*90685d2cSjp161948 622*90685d2cSjp161948 /* If multiple matches, dst may be directory or unspecified */ 623*90685d2cSjp161948 if (g.gl_matchc > 1 && tmp_dst && !remote_is_dir(conn, tmp_dst)) { 624*90685d2cSjp161948 error("Multiple files match, but \"%s\" is not a directory", 625*90685d2cSjp161948 tmp_dst); 626*90685d2cSjp161948 err = -1; 627*90685d2cSjp161948 goto out; 628*90685d2cSjp161948 } 629*90685d2cSjp161948 630*90685d2cSjp161948 for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 631*90685d2cSjp161948 if (!is_reg(g.gl_pathv[i])) { 632*90685d2cSjp161948 error("skipping non-regular file %s", 633*90685d2cSjp161948 g.gl_pathv[i]); 634*90685d2cSjp161948 continue; 635*90685d2cSjp161948 } 636*90685d2cSjp161948 if (infer_path(g.gl_pathv[i], &tmp)) { 637*90685d2cSjp161948 err = -1; 638*90685d2cSjp161948 goto out; 639*90685d2cSjp161948 } 640*90685d2cSjp161948 641*90685d2cSjp161948 if (g.gl_matchc == 1 && tmp_dst) { 642*90685d2cSjp161948 /* If directory specified, append filename */ 643*90685d2cSjp161948 if (remote_is_dir(conn, tmp_dst)) { 644*90685d2cSjp161948 if (infer_path(g.gl_pathv[0], &tmp)) { 645*90685d2cSjp161948 err = 1; 646*90685d2cSjp161948 goto out; 647*90685d2cSjp161948 } 648*90685d2cSjp161948 abs_dst = path_append(tmp_dst, tmp); 649*90685d2cSjp161948 xfree(tmp); 650*90685d2cSjp161948 } else 651*90685d2cSjp161948 abs_dst = xstrdup(tmp_dst); 652*90685d2cSjp161948 653*90685d2cSjp161948 } else if (tmp_dst) { 654*90685d2cSjp161948 abs_dst = path_append(tmp_dst, tmp); 655*90685d2cSjp161948 xfree(tmp); 656*90685d2cSjp161948 } else 657*90685d2cSjp161948 abs_dst = make_absolute(tmp, pwd); 658*90685d2cSjp161948 659*90685d2cSjp161948 printf(gettext("Uploading %s to %s\n"), g.gl_pathv[i], abs_dst); 660*90685d2cSjp161948 if (do_upload(conn, g.gl_pathv[i], abs_dst, pflag) == -1) 661*90685d2cSjp161948 err = -1; 662*90685d2cSjp161948 } 663*90685d2cSjp161948 664*90685d2cSjp161948 out: 665*90685d2cSjp161948 if (abs_dst) 666*90685d2cSjp161948 xfree(abs_dst); 667*90685d2cSjp161948 if (tmp_dst) 668*90685d2cSjp161948 xfree(tmp_dst); 669*90685d2cSjp161948 globfree(&g); 670*90685d2cSjp161948 return(err); 671*90685d2cSjp161948 } 672*90685d2cSjp161948 673*90685d2cSjp161948 static int 674*90685d2cSjp161948 sdirent_comp(const void *aa, const void *bb) 675*90685d2cSjp161948 { 676*90685d2cSjp161948 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa; 677*90685d2cSjp161948 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb; 678*90685d2cSjp161948 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1; 679*90685d2cSjp161948 680*90685d2cSjp161948 #define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1)) 681*90685d2cSjp161948 if (sort_flag & LS_NAME_SORT) 682*90685d2cSjp161948 return (rmul * strcmp(a->filename, b->filename)); 683*90685d2cSjp161948 else if (sort_flag & LS_TIME_SORT) 684*90685d2cSjp161948 return (rmul * NCMP(a->a.mtime, b->a.mtime)); 685*90685d2cSjp161948 else if (sort_flag & LS_SIZE_SORT) 686*90685d2cSjp161948 return (rmul * NCMP(a->a.size, b->a.size)); 687*90685d2cSjp161948 688*90685d2cSjp161948 fatal("Unknown ls sort type"); 689*90685d2cSjp161948 690*90685d2cSjp161948 /* NOTREACHED */ 691*90685d2cSjp161948 return (0); 692*90685d2cSjp161948 } 693*90685d2cSjp161948 694*90685d2cSjp161948 /* sftp ls.1 replacement for directories */ 695*90685d2cSjp161948 static int 696*90685d2cSjp161948 do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag) 697*90685d2cSjp161948 { 698*90685d2cSjp161948 int n; 699*90685d2cSjp161948 u_int c = 1, colspace = 0, columns = 1; 700*90685d2cSjp161948 SFTP_DIRENT **d; 701*90685d2cSjp161948 702*90685d2cSjp161948 if ((n = do_readdir(conn, path, &d)) != 0) 703*90685d2cSjp161948 return (n); 704*90685d2cSjp161948 705*90685d2cSjp161948 if (!(lflag & LS_SHORT_VIEW)) { 706*90685d2cSjp161948 u_int m = 0, width = 80; 707*90685d2cSjp161948 struct winsize ws; 708*90685d2cSjp161948 char *tmp; 709*90685d2cSjp161948 710*90685d2cSjp161948 /* Count entries for sort and find longest filename */ 711*90685d2cSjp161948 for (n = 0; d[n] != NULL; n++) { 712*90685d2cSjp161948 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL)) 713*90685d2cSjp161948 m = MAX(m, strlen(d[n]->filename)); 714*90685d2cSjp161948 } 715*90685d2cSjp161948 716*90685d2cSjp161948 /* Add any subpath that also needs to be counted */ 717*90685d2cSjp161948 tmp = path_strip(path, strip_path); 718*90685d2cSjp161948 m += strlen(tmp); 719*90685d2cSjp161948 xfree(tmp); 720*90685d2cSjp161948 721*90685d2cSjp161948 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1) 722*90685d2cSjp161948 width = ws.ws_col; 723*90685d2cSjp161948 724*90685d2cSjp161948 columns = width / (m + 2); 725*90685d2cSjp161948 columns = MAX(columns, 1); 726*90685d2cSjp161948 colspace = width / columns; 727*90685d2cSjp161948 colspace = MIN(colspace, width); 728*90685d2cSjp161948 } 729*90685d2cSjp161948 730*90685d2cSjp161948 if (lflag & SORT_FLAGS) { 731*90685d2cSjp161948 for (n = 0; d[n] != NULL; n++) 732*90685d2cSjp161948 ; /* count entries */ 733*90685d2cSjp161948 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT); 734*90685d2cSjp161948 qsort(d, n, sizeof(*d), sdirent_comp); 735*90685d2cSjp161948 } 736*90685d2cSjp161948 737*90685d2cSjp161948 for (n = 0; d[n] != NULL && !interrupted; n++) { 738*90685d2cSjp161948 char *tmp, *fname; 739*90685d2cSjp161948 740*90685d2cSjp161948 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL)) 741*90685d2cSjp161948 continue; 742*90685d2cSjp161948 743*90685d2cSjp161948 tmp = path_append(path, d[n]->filename); 744*90685d2cSjp161948 fname = path_strip(tmp, strip_path); 745*90685d2cSjp161948 xfree(tmp); 746*90685d2cSjp161948 747*90685d2cSjp161948 if (lflag & LS_LONG_VIEW) { 748*90685d2cSjp161948 if (lflag & LS_NUMERIC_VIEW) { 749*90685d2cSjp161948 char *lname; 750*90685d2cSjp161948 struct stat sb; 751*90685d2cSjp161948 752*90685d2cSjp161948 memset(&sb, 0, sizeof(sb)); 753*90685d2cSjp161948 attrib_to_stat(&d[n]->a, &sb); 754*90685d2cSjp161948 lname = ls_file(fname, &sb, 1); 755*90685d2cSjp161948 printf("%s\n", lname); 756*90685d2cSjp161948 xfree(lname); 757*90685d2cSjp161948 } else 758*90685d2cSjp161948 printf("%s\n", d[n]->longname); 759*90685d2cSjp161948 } else { 760*90685d2cSjp161948 printf("%-*s", colspace, fname); 761*90685d2cSjp161948 if (c >= columns) { 762*90685d2cSjp161948 printf("\n"); 763*90685d2cSjp161948 c = 1; 764*90685d2cSjp161948 } else 765*90685d2cSjp161948 c++; 766*90685d2cSjp161948 } 767*90685d2cSjp161948 768*90685d2cSjp161948 xfree(fname); 769*90685d2cSjp161948 } 770*90685d2cSjp161948 771*90685d2cSjp161948 if (!(lflag & LS_LONG_VIEW) && (c != 1)) 772*90685d2cSjp161948 printf("\n"); 773*90685d2cSjp161948 774*90685d2cSjp161948 free_sftp_dirents(d); 775*90685d2cSjp161948 return (0); 776*90685d2cSjp161948 } 777*90685d2cSjp161948 778*90685d2cSjp161948 /* sftp ls.1 replacement which handles path globs */ 779*90685d2cSjp161948 static int 780*90685d2cSjp161948 do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path, 781*90685d2cSjp161948 int lflag) 782*90685d2cSjp161948 { 783*90685d2cSjp161948 glob_t g; 784*90685d2cSjp161948 u_int i, c = 1, colspace = 0, columns = 1; 785*90685d2cSjp161948 Attrib *a = NULL; 786*90685d2cSjp161948 787*90685d2cSjp161948 memset(&g, 0, sizeof(g)); 788*90685d2cSjp161948 789*90685d2cSjp161948 if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE, 790*90685d2cSjp161948 NULL, &g) || (g.gl_pathc && !g.gl_matchc)) { 791*90685d2cSjp161948 if (g.gl_pathc) 792*90685d2cSjp161948 globfree(&g); 793*90685d2cSjp161948 error("Can't ls: \"%s\" not found", path); 794*90685d2cSjp161948 return (-1); 795*90685d2cSjp161948 } 796*90685d2cSjp161948 797*90685d2cSjp161948 if (interrupted) 798*90685d2cSjp161948 goto out; 799*90685d2cSjp161948 800*90685d2cSjp161948 /* 801*90685d2cSjp161948 * If the glob returns a single match and it is a directory, 802*90685d2cSjp161948 * then just list its contents. 803*90685d2cSjp161948 */ 804*90685d2cSjp161948 if (g.gl_matchc == 1) { 805*90685d2cSjp161948 if ((a = do_lstat(conn, g.gl_pathv[0], 1)) == NULL) { 806*90685d2cSjp161948 globfree(&g); 807*90685d2cSjp161948 return (-1); 808*90685d2cSjp161948 } 809*90685d2cSjp161948 if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) && 810*90685d2cSjp161948 S_ISDIR(a->perm)) { 811*90685d2cSjp161948 int err; 812*90685d2cSjp161948 813*90685d2cSjp161948 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag); 814*90685d2cSjp161948 globfree(&g); 815*90685d2cSjp161948 return (err); 816*90685d2cSjp161948 } 817*90685d2cSjp161948 } 818*90685d2cSjp161948 819*90685d2cSjp161948 if (!(lflag & LS_SHORT_VIEW)) { 820*90685d2cSjp161948 u_int m = 0, width = 80; 821*90685d2cSjp161948 struct winsize ws; 822*90685d2cSjp161948 823*90685d2cSjp161948 /* Count entries for sort and find longest filename */ 824*90685d2cSjp161948 for (i = 0; g.gl_pathv[i]; i++) 825*90685d2cSjp161948 m = MAX(m, strlen(g.gl_pathv[i])); 826*90685d2cSjp161948 827*90685d2cSjp161948 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1) 828*90685d2cSjp161948 width = ws.ws_col; 829*90685d2cSjp161948 830*90685d2cSjp161948 columns = width / (m + 2); 831*90685d2cSjp161948 columns = MAX(columns, 1); 832*90685d2cSjp161948 colspace = width / columns; 833*90685d2cSjp161948 } 834*90685d2cSjp161948 835*90685d2cSjp161948 for (i = 0; g.gl_pathv[i] && !interrupted; i++, a = NULL) { 836*90685d2cSjp161948 char *fname; 837*90685d2cSjp161948 838*90685d2cSjp161948 fname = path_strip(g.gl_pathv[i], strip_path); 839*90685d2cSjp161948 840*90685d2cSjp161948 if (lflag & LS_LONG_VIEW) { 841*90685d2cSjp161948 char *lname; 842*90685d2cSjp161948 struct stat sb; 843*90685d2cSjp161948 844*90685d2cSjp161948 /* 845*90685d2cSjp161948 * XXX: this is slow - 1 roundtrip per path 846*90685d2cSjp161948 * A solution to this is to fork glob() and 847*90685d2cSjp161948 * build a sftp specific version which keeps the 848*90685d2cSjp161948 * attribs (which currently get thrown away) 849*90685d2cSjp161948 * that the server returns as well as the filenames. 850*90685d2cSjp161948 */ 851*90685d2cSjp161948 memset(&sb, 0, sizeof(sb)); 852*90685d2cSjp161948 if (a == NULL) 853*90685d2cSjp161948 a = do_lstat(conn, g.gl_pathv[i], 1); 854*90685d2cSjp161948 if (a != NULL) 855*90685d2cSjp161948 attrib_to_stat(a, &sb); 856*90685d2cSjp161948 lname = ls_file(fname, &sb, 1); 857*90685d2cSjp161948 printf("%s\n", lname); 858*90685d2cSjp161948 xfree(lname); 859*90685d2cSjp161948 } else { 860*90685d2cSjp161948 printf("%-*s", colspace, fname); 861*90685d2cSjp161948 if (c >= columns) { 862*90685d2cSjp161948 printf("\n"); 863*90685d2cSjp161948 c = 1; 864*90685d2cSjp161948 } else 865*90685d2cSjp161948 c++; 866*90685d2cSjp161948 } 867*90685d2cSjp161948 xfree(fname); 868*90685d2cSjp161948 } 869*90685d2cSjp161948 870*90685d2cSjp161948 if (!(lflag & LS_LONG_VIEW) && (c != 1)) 871*90685d2cSjp161948 printf("\n"); 872*90685d2cSjp161948 873*90685d2cSjp161948 out: 874*90685d2cSjp161948 if (g.gl_pathc) 875*90685d2cSjp161948 globfree(&g); 876*90685d2cSjp161948 877*90685d2cSjp161948 return (0); 878*90685d2cSjp161948 } 879*90685d2cSjp161948 880*90685d2cSjp161948 static int 881*90685d2cSjp161948 parse_args(const char **cpp, int *pflag, int *lflag, int *iflag, 882*90685d2cSjp161948 unsigned long *n_arg, char **path1, char **path2) 883*90685d2cSjp161948 { 884*90685d2cSjp161948 const char *cmd, *cp = *cpp; 885*90685d2cSjp161948 char *cp2; 886*90685d2cSjp161948 int base = 0; 887*90685d2cSjp161948 long l; 888*90685d2cSjp161948 int i, cmdnum; 889*90685d2cSjp161948 890*90685d2cSjp161948 /* Skip leading whitespace */ 891*90685d2cSjp161948 cp = cp + strspn(cp, WHITESPACE); 892*90685d2cSjp161948 893*90685d2cSjp161948 /* Ignore blank lines and lines which begin with comment '#' char */ 894*90685d2cSjp161948 if (*cp == '\0' || *cp == '#') 895*90685d2cSjp161948 return (0); 896*90685d2cSjp161948 897*90685d2cSjp161948 /* Check for leading '-' (disable error processing) */ 898*90685d2cSjp161948 *iflag = 0; 899*90685d2cSjp161948 if (*cp == '-') { 900*90685d2cSjp161948 *iflag = 1; 901*90685d2cSjp161948 cp++; 902*90685d2cSjp161948 } 903*90685d2cSjp161948 904*90685d2cSjp161948 /* Figure out which command we have */ 905*90685d2cSjp161948 for (i = 0; cmds[i].c; i++) { 906*90685d2cSjp161948 int cmdlen = strlen(cmds[i].c); 907*90685d2cSjp161948 908*90685d2cSjp161948 /* Check for command followed by whitespace */ 909*90685d2cSjp161948 if (!strncasecmp(cp, cmds[i].c, cmdlen) && 910*90685d2cSjp161948 strchr(WHITESPACE, cp[cmdlen])) { 911*90685d2cSjp161948 cp += cmdlen; 912*90685d2cSjp161948 cp = cp + strspn(cp, WHITESPACE); 913*90685d2cSjp161948 break; 914*90685d2cSjp161948 } 915*90685d2cSjp161948 } 916*90685d2cSjp161948 cmdnum = cmds[i].n; 917*90685d2cSjp161948 cmd = cmds[i].c; 918*90685d2cSjp161948 919*90685d2cSjp161948 /* Special case */ 920*90685d2cSjp161948 if (*cp == '!') { 921*90685d2cSjp161948 cp++; 922*90685d2cSjp161948 cmdnum = I_SHELL; 923*90685d2cSjp161948 } else if (cmdnum == -1) { 924*90685d2cSjp161948 error("Invalid command."); 925*90685d2cSjp161948 return (-1); 926*90685d2cSjp161948 } 927*90685d2cSjp161948 928*90685d2cSjp161948 /* Get arguments and parse flags */ 929*90685d2cSjp161948 *lflag = *pflag = *n_arg = 0; 930*90685d2cSjp161948 *path1 = *path2 = NULL; 931*90685d2cSjp161948 switch (cmdnum) { 932*90685d2cSjp161948 case I_GET: 933*90685d2cSjp161948 case I_PUT: 934*90685d2cSjp161948 if (parse_getput_flags(&cp, pflag)) 935*90685d2cSjp161948 return(-1); 936*90685d2cSjp161948 /* Get first pathname (mandatory) */ 937*90685d2cSjp161948 if (get_pathname(&cp, path1)) 938*90685d2cSjp161948 return(-1); 939*90685d2cSjp161948 if (*path1 == NULL) { 940*90685d2cSjp161948 error("You must specify at least one path after a " 941*90685d2cSjp161948 "%s command.", cmd); 942*90685d2cSjp161948 return(-1); 943*90685d2cSjp161948 } 944*90685d2cSjp161948 /* Try to get second pathname (optional) */ 945*90685d2cSjp161948 if (get_pathname(&cp, path2)) 946*90685d2cSjp161948 return(-1); 947*90685d2cSjp161948 break; 948*90685d2cSjp161948 case I_RENAME: 949*90685d2cSjp161948 case I_SYMLINK: 950*90685d2cSjp161948 if (get_pathname(&cp, path1)) 951*90685d2cSjp161948 return(-1); 952*90685d2cSjp161948 if (get_pathname(&cp, path2)) 953*90685d2cSjp161948 return(-1); 954*90685d2cSjp161948 if (!*path1 || !*path2) { 955*90685d2cSjp161948 error("You must specify two paths after a %s " 956*90685d2cSjp161948 "command.", cmd); 957*90685d2cSjp161948 return(-1); 958*90685d2cSjp161948 } 959*90685d2cSjp161948 break; 960*90685d2cSjp161948 case I_RM: 961*90685d2cSjp161948 case I_MKDIR: 962*90685d2cSjp161948 case I_RMDIR: 963*90685d2cSjp161948 case I_CHDIR: 964*90685d2cSjp161948 case I_LCHDIR: 965*90685d2cSjp161948 case I_LMKDIR: 966*90685d2cSjp161948 /* Get pathname (mandatory) */ 967*90685d2cSjp161948 if (get_pathname(&cp, path1)) 968*90685d2cSjp161948 return(-1); 969*90685d2cSjp161948 if (*path1 == NULL) { 970*90685d2cSjp161948 error("You must specify a path after a %s command.", 971*90685d2cSjp161948 cmd); 972*90685d2cSjp161948 return(-1); 973*90685d2cSjp161948 } 974*90685d2cSjp161948 break; 975*90685d2cSjp161948 case I_LS: 976*90685d2cSjp161948 if (parse_ls_flags(&cp, lflag)) 977*90685d2cSjp161948 return(-1); 978*90685d2cSjp161948 /* Path is optional */ 979*90685d2cSjp161948 if (get_pathname(&cp, path1)) 980*90685d2cSjp161948 return(-1); 981*90685d2cSjp161948 break; 982*90685d2cSjp161948 case I_LLS: 983*90685d2cSjp161948 case I_SHELL: 984*90685d2cSjp161948 /* Uses the rest of the line */ 985*90685d2cSjp161948 break; 986*90685d2cSjp161948 case I_LUMASK: 987*90685d2cSjp161948 base = 8; 988*90685d2cSjp161948 /* FALLTHRU */ 989*90685d2cSjp161948 case I_CHMOD: 990*90685d2cSjp161948 base = 8; 991*90685d2cSjp161948 /* FALLTHRU */ 992*90685d2cSjp161948 case I_CHOWN: 993*90685d2cSjp161948 case I_CHGRP: 994*90685d2cSjp161948 /* Get numeric arg (mandatory) */ 995*90685d2cSjp161948 errno = 0; 996*90685d2cSjp161948 l = strtol(cp, &cp2, base); 997*90685d2cSjp161948 if (cp2 == cp || ((l == LONG_MIN || l == LONG_MAX) && 998*90685d2cSjp161948 errno == ERANGE) || l < 0) { 999*90685d2cSjp161948 error("You must supply a numeric argument " 1000*90685d2cSjp161948 "to the %s command.", cmd); 1001*90685d2cSjp161948 return(-1); 1002*90685d2cSjp161948 } 1003*90685d2cSjp161948 cp = cp2; 1004*90685d2cSjp161948 *n_arg = l; 1005*90685d2cSjp161948 if (cmdnum == I_LUMASK && strchr(WHITESPACE, *cp)) 1006*90685d2cSjp161948 break; 1007*90685d2cSjp161948 if (cmdnum == I_LUMASK || !strchr(WHITESPACE, *cp)) { 1008*90685d2cSjp161948 error("You must supply a numeric argument " 1009*90685d2cSjp161948 "to the %s command.", cmd); 1010*90685d2cSjp161948 return(-1); 1011*90685d2cSjp161948 } 1012*90685d2cSjp161948 cp += strspn(cp, WHITESPACE); 1013*90685d2cSjp161948 1014*90685d2cSjp161948 /* Get pathname (mandatory) */ 1015*90685d2cSjp161948 if (get_pathname(&cp, path1)) 1016*90685d2cSjp161948 return(-1); 1017*90685d2cSjp161948 if (*path1 == NULL) { 1018*90685d2cSjp161948 error("You must specify a path after a %s command.", 1019*90685d2cSjp161948 cmd); 1020*90685d2cSjp161948 return(-1); 1021*90685d2cSjp161948 } 1022*90685d2cSjp161948 break; 1023*90685d2cSjp161948 case I_QUIT: 1024*90685d2cSjp161948 case I_PWD: 1025*90685d2cSjp161948 case I_LPWD: 1026*90685d2cSjp161948 case I_HELP: 1027*90685d2cSjp161948 case I_VERSION: 1028*90685d2cSjp161948 case I_PROGRESS: 1029*90685d2cSjp161948 break; 1030*90685d2cSjp161948 default: 1031*90685d2cSjp161948 fatal("Command not implemented"); 1032*90685d2cSjp161948 } 1033*90685d2cSjp161948 1034*90685d2cSjp161948 *cpp = cp; 1035*90685d2cSjp161948 return(cmdnum); 1036*90685d2cSjp161948 } 1037*90685d2cSjp161948 1038*90685d2cSjp161948 static int 1039*90685d2cSjp161948 parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, 1040*90685d2cSjp161948 int err_abort) 1041*90685d2cSjp161948 { 1042*90685d2cSjp161948 char *path1, *path2, *tmp; 1043*90685d2cSjp161948 int pflag, lflag, iflag, cmdnum, i; 1044*90685d2cSjp161948 unsigned long n_arg; 1045*90685d2cSjp161948 Attrib a, *aa; 1046*90685d2cSjp161948 char path_buf[MAXPATHLEN]; 1047*90685d2cSjp161948 int err = 0; 1048*90685d2cSjp161948 glob_t g; 1049*90685d2cSjp161948 1050*90685d2cSjp161948 path1 = path2 = NULL; 1051*90685d2cSjp161948 cmdnum = parse_args(&cmd, &pflag, &lflag, &iflag, &n_arg, 1052*90685d2cSjp161948 &path1, &path2); 1053*90685d2cSjp161948 1054*90685d2cSjp161948 if (iflag != 0) 1055*90685d2cSjp161948 err_abort = 0; 1056*90685d2cSjp161948 1057*90685d2cSjp161948 memset(&g, 0, sizeof(g)); 1058*90685d2cSjp161948 1059*90685d2cSjp161948 /* Perform command */ 1060*90685d2cSjp161948 switch (cmdnum) { 1061*90685d2cSjp161948 case 0: 1062*90685d2cSjp161948 /* Blank line */ 1063*90685d2cSjp161948 break; 1064*90685d2cSjp161948 case -1: 1065*90685d2cSjp161948 /* Unrecognized command */ 1066*90685d2cSjp161948 err = -1; 1067*90685d2cSjp161948 break; 1068*90685d2cSjp161948 case I_GET: 1069*90685d2cSjp161948 err = process_get(conn, path1, path2, *pwd, pflag); 1070*90685d2cSjp161948 break; 1071*90685d2cSjp161948 case I_PUT: 1072*90685d2cSjp161948 err = process_put(conn, path1, path2, *pwd, pflag); 1073*90685d2cSjp161948 break; 1074*90685d2cSjp161948 case I_RENAME: 1075*90685d2cSjp161948 path1 = make_absolute(path1, *pwd); 1076*90685d2cSjp161948 path2 = make_absolute(path2, *pwd); 1077*90685d2cSjp161948 err = do_rename(conn, path1, path2); 1078*90685d2cSjp161948 break; 1079*90685d2cSjp161948 case I_SYMLINK: 1080*90685d2cSjp161948 path2 = make_absolute(path2, *pwd); 1081*90685d2cSjp161948 err = do_symlink(conn, path1, path2); 1082*90685d2cSjp161948 break; 1083*90685d2cSjp161948 case I_RM: 1084*90685d2cSjp161948 path1 = make_absolute(path1, *pwd); 1085*90685d2cSjp161948 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); 1086*90685d2cSjp161948 for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 1087*90685d2cSjp161948 printf(gettext("Removing %s\n"), g.gl_pathv[i]); 1088*90685d2cSjp161948 err = do_rm(conn, g.gl_pathv[i]); 1089*90685d2cSjp161948 if (err != 0 && err_abort) 1090*90685d2cSjp161948 break; 1091*90685d2cSjp161948 } 1092*90685d2cSjp161948 break; 1093*90685d2cSjp161948 case I_MKDIR: 1094*90685d2cSjp161948 path1 = make_absolute(path1, *pwd); 1095*90685d2cSjp161948 attrib_clear(&a); 1096*90685d2cSjp161948 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; 1097*90685d2cSjp161948 a.perm = 0777; 1098*90685d2cSjp161948 err = do_mkdir(conn, path1, &a); 1099*90685d2cSjp161948 break; 1100*90685d2cSjp161948 case I_RMDIR: 1101*90685d2cSjp161948 path1 = make_absolute(path1, *pwd); 1102*90685d2cSjp161948 err = do_rmdir(conn, path1); 1103*90685d2cSjp161948 break; 1104*90685d2cSjp161948 case I_CHDIR: 1105*90685d2cSjp161948 path1 = make_absolute(path1, *pwd); 1106*90685d2cSjp161948 if ((tmp = do_realpath(conn, path1)) == NULL) { 1107*90685d2cSjp161948 err = 1; 1108*90685d2cSjp161948 break; 1109*90685d2cSjp161948 } 1110*90685d2cSjp161948 if ((aa = do_stat(conn, tmp, 0)) == NULL) { 1111*90685d2cSjp161948 xfree(tmp); 1112*90685d2cSjp161948 err = 1; 1113*90685d2cSjp161948 break; 1114*90685d2cSjp161948 } 1115*90685d2cSjp161948 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) { 1116*90685d2cSjp161948 error("Can't change directory: Can't check target"); 1117*90685d2cSjp161948 xfree(tmp); 1118*90685d2cSjp161948 err = 1; 1119*90685d2cSjp161948 break; 1120*90685d2cSjp161948 } 1121*90685d2cSjp161948 if (!S_ISDIR(aa->perm)) { 1122*90685d2cSjp161948 error("Can't change directory: \"%s\" is not " 1123*90685d2cSjp161948 "a directory", tmp); 1124*90685d2cSjp161948 xfree(tmp); 1125*90685d2cSjp161948 err = 1; 1126*90685d2cSjp161948 break; 1127*90685d2cSjp161948 } 1128*90685d2cSjp161948 xfree(*pwd); 1129*90685d2cSjp161948 *pwd = tmp; 1130*90685d2cSjp161948 break; 1131*90685d2cSjp161948 case I_LS: 1132*90685d2cSjp161948 if (!path1) { 1133*90685d2cSjp161948 do_globbed_ls(conn, *pwd, *pwd, lflag); 1134*90685d2cSjp161948 break; 1135*90685d2cSjp161948 } 1136*90685d2cSjp161948 1137*90685d2cSjp161948 /* Strip pwd off beginning of non-absolute paths */ 1138*90685d2cSjp161948 tmp = NULL; 1139*90685d2cSjp161948 if (*path1 != '/') 1140*90685d2cSjp161948 tmp = *pwd; 1141*90685d2cSjp161948 1142*90685d2cSjp161948 path1 = make_absolute(path1, *pwd); 1143*90685d2cSjp161948 err = do_globbed_ls(conn, path1, tmp, lflag); 1144*90685d2cSjp161948 break; 1145*90685d2cSjp161948 case I_LCHDIR: 1146*90685d2cSjp161948 if (chdir(path1) == -1) { 1147*90685d2cSjp161948 error("Couldn't change local directory to " 1148*90685d2cSjp161948 "\"%s\": %s", path1, strerror(errno)); 1149*90685d2cSjp161948 err = 1; 1150*90685d2cSjp161948 } 1151*90685d2cSjp161948 break; 1152*90685d2cSjp161948 case I_LMKDIR: 1153*90685d2cSjp161948 if (mkdir(path1, 0777) == -1) { 1154*90685d2cSjp161948 error("Couldn't create local directory " 1155*90685d2cSjp161948 "\"%s\": %s", path1, strerror(errno)); 1156*90685d2cSjp161948 err = 1; 1157*90685d2cSjp161948 } 1158*90685d2cSjp161948 break; 1159*90685d2cSjp161948 case I_LLS: 1160*90685d2cSjp161948 local_do_ls(cmd); 1161*90685d2cSjp161948 break; 1162*90685d2cSjp161948 case I_SHELL: 1163*90685d2cSjp161948 local_do_shell(cmd); 1164*90685d2cSjp161948 break; 1165*90685d2cSjp161948 case I_LUMASK: 1166*90685d2cSjp161948 umask(n_arg); 1167*90685d2cSjp161948 printf(gettext("Local umask: %03lo\n"), n_arg); 1168*90685d2cSjp161948 break; 1169*90685d2cSjp161948 case I_CHMOD: 1170*90685d2cSjp161948 path1 = make_absolute(path1, *pwd); 1171*90685d2cSjp161948 attrib_clear(&a); 1172*90685d2cSjp161948 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; 1173*90685d2cSjp161948 a.perm = n_arg; 1174*90685d2cSjp161948 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); 1175*90685d2cSjp161948 for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 1176*90685d2cSjp161948 printf(gettext("Changing mode on %s\n"), g.gl_pathv[i]); 1177*90685d2cSjp161948 err = do_setstat(conn, g.gl_pathv[i], &a); 1178*90685d2cSjp161948 if (err != 0 && err_abort) 1179*90685d2cSjp161948 break; 1180*90685d2cSjp161948 } 1181*90685d2cSjp161948 break; 1182*90685d2cSjp161948 case I_CHOWN: 1183*90685d2cSjp161948 case I_CHGRP: 1184*90685d2cSjp161948 path1 = make_absolute(path1, *pwd); 1185*90685d2cSjp161948 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); 1186*90685d2cSjp161948 for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 1187*90685d2cSjp161948 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) { 1188*90685d2cSjp161948 if (err != 0 && err_abort) 1189*90685d2cSjp161948 break; 1190*90685d2cSjp161948 else 1191*90685d2cSjp161948 continue; 1192*90685d2cSjp161948 } 1193*90685d2cSjp161948 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) { 1194*90685d2cSjp161948 error("Can't get current ownership of " 1195*90685d2cSjp161948 "remote file \"%s\"", g.gl_pathv[i]); 1196*90685d2cSjp161948 if (err != 0 && err_abort) 1197*90685d2cSjp161948 break; 1198*90685d2cSjp161948 else 1199*90685d2cSjp161948 continue; 1200*90685d2cSjp161948 } 1201*90685d2cSjp161948 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID; 1202*90685d2cSjp161948 if (cmdnum == I_CHOWN) { 1203*90685d2cSjp161948 printf(gettext("Changing owner on %s\n"), g.gl_pathv[i]); 1204*90685d2cSjp161948 aa->uid = n_arg; 1205*90685d2cSjp161948 } else { 1206*90685d2cSjp161948 printf(gettext("Changing group on %s\n"), g.gl_pathv[i]); 1207*90685d2cSjp161948 aa->gid = n_arg; 1208*90685d2cSjp161948 } 1209*90685d2cSjp161948 err = do_setstat(conn, g.gl_pathv[i], aa); 1210*90685d2cSjp161948 if (err != 0 && err_abort) 1211*90685d2cSjp161948 break; 1212*90685d2cSjp161948 } 1213*90685d2cSjp161948 break; 1214*90685d2cSjp161948 case I_PWD: 1215*90685d2cSjp161948 printf(gettext("Remote working directory: %s\n"), *pwd); 1216*90685d2cSjp161948 break; 1217*90685d2cSjp161948 case I_LPWD: 1218*90685d2cSjp161948 if (!getcwd(path_buf, sizeof(path_buf))) { 1219*90685d2cSjp161948 error("Couldn't get local cwd: %s", strerror(errno)); 1220*90685d2cSjp161948 err = -1; 1221*90685d2cSjp161948 break; 1222*90685d2cSjp161948 } 1223*90685d2cSjp161948 printf(gettext("Local working directory: %s\n"), path_buf); 1224*90685d2cSjp161948 break; 1225*90685d2cSjp161948 case I_QUIT: 1226*90685d2cSjp161948 /* Processed below */ 1227*90685d2cSjp161948 break; 1228*90685d2cSjp161948 case I_HELP: 1229*90685d2cSjp161948 help(); 1230*90685d2cSjp161948 break; 1231*90685d2cSjp161948 case I_VERSION: 1232*90685d2cSjp161948 printf(gettext("SFTP protocol version %u\n"), sftp_proto_version(conn)); 1233*90685d2cSjp161948 break; 1234*90685d2cSjp161948 case I_PROGRESS: 1235*90685d2cSjp161948 showprogress = !showprogress; 1236*90685d2cSjp161948 if (showprogress) 1237*90685d2cSjp161948 printf("Progress meter enabled\n"); 1238*90685d2cSjp161948 else 1239*90685d2cSjp161948 printf("Progress meter disabled\n"); 1240*90685d2cSjp161948 break; 1241*90685d2cSjp161948 default: 1242*90685d2cSjp161948 fatal("%d is not implemented", cmdnum); 1243*90685d2cSjp161948 } 1244*90685d2cSjp161948 1245*90685d2cSjp161948 if (g.gl_pathc) 1246*90685d2cSjp161948 globfree(&g); 1247*90685d2cSjp161948 if (path1) 1248*90685d2cSjp161948 xfree(path1); 1249*90685d2cSjp161948 if (path2) 1250*90685d2cSjp161948 xfree(path2); 1251*90685d2cSjp161948 1252*90685d2cSjp161948 /* If an unignored error occurs in batch mode we should abort. */ 1253*90685d2cSjp161948 if (err_abort && err != 0) 1254*90685d2cSjp161948 return (-1); 1255*90685d2cSjp161948 else if (cmdnum == I_QUIT) 1256*90685d2cSjp161948 return (1); 1257*90685d2cSjp161948 1258*90685d2cSjp161948 return (0); 1259*90685d2cSjp161948 } 1260*90685d2cSjp161948 1261*90685d2cSjp161948 #ifdef USE_LIBEDIT 1262*90685d2cSjp161948 static char * 1263*90685d2cSjp161948 prompt(EditLine *el) 1264*90685d2cSjp161948 { 1265*90685d2cSjp161948 return ("sftp> "); 1266*90685d2cSjp161948 } 1267*90685d2cSjp161948 #endif 1268*90685d2cSjp161948 1269*90685d2cSjp161948 int 1270*90685d2cSjp161948 interactive_loop(int fd_in, int fd_out, char *file1, char *file2) 1271*90685d2cSjp161948 { 1272*90685d2cSjp161948 char *pwd; 1273*90685d2cSjp161948 char *dir = NULL; 1274*90685d2cSjp161948 char cmd[2048]; 1275*90685d2cSjp161948 struct sftp_conn *conn; 1276*90685d2cSjp161948 int err, interactive; 1277*90685d2cSjp161948 EditLine *el = NULL; 1278*90685d2cSjp161948 #ifdef USE_LIBEDIT 1279*90685d2cSjp161948 History *hl = NULL; 1280*90685d2cSjp161948 HistEvent hev; 1281*90685d2cSjp161948 1282*90685d2cSjp161948 if (!batchmode && isatty(STDIN_FILENO)) { 1283*90685d2cSjp161948 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL) 1284*90685d2cSjp161948 fatal("Couldn't initialise editline"); 1285*90685d2cSjp161948 if ((hl = history_init()) == NULL) 1286*90685d2cSjp161948 fatal("Couldn't initialise editline history"); 1287*90685d2cSjp161948 history(hl, &hev, H_SETSIZE, 100); 1288*90685d2cSjp161948 el_set(el, EL_HIST, history, hl); 1289*90685d2cSjp161948 1290*90685d2cSjp161948 el_set(el, EL_PROMPT, prompt); 1291*90685d2cSjp161948 el_set(el, EL_EDITOR, "emacs"); 1292*90685d2cSjp161948 el_set(el, EL_TERMINAL, NULL); 1293*90685d2cSjp161948 el_set(el, EL_SIGNAL, 1); 1294*90685d2cSjp161948 el_source(el, NULL); 1295*90685d2cSjp161948 } 1296*90685d2cSjp161948 #endif /* USE_LIBEDIT */ 1297*90685d2cSjp161948 1298*90685d2cSjp161948 conn = do_init(fd_in, fd_out, copy_buffer_len, num_requests); 1299*90685d2cSjp161948 if (conn == NULL) 1300*90685d2cSjp161948 fatal("Couldn't initialise connection to server"); 1301*90685d2cSjp161948 1302*90685d2cSjp161948 pwd = do_realpath(conn, "."); 1303*90685d2cSjp161948 if (pwd == NULL) 1304*90685d2cSjp161948 fatal("Need cwd"); 1305*90685d2cSjp161948 1306*90685d2cSjp161948 if (file1 != NULL) { 1307*90685d2cSjp161948 dir = xstrdup(file1); 1308*90685d2cSjp161948 dir = make_absolute(dir, pwd); 1309*90685d2cSjp161948 1310*90685d2cSjp161948 if (remote_is_dir(conn, dir) && file2 == NULL) { 1311*90685d2cSjp161948 printf(gettext("Changing to: %s\n"), dir); 1312*90685d2cSjp161948 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir); 1313*90685d2cSjp161948 if (parse_dispatch_command(conn, cmd, &pwd, 1) != 0) { 1314*90685d2cSjp161948 xfree(dir); 1315*90685d2cSjp161948 xfree(pwd); 1316*90685d2cSjp161948 xfree(conn); 1317*90685d2cSjp161948 return (-1); 1318*90685d2cSjp161948 } 1319*90685d2cSjp161948 } else { 1320*90685d2cSjp161948 if (file2 == NULL) 1321*90685d2cSjp161948 snprintf(cmd, sizeof cmd, "get %s", dir); 1322*90685d2cSjp161948 else 1323*90685d2cSjp161948 snprintf(cmd, sizeof cmd, "get %s %s", dir, 1324*90685d2cSjp161948 file2); 1325*90685d2cSjp161948 1326*90685d2cSjp161948 err = parse_dispatch_command(conn, cmd, &pwd, 1); 1327*90685d2cSjp161948 xfree(dir); 1328*90685d2cSjp161948 xfree(pwd); 1329*90685d2cSjp161948 xfree(conn); 1330*90685d2cSjp161948 return (err); 1331*90685d2cSjp161948 } 1332*90685d2cSjp161948 xfree(dir); 1333*90685d2cSjp161948 } 1334*90685d2cSjp161948 1335*90685d2cSjp161948 #if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF) 1336*90685d2cSjp161948 setvbuf(stdout, NULL, _IOLBF, 0); 1337*90685d2cSjp161948 setvbuf(infile, NULL, _IOLBF, 0); 1338*90685d2cSjp161948 #else 1339*90685d2cSjp161948 setlinebuf(stdout); 1340*90685d2cSjp161948 setlinebuf(infile); 1341*90685d2cSjp161948 #endif 1342*90685d2cSjp161948 1343*90685d2cSjp161948 interactive = !batchmode && isatty(STDIN_FILENO); 1344*90685d2cSjp161948 err = 0; 1345*90685d2cSjp161948 for (;;) { 1346*90685d2cSjp161948 char *cp; 1347*90685d2cSjp161948 1348*90685d2cSjp161948 signal(SIGINT, SIG_IGN); 1349*90685d2cSjp161948 1350*90685d2cSjp161948 if (el == NULL) { 1351*90685d2cSjp161948 if (interactive) 1352*90685d2cSjp161948 printf("sftp> "); 1353*90685d2cSjp161948 if (fgets(cmd, sizeof(cmd), infile) == NULL) { 1354*90685d2cSjp161948 if (interactive) 1355*90685d2cSjp161948 printf("\n"); 1356*90685d2cSjp161948 break; 1357*90685d2cSjp161948 } 1358*90685d2cSjp161948 if (!interactive) { /* Echo command */ 1359*90685d2cSjp161948 printf("sftp> %s", cmd); 1360*90685d2cSjp161948 if (strlen(cmd) > 0 && 1361*90685d2cSjp161948 cmd[strlen(cmd) - 1] != '\n') 1362*90685d2cSjp161948 printf("\n"); 1363*90685d2cSjp161948 } 1364*90685d2cSjp161948 } 1365*90685d2cSjp161948 #ifdef USE_LIBEDIT 1366*90685d2cSjp161948 else { 1367*90685d2cSjp161948 const char *line; 1368*90685d2cSjp161948 int count = 0; 1369*90685d2cSjp161948 1370*90685d2cSjp161948 if ((line = el_gets(el, &count)) == NULL || count <= 0) { 1371*90685d2cSjp161948 printf("\n"); 1372*90685d2cSjp161948 break; 1373*90685d2cSjp161948 } 1374*90685d2cSjp161948 history(hl, &hev, H_ENTER, line); 1375*90685d2cSjp161948 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) { 1376*90685d2cSjp161948 fprintf(stderr, gettext("Error: input line too long\n")); 1377*90685d2cSjp161948 continue; 1378*90685d2cSjp161948 } 1379*90685d2cSjp161948 } 1380*90685d2cSjp161948 #endif /* USE_LIBEDIT */ 1381*90685d2cSjp161948 1382*90685d2cSjp161948 cp = strrchr(cmd, '\n'); 1383*90685d2cSjp161948 if (cp) 1384*90685d2cSjp161948 *cp = '\0'; 1385*90685d2cSjp161948 1386*90685d2cSjp161948 /* Handle user interrupts gracefully during commands */ 1387*90685d2cSjp161948 interrupted = 0; 1388*90685d2cSjp161948 signal(SIGINT, cmd_interrupt); 1389*90685d2cSjp161948 1390*90685d2cSjp161948 err = parse_dispatch_command(conn, cmd, &pwd, batchmode); 1391*90685d2cSjp161948 if (err != 0) 1392*90685d2cSjp161948 break; 1393*90685d2cSjp161948 } 1394*90685d2cSjp161948 xfree(pwd); 1395*90685d2cSjp161948 xfree(conn); 1396*90685d2cSjp161948 1397*90685d2cSjp161948 #ifdef USE_LIBEDIT 1398*90685d2cSjp161948 if (el != NULL) 1399*90685d2cSjp161948 el_end(el); 1400*90685d2cSjp161948 #endif /* USE_LIBEDIT */ 1401*90685d2cSjp161948 1402*90685d2cSjp161948 /* err == 1 signifies normal "quit" exit */ 1403*90685d2cSjp161948 return (err >= 0 ? 0 : -1); 1404*90685d2cSjp161948 } 1405*90685d2cSjp161948 1406*90685d2cSjp161948 static void 1407*90685d2cSjp161948 connect_to_server(char *path, char **args, int *in, int *out) 14087c478bd9Sstevel@tonic-gate { 14097c478bd9Sstevel@tonic-gate int c_in, c_out; 14107c478bd9Sstevel@tonic-gate 14117c478bd9Sstevel@tonic-gate #ifdef USE_PIPES 14127c478bd9Sstevel@tonic-gate int pin[2], pout[2]; 14137c478bd9Sstevel@tonic-gate 14147c478bd9Sstevel@tonic-gate if ((pipe(pin) == -1) || (pipe(pout) == -1)) 14157c478bd9Sstevel@tonic-gate fatal("pipe: %s", strerror(errno)); 14167c478bd9Sstevel@tonic-gate *in = pin[0]; 14177c478bd9Sstevel@tonic-gate *out = pout[1]; 14187c478bd9Sstevel@tonic-gate c_in = pout[0]; 14197c478bd9Sstevel@tonic-gate c_out = pin[1]; 14207c478bd9Sstevel@tonic-gate #else /* USE_PIPES */ 14217c478bd9Sstevel@tonic-gate int inout[2]; 14227c478bd9Sstevel@tonic-gate 14237c478bd9Sstevel@tonic-gate if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1) 14247c478bd9Sstevel@tonic-gate fatal("socketpair: %s", strerror(errno)); 14257c478bd9Sstevel@tonic-gate *in = *out = inout[0]; 14267c478bd9Sstevel@tonic-gate c_in = c_out = inout[1]; 14277c478bd9Sstevel@tonic-gate #endif /* USE_PIPES */ 14287c478bd9Sstevel@tonic-gate 1429*90685d2cSjp161948 if ((sshpid = fork()) == -1) 14307c478bd9Sstevel@tonic-gate fatal("fork: %s", strerror(errno)); 1431*90685d2cSjp161948 else if (sshpid == 0) { 14327c478bd9Sstevel@tonic-gate if ((dup2(c_in, STDIN_FILENO) == -1) || 14337c478bd9Sstevel@tonic-gate (dup2(c_out, STDOUT_FILENO) == -1)) { 14347c478bd9Sstevel@tonic-gate fprintf(stderr, "dup2: %s\n", strerror(errno)); 1435*90685d2cSjp161948 _exit(1); 14367c478bd9Sstevel@tonic-gate } 14377c478bd9Sstevel@tonic-gate close(*in); 14387c478bd9Sstevel@tonic-gate close(*out); 14397c478bd9Sstevel@tonic-gate close(c_in); 14407c478bd9Sstevel@tonic-gate close(c_out); 1441*90685d2cSjp161948 1442*90685d2cSjp161948 /* 1443*90685d2cSjp161948 * The underlying ssh is in the same process group, so we must 1444*90685d2cSjp161948 * ignore SIGINT if we want to gracefully abort commands, 1445*90685d2cSjp161948 * otherwise the signal will make it to the ssh process and 1446*90685d2cSjp161948 * kill it too 1447*90685d2cSjp161948 */ 1448*90685d2cSjp161948 signal(SIGINT, SIG_IGN); 1449*90685d2cSjp161948 execvp(path, args); 14507c478bd9Sstevel@tonic-gate fprintf(stderr, "exec: %s: %s\n", path, strerror(errno)); 1451*90685d2cSjp161948 _exit(1); 14527c478bd9Sstevel@tonic-gate } 14537c478bd9Sstevel@tonic-gate 1454*90685d2cSjp161948 signal(SIGTERM, killchild); 1455*90685d2cSjp161948 signal(SIGINT, killchild); 1456*90685d2cSjp161948 signal(SIGHUP, killchild); 14577c478bd9Sstevel@tonic-gate close(c_in); 14587c478bd9Sstevel@tonic-gate close(c_out); 14597c478bd9Sstevel@tonic-gate } 14607c478bd9Sstevel@tonic-gate 14617c478bd9Sstevel@tonic-gate static void 14627c478bd9Sstevel@tonic-gate usage(void) 14637c478bd9Sstevel@tonic-gate { 14647c478bd9Sstevel@tonic-gate fprintf(stderr, 1465*90685d2cSjp161948 gettext("Usage: %s [-1Cv] [-b batchfile] [-B buffer_size]\n" 1466*90685d2cSjp161948 " [-F ssh_config] [-o ssh_option] [-P sftp_server_path]\n" 1467*90685d2cSjp161948 " [-R num_requests] [-s subsystem | sftp_server]\n" 1468*90685d2cSjp161948 " [-S program] [user@]host[:dir[/] | :file [file]]\n"), 1469*90685d2cSjp161948 __progname, __progname, __progname, __progname); 14707c478bd9Sstevel@tonic-gate exit(1); 14717c478bd9Sstevel@tonic-gate } 14727c478bd9Sstevel@tonic-gate 14737c478bd9Sstevel@tonic-gate int 14747c478bd9Sstevel@tonic-gate main(int argc, char **argv) 14757c478bd9Sstevel@tonic-gate { 1476*90685d2cSjp161948 int in, out, ch, err; 1477*90685d2cSjp161948 char *host, *userhost, *cp, *file2 = NULL; 14787c478bd9Sstevel@tonic-gate int debug_level = 0, sshver = 2; 14797c478bd9Sstevel@tonic-gate char *file1 = NULL, *sftp_server = NULL; 14807c478bd9Sstevel@tonic-gate char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL; 14817c478bd9Sstevel@tonic-gate LogLevel ll = SYSLOG_LEVEL_INFO; 14827c478bd9Sstevel@tonic-gate arglist args; 14837c478bd9Sstevel@tonic-gate extern int optind; 14847c478bd9Sstevel@tonic-gate extern char *optarg; 14857c478bd9Sstevel@tonic-gate 1486*90685d2cSjp161948 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ 1487*90685d2cSjp161948 sanitise_stdfd(); 1488*90685d2cSjp161948 14897c478bd9Sstevel@tonic-gate __progname = get_progname(argv[0]); 14907c478bd9Sstevel@tonic-gate 14917c478bd9Sstevel@tonic-gate (void) g11n_setlocale(LC_ALL, ""); 1492*90685d2cSjp161948 1493*90685d2cSjp161948 memset(&args, '\0', sizeof(args)); 14947c478bd9Sstevel@tonic-gate args.list = NULL; 1495*90685d2cSjp161948 addargs(&args, "%s", ssh_program); 14967c478bd9Sstevel@tonic-gate addargs(&args, "-oForwardX11 no"); 14977c478bd9Sstevel@tonic-gate addargs(&args, "-oForwardAgent no"); 14987c478bd9Sstevel@tonic-gate addargs(&args, "-oClearAllForwardings yes"); 1499*90685d2cSjp161948 15007c478bd9Sstevel@tonic-gate ll = SYSLOG_LEVEL_INFO; 1501*90685d2cSjp161948 infile = stdin; 15027c478bd9Sstevel@tonic-gate 15037c478bd9Sstevel@tonic-gate while ((ch = getopt(argc, argv, "1hvCo:s:S:b:B:F:P:R:")) != -1) { 15047c478bd9Sstevel@tonic-gate switch (ch) { 15057c478bd9Sstevel@tonic-gate case 'C': 15067c478bd9Sstevel@tonic-gate addargs(&args, "-C"); 15077c478bd9Sstevel@tonic-gate break; 15087c478bd9Sstevel@tonic-gate case 'v': 15097c478bd9Sstevel@tonic-gate if (debug_level < 3) { 15107c478bd9Sstevel@tonic-gate addargs(&args, "-v"); 15117c478bd9Sstevel@tonic-gate ll = SYSLOG_LEVEL_DEBUG1 + debug_level; 15127c478bd9Sstevel@tonic-gate } 15137c478bd9Sstevel@tonic-gate debug_level++; 15147c478bd9Sstevel@tonic-gate break; 15157c478bd9Sstevel@tonic-gate case 'F': 15167c478bd9Sstevel@tonic-gate case 'o': 15177c478bd9Sstevel@tonic-gate addargs(&args, "-%c%s", ch, optarg); 15187c478bd9Sstevel@tonic-gate break; 15197c478bd9Sstevel@tonic-gate case '1': 15207c478bd9Sstevel@tonic-gate sshver = 1; 15217c478bd9Sstevel@tonic-gate if (sftp_server == NULL) 15227c478bd9Sstevel@tonic-gate sftp_server = _PATH_SFTP_SERVER; 15237c478bd9Sstevel@tonic-gate break; 15247c478bd9Sstevel@tonic-gate case 's': 15257c478bd9Sstevel@tonic-gate sftp_server = optarg; 15267c478bd9Sstevel@tonic-gate break; 15277c478bd9Sstevel@tonic-gate case 'S': 15287c478bd9Sstevel@tonic-gate ssh_program = optarg; 1529*90685d2cSjp161948 replacearg(&args, 0, "%s", ssh_program); 15307c478bd9Sstevel@tonic-gate break; 15317c478bd9Sstevel@tonic-gate case 'b': 1532*90685d2cSjp161948 if (batchmode) 1533*90685d2cSjp161948 fatal("Batch file already specified."); 1534*90685d2cSjp161948 1535*90685d2cSjp161948 /* Allow "-" as stdin */ 1536*90685d2cSjp161948 if (strcmp(optarg, "-") != 0 && 1537*90685d2cSjp161948 (infile = fopen(optarg, "r")) == NULL) 15387c478bd9Sstevel@tonic-gate fatal("%s (%s).", strerror(errno), optarg); 1539*90685d2cSjp161948 showprogress = 0; 1540*90685d2cSjp161948 batchmode = 1; 1541*90685d2cSjp161948 addargs(&args, "-obatchmode yes"); 15427c478bd9Sstevel@tonic-gate break; 15437c478bd9Sstevel@tonic-gate case 'P': 15447c478bd9Sstevel@tonic-gate sftp_direct = optarg; 15457c478bd9Sstevel@tonic-gate break; 15467c478bd9Sstevel@tonic-gate case 'B': 15477c478bd9Sstevel@tonic-gate copy_buffer_len = strtol(optarg, &cp, 10); 15487c478bd9Sstevel@tonic-gate if (copy_buffer_len == 0 || *cp != '\0') 15497c478bd9Sstevel@tonic-gate fatal("Invalid buffer size \"%s\"", optarg); 15507c478bd9Sstevel@tonic-gate break; 15517c478bd9Sstevel@tonic-gate case 'R': 15527c478bd9Sstevel@tonic-gate num_requests = strtol(optarg, &cp, 10); 15537c478bd9Sstevel@tonic-gate if (num_requests == 0 || *cp != '\0') 15547c478bd9Sstevel@tonic-gate fatal("Invalid number of requests \"%s\"", 15557c478bd9Sstevel@tonic-gate optarg); 15567c478bd9Sstevel@tonic-gate break; 15577c478bd9Sstevel@tonic-gate case 'h': 15587c478bd9Sstevel@tonic-gate default: 15597c478bd9Sstevel@tonic-gate usage(); 15607c478bd9Sstevel@tonic-gate } 15617c478bd9Sstevel@tonic-gate } 15627c478bd9Sstevel@tonic-gate 1563*90685d2cSjp161948 if (!isatty(STDERR_FILENO)) 1564*90685d2cSjp161948 showprogress = 0; 1565*90685d2cSjp161948 15667c478bd9Sstevel@tonic-gate log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1); 15677c478bd9Sstevel@tonic-gate 15687c478bd9Sstevel@tonic-gate if (sftp_direct == NULL) { 15697c478bd9Sstevel@tonic-gate if (optind == argc || argc > (optind + 2)) 15707c478bd9Sstevel@tonic-gate usage(); 15717c478bd9Sstevel@tonic-gate 15727c478bd9Sstevel@tonic-gate userhost = xstrdup(argv[optind]); 15737c478bd9Sstevel@tonic-gate file2 = argv[optind+1]; 15747c478bd9Sstevel@tonic-gate 1575*90685d2cSjp161948 if ((host = strrchr(userhost, '@')) == NULL) 15767c478bd9Sstevel@tonic-gate host = userhost; 15777c478bd9Sstevel@tonic-gate else { 15787c478bd9Sstevel@tonic-gate *host++ = '\0'; 15797c478bd9Sstevel@tonic-gate if (!userhost[0]) { 15807c478bd9Sstevel@tonic-gate fprintf(stderr, gettext("Missing username\n")); 15817c478bd9Sstevel@tonic-gate usage(); 15827c478bd9Sstevel@tonic-gate } 15837c478bd9Sstevel@tonic-gate addargs(&args, "-l%s", userhost); 15847c478bd9Sstevel@tonic-gate } 15857c478bd9Sstevel@tonic-gate 1586*90685d2cSjp161948 if ((cp = colon(host)) != NULL) { 1587*90685d2cSjp161948 *cp++ = '\0'; 1588*90685d2cSjp161948 file1 = cp; 1589*90685d2cSjp161948 } 1590*90685d2cSjp161948 15917c478bd9Sstevel@tonic-gate host = cleanhostname(host); 15927c478bd9Sstevel@tonic-gate if (!*host) { 15937c478bd9Sstevel@tonic-gate fprintf(stderr, gettext("Missing hostname\n")); 15947c478bd9Sstevel@tonic-gate usage(); 15957c478bd9Sstevel@tonic-gate } 15967c478bd9Sstevel@tonic-gate 15977c478bd9Sstevel@tonic-gate addargs(&args, "-oProtocol %d", sshver); 15987c478bd9Sstevel@tonic-gate 15997c478bd9Sstevel@tonic-gate /* no subsystem if the server-spec contains a '/' */ 16007c478bd9Sstevel@tonic-gate if (sftp_server == NULL || strchr(sftp_server, '/') == NULL) 16017c478bd9Sstevel@tonic-gate addargs(&args, "-s"); 16027c478bd9Sstevel@tonic-gate 16037c478bd9Sstevel@tonic-gate addargs(&args, "%s", host); 16047c478bd9Sstevel@tonic-gate addargs(&args, "%s", (sftp_server != NULL ? 16057c478bd9Sstevel@tonic-gate sftp_server : "sftp")); 16067c478bd9Sstevel@tonic-gate 1607*90685d2cSjp161948 if (!batchmode) 16087c478bd9Sstevel@tonic-gate fprintf(stderr, gettext("Connecting to %s...\n"), host); 1609*90685d2cSjp161948 connect_to_server(ssh_program, args.list, &in, &out); 16107c478bd9Sstevel@tonic-gate } else { 16117c478bd9Sstevel@tonic-gate args.list = NULL; 16127c478bd9Sstevel@tonic-gate addargs(&args, "sftp-server"); 16137c478bd9Sstevel@tonic-gate 1614*90685d2cSjp161948 if (!batchmode) 16157c478bd9Sstevel@tonic-gate fprintf(stderr, gettext("Attaching to %s...\n"), sftp_direct); 1616*90685d2cSjp161948 connect_to_server(sftp_direct, args.list, &in, &out); 16177c478bd9Sstevel@tonic-gate } 1618*90685d2cSjp161948 freeargs(&args); 16197c478bd9Sstevel@tonic-gate 1620*90685d2cSjp161948 err = interactive_loop(in, out, file1, file2); 16217c478bd9Sstevel@tonic-gate 16227c478bd9Sstevel@tonic-gate #if !defined(USE_PIPES) 16237c478bd9Sstevel@tonic-gate shutdown(in, SHUT_RDWR); 16247c478bd9Sstevel@tonic-gate shutdown(out, SHUT_RDWR); 16257c478bd9Sstevel@tonic-gate #endif 16267c478bd9Sstevel@tonic-gate 16277c478bd9Sstevel@tonic-gate close(in); 16287c478bd9Sstevel@tonic-gate close(out); 1629*90685d2cSjp161948 if (batchmode) 16307c478bd9Sstevel@tonic-gate fclose(infile); 16317c478bd9Sstevel@tonic-gate 16327c478bd9Sstevel@tonic-gate while (waitpid(sshpid, NULL, 0) == -1) 16337c478bd9Sstevel@tonic-gate if (errno != EINTR) 16347c478bd9Sstevel@tonic-gate fatal("Couldn't wait for ssh process: %s", 16357c478bd9Sstevel@tonic-gate strerror(errno)); 16367c478bd9Sstevel@tonic-gate 1637*90685d2cSjp161948 return (err == 0 ? 0 : 1); 16387c478bd9Sstevel@tonic-gate } 1639