blob: 969328de4df013e7c9f1bf6dee4de10558748ae9 [file] [log] [blame]
Damien Miller0d032412013-07-25 11:56:52 +10001/* $OpenBSD: sftp.c,v 1.148 2013/07/25 00:56:52 djm Exp $ */
Damien Miller33804262001-02-04 23:20:18 +11002/*
Damien Miller4e60ed72004-02-17 17:07:59 +11003 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
Damien Miller33804262001-02-04 23:20:18 +11004 *
Damien Miller4e60ed72004-02-17 17:07:59 +11005 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
Damien Miller33804262001-02-04 23:20:18 +11008 *
Damien Miller4e60ed72004-02-17 17:07:59 +11009 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Damien Miller33804262001-02-04 23:20:18 +110016 */
17
18#include "includes.h"
19
Damien Miller9cf6d072006-03-15 11:29:24 +110020#include <sys/types.h>
Damien Millerd7834352006-08-05 12:39:39 +100021#include <sys/ioctl.h>
Damien Millerf17883e2006-03-15 11:45:54 +110022#ifdef HAVE_SYS_STAT_H
23# include <sys/stat.h>
24#endif
Damien Miller8dbffe72006-08-05 11:02:17 +100025#include <sys/param.h>
Damien Millere3b60b52006-07-10 21:08:03 +100026#include <sys/socket.h>
Damien Miller9cf6d072006-03-15 11:29:24 +110027#include <sys/wait.h>
Darren Tucker5b2e2ba2008-06-08 09:25:28 +100028#ifdef HAVE_SYS_STATVFS_H
Damien Millerd671e5a2008-05-19 14:53:33 +100029#include <sys/statvfs.h>
Darren Tucker5b2e2ba2008-06-08 09:25:28 +100030#endif
Darren Tucker2d963d82004-11-07 20:04:10 +110031
Damien Miller1cbc2922007-10-26 14:27:45 +100032#include <ctype.h>
Darren Tucker39972492006-07-12 22:22:46 +100033#include <errno.h>
34
Damien Miller03e20032006-03-15 11:16:59 +110035#ifdef HAVE_PATHS_H
Damien Millera9263d02006-03-15 11:18:26 +110036# include <paths.h>
Damien Miller03e20032006-03-15 11:16:59 +110037#endif
Darren Tucker1b0dd172009-10-07 08:37:48 +110038#ifdef HAVE_LIBGEN_H
39#include <libgen.h>
40#endif
Darren Tuckerae133d42013-06-06 08:30:20 +100041#ifdef HAVE_LOCALE_H
42# include <locale.h>
43#endif
Darren Tucker2d963d82004-11-07 20:04:10 +110044#ifdef USE_LIBEDIT
45#include <histedit.h>
46#else
47typedef void EditLine;
48#endif
Damien Miller6ff3cad2006-03-15 11:52:09 +110049#include <signal.h>
Damien Millere7a1e5c2006-08-05 11:34:19 +100050#include <stdlib.h>
Damien Millera7a73ee2006-08-05 11:37:59 +100051#include <stdio.h>
Damien Millere3476ed2006-07-24 14:13:33 +100052#include <string.h>
Damien Millere6b3b612006-07-24 14:01:23 +100053#include <unistd.h>
Damien Millerd7834352006-08-05 12:39:39 +100054#include <stdarg.h>
Damien Miller33804262001-02-04 23:20:18 +110055
Damien Millera7058ec2008-05-20 08:57:06 +100056#ifdef HAVE_UTIL_H
57# include <util.h>
58#endif
59
Damien Miller33804262001-02-04 23:20:18 +110060#include "xmalloc.h"
61#include "log.h"
62#include "pathnames.h"
Ben Lindstrom4529b702001-05-03 23:39:53 +000063#include "misc.h"
Damien Miller33804262001-02-04 23:20:18 +110064
65#include "sftp.h"
Damien Millerd7834352006-08-05 12:39:39 +100066#include "buffer.h"
Damien Miller33804262001-02-04 23:20:18 +110067#include "sftp-common.h"
68#include "sftp-client.h"
Damien Millerd7d46bb2004-02-18 14:11:13 +110069
Darren Tucker21063192010-01-08 17:10:36 +110070#define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */
71#define DEFAULT_NUM_REQUESTS 64 /* # concurrent outstanding requests */
72
Damien Miller20e1fab2004-02-18 14:30:55 +110073/* File to read commands from */
74FILE* infile;
75
76/* Are we in batchfile mode? */
77int batchmode = 0;
78
Damien Miller20e1fab2004-02-18 14:30:55 +110079/* PID of ssh transport process */
80static pid_t sshpid = -1;
81
Damien Miller9303e652013-04-23 15:22:40 +100082/* Suppress diagnositic messages */
83int quiet = 0;
84
Damien Miller20e1fab2004-02-18 14:30:55 +110085/* This is set to 0 if the progressmeter is not desired. */
Damien Millerc0f27d82004-03-08 23:12:19 +110086int showprogress = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +110087
Darren Tucker1b0dd172009-10-07 08:37:48 +110088/* When this option is set, we always recursively download/upload directories */
89int global_rflag = 0;
90
Damien Miller0d032412013-07-25 11:56:52 +100091/* When this option is set, we resume download if possible */
92int global_aflag = 0;
93
Darren Tucker1b0dd172009-10-07 08:37:48 +110094/* When this option is set, the file transfers will always preserve times */
95int global_pflag = 0;
96
Darren Tuckercdf547a2004-05-24 10:12:19 +100097/* SIGINT received during command processing */
98volatile sig_atomic_t interrupted = 0;
99
Darren Tuckerb9123452004-06-22 13:06:45 +1000100/* I wish qsort() took a separate ctx for the comparison function...*/
101int sort_flag;
102
Darren Tucker909d8582010-01-08 19:02:40 +1100103/* Context used for commandline completion */
104struct complete_ctx {
105 struct sftp_conn *conn;
106 char **remote_pathp;
107};
108
Damien Miller20e1fab2004-02-18 14:30:55 +1100109int remote_glob(struct sftp_conn *, const char *, int,
110 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
Damien Miller33804262001-02-04 23:20:18 +1100111
Kevin Steves12888d12001-03-05 19:50:57 +0000112extern char *__progname;
Kevin Steves12888d12001-03-05 19:50:57 +0000113
Damien Miller20e1fab2004-02-18 14:30:55 +1100114/* Separators for interactive commands */
115#define WHITESPACE " \t\r\n"
Damien Millerd7686fd2001-02-10 00:40:03 +1100116
Darren Tuckerb9123452004-06-22 13:06:45 +1000117/* ls flags */
Darren Tucker2901e2d2010-01-13 22:44:06 +1100118#define LS_LONG_VIEW 0x0001 /* Full view ala ls -l */
119#define LS_SHORT_VIEW 0x0002 /* Single row view ala ls -1 */
120#define LS_NUMERIC_VIEW 0x0004 /* Long view with numeric uid/gid */
121#define LS_NAME_SORT 0x0008 /* Sort by name (default) */
122#define LS_TIME_SORT 0x0010 /* Sort by mtime */
123#define LS_SIZE_SORT 0x0020 /* Sort by file size */
124#define LS_REVERSE_SORT 0x0040 /* Reverse sort order */
125#define LS_SHOW_ALL 0x0080 /* Don't skip filenames starting with '.' */
126#define LS_SI_UNITS 0x0100 /* Display sizes as K, M, G, etc. */
Darren Tuckerb9123452004-06-22 13:06:45 +1000127
Darren Tucker2901e2d2010-01-13 22:44:06 +1100128#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS)
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000129#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
Damien Miller20e1fab2004-02-18 14:30:55 +1100130
131/* Commands for interactive mode */
132#define I_CHDIR 1
133#define I_CHGRP 2
134#define I_CHMOD 3
135#define I_CHOWN 4
Damien Millerd671e5a2008-05-19 14:53:33 +1000136#define I_DF 24
Damien Miller20e1fab2004-02-18 14:30:55 +1100137#define I_GET 5
138#define I_HELP 6
139#define I_LCHDIR 7
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100140#define I_LINK 25
Damien Miller20e1fab2004-02-18 14:30:55 +1100141#define I_LLS 8
142#define I_LMKDIR 9
143#define I_LPWD 10
144#define I_LS 11
145#define I_LUMASK 12
146#define I_MKDIR 13
147#define I_PUT 14
148#define I_PWD 15
149#define I_QUIT 16
150#define I_RENAME 17
151#define I_RM 18
152#define I_RMDIR 19
153#define I_SHELL 20
154#define I_SYMLINK 21
155#define I_VERSION 22
156#define I_PROGRESS 23
Damien Miller0d032412013-07-25 11:56:52 +1000157#define I_REGET 26
Damien Miller20e1fab2004-02-18 14:30:55 +1100158
159struct CMD {
160 const char *c;
161 const int n;
Darren Tucker909d8582010-01-08 19:02:40 +1100162 const int t;
Damien Miller20e1fab2004-02-18 14:30:55 +1100163};
164
Darren Tucker909d8582010-01-08 19:02:40 +1100165/* Type of completion */
166#define NOARGS 0
167#define REMOTE 1
168#define LOCAL 2
169
Damien Miller20e1fab2004-02-18 14:30:55 +1100170static const struct CMD cmds[] = {
Darren Tucker909d8582010-01-08 19:02:40 +1100171 { "bye", I_QUIT, NOARGS },
172 { "cd", I_CHDIR, REMOTE },
173 { "chdir", I_CHDIR, REMOTE },
174 { "chgrp", I_CHGRP, REMOTE },
175 { "chmod", I_CHMOD, REMOTE },
176 { "chown", I_CHOWN, REMOTE },
177 { "df", I_DF, REMOTE },
178 { "dir", I_LS, REMOTE },
179 { "exit", I_QUIT, NOARGS },
180 { "get", I_GET, REMOTE },
181 { "help", I_HELP, NOARGS },
182 { "lcd", I_LCHDIR, LOCAL },
183 { "lchdir", I_LCHDIR, LOCAL },
184 { "lls", I_LLS, LOCAL },
185 { "lmkdir", I_LMKDIR, LOCAL },
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100186 { "ln", I_LINK, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100187 { "lpwd", I_LPWD, LOCAL },
188 { "ls", I_LS, REMOTE },
189 { "lumask", I_LUMASK, NOARGS },
190 { "mkdir", I_MKDIR, REMOTE },
Damien Miller099fc162010-05-10 11:56:50 +1000191 { "mget", I_GET, REMOTE },
192 { "mput", I_PUT, LOCAL },
Darren Tucker909d8582010-01-08 19:02:40 +1100193 { "progress", I_PROGRESS, NOARGS },
194 { "put", I_PUT, LOCAL },
195 { "pwd", I_PWD, REMOTE },
196 { "quit", I_QUIT, NOARGS },
Damien Miller0d032412013-07-25 11:56:52 +1000197 { "reget", I_REGET, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100198 { "rename", I_RENAME, REMOTE },
199 { "rm", I_RM, REMOTE },
200 { "rmdir", I_RMDIR, REMOTE },
201 { "symlink", I_SYMLINK, REMOTE },
202 { "version", I_VERSION, NOARGS },
203 { "!", I_SHELL, NOARGS },
204 { "?", I_HELP, NOARGS },
205 { NULL, -1, -1 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100206};
207
Darren Tucker21063192010-01-08 17:10:36 +1100208int interactive_loop(struct sftp_conn *, char *file1, char *file2);
Damien Miller20e1fab2004-02-18 14:30:55 +1100209
Damien Millerb6c85fc2007-01-05 16:30:41 +1100210/* ARGSUSED */
Damien Miller20e1fab2004-02-18 14:30:55 +1100211static void
Darren Tuckercdf547a2004-05-24 10:12:19 +1000212killchild(int signo)
213{
Darren Tuckerba66df82005-01-24 21:57:40 +1100214 if (sshpid > 1) {
Darren Tuckercdf547a2004-05-24 10:12:19 +1000215 kill(sshpid, SIGTERM);
Darren Tuckerba66df82005-01-24 21:57:40 +1100216 waitpid(sshpid, NULL, 0);
217 }
Darren Tuckercdf547a2004-05-24 10:12:19 +1000218
219 _exit(1);
220}
221
Damien Millerb6c85fc2007-01-05 16:30:41 +1100222/* ARGSUSED */
Darren Tuckercdf547a2004-05-24 10:12:19 +1000223static void
224cmd_interrupt(int signo)
225{
226 const char msg[] = "\rInterrupt \n";
Darren Tuckere2f189a2004-12-06 22:45:53 +1100227 int olderrno = errno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000228
Darren Tuckerdbee3082013-05-16 20:32:29 +1000229 (void)write(STDERR_FILENO, msg, sizeof(msg) - 1);
Darren Tuckercdf547a2004-05-24 10:12:19 +1000230 interrupted = 1;
Darren Tuckere2f189a2004-12-06 22:45:53 +1100231 errno = olderrno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000232}
233
234static void
Damien Miller20e1fab2004-02-18 14:30:55 +1100235help(void)
236{
Damien Miller62fd18a2009-01-28 16:14:09 +1100237 printf("Available commands:\n"
238 "bye Quit sftp\n"
239 "cd path Change remote directory to 'path'\n"
240 "chgrp grp path Change group of file 'path' to 'grp'\n"
241 "chmod mode path Change permissions of file 'path' to 'mode'\n"
242 "chown own path Change owner of file 'path' to 'own'\n"
243 "df [-hi] [path] Display statistics for current directory or\n"
244 " filesystem containing 'path'\n"
245 "exit Quit sftp\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100246 "get [-Ppr] remote [local] Download file\n"
Damien Miller0d032412013-07-25 11:56:52 +1000247 "reget remote [local] Resume download file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100248 "help Display this help text\n"
249 "lcd path Change local directory to 'path'\n"
250 "lls [ls-options [path]] Display local directory listing\n"
251 "lmkdir path Create local directory\n"
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100252 "ln [-s] oldpath newpath Link remote file (-s for symlink)\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100253 "lpwd Print local working directory\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100254 "ls [-1afhlnrSt] [path] Display remote directory listing\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100255 "lumask umask Set local umask to 'umask'\n"
256 "mkdir path Create remote directory\n"
257 "progress Toggle display of progress meter\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100258 "put [-Ppr] local [remote] Upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100259 "pwd Display remote working directory\n"
260 "quit Quit sftp\n"
261 "rename oldpath newpath Rename remote file\n"
262 "rm path Delete remote file\n"
263 "rmdir path Remove remote directory\n"
264 "symlink oldpath newpath Symlink remote file\n"
265 "version Show SFTP version\n"
266 "!command Execute 'command' in local shell\n"
267 "! Escape to local shell\n"
268 "? Synonym for help\n");
Damien Miller20e1fab2004-02-18 14:30:55 +1100269}
270
271static void
272local_do_shell(const char *args)
273{
274 int status;
275 char *shell;
276 pid_t pid;
277
278 if (!*args)
279 args = NULL;
280
Damien Miller38d9a962010-10-07 22:07:11 +1100281 if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
Damien Miller20e1fab2004-02-18 14:30:55 +1100282 shell = _PATH_BSHELL;
283
284 if ((pid = fork()) == -1)
285 fatal("Couldn't fork: %s", strerror(errno));
286
287 if (pid == 0) {
288 /* XXX: child has pipe fds to ssh subproc open - issue? */
289 if (args) {
290 debug3("Executing %s -c \"%s\"", shell, args);
291 execl(shell, shell, "-c", args, (char *)NULL);
292 } else {
293 debug3("Executing %s", shell);
294 execl(shell, shell, (char *)NULL);
295 }
296 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
297 strerror(errno));
298 _exit(1);
299 }
300 while (waitpid(pid, &status, 0) == -1)
301 if (errno != EINTR)
302 fatal("Couldn't wait for child: %s", strerror(errno));
303 if (!WIFEXITED(status))
Damien Miller55b04f12006-03-26 14:23:17 +1100304 error("Shell exited abnormally");
Damien Miller20e1fab2004-02-18 14:30:55 +1100305 else if (WEXITSTATUS(status))
306 error("Shell exited with status %d", WEXITSTATUS(status));
307}
308
309static void
310local_do_ls(const char *args)
311{
312 if (!args || !*args)
313 local_do_shell(_PATH_LS);
314 else {
315 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
316 char *buf = xmalloc(len);
317
318 /* XXX: quoting - rip quoting code from ftp? */
319 snprintf(buf, len, _PATH_LS " %s", args);
320 local_do_shell(buf);
Darren Tuckera627d422013-06-02 07:31:17 +1000321 free(buf);
Damien Miller20e1fab2004-02-18 14:30:55 +1100322 }
323}
324
325/* Strip one path (usually the pwd) from the start of another */
326static char *
327path_strip(char *path, char *strip)
328{
329 size_t len;
330
331 if (strip == NULL)
332 return (xstrdup(path));
333
334 len = strlen(strip);
Darren Tuckere2f189a2004-12-06 22:45:53 +1100335 if (strncmp(path, strip, len) == 0) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100336 if (strip[len - 1] != '/' && path[len] == '/')
337 len++;
338 return (xstrdup(path + len));
339 }
340
341 return (xstrdup(path));
342}
343
344static char *
Damien Miller20e1fab2004-02-18 14:30:55 +1100345make_absolute(char *p, char *pwd)
346{
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000347 char *abs_str;
Damien Miller20e1fab2004-02-18 14:30:55 +1100348
349 /* Derelativise */
350 if (p && p[0] != '/') {
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000351 abs_str = path_append(pwd, p);
Darren Tuckera627d422013-06-02 07:31:17 +1000352 free(p);
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000353 return(abs_str);
Damien Miller20e1fab2004-02-18 14:30:55 +1100354 } else
355 return(p);
356}
357
358static int
Damien Miller0d032412013-07-25 11:56:52 +1000359parse_getput_flags(const char *cmd, char **argv, int argc,
360 int *aflag, int *pflag, int *rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100361{
Damien Millerf184bcf2008-06-29 22:45:13 +1000362 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000363 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100364
Damien Miller1cbc2922007-10-26 14:27:45 +1000365 optind = optreset = 1;
366 opterr = 0;
367
Damien Miller0d032412013-07-25 11:56:52 +1000368 *aflag = *rflag = *pflag = 0;
369 while ((ch = getopt(argc, argv, "aPpRr")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000370 switch (ch) {
Damien Miller0d032412013-07-25 11:56:52 +1000371 case 'a':
372 *aflag = 1;
373 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100374 case 'p':
375 case 'P':
376 *pflag = 1;
377 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100378 case 'r':
379 case 'R':
380 *rflag = 1;
381 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100382 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000383 error("%s: Invalid flag -%c", cmd, optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000384 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100385 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100386 }
387
Damien Miller1cbc2922007-10-26 14:27:45 +1000388 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100389}
390
391static int
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100392parse_link_flags(const char *cmd, char **argv, int argc, int *sflag)
393{
394 extern int opterr, optind, optopt, optreset;
395 int ch;
396
397 optind = optreset = 1;
398 opterr = 0;
399
400 *sflag = 0;
401 while ((ch = getopt(argc, argv, "s")) != -1) {
402 switch (ch) {
403 case 's':
404 *sflag = 1;
405 break;
406 default:
407 error("%s: Invalid flag -%c", cmd, optopt);
408 return -1;
409 }
410 }
411
412 return optind;
413}
414
415static int
Damien Miller1cbc2922007-10-26 14:27:45 +1000416parse_ls_flags(char **argv, int argc, int *lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100417{
Damien Millerf184bcf2008-06-29 22:45:13 +1000418 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000419 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100420
Damien Miller1cbc2922007-10-26 14:27:45 +1000421 optind = optreset = 1;
422 opterr = 0;
423
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000424 *lflag = LS_NAME_SORT;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100425 while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000426 switch (ch) {
427 case '1':
428 *lflag &= ~VIEW_FLAGS;
429 *lflag |= LS_SHORT_VIEW;
430 break;
431 case 'S':
432 *lflag &= ~SORT_FLAGS;
433 *lflag |= LS_SIZE_SORT;
434 break;
435 case 'a':
436 *lflag |= LS_SHOW_ALL;
437 break;
438 case 'f':
439 *lflag &= ~SORT_FLAGS;
440 break;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100441 case 'h':
442 *lflag |= LS_SI_UNITS;
443 break;
Damien Miller1cbc2922007-10-26 14:27:45 +1000444 case 'l':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100445 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000446 *lflag |= LS_LONG_VIEW;
447 break;
448 case 'n':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100449 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000450 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
451 break;
452 case 'r':
453 *lflag |= LS_REVERSE_SORT;
454 break;
455 case 't':
456 *lflag &= ~SORT_FLAGS;
457 *lflag |= LS_TIME_SORT;
458 break;
459 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000460 error("ls: Invalid flag -%c", optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000461 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100462 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100463 }
464
Damien Miller1cbc2922007-10-26 14:27:45 +1000465 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100466}
467
468static int
Damien Millerd671e5a2008-05-19 14:53:33 +1000469parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
470{
Damien Millerf184bcf2008-06-29 22:45:13 +1000471 extern int opterr, optind, optopt, optreset;
Damien Millerd671e5a2008-05-19 14:53:33 +1000472 int ch;
473
474 optind = optreset = 1;
475 opterr = 0;
476
477 *hflag = *iflag = 0;
478 while ((ch = getopt(argc, argv, "hi")) != -1) {
479 switch (ch) {
480 case 'h':
481 *hflag = 1;
482 break;
483 case 'i':
484 *iflag = 1;
485 break;
486 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000487 error("%s: Invalid flag -%c", cmd, optopt);
Damien Millerd671e5a2008-05-19 14:53:33 +1000488 return -1;
489 }
490 }
491
492 return optind;
493}
494
495static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100496is_dir(char *path)
497{
498 struct stat sb;
499
500 /* XXX: report errors? */
501 if (stat(path, &sb) == -1)
502 return(0);
503
Darren Tucker1e80e402006-09-21 12:59:33 +1000504 return(S_ISDIR(sb.st_mode));
Damien Miller20e1fab2004-02-18 14:30:55 +1100505}
506
507static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100508remote_is_dir(struct sftp_conn *conn, char *path)
509{
510 Attrib *a;
511
512 /* XXX: report errors? */
513 if ((a = do_stat(conn, path, 1)) == NULL)
514 return(0);
515 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
516 return(0);
Darren Tucker1e80e402006-09-21 12:59:33 +1000517 return(S_ISDIR(a->perm));
Damien Miller20e1fab2004-02-18 14:30:55 +1100518}
519
Darren Tucker1b0dd172009-10-07 08:37:48 +1100520/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
Damien Miller20e1fab2004-02-18 14:30:55 +1100521static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100522pathname_is_dir(char *pathname)
523{
524 size_t l = strlen(pathname);
525
526 return l > 0 && pathname[l - 1] == '/';
527}
528
529static int
530process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
Damien Miller0d032412013-07-25 11:56:52 +1000531 int pflag, int rflag, int resume)
Damien Miller20e1fab2004-02-18 14:30:55 +1100532{
533 char *abs_src = NULL;
534 char *abs_dst = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100535 glob_t g;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100536 char *filename, *tmp=NULL;
537 int i, err = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100538
539 abs_src = xstrdup(src);
540 abs_src = make_absolute(abs_src, pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +1100541 memset(&g, 0, sizeof(g));
Darren Tucker1b0dd172009-10-07 08:37:48 +1100542
Damien Miller20e1fab2004-02-18 14:30:55 +1100543 debug3("Looking up %s", abs_src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100544 if (remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100545 error("File \"%s\" not found.", abs_src);
546 err = -1;
547 goto out;
548 }
549
Darren Tucker1b0dd172009-10-07 08:37:48 +1100550 /*
551 * If multiple matches then dst must be a directory or
552 * unspecified.
553 */
554 if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
555 error("Multiple source paths, but destination "
556 "\"%s\" is not a directory", dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100557 err = -1;
558 goto out;
559 }
560
Darren Tuckercdf547a2004-05-24 10:12:19 +1000561 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100562 tmp = xstrdup(g.gl_pathv[i]);
563 if ((filename = basename(tmp)) == NULL) {
564 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000565 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100566 err = -1;
567 goto out;
568 }
569
570 if (g.gl_matchc == 1 && dst) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100571 if (is_dir(dst)) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100572 abs_dst = path_append(dst, filename);
573 } else {
Damien Miller20e1fab2004-02-18 14:30:55 +1100574 abs_dst = xstrdup(dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100575 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100576 } else if (dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100577 abs_dst = path_append(dst, filename);
578 } else {
579 abs_dst = xstrdup(filename);
580 }
Darren Tuckera627d422013-06-02 07:31:17 +1000581 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100582
Damien Miller0d032412013-07-25 11:56:52 +1000583 resume |= global_aflag;
584 if (!quiet && resume)
585 printf("Resuming %s to %s\n", g.gl_pathv[i], abs_dst);
586 else if (!quiet && !resume)
Damien Miller9303e652013-04-23 15:22:40 +1000587 printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100588 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
Damien Miller0d032412013-07-25 11:56:52 +1000589 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
590 pflag || global_pflag, 1, resume) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100591 err = -1;
592 } else {
593 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
Damien Miller0d032412013-07-25 11:56:52 +1000594 pflag || global_pflag, resume) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100595 err = -1;
596 }
Darren Tuckera627d422013-06-02 07:31:17 +1000597 free(abs_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100598 abs_dst = NULL;
599 }
600
601out:
Darren Tuckera627d422013-06-02 07:31:17 +1000602 free(abs_src);
Damien Miller20e1fab2004-02-18 14:30:55 +1100603 globfree(&g);
604 return(err);
605}
606
607static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100608process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
609 int pflag, int rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100610{
611 char *tmp_dst = NULL;
612 char *abs_dst = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100613 char *tmp = NULL, *filename = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100614 glob_t g;
615 int err = 0;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100616 int i, dst_is_dir = 1;
Damien Milleraec5cf82008-02-10 22:26:24 +1100617 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100618
619 if (dst) {
620 tmp_dst = xstrdup(dst);
621 tmp_dst = make_absolute(tmp_dst, pwd);
622 }
623
624 memset(&g, 0, sizeof(g));
625 debug3("Looking up %s", src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100626 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100627 error("File \"%s\" not found.", src);
628 err = -1;
629 goto out;
630 }
631
Darren Tucker1b0dd172009-10-07 08:37:48 +1100632 /* If we aren't fetching to pwd then stash this status for later */
633 if (tmp_dst != NULL)
634 dst_is_dir = remote_is_dir(conn, tmp_dst);
635
Damien Miller20e1fab2004-02-18 14:30:55 +1100636 /* If multiple matches, dst may be directory or unspecified */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100637 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
638 error("Multiple paths match, but destination "
639 "\"%s\" is not a directory", tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100640 err = -1;
641 goto out;
642 }
643
Darren Tuckercdf547a2004-05-24 10:12:19 +1000644 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Milleraec5cf82008-02-10 22:26:24 +1100645 if (stat(g.gl_pathv[i], &sb) == -1) {
646 err = -1;
647 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
648 continue;
649 }
Darren Tucker1b0dd172009-10-07 08:37:48 +1100650
651 tmp = xstrdup(g.gl_pathv[i]);
652 if ((filename = basename(tmp)) == NULL) {
653 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000654 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100655 err = -1;
656 goto out;
657 }
658
659 if (g.gl_matchc == 1 && tmp_dst) {
660 /* If directory specified, append filename */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100661 if (dst_is_dir)
662 abs_dst = path_append(tmp_dst, filename);
663 else
Damien Miller20e1fab2004-02-18 14:30:55 +1100664 abs_dst = xstrdup(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100665 } else if (tmp_dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100666 abs_dst = path_append(tmp_dst, filename);
667 } else {
668 abs_dst = make_absolute(xstrdup(filename), pwd);
669 }
Darren Tuckera627d422013-06-02 07:31:17 +1000670 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100671
Damien Miller9303e652013-04-23 15:22:40 +1000672 if (!quiet)
673 printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100674 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
675 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
676 pflag || global_pflag, 1) == -1)
677 err = -1;
678 } else {
679 if (do_upload(conn, g.gl_pathv[i], abs_dst,
680 pflag || global_pflag) == -1)
681 err = -1;
682 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100683 }
684
685out:
Darren Tuckera627d422013-06-02 07:31:17 +1000686 free(abs_dst);
687 free(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100688 globfree(&g);
689 return(err);
690}
691
692static int
693sdirent_comp(const void *aa, const void *bb)
694{
695 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
696 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000697 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100698
Darren Tuckerb9123452004-06-22 13:06:45 +1000699#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000700 if (sort_flag & LS_NAME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000701 return (rmul * strcmp(a->filename, b->filename));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000702 else if (sort_flag & LS_TIME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000703 return (rmul * NCMP(a->a.mtime, b->a.mtime));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000704 else if (sort_flag & LS_SIZE_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000705 return (rmul * NCMP(a->a.size, b->a.size));
706
707 fatal("Unknown ls sort type");
Damien Miller20e1fab2004-02-18 14:30:55 +1100708}
709
710/* sftp ls.1 replacement for directories */
711static int
712do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
713{
Damien Millereccb9de2005-06-17 12:59:34 +1000714 int n;
715 u_int c = 1, colspace = 0, columns = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100716 SFTP_DIRENT **d;
717
718 if ((n = do_readdir(conn, path, &d)) != 0)
719 return (n);
720
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000721 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000722 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100723 struct winsize ws;
724 char *tmp;
725
726 /* Count entries for sort and find longest filename */
Darren Tucker9a526452004-06-22 13:09:55 +1000727 for (n = 0; d[n] != NULL; n++) {
728 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
729 m = MAX(m, strlen(d[n]->filename));
730 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100731
732 /* Add any subpath that also needs to be counted */
733 tmp = path_strip(path, strip_path);
734 m += strlen(tmp);
Darren Tuckera627d422013-06-02 07:31:17 +1000735 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100736
737 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
738 width = ws.ws_col;
739
740 columns = width / (m + 2);
741 columns = MAX(columns, 1);
742 colspace = width / columns;
743 colspace = MIN(colspace, width);
744 }
745
Darren Tuckerb9123452004-06-22 13:06:45 +1000746 if (lflag & SORT_FLAGS) {
Damien Miller653b93b2005-11-05 15:15:23 +1100747 for (n = 0; d[n] != NULL; n++)
748 ; /* count entries */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000749 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
Darren Tuckerb9123452004-06-22 13:06:45 +1000750 qsort(d, n, sizeof(*d), sdirent_comp);
751 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100752
Darren Tuckercdf547a2004-05-24 10:12:19 +1000753 for (n = 0; d[n] != NULL && !interrupted; n++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100754 char *tmp, *fname;
755
Darren Tucker9a526452004-06-22 13:09:55 +1000756 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
757 continue;
758
Damien Miller20e1fab2004-02-18 14:30:55 +1100759 tmp = path_append(path, d[n]->filename);
760 fname = path_strip(tmp, strip_path);
Darren Tuckera627d422013-06-02 07:31:17 +1000761 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100762
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000763 if (lflag & LS_LONG_VIEW) {
Darren Tucker2901e2d2010-01-13 22:44:06 +1100764 if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000765 char *lname;
766 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100767
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000768 memset(&sb, 0, sizeof(sb));
769 attrib_to_stat(&d[n]->a, &sb);
Darren Tucker2901e2d2010-01-13 22:44:06 +1100770 lname = ls_file(fname, &sb, 1,
771 (lflag & LS_SI_UNITS));
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000772 printf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000773 free(lname);
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000774 } else
775 printf("%s\n", d[n]->longname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100776 } else {
777 printf("%-*s", colspace, fname);
778 if (c >= columns) {
779 printf("\n");
780 c = 1;
781 } else
782 c++;
783 }
784
Darren Tuckera627d422013-06-02 07:31:17 +1000785 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100786 }
787
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000788 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100789 printf("\n");
790
791 free_sftp_dirents(d);
792 return (0);
793}
794
795/* sftp ls.1 replacement which handles path globs */
796static int
797do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
798 int lflag)
799{
Damien Millera6e121a2010-10-07 21:39:17 +1100800 char *fname, *lname;
Damien Miller68e2e562010-10-07 21:39:55 +1100801 glob_t g;
802 int err;
803 struct winsize ws;
804 u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100805
806 memset(&g, 0, sizeof(g));
807
Damien Millera6e121a2010-10-07 21:39:17 +1100808 if (remote_glob(conn, path,
Damien Millerd7be70d2011-09-22 21:43:06 +1000809 GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
810 NULL, &g) ||
Damien Millera6e121a2010-10-07 21:39:17 +1100811 (g.gl_pathc && !g.gl_matchc)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100812 if (g.gl_pathc)
813 globfree(&g);
Damien Miller20e1fab2004-02-18 14:30:55 +1100814 error("Can't ls: \"%s\" not found", path);
Damien Millera6e121a2010-10-07 21:39:17 +1100815 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100816 }
817
Darren Tuckercdf547a2004-05-24 10:12:19 +1000818 if (interrupted)
819 goto out;
820
Damien Miller20e1fab2004-02-18 14:30:55 +1100821 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100822 * If the glob returns a single match and it is a directory,
823 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100824 */
Damien Millera6e121a2010-10-07 21:39:17 +1100825 if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
826 S_ISDIR(g.gl_statv[0]->st_mode)) {
827 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
828 globfree(&g);
829 return err;
Damien Miller20e1fab2004-02-18 14:30:55 +1100830 }
831
Damien Miller68e2e562010-10-07 21:39:55 +1100832 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
833 width = ws.ws_col;
Damien Miller20e1fab2004-02-18 14:30:55 +1100834
Damien Miller68e2e562010-10-07 21:39:55 +1100835 if (!(lflag & LS_SHORT_VIEW)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100836 /* Count entries for sort and find longest filename */
837 for (i = 0; g.gl_pathv[i]; i++)
838 m = MAX(m, strlen(g.gl_pathv[i]));
839
Damien Miller20e1fab2004-02-18 14:30:55 +1100840 columns = width / (m + 2);
841 columns = MAX(columns, 1);
842 colspace = width / columns;
843 }
844
Damien Millerea858292012-06-30 08:33:32 +1000845 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100846 fname = path_strip(g.gl_pathv[i], strip_path);
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000847 if (lflag & LS_LONG_VIEW) {
Damien Millera6e121a2010-10-07 21:39:17 +1100848 if (g.gl_statv[i] == NULL) {
849 error("no stat information for %s", fname);
850 continue;
851 }
852 lname = ls_file(fname, g.gl_statv[i], 1,
853 (lflag & LS_SI_UNITS));
Damien Miller20e1fab2004-02-18 14:30:55 +1100854 printf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000855 free(lname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100856 } else {
857 printf("%-*s", colspace, fname);
858 if (c >= columns) {
859 printf("\n");
860 c = 1;
861 } else
862 c++;
863 }
Darren Tuckera627d422013-06-02 07:31:17 +1000864 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100865 }
866
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000867 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100868 printf("\n");
869
Darren Tuckercdf547a2004-05-24 10:12:19 +1000870 out:
Damien Miller20e1fab2004-02-18 14:30:55 +1100871 if (g.gl_pathc)
872 globfree(&g);
873
Damien Millera6e121a2010-10-07 21:39:17 +1100874 return 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100875}
876
Damien Millerd671e5a2008-05-19 14:53:33 +1000877static int
878do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
879{
Darren Tucker7b598892008-06-09 22:49:36 +1000880 struct sftp_statvfs st;
Damien Millerd671e5a2008-05-19 14:53:33 +1000881 char s_used[FMT_SCALED_STRSIZE];
882 char s_avail[FMT_SCALED_STRSIZE];
883 char s_root[FMT_SCALED_STRSIZE];
884 char s_total[FMT_SCALED_STRSIZE];
Darren Tuckerb5082e92010-01-08 18:51:47 +1100885 unsigned long long ffree;
Damien Millerd671e5a2008-05-19 14:53:33 +1000886
887 if (do_statvfs(conn, path, &st, 1) == -1)
888 return -1;
889 if (iflag) {
Darren Tuckerb5082e92010-01-08 18:51:47 +1100890 ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
Damien Millerd671e5a2008-05-19 14:53:33 +1000891 printf(" Inodes Used Avail "
892 "(root) %%Capacity\n");
893 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
894 (unsigned long long)st.f_files,
895 (unsigned long long)(st.f_files - st.f_ffree),
896 (unsigned long long)st.f_favail,
Darren Tuckerb5082e92010-01-08 18:51:47 +1100897 (unsigned long long)st.f_ffree, ffree);
Damien Millerd671e5a2008-05-19 14:53:33 +1000898 } else if (hflag) {
899 strlcpy(s_used, "error", sizeof(s_used));
900 strlcpy(s_avail, "error", sizeof(s_avail));
901 strlcpy(s_root, "error", sizeof(s_root));
902 strlcpy(s_total, "error", sizeof(s_total));
903 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
904 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
905 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
906 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
907 printf(" Size Used Avail (root) %%Capacity\n");
908 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
909 s_total, s_used, s_avail, s_root,
910 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
911 st.f_blocks));
912 } else {
913 printf(" Size Used Avail "
914 "(root) %%Capacity\n");
915 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
916 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
917 (unsigned long long)(st.f_frsize *
918 (st.f_blocks - st.f_bfree) / 1024),
919 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
920 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
921 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
922 st.f_blocks));
923 }
924 return 0;
925}
926
Damien Miller1cbc2922007-10-26 14:27:45 +1000927/*
928 * Undo escaping of glob sequences in place. Used to undo extra escaping
929 * applied in makeargv() when the string is destined for a function that
930 * does not glob it.
931 */
932static void
933undo_glob_escape(char *s)
934{
935 size_t i, j;
936
937 for (i = j = 0;;) {
938 if (s[i] == '\0') {
939 s[j] = '\0';
940 return;
941 }
942 if (s[i] != '\\') {
943 s[j++] = s[i++];
944 continue;
945 }
946 /* s[i] == '\\' */
947 ++i;
948 switch (s[i]) {
949 case '?':
950 case '[':
951 case '*':
952 case '\\':
953 s[j++] = s[i++];
954 break;
955 case '\0':
956 s[j++] = '\\';
957 s[j] = '\0';
958 return;
959 default:
960 s[j++] = '\\';
961 s[j++] = s[i++];
962 break;
963 }
964 }
965}
966
967/*
968 * Split a string into an argument vector using sh(1)-style quoting,
969 * comment and escaping rules, but with some tweaks to handle glob(3)
970 * wildcards.
Darren Tucker909d8582010-01-08 19:02:40 +1100971 * The "sloppy" flag allows for recovery from missing terminating quote, for
972 * use in parsing incomplete commandlines during tab autocompletion.
973 *
Damien Miller1cbc2922007-10-26 14:27:45 +1000974 * Returns NULL on error or a NULL-terminated array of arguments.
Darren Tucker909d8582010-01-08 19:02:40 +1100975 *
976 * If "lastquote" is not NULL, the quoting character used for the last
977 * argument is placed in *lastquote ("\0", "'" or "\"").
978 *
979 * If "terminated" is not NULL, *terminated will be set to 1 when the
980 * last argument's quote has been properly terminated or 0 otherwise.
981 * This parameter is only of use if "sloppy" is set.
Damien Miller1cbc2922007-10-26 14:27:45 +1000982 */
983#define MAXARGS 128
984#define MAXARGLEN 8192
985static char **
Darren Tucker909d8582010-01-08 19:02:40 +1100986makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
987 u_int *terminated)
Damien Miller1cbc2922007-10-26 14:27:45 +1000988{
989 int argc, quot;
990 size_t i, j;
991 static char argvs[MAXARGLEN];
992 static char *argv[MAXARGS + 1];
993 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
994
995 *argcp = argc = 0;
996 if (strlen(arg) > sizeof(argvs) - 1) {
997 args_too_longs:
998 error("string too long");
999 return NULL;
1000 }
Darren Tucker909d8582010-01-08 19:02:40 +11001001 if (terminated != NULL)
1002 *terminated = 1;
1003 if (lastquote != NULL)
1004 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001005 state = MA_START;
1006 i = j = 0;
1007 for (;;) {
Damien Miller07daed52012-10-31 08:57:55 +11001008 if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){
Darren Tucker063018d2012-10-05 10:43:58 +10001009 error("Too many arguments.");
1010 return NULL;
1011 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001012 if (isspace(arg[i])) {
1013 if (state == MA_UNQUOTED) {
1014 /* Terminate current argument */
1015 argvs[j++] = '\0';
1016 argc++;
1017 state = MA_START;
1018 } else if (state != MA_START)
1019 argvs[j++] = arg[i];
1020 } else if (arg[i] == '"' || arg[i] == '\'') {
1021 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1022 if (state == MA_START) {
1023 argv[argc] = argvs + j;
1024 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +11001025 if (lastquote != NULL)
1026 *lastquote = arg[i];
Damien Miller1cbc2922007-10-26 14:27:45 +10001027 } else if (state == MA_UNQUOTED)
1028 state = q;
1029 else if (state == q)
1030 state = MA_UNQUOTED;
1031 else
1032 argvs[j++] = arg[i];
1033 } else if (arg[i] == '\\') {
1034 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1035 quot = state == MA_SQUOTE ? '\'' : '"';
1036 /* Unescape quote we are in */
1037 /* XXX support \n and friends? */
1038 if (arg[i + 1] == quot) {
1039 i++;
1040 argvs[j++] = arg[i];
1041 } else if (arg[i + 1] == '?' ||
1042 arg[i + 1] == '[' || arg[i + 1] == '*') {
1043 /*
1044 * Special case for sftp: append
1045 * double-escaped glob sequence -
1046 * glob will undo one level of
1047 * escaping. NB. string can grow here.
1048 */
1049 if (j >= sizeof(argvs) - 5)
1050 goto args_too_longs;
1051 argvs[j++] = '\\';
1052 argvs[j++] = arg[i++];
1053 argvs[j++] = '\\';
1054 argvs[j++] = arg[i];
1055 } else {
1056 argvs[j++] = arg[i++];
1057 argvs[j++] = arg[i];
1058 }
1059 } else {
1060 if (state == MA_START) {
1061 argv[argc] = argvs + j;
1062 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001063 if (lastquote != NULL)
1064 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001065 }
1066 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1067 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1068 /*
1069 * Special case for sftp: append
1070 * escaped glob sequence -
1071 * glob will undo one level of
1072 * escaping.
1073 */
1074 argvs[j++] = arg[i++];
1075 argvs[j++] = arg[i];
1076 } else {
1077 /* Unescape everything */
1078 /* XXX support \n and friends? */
1079 i++;
1080 argvs[j++] = arg[i];
1081 }
1082 }
1083 } else if (arg[i] == '#') {
1084 if (state == MA_SQUOTE || state == MA_DQUOTE)
1085 argvs[j++] = arg[i];
1086 else
1087 goto string_done;
1088 } else if (arg[i] == '\0') {
1089 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001090 if (sloppy) {
1091 state = MA_UNQUOTED;
1092 if (terminated != NULL)
1093 *terminated = 0;
1094 goto string_done;
1095 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001096 error("Unterminated quoted argument");
1097 return NULL;
1098 }
1099 string_done:
1100 if (state == MA_UNQUOTED) {
1101 argvs[j++] = '\0';
1102 argc++;
1103 }
1104 break;
1105 } else {
1106 if (state == MA_START) {
1107 argv[argc] = argvs + j;
1108 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001109 if (lastquote != NULL)
1110 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001111 }
1112 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1113 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1114 /*
1115 * Special case for sftp: escape quoted
1116 * glob(3) wildcards. NB. string can grow
1117 * here.
1118 */
1119 if (j >= sizeof(argvs) - 3)
1120 goto args_too_longs;
1121 argvs[j++] = '\\';
1122 argvs[j++] = arg[i];
1123 } else
1124 argvs[j++] = arg[i];
1125 }
1126 i++;
1127 }
1128 *argcp = argc;
1129 return argv;
1130}
1131
Damien Miller20e1fab2004-02-18 14:30:55 +11001132static int
Damien Miller0d032412013-07-25 11:56:52 +10001133parse_args(const char **cpp, int *aflag, int *hflag, int *iflag, int *lflag,
1134 int *pflag, int *rflag, int *sflag, unsigned long *n_arg,
1135 char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001136{
1137 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001138 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001139 int base = 0;
1140 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001141 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001142
1143 /* Skip leading whitespace */
1144 cp = cp + strspn(cp, WHITESPACE);
1145
Damien Miller20e1fab2004-02-18 14:30:55 +11001146 /* Check for leading '-' (disable error processing) */
1147 *iflag = 0;
1148 if (*cp == '-') {
1149 *iflag = 1;
1150 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001151 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001152 }
1153
Darren Tucker70cc0922010-01-09 22:28:03 +11001154 /* Ignore blank lines and lines which begin with comment '#' char */
1155 if (*cp == '\0' || *cp == '#')
1156 return (0);
1157
Darren Tucker909d8582010-01-08 19:02:40 +11001158 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001159 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001160
Damien Miller1cbc2922007-10-26 14:27:45 +10001161 /* Figure out which command we have */
1162 for (i = 0; cmds[i].c != NULL; i++) {
Damien Millerd6d9fa02013-02-12 11:02:46 +11001163 if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001164 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001165 }
1166 cmdnum = cmds[i].n;
1167 cmd = cmds[i].c;
1168
1169 /* Special case */
1170 if (*cp == '!') {
1171 cp++;
1172 cmdnum = I_SHELL;
1173 } else if (cmdnum == -1) {
1174 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001175 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001176 }
1177
1178 /* Get arguments and parse flags */
Damien Miller0d032412013-07-25 11:56:52 +10001179 *aflag = *lflag = *pflag = *rflag = *hflag = *n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001180 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001181 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001182 switch (cmdnum) {
1183 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001184 case I_REGET:
Damien Miller20e1fab2004-02-18 14:30:55 +11001185 case I_PUT:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001186 if ((optidx = parse_getput_flags(cmd, argv, argc,
Damien Miller0d032412013-07-25 11:56:52 +10001187 aflag, pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001188 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001189 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001190 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001191 error("You must specify at least one path after a "
1192 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001193 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001194 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001195 *path1 = xstrdup(argv[optidx]);
1196 /* Get second pathname (optional) */
1197 if (argc - optidx > 1) {
1198 *path2 = xstrdup(argv[optidx + 1]);
1199 /* Destination is not globbed */
1200 undo_glob_escape(*path2);
1201 }
Damien Miller0d032412013-07-25 11:56:52 +10001202 if (*aflag && cmdnum == I_PUT) {
1203 /* XXX implement resume for uploads */
1204 error("Resume is not supported for uploads");
1205 return -1;
1206 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001207 break;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001208 case I_LINK:
1209 if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1210 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001211 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001212 case I_RENAME:
Damien Miller1cbc2922007-10-26 14:27:45 +10001213 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001214 error("You must specify two paths after a %s "
1215 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001216 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001217 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001218 *path1 = xstrdup(argv[optidx]);
1219 *path2 = xstrdup(argv[optidx + 1]);
1220 /* Paths are not globbed */
1221 undo_glob_escape(*path1);
1222 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001223 break;
1224 case I_RM:
1225 case I_MKDIR:
1226 case I_RMDIR:
1227 case I_CHDIR:
1228 case I_LCHDIR:
1229 case I_LMKDIR:
1230 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001231 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001232 error("You must specify a path after a %s command.",
1233 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001234 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001235 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001236 *path1 = xstrdup(argv[optidx]);
1237 /* Only "rm" globs */
1238 if (cmdnum != I_RM)
1239 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001240 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001241 case I_DF:
1242 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1243 iflag)) == -1)
1244 return -1;
1245 /* Default to current directory if no path specified */
1246 if (argc - optidx < 1)
1247 *path1 = NULL;
1248 else {
1249 *path1 = xstrdup(argv[optidx]);
1250 undo_glob_escape(*path1);
1251 }
1252 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001253 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001254 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001255 return(-1);
1256 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001257 if (argc - optidx > 0)
1258 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001259 break;
1260 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001261 /* Skip ls command and following whitespace */
1262 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001263 case I_SHELL:
1264 /* Uses the rest of the line */
1265 break;
1266 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001267 case I_CHMOD:
1268 base = 8;
1269 case I_CHOWN:
1270 case I_CHGRP:
1271 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001272 if (argc - optidx < 1)
1273 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001274 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001275 l = strtol(argv[optidx], &cp2, base);
1276 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1277 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1278 l < 0) {
1279 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001280 error("You must supply a numeric argument "
1281 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001282 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001283 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001284 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001285 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001286 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001287 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001288 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001289 error("You must specify a path after a %s command.",
1290 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001291 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001292 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001293 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001294 break;
1295 case I_QUIT:
1296 case I_PWD:
1297 case I_LPWD:
1298 case I_HELP:
1299 case I_VERSION:
1300 case I_PROGRESS:
1301 break;
1302 default:
1303 fatal("Command not implemented");
1304 }
1305
1306 *cpp = cp;
1307 return(cmdnum);
1308}
1309
1310static int
1311parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1312 int err_abort)
1313{
1314 char *path1, *path2, *tmp;
Damien Miller0d032412013-07-25 11:56:52 +10001315 int aflag = 0, hflag = 0, iflag = 0, lflag = 0, pflag = 0;
1316 int rflag = 0, sflag = 0;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001317 int cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001318 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001319 Attrib a, *aa;
1320 char path_buf[MAXPATHLEN];
1321 int err = 0;
1322 glob_t g;
1323
1324 path1 = path2 = NULL;
Damien Miller0d032412013-07-25 11:56:52 +10001325 cmdnum = parse_args(&cmd, &aflag, &hflag, &iflag, &lflag, &pflag,
1326 &rflag, &sflag, &n_arg, &path1, &path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001327 if (iflag != 0)
1328 err_abort = 0;
1329
1330 memset(&g, 0, sizeof(g));
1331
1332 /* Perform command */
1333 switch (cmdnum) {
1334 case 0:
1335 /* Blank line */
1336 break;
1337 case -1:
1338 /* Unrecognized command */
1339 err = -1;
1340 break;
Damien Miller0d032412013-07-25 11:56:52 +10001341 case I_REGET:
1342 aflag = 1;
1343 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001344 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001345 err = process_get(conn, path1, path2, *pwd, pflag,
1346 rflag, aflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001347 break;
1348 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001349 err = process_put(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001350 break;
1351 case I_RENAME:
1352 path1 = make_absolute(path1, *pwd);
1353 path2 = make_absolute(path2, *pwd);
1354 err = do_rename(conn, path1, path2);
1355 break;
1356 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001357 sflag = 1;
1358 case I_LINK:
1359 path1 = make_absolute(path1, *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001360 path2 = make_absolute(path2, *pwd);
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001361 err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001362 break;
1363 case I_RM:
1364 path1 = make_absolute(path1, *pwd);
1365 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001366 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001367 if (!quiet)
1368 printf("Removing %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001369 err = do_rm(conn, g.gl_pathv[i]);
1370 if (err != 0 && err_abort)
1371 break;
1372 }
1373 break;
1374 case I_MKDIR:
1375 path1 = make_absolute(path1, *pwd);
1376 attrib_clear(&a);
1377 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1378 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001379 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001380 break;
1381 case I_RMDIR:
1382 path1 = make_absolute(path1, *pwd);
1383 err = do_rmdir(conn, path1);
1384 break;
1385 case I_CHDIR:
1386 path1 = make_absolute(path1, *pwd);
1387 if ((tmp = do_realpath(conn, path1)) == NULL) {
1388 err = 1;
1389 break;
1390 }
1391 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
Darren Tuckera627d422013-06-02 07:31:17 +10001392 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001393 err = 1;
1394 break;
1395 }
1396 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1397 error("Can't change directory: Can't check target");
Darren Tuckera627d422013-06-02 07:31:17 +10001398 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001399 err = 1;
1400 break;
1401 }
1402 if (!S_ISDIR(aa->perm)) {
1403 error("Can't change directory: \"%s\" is not "
1404 "a directory", tmp);
Darren Tuckera627d422013-06-02 07:31:17 +10001405 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001406 err = 1;
1407 break;
1408 }
Darren Tuckera627d422013-06-02 07:31:17 +10001409 free(*pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001410 *pwd = tmp;
1411 break;
1412 case I_LS:
1413 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001414 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001415 break;
1416 }
1417
1418 /* Strip pwd off beginning of non-absolute paths */
1419 tmp = NULL;
1420 if (*path1 != '/')
1421 tmp = *pwd;
1422
1423 path1 = make_absolute(path1, *pwd);
1424 err = do_globbed_ls(conn, path1, tmp, lflag);
1425 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001426 case I_DF:
1427 /* Default to current directory if no path specified */
1428 if (path1 == NULL)
1429 path1 = xstrdup(*pwd);
1430 path1 = make_absolute(path1, *pwd);
1431 err = do_df(conn, path1, hflag, iflag);
1432 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001433 case I_LCHDIR:
1434 if (chdir(path1) == -1) {
1435 error("Couldn't change local directory to "
1436 "\"%s\": %s", path1, strerror(errno));
1437 err = 1;
1438 }
1439 break;
1440 case I_LMKDIR:
1441 if (mkdir(path1, 0777) == -1) {
1442 error("Couldn't create local directory "
1443 "\"%s\": %s", path1, strerror(errno));
1444 err = 1;
1445 }
1446 break;
1447 case I_LLS:
1448 local_do_ls(cmd);
1449 break;
1450 case I_SHELL:
1451 local_do_shell(cmd);
1452 break;
1453 case I_LUMASK:
1454 umask(n_arg);
1455 printf("Local umask: %03lo\n", n_arg);
1456 break;
1457 case I_CHMOD:
1458 path1 = make_absolute(path1, *pwd);
1459 attrib_clear(&a);
1460 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1461 a.perm = n_arg;
1462 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001463 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001464 if (!quiet)
1465 printf("Changing mode on %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001466 err = do_setstat(conn, g.gl_pathv[i], &a);
1467 if (err != 0 && err_abort)
1468 break;
1469 }
1470 break;
1471 case I_CHOWN:
1472 case I_CHGRP:
1473 path1 = make_absolute(path1, *pwd);
1474 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001475 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001476 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001477 if (err_abort) {
1478 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001479 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001480 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001481 continue;
1482 }
1483 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1484 error("Can't get current ownership of "
1485 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001486 if (err_abort) {
1487 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001488 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001489 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001490 continue;
1491 }
1492 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1493 if (cmdnum == I_CHOWN) {
Damien Miller9303e652013-04-23 15:22:40 +10001494 if (!quiet)
1495 printf("Changing owner on %s\n",
1496 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001497 aa->uid = n_arg;
1498 } else {
Damien Miller9303e652013-04-23 15:22:40 +10001499 if (!quiet)
1500 printf("Changing group on %s\n",
1501 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001502 aa->gid = n_arg;
1503 }
1504 err = do_setstat(conn, g.gl_pathv[i], aa);
1505 if (err != 0 && err_abort)
1506 break;
1507 }
1508 break;
1509 case I_PWD:
1510 printf("Remote working directory: %s\n", *pwd);
1511 break;
1512 case I_LPWD:
1513 if (!getcwd(path_buf, sizeof(path_buf))) {
1514 error("Couldn't get local cwd: %s", strerror(errno));
1515 err = -1;
1516 break;
1517 }
1518 printf("Local working directory: %s\n", path_buf);
1519 break;
1520 case I_QUIT:
1521 /* Processed below */
1522 break;
1523 case I_HELP:
1524 help();
1525 break;
1526 case I_VERSION:
1527 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1528 break;
1529 case I_PROGRESS:
1530 showprogress = !showprogress;
1531 if (showprogress)
1532 printf("Progress meter enabled\n");
1533 else
1534 printf("Progress meter disabled\n");
1535 break;
1536 default:
1537 fatal("%d is not implemented", cmdnum);
1538 }
1539
1540 if (g.gl_pathc)
1541 globfree(&g);
Darren Tuckera627d422013-06-02 07:31:17 +10001542 free(path1);
1543 free(path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001544
1545 /* If an unignored error occurs in batch mode we should abort. */
1546 if (err_abort && err != 0)
1547 return (-1);
1548 else if (cmdnum == I_QUIT)
1549 return (1);
1550
1551 return (0);
1552}
1553
Darren Tucker2d963d82004-11-07 20:04:10 +11001554#ifdef USE_LIBEDIT
1555static char *
1556prompt(EditLine *el)
1557{
1558 return ("sftp> ");
1559}
Darren Tucker2d963d82004-11-07 20:04:10 +11001560
Darren Tucker909d8582010-01-08 19:02:40 +11001561/* Display entries in 'list' after skipping the first 'len' chars */
1562static void
1563complete_display(char **list, u_int len)
1564{
1565 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1566 struct winsize ws;
1567 char *tmp;
1568
1569 /* Count entries for sort and find longest */
1570 for (y = 0; list[y]; y++)
1571 m = MAX(m, strlen(list[y]));
1572
1573 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1574 width = ws.ws_col;
1575
1576 m = m > len ? m - len : 0;
1577 columns = width / (m + 2);
1578 columns = MAX(columns, 1);
1579 colspace = width / columns;
1580 colspace = MIN(colspace, width);
1581
1582 printf("\n");
1583 m = 1;
1584 for (y = 0; list[y]; y++) {
1585 llen = strlen(list[y]);
1586 tmp = llen > len ? list[y] + len : "";
1587 printf("%-*s", colspace, tmp);
1588 if (m >= columns) {
1589 printf("\n");
1590 m = 1;
1591 } else
1592 m++;
1593 }
1594 printf("\n");
1595}
1596
1597/*
1598 * Given a "list" of words that begin with a common prefix of "word",
1599 * attempt to find an autocompletion to extends "word" by the next
1600 * characters common to all entries in "list".
1601 */
1602static char *
1603complete_ambiguous(const char *word, char **list, size_t count)
1604{
1605 if (word == NULL)
1606 return NULL;
1607
1608 if (count > 0) {
1609 u_int y, matchlen = strlen(list[0]);
1610
1611 /* Find length of common stem */
1612 for (y = 1; list[y]; y++) {
1613 u_int x;
1614
1615 for (x = 0; x < matchlen; x++)
1616 if (list[0][x] != list[y][x])
1617 break;
1618
1619 matchlen = x;
1620 }
1621
1622 if (matchlen > strlen(word)) {
1623 char *tmp = xstrdup(list[0]);
1624
Darren Tucker340d1682010-01-09 08:54:31 +11001625 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001626 return tmp;
1627 }
1628 }
1629
1630 return xstrdup(word);
1631}
1632
1633/* Autocomplete a sftp command */
1634static int
1635complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1636 int terminated)
1637{
1638 u_int y, count = 0, cmdlen, tmplen;
1639 char *tmp, **list, argterm[3];
1640 const LineInfo *lf;
1641
1642 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1643
1644 /* No command specified: display all available commands */
1645 if (cmd == NULL) {
1646 for (y = 0; cmds[y].c; y++)
1647 list[count++] = xstrdup(cmds[y].c);
1648
1649 list[count] = NULL;
1650 complete_display(list, 0);
1651
1652 for (y = 0; list[y] != NULL; y++)
Darren Tuckera627d422013-06-02 07:31:17 +10001653 free(list[y]);
1654 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001655 return count;
1656 }
1657
1658 /* Prepare subset of commands that start with "cmd" */
1659 cmdlen = strlen(cmd);
1660 for (y = 0; cmds[y].c; y++) {
1661 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
1662 list[count++] = xstrdup(cmds[y].c);
1663 }
1664 list[count] = NULL;
1665
Damien Miller47d81152011-11-25 13:53:48 +11001666 if (count == 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10001667 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001668 return 0;
Damien Miller47d81152011-11-25 13:53:48 +11001669 }
Darren Tucker909d8582010-01-08 19:02:40 +11001670
1671 /* Complete ambigious command */
1672 tmp = complete_ambiguous(cmd, list, count);
1673 if (count > 1)
1674 complete_display(list, 0);
1675
1676 for (y = 0; list[y]; y++)
Darren Tuckera627d422013-06-02 07:31:17 +10001677 free(list[y]);
1678 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001679
1680 if (tmp != NULL) {
1681 tmplen = strlen(tmp);
1682 cmdlen = strlen(cmd);
1683 /* If cmd may be extended then do so */
1684 if (tmplen > cmdlen)
1685 if (el_insertstr(el, tmp + cmdlen) == -1)
1686 fatal("el_insertstr failed.");
1687 lf = el_line(el);
1688 /* Terminate argument cleanly */
1689 if (count == 1) {
1690 y = 0;
1691 if (!terminated)
1692 argterm[y++] = quote;
1693 if (lastarg || *(lf->cursor) != ' ')
1694 argterm[y++] = ' ';
1695 argterm[y] = '\0';
1696 if (y > 0 && el_insertstr(el, argterm) == -1)
1697 fatal("el_insertstr failed.");
1698 }
Darren Tuckera627d422013-06-02 07:31:17 +10001699 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001700 }
1701
1702 return count;
1703}
1704
1705/*
1706 * Determine whether a particular sftp command's arguments (if any)
1707 * represent local or remote files.
1708 */
1709static int
1710complete_is_remote(char *cmd) {
1711 int i;
1712
1713 if (cmd == NULL)
1714 return -1;
1715
1716 for (i = 0; cmds[i].c; i++) {
1717 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
1718 return cmds[i].t;
1719 }
1720
1721 return -1;
1722}
1723
1724/* Autocomplete a filename "file" */
1725static int
1726complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1727 char *file, int remote, int lastarg, char quote, int terminated)
1728{
1729 glob_t g;
Darren Tuckerea647212013-06-06 08:19:09 +10001730 char *tmp, *tmp2, ins[8];
Darren Tucker17146d32012-10-05 10:46:16 +10001731 u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
Darren Tuckerea647212013-06-06 08:19:09 +10001732 int clen;
Darren Tucker909d8582010-01-08 19:02:40 +11001733 const LineInfo *lf;
1734
1735 /* Glob from "file" location */
1736 if (file == NULL)
1737 tmp = xstrdup("*");
1738 else
1739 xasprintf(&tmp, "%s*", file);
1740
Darren Tucker191fcc62012-10-05 10:45:01 +10001741 /* Check if the path is absolute. */
1742 isabs = tmp[0] == '/';
1743
Darren Tucker909d8582010-01-08 19:02:40 +11001744 memset(&g, 0, sizeof(g));
1745 if (remote != LOCAL) {
1746 tmp = make_absolute(tmp, remote_path);
1747 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1748 } else
1749 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1750
1751 /* Determine length of pwd so we can trim completion display */
1752 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1753 /* Terminate counting on first unescaped glob metacharacter */
1754 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1755 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1756 hadglob = 1;
1757 break;
1758 }
1759 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1760 tmplen++;
1761 if (tmp[tmplen] == '/')
1762 pwdlen = tmplen + 1; /* track last seen '/' */
1763 }
Darren Tuckera627d422013-06-02 07:31:17 +10001764 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001765
1766 if (g.gl_matchc == 0)
1767 goto out;
1768
1769 if (g.gl_matchc > 1)
1770 complete_display(g.gl_pathv, pwdlen);
1771
1772 tmp = NULL;
1773 /* Don't try to extend globs */
1774 if (file == NULL || hadglob)
1775 goto out;
1776
1777 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
Darren Tucker191fcc62012-10-05 10:45:01 +10001778 tmp = path_strip(tmp2, isabs ? NULL : remote_path);
Darren Tuckera627d422013-06-02 07:31:17 +10001779 free(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001780
1781 if (tmp == NULL)
1782 goto out;
1783
1784 tmplen = strlen(tmp);
1785 filelen = strlen(file);
1786
Darren Tucker17146d32012-10-05 10:46:16 +10001787 /* Count the number of escaped characters in the input string. */
1788 cesc = isesc = 0;
1789 for (i = 0; i < filelen; i++) {
1790 if (!isesc && file[i] == '\\' && i + 1 < filelen){
1791 isesc = 1;
1792 cesc++;
1793 } else
1794 isesc = 0;
1795 }
1796
1797 if (tmplen > (filelen - cesc)) {
1798 tmp2 = tmp + filelen - cesc;
Darren Tucker909d8582010-01-08 19:02:40 +11001799 len = strlen(tmp2);
1800 /* quote argument on way out */
Darren Tuckerea647212013-06-06 08:19:09 +10001801 for (i = 0; i < len; i += clen) {
1802 if ((clen = mblen(tmp2 + i, len - i)) < 0 ||
1803 (size_t)clen > sizeof(ins) - 2)
1804 fatal("invalid multibyte character");
Darren Tucker909d8582010-01-08 19:02:40 +11001805 ins[0] = '\\';
Darren Tuckerea647212013-06-06 08:19:09 +10001806 memcpy(ins + 1, tmp2 + i, clen);
1807 ins[clen + 1] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001808 switch (tmp2[i]) {
1809 case '\'':
1810 case '"':
1811 case '\\':
1812 case '\t':
Darren Tuckerd78739a2010-10-24 10:56:32 +11001813 case '[':
Darren Tucker909d8582010-01-08 19:02:40 +11001814 case ' ':
Darren Tucker17146d32012-10-05 10:46:16 +10001815 case '#':
1816 case '*':
Darren Tucker909d8582010-01-08 19:02:40 +11001817 if (quote == '\0' || tmp2[i] == quote) {
1818 if (el_insertstr(el, ins) == -1)
1819 fatal("el_insertstr "
1820 "failed.");
1821 break;
1822 }
1823 /* FALLTHROUGH */
1824 default:
1825 if (el_insertstr(el, ins + 1) == -1)
1826 fatal("el_insertstr failed.");
1827 break;
1828 }
1829 }
1830 }
1831
1832 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11001833 if (g.gl_matchc == 1) {
1834 i = 0;
1835 if (!terminated)
1836 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11001837 if (*(lf->cursor - 1) != '/' &&
1838 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11001839 ins[i++] = ' ';
1840 ins[i] = '\0';
1841 if (i > 0 && el_insertstr(el, ins) == -1)
1842 fatal("el_insertstr failed.");
1843 }
Darren Tuckera627d422013-06-02 07:31:17 +10001844 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001845
1846 out:
1847 globfree(&g);
1848 return g.gl_matchc;
1849}
1850
1851/* tab-completion hook function, called via libedit */
1852static unsigned char
1853complete(EditLine *el, int ch)
1854{
1855 char **argv, *line, quote;
Damien Miller746d1a62013-07-18 16:13:02 +10001856 int argc, carg;
1857 u_int cursor, len, terminated, ret = CC_ERROR;
Darren Tucker909d8582010-01-08 19:02:40 +11001858 const LineInfo *lf;
1859 struct complete_ctx *complete_ctx;
1860
1861 lf = el_line(el);
1862 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1863 fatal("%s: el_get failed", __func__);
1864
1865 /* Figure out which argument the cursor points to */
1866 cursor = lf->cursor - lf->buffer;
1867 line = (char *)xmalloc(cursor + 1);
1868 memcpy(line, lf->buffer, cursor);
1869 line[cursor] = '\0';
1870 argv = makeargv(line, &carg, 1, &quote, &terminated);
Darren Tuckera627d422013-06-02 07:31:17 +10001871 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001872
1873 /* Get all the arguments on the line */
1874 len = lf->lastchar - lf->buffer;
1875 line = (char *)xmalloc(len + 1);
1876 memcpy(line, lf->buffer, len);
1877 line[len] = '\0';
1878 argv = makeargv(line, &argc, 1, NULL, NULL);
1879
1880 /* Ensure cursor is at EOL or a argument boundary */
1881 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1882 line[cursor] != '\n') {
Darren Tuckera627d422013-06-02 07:31:17 +10001883 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001884 return ret;
1885 }
1886
1887 if (carg == 0) {
1888 /* Show all available commands */
1889 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1890 ret = CC_REDISPLAY;
1891 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1892 /* Handle the command parsing */
1893 if (complete_cmd_parse(el, argv[0], argc == carg,
1894 quote, terminated) != 0)
1895 ret = CC_REDISPLAY;
1896 } else if (carg >= 1) {
1897 /* Handle file parsing */
1898 int remote = complete_is_remote(argv[0]);
1899 char *filematch = NULL;
1900
1901 if (carg > 1 && line[cursor-1] != ' ')
1902 filematch = argv[carg - 1];
1903
1904 if (remote != 0 &&
1905 complete_match(el, complete_ctx->conn,
1906 *complete_ctx->remote_pathp, filematch,
1907 remote, carg == argc, quote, terminated) != 0)
1908 ret = CC_REDISPLAY;
1909 }
1910
Darren Tuckera627d422013-06-02 07:31:17 +10001911 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001912 return ret;
1913}
Darren Tuckere67f7db2010-01-08 19:50:02 +11001914#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11001915
Damien Miller20e1fab2004-02-18 14:30:55 +11001916int
Darren Tucker21063192010-01-08 17:10:36 +11001917interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001918{
Darren Tucker909d8582010-01-08 19:02:40 +11001919 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11001920 char *dir = NULL;
1921 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10001922 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11001923 EditLine *el = NULL;
1924#ifdef USE_LIBEDIT
1925 History *hl = NULL;
1926 HistEvent hev;
1927 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11001928 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11001929
1930 if (!batchmode && isatty(STDIN_FILENO)) {
1931 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1932 fatal("Couldn't initialise editline");
1933 if ((hl = history_init()) == NULL)
1934 fatal("Couldn't initialise editline history");
1935 history(hl, &hev, H_SETSIZE, 100);
1936 el_set(el, EL_HIST, history, hl);
1937
1938 el_set(el, EL_PROMPT, prompt);
1939 el_set(el, EL_EDITOR, "emacs");
1940 el_set(el, EL_TERMINAL, NULL);
1941 el_set(el, EL_SIGNAL, 1);
1942 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11001943
1944 /* Tab Completion */
1945 el_set(el, EL_ADDFN, "ftp-complete",
Darren Tuckerd78739a2010-10-24 10:56:32 +11001946 "Context sensitive argument completion", complete);
Darren Tucker909d8582010-01-08 19:02:40 +11001947 complete_ctx.conn = conn;
1948 complete_ctx.remote_pathp = &remote_path;
1949 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
1950 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11001951 }
1952#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001953
Darren Tucker909d8582010-01-08 19:02:40 +11001954 remote_path = do_realpath(conn, ".");
1955 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11001956 fatal("Need cwd");
1957
1958 if (file1 != NULL) {
1959 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11001960 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11001961
1962 if (remote_is_dir(conn, dir) && file2 == NULL) {
Damien Miller9303e652013-04-23 15:22:40 +10001963 if (!quiet)
1964 printf("Changing to: %s\n", dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11001965 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001966 if (parse_dispatch_command(conn, cmd,
1967 &remote_path, 1) != 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10001968 free(dir);
1969 free(remote_path);
1970 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001971 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11001972 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001973 } else {
Darren Tucker0af24052012-10-05 10:41:25 +10001974 /* XXX this is wrong wrt quoting */
Damien Miller0d032412013-07-25 11:56:52 +10001975 snprintf(cmd, sizeof cmd, "get%s %s%s%s",
1976 global_aflag ? " -a" : "", dir,
1977 file2 == NULL ? "" : " ",
1978 file2 == NULL ? "" : file2);
Darren Tucker909d8582010-01-08 19:02:40 +11001979 err = parse_dispatch_command(conn, cmd,
1980 &remote_path, 1);
Darren Tuckera627d422013-06-02 07:31:17 +10001981 free(dir);
1982 free(remote_path);
1983 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001984 return (err);
1985 }
Darren Tuckera627d422013-06-02 07:31:17 +10001986 free(dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11001987 }
1988
Damien Miller37294fb2005-07-17 17:18:49 +10001989 setlinebuf(stdout);
1990 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11001991
Damien Miller0e2c1022005-08-12 22:16:22 +10001992 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11001993 err = 0;
1994 for (;;) {
1995 char *cp;
1996
Darren Tuckercdf547a2004-05-24 10:12:19 +10001997 signal(SIGINT, SIG_IGN);
1998
Darren Tucker2d963d82004-11-07 20:04:10 +11001999 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002000 if (interactive)
2001 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11002002 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002003 if (interactive)
2004 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11002005 break;
2006 }
Damien Miller0e2c1022005-08-12 22:16:22 +10002007 if (!interactive) { /* Echo command */
2008 printf("sftp> %s", cmd);
2009 if (strlen(cmd) > 0 &&
2010 cmd[strlen(cmd) - 1] != '\n')
2011 printf("\n");
2012 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002013 } else {
2014#ifdef USE_LIBEDIT
2015 const char *line;
2016 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11002017
Darren Tucker909d8582010-01-08 19:02:40 +11002018 if ((line = el_gets(el, &count)) == NULL ||
2019 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002020 printf("\n");
2021 break;
2022 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002023 history(hl, &hev, H_ENTER, line);
2024 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
2025 fprintf(stderr, "Error: input line too long\n");
2026 continue;
2027 }
2028#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002029 }
2030
Damien Miller20e1fab2004-02-18 14:30:55 +11002031 cp = strrchr(cmd, '\n');
2032 if (cp)
2033 *cp = '\0';
2034
Darren Tuckercdf547a2004-05-24 10:12:19 +10002035 /* Handle user interrupts gracefully during commands */
2036 interrupted = 0;
2037 signal(SIGINT, cmd_interrupt);
2038
Darren Tucker909d8582010-01-08 19:02:40 +11002039 err = parse_dispatch_command(conn, cmd, &remote_path,
2040 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11002041 if (err != 0)
2042 break;
2043 }
Darren Tuckera627d422013-06-02 07:31:17 +10002044 free(remote_path);
2045 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002046
Tim Rice027e8b12005-08-15 14:52:50 -07002047#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10002048 if (el != NULL)
2049 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07002050#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10002051
Damien Miller20e1fab2004-02-18 14:30:55 +11002052 /* err == 1 signifies normal "quit" exit */
2053 return (err >= 0 ? 0 : -1);
2054}
Damien Miller62d57f62003-01-10 21:43:24 +11002055
Ben Lindstrombba81212001-06-25 05:01:22 +00002056static void
Damien Millercc685c12003-06-04 22:51:38 +10002057connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11002058{
2059 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002060
Damien Miller33804262001-02-04 23:20:18 +11002061#ifdef USE_PIPES
2062 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002063
Damien Miller33804262001-02-04 23:20:18 +11002064 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2065 fatal("pipe: %s", strerror(errno));
2066 *in = pin[0];
2067 *out = pout[1];
2068 c_in = pout[0];
2069 c_out = pin[1];
2070#else /* USE_PIPES */
2071 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002072
Damien Miller33804262001-02-04 23:20:18 +11002073 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2074 fatal("socketpair: %s", strerror(errno));
2075 *in = *out = inout[0];
2076 c_in = c_out = inout[1];
2077#endif /* USE_PIPES */
2078
Damien Millercc685c12003-06-04 22:51:38 +10002079 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002080 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002081 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002082 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2083 (dup2(c_out, STDOUT_FILENO) == -1)) {
2084 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002085 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002086 }
2087 close(*in);
2088 close(*out);
2089 close(c_in);
2090 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002091
2092 /*
2093 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002094 * ignore SIGINT if we want to gracefully abort commands,
2095 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002096 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2097 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002098 */
2099 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002100 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002101 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002102 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002103 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002104 }
2105
Damien Millercc685c12003-06-04 22:51:38 +10002106 signal(SIGTERM, killchild);
2107 signal(SIGINT, killchild);
2108 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11002109 close(c_in);
2110 close(c_out);
2111}
2112
Ben Lindstrombba81212001-06-25 05:01:22 +00002113static void
Damien Miller33804262001-02-04 23:20:18 +11002114usage(void)
2115{
Damien Miller025e01c2002-02-08 22:06:29 +11002116 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002117
Ben Lindstrom1e243242001-09-18 05:38:44 +00002118 fprintf(stderr,
Darren Tucker1b0dd172009-10-07 08:37:48 +11002119 "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002120 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002121 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002122 " [-o ssh_option] [-P port] [-R num_requests] "
2123 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002124 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002125 " %s [user@]host[:file ...]\n"
2126 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002127 " %s -b batchfile [user@]host\n",
2128 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002129 exit(1);
2130}
2131
Kevin Stevesef4eea92001-02-05 12:42:17 +00002132int
Damien Miller33804262001-02-04 23:20:18 +11002133main(int argc, char **argv)
2134{
Damien Miller956f3fb2003-01-10 21:40:00 +11002135 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002136 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002137 int debug_level = 0, sshver = 2;
2138 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002139 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002140 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002141 LogLevel ll = SYSLOG_LEVEL_INFO;
2142 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002143 extern int optind;
2144 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002145 struct sftp_conn *conn;
2146 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2147 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002148 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002149
Darren Tuckerce321d82005-10-03 18:11:24 +10002150 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2151 sanitise_stdfd();
Darren Tuckerea647212013-06-06 08:19:09 +10002152 setlocale(LC_CTYPE, "");
Darren Tuckerce321d82005-10-03 18:11:24 +10002153
Damien Miller59d3d5b2003-08-22 09:34:41 +10002154 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002155 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002156 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002157 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002158 addargs(&args, "-oForwardX11 no");
2159 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002160 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002161 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002162
Ben Lindstrom387c4722001-05-08 20:27:25 +00002163 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002164 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002165
Darren Tucker282b4022009-10-07 08:23:06 +11002166 while ((ch = getopt(argc, argv,
Damien Miller0d032412013-07-25 11:56:52 +10002167 "1246ahpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002168 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002169 /* Passed through to ssh(1) */
2170 case '4':
2171 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002172 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002173 addargs(&args, "-%c", ch);
2174 break;
2175 /* Passed through to ssh(1) with argument */
2176 case 'F':
2177 case 'c':
2178 case 'i':
2179 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002180 addargs(&args, "-%c", ch);
2181 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002182 break;
2183 case 'q':
Damien Miller9303e652013-04-23 15:22:40 +10002184 ll = SYSLOG_LEVEL_ERROR;
2185 quiet = 1;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002186 showprogress = 0;
2187 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002188 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002189 case 'P':
2190 addargs(&args, "-oPort %s", optarg);
2191 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002192 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002193 if (debug_level < 3) {
2194 addargs(&args, "-v");
2195 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2196 }
2197 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002198 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002199 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002200 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002201 if (sftp_server == NULL)
2202 sftp_server = _PATH_SFTP_SERVER;
2203 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002204 case '2':
2205 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002206 break;
Damien Miller0d032412013-07-25 11:56:52 +10002207 case 'a':
2208 global_aflag = 1;
2209 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002210 case 'B':
2211 copy_buffer_len = strtol(optarg, &cp, 10);
2212 if (copy_buffer_len == 0 || *cp != '\0')
2213 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002214 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002215 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002216 if (batchmode)
2217 fatal("Batch file already specified.");
2218
2219 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002220 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002221 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002222 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002223 showprogress = 0;
Damien Miller9303e652013-04-23 15:22:40 +10002224 quiet = batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002225 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002226 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002227 case 'p':
2228 global_pflag = 1;
2229 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002230 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002231 sftp_direct = optarg;
2232 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002233 case 'l':
2234 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2235 &errstr);
2236 if (errstr != NULL)
2237 usage();
2238 limit_kbps *= 1024; /* kbps */
2239 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002240 case 'r':
2241 global_rflag = 1;
2242 break;
Damien Miller16a13332002-02-13 14:03:56 +11002243 case 'R':
2244 num_requests = strtol(optarg, &cp, 10);
2245 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002246 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002247 optarg);
2248 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002249 case 's':
2250 sftp_server = optarg;
2251 break;
2252 case 'S':
2253 ssh_program = optarg;
2254 replacearg(&args, 0, "%s", ssh_program);
2255 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002256 case 'h':
2257 default:
Damien Miller33804262001-02-04 23:20:18 +11002258 usage();
2259 }
2260 }
2261
Damien Millerc0f27d82004-03-08 23:12:19 +11002262 if (!isatty(STDERR_FILENO))
2263 showprogress = 0;
2264
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002265 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2266
Damien Millerd14ee1e2002-02-05 12:27:31 +11002267 if (sftp_direct == NULL) {
2268 if (optind == argc || argc > (optind + 2))
2269 usage();
Damien Miller33804262001-02-04 23:20:18 +11002270
Damien Millerd14ee1e2002-02-05 12:27:31 +11002271 userhost = xstrdup(argv[optind]);
2272 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002273
Ben Lindstromc276c122002-12-23 02:14:51 +00002274 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002275 host = userhost;
2276 else {
2277 *host++ = '\0';
2278 if (!userhost[0]) {
2279 fprintf(stderr, "Missing username\n");
2280 usage();
2281 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002282 addargs(&args, "-l");
2283 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002284 }
2285
Damien Millerec692032004-01-27 21:22:00 +11002286 if ((cp = colon(host)) != NULL) {
2287 *cp++ = '\0';
2288 file1 = cp;
2289 }
2290
Damien Millerd14ee1e2002-02-05 12:27:31 +11002291 host = cleanhostname(host);
2292 if (!*host) {
2293 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002294 usage();
2295 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002296
Damien Millerd14ee1e2002-02-05 12:27:31 +11002297 addargs(&args, "-oProtocol %d", sshver);
2298
2299 /* no subsystem if the server-spec contains a '/' */
2300 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2301 addargs(&args, "-s");
2302
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002303 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002304 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002305 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002306 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002307
Damien Millercc685c12003-06-04 22:51:38 +10002308 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002309 } else {
2310 args.list = NULL;
2311 addargs(&args, "sftp-server");
2312
Damien Millercc685c12003-06-04 22:51:38 +10002313 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002314 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002315 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002316
Damien Miller65e42f82010-09-24 22:15:11 +10002317 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002318 if (conn == NULL)
2319 fatal("Couldn't initialise connection to server");
2320
Damien Miller9303e652013-04-23 15:22:40 +10002321 if (!quiet) {
Darren Tucker21063192010-01-08 17:10:36 +11002322 if (sftp_direct == NULL)
2323 fprintf(stderr, "Connected to %s.\n", host);
2324 else
2325 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2326 }
2327
2328 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002329
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002330#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002331 shutdown(in, SHUT_RDWR);
2332 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002333#endif
2334
Damien Miller33804262001-02-04 23:20:18 +11002335 close(in);
2336 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002337 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002338 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002339
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002340 while (waitpid(sshpid, NULL, 0) == -1)
2341 if (errno != EINTR)
2342 fatal("Couldn't wait for ssh process: %s",
2343 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002344
Damien Miller956f3fb2003-01-10 21:40:00 +11002345 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002346}