blob: f0daaefa40f83c2c585aab16c668daa530fc429a [file] [log] [blame]
Damien Miller746d1a62013-07-18 16:13:02 +10001/* $OpenBSD: sftp.c,v 1.147 2013/07/12 00:20:00 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
91/* When this option is set, the file transfers will always preserve times */
92int global_pflag = 0;
93
Darren Tuckercdf547a2004-05-24 10:12:19 +100094/* SIGINT received during command processing */
95volatile sig_atomic_t interrupted = 0;
96
Darren Tuckerb9123452004-06-22 13:06:45 +100097/* I wish qsort() took a separate ctx for the comparison function...*/
98int sort_flag;
99
Darren Tucker909d8582010-01-08 19:02:40 +1100100/* Context used for commandline completion */
101struct complete_ctx {
102 struct sftp_conn *conn;
103 char **remote_pathp;
104};
105
Damien Miller20e1fab2004-02-18 14:30:55 +1100106int remote_glob(struct sftp_conn *, const char *, int,
107 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
Damien Miller33804262001-02-04 23:20:18 +1100108
Kevin Steves12888d12001-03-05 19:50:57 +0000109extern char *__progname;
Kevin Steves12888d12001-03-05 19:50:57 +0000110
Damien Miller20e1fab2004-02-18 14:30:55 +1100111/* Separators for interactive commands */
112#define WHITESPACE " \t\r\n"
Damien Millerd7686fd2001-02-10 00:40:03 +1100113
Darren Tuckerb9123452004-06-22 13:06:45 +1000114/* ls flags */
Darren Tucker2901e2d2010-01-13 22:44:06 +1100115#define LS_LONG_VIEW 0x0001 /* Full view ala ls -l */
116#define LS_SHORT_VIEW 0x0002 /* Single row view ala ls -1 */
117#define LS_NUMERIC_VIEW 0x0004 /* Long view with numeric uid/gid */
118#define LS_NAME_SORT 0x0008 /* Sort by name (default) */
119#define LS_TIME_SORT 0x0010 /* Sort by mtime */
120#define LS_SIZE_SORT 0x0020 /* Sort by file size */
121#define LS_REVERSE_SORT 0x0040 /* Reverse sort order */
122#define LS_SHOW_ALL 0x0080 /* Don't skip filenames starting with '.' */
123#define LS_SI_UNITS 0x0100 /* Display sizes as K, M, G, etc. */
Darren Tuckerb9123452004-06-22 13:06:45 +1000124
Darren Tucker2901e2d2010-01-13 22:44:06 +1100125#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS)
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000126#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
Damien Miller20e1fab2004-02-18 14:30:55 +1100127
128/* Commands for interactive mode */
129#define I_CHDIR 1
130#define I_CHGRP 2
131#define I_CHMOD 3
132#define I_CHOWN 4
Damien Millerd671e5a2008-05-19 14:53:33 +1000133#define I_DF 24
Damien Miller20e1fab2004-02-18 14:30:55 +1100134#define I_GET 5
135#define I_HELP 6
136#define I_LCHDIR 7
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100137#define I_LINK 25
Damien Miller20e1fab2004-02-18 14:30:55 +1100138#define I_LLS 8
139#define I_LMKDIR 9
140#define I_LPWD 10
141#define I_LS 11
142#define I_LUMASK 12
143#define I_MKDIR 13
144#define I_PUT 14
145#define I_PWD 15
146#define I_QUIT 16
147#define I_RENAME 17
148#define I_RM 18
149#define I_RMDIR 19
150#define I_SHELL 20
151#define I_SYMLINK 21
152#define I_VERSION 22
153#define I_PROGRESS 23
154
155struct CMD {
156 const char *c;
157 const int n;
Darren Tucker909d8582010-01-08 19:02:40 +1100158 const int t;
Damien Miller20e1fab2004-02-18 14:30:55 +1100159};
160
Darren Tucker909d8582010-01-08 19:02:40 +1100161/* Type of completion */
162#define NOARGS 0
163#define REMOTE 1
164#define LOCAL 2
165
Damien Miller20e1fab2004-02-18 14:30:55 +1100166static const struct CMD cmds[] = {
Darren Tucker909d8582010-01-08 19:02:40 +1100167 { "bye", I_QUIT, NOARGS },
168 { "cd", I_CHDIR, REMOTE },
169 { "chdir", I_CHDIR, REMOTE },
170 { "chgrp", I_CHGRP, REMOTE },
171 { "chmod", I_CHMOD, REMOTE },
172 { "chown", I_CHOWN, REMOTE },
173 { "df", I_DF, REMOTE },
174 { "dir", I_LS, REMOTE },
175 { "exit", I_QUIT, NOARGS },
176 { "get", I_GET, REMOTE },
177 { "help", I_HELP, NOARGS },
178 { "lcd", I_LCHDIR, LOCAL },
179 { "lchdir", I_LCHDIR, LOCAL },
180 { "lls", I_LLS, LOCAL },
181 { "lmkdir", I_LMKDIR, LOCAL },
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100182 { "ln", I_LINK, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100183 { "lpwd", I_LPWD, LOCAL },
184 { "ls", I_LS, REMOTE },
185 { "lumask", I_LUMASK, NOARGS },
186 { "mkdir", I_MKDIR, REMOTE },
Damien Miller099fc162010-05-10 11:56:50 +1000187 { "mget", I_GET, REMOTE },
188 { "mput", I_PUT, LOCAL },
Darren Tucker909d8582010-01-08 19:02:40 +1100189 { "progress", I_PROGRESS, NOARGS },
190 { "put", I_PUT, LOCAL },
191 { "pwd", I_PWD, REMOTE },
192 { "quit", I_QUIT, NOARGS },
193 { "rename", I_RENAME, REMOTE },
194 { "rm", I_RM, REMOTE },
195 { "rmdir", I_RMDIR, REMOTE },
196 { "symlink", I_SYMLINK, REMOTE },
197 { "version", I_VERSION, NOARGS },
198 { "!", I_SHELL, NOARGS },
199 { "?", I_HELP, NOARGS },
200 { NULL, -1, -1 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100201};
202
Darren Tucker21063192010-01-08 17:10:36 +1100203int interactive_loop(struct sftp_conn *, char *file1, char *file2);
Damien Miller20e1fab2004-02-18 14:30:55 +1100204
Damien Millerb6c85fc2007-01-05 16:30:41 +1100205/* ARGSUSED */
Damien Miller20e1fab2004-02-18 14:30:55 +1100206static void
Darren Tuckercdf547a2004-05-24 10:12:19 +1000207killchild(int signo)
208{
Darren Tuckerba66df82005-01-24 21:57:40 +1100209 if (sshpid > 1) {
Darren Tuckercdf547a2004-05-24 10:12:19 +1000210 kill(sshpid, SIGTERM);
Darren Tuckerba66df82005-01-24 21:57:40 +1100211 waitpid(sshpid, NULL, 0);
212 }
Darren Tuckercdf547a2004-05-24 10:12:19 +1000213
214 _exit(1);
215}
216
Damien Millerb6c85fc2007-01-05 16:30:41 +1100217/* ARGSUSED */
Darren Tuckercdf547a2004-05-24 10:12:19 +1000218static void
219cmd_interrupt(int signo)
220{
221 const char msg[] = "\rInterrupt \n";
Darren Tuckere2f189a2004-12-06 22:45:53 +1100222 int olderrno = errno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000223
Darren Tuckerdbee3082013-05-16 20:32:29 +1000224 (void)write(STDERR_FILENO, msg, sizeof(msg) - 1);
Darren Tuckercdf547a2004-05-24 10:12:19 +1000225 interrupted = 1;
Darren Tuckere2f189a2004-12-06 22:45:53 +1100226 errno = olderrno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000227}
228
229static void
Damien Miller20e1fab2004-02-18 14:30:55 +1100230help(void)
231{
Damien Miller62fd18a2009-01-28 16:14:09 +1100232 printf("Available commands:\n"
233 "bye Quit sftp\n"
234 "cd path Change remote directory to 'path'\n"
235 "chgrp grp path Change group of file 'path' to 'grp'\n"
236 "chmod mode path Change permissions of file 'path' to 'mode'\n"
237 "chown own path Change owner of file 'path' to 'own'\n"
238 "df [-hi] [path] Display statistics for current directory or\n"
239 " filesystem containing 'path'\n"
240 "exit Quit sftp\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100241 "get [-Ppr] remote [local] Download file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100242 "help Display this help text\n"
243 "lcd path Change local directory to 'path'\n"
244 "lls [ls-options [path]] Display local directory listing\n"
245 "lmkdir path Create local directory\n"
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100246 "ln [-s] oldpath newpath Link remote file (-s for symlink)\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100247 "lpwd Print local working directory\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100248 "ls [-1afhlnrSt] [path] Display remote directory listing\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100249 "lumask umask Set local umask to 'umask'\n"
250 "mkdir path Create remote directory\n"
251 "progress Toggle display of progress meter\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100252 "put [-Ppr] local [remote] Upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100253 "pwd Display remote working directory\n"
254 "quit Quit sftp\n"
255 "rename oldpath newpath Rename remote file\n"
256 "rm path Delete remote file\n"
257 "rmdir path Remove remote directory\n"
258 "symlink oldpath newpath Symlink remote file\n"
259 "version Show SFTP version\n"
260 "!command Execute 'command' in local shell\n"
261 "! Escape to local shell\n"
262 "? Synonym for help\n");
Damien Miller20e1fab2004-02-18 14:30:55 +1100263}
264
265static void
266local_do_shell(const char *args)
267{
268 int status;
269 char *shell;
270 pid_t pid;
271
272 if (!*args)
273 args = NULL;
274
Damien Miller38d9a962010-10-07 22:07:11 +1100275 if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
Damien Miller20e1fab2004-02-18 14:30:55 +1100276 shell = _PATH_BSHELL;
277
278 if ((pid = fork()) == -1)
279 fatal("Couldn't fork: %s", strerror(errno));
280
281 if (pid == 0) {
282 /* XXX: child has pipe fds to ssh subproc open - issue? */
283 if (args) {
284 debug3("Executing %s -c \"%s\"", shell, args);
285 execl(shell, shell, "-c", args, (char *)NULL);
286 } else {
287 debug3("Executing %s", shell);
288 execl(shell, shell, (char *)NULL);
289 }
290 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
291 strerror(errno));
292 _exit(1);
293 }
294 while (waitpid(pid, &status, 0) == -1)
295 if (errno != EINTR)
296 fatal("Couldn't wait for child: %s", strerror(errno));
297 if (!WIFEXITED(status))
Damien Miller55b04f12006-03-26 14:23:17 +1100298 error("Shell exited abnormally");
Damien Miller20e1fab2004-02-18 14:30:55 +1100299 else if (WEXITSTATUS(status))
300 error("Shell exited with status %d", WEXITSTATUS(status));
301}
302
303static void
304local_do_ls(const char *args)
305{
306 if (!args || !*args)
307 local_do_shell(_PATH_LS);
308 else {
309 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
310 char *buf = xmalloc(len);
311
312 /* XXX: quoting - rip quoting code from ftp? */
313 snprintf(buf, len, _PATH_LS " %s", args);
314 local_do_shell(buf);
Darren Tuckera627d422013-06-02 07:31:17 +1000315 free(buf);
Damien Miller20e1fab2004-02-18 14:30:55 +1100316 }
317}
318
319/* Strip one path (usually the pwd) from the start of another */
320static char *
321path_strip(char *path, char *strip)
322{
323 size_t len;
324
325 if (strip == NULL)
326 return (xstrdup(path));
327
328 len = strlen(strip);
Darren Tuckere2f189a2004-12-06 22:45:53 +1100329 if (strncmp(path, strip, len) == 0) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100330 if (strip[len - 1] != '/' && path[len] == '/')
331 len++;
332 return (xstrdup(path + len));
333 }
334
335 return (xstrdup(path));
336}
337
338static char *
Damien Miller20e1fab2004-02-18 14:30:55 +1100339make_absolute(char *p, char *pwd)
340{
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000341 char *abs_str;
Damien Miller20e1fab2004-02-18 14:30:55 +1100342
343 /* Derelativise */
344 if (p && p[0] != '/') {
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000345 abs_str = path_append(pwd, p);
Darren Tuckera627d422013-06-02 07:31:17 +1000346 free(p);
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000347 return(abs_str);
Damien Miller20e1fab2004-02-18 14:30:55 +1100348 } else
349 return(p);
350}
351
352static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100353parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag,
354 int *rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100355{
Damien Millerf184bcf2008-06-29 22:45:13 +1000356 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000357 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100358
Damien Miller1cbc2922007-10-26 14:27:45 +1000359 optind = optreset = 1;
360 opterr = 0;
361
Darren Tucker1b0dd172009-10-07 08:37:48 +1100362 *rflag = *pflag = 0;
363 while ((ch = getopt(argc, argv, "PpRr")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000364 switch (ch) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100365 case 'p':
366 case 'P':
367 *pflag = 1;
368 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100369 case 'r':
370 case 'R':
371 *rflag = 1;
372 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100373 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000374 error("%s: Invalid flag -%c", cmd, optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000375 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100376 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100377 }
378
Damien Miller1cbc2922007-10-26 14:27:45 +1000379 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100380}
381
382static int
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100383parse_link_flags(const char *cmd, char **argv, int argc, int *sflag)
384{
385 extern int opterr, optind, optopt, optreset;
386 int ch;
387
388 optind = optreset = 1;
389 opterr = 0;
390
391 *sflag = 0;
392 while ((ch = getopt(argc, argv, "s")) != -1) {
393 switch (ch) {
394 case 's':
395 *sflag = 1;
396 break;
397 default:
398 error("%s: Invalid flag -%c", cmd, optopt);
399 return -1;
400 }
401 }
402
403 return optind;
404}
405
406static int
Damien Miller1cbc2922007-10-26 14:27:45 +1000407parse_ls_flags(char **argv, int argc, int *lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100408{
Damien Millerf184bcf2008-06-29 22:45:13 +1000409 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000410 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100411
Damien Miller1cbc2922007-10-26 14:27:45 +1000412 optind = optreset = 1;
413 opterr = 0;
414
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000415 *lflag = LS_NAME_SORT;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100416 while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000417 switch (ch) {
418 case '1':
419 *lflag &= ~VIEW_FLAGS;
420 *lflag |= LS_SHORT_VIEW;
421 break;
422 case 'S':
423 *lflag &= ~SORT_FLAGS;
424 *lflag |= LS_SIZE_SORT;
425 break;
426 case 'a':
427 *lflag |= LS_SHOW_ALL;
428 break;
429 case 'f':
430 *lflag &= ~SORT_FLAGS;
431 break;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100432 case 'h':
433 *lflag |= LS_SI_UNITS;
434 break;
Damien Miller1cbc2922007-10-26 14:27:45 +1000435 case 'l':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100436 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000437 *lflag |= LS_LONG_VIEW;
438 break;
439 case 'n':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100440 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000441 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
442 break;
443 case 'r':
444 *lflag |= LS_REVERSE_SORT;
445 break;
446 case 't':
447 *lflag &= ~SORT_FLAGS;
448 *lflag |= LS_TIME_SORT;
449 break;
450 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000451 error("ls: Invalid flag -%c", optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000452 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100453 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100454 }
455
Damien Miller1cbc2922007-10-26 14:27:45 +1000456 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100457}
458
459static int
Damien Millerd671e5a2008-05-19 14:53:33 +1000460parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
461{
Damien Millerf184bcf2008-06-29 22:45:13 +1000462 extern int opterr, optind, optopt, optreset;
Damien Millerd671e5a2008-05-19 14:53:33 +1000463 int ch;
464
465 optind = optreset = 1;
466 opterr = 0;
467
468 *hflag = *iflag = 0;
469 while ((ch = getopt(argc, argv, "hi")) != -1) {
470 switch (ch) {
471 case 'h':
472 *hflag = 1;
473 break;
474 case 'i':
475 *iflag = 1;
476 break;
477 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000478 error("%s: Invalid flag -%c", cmd, optopt);
Damien Millerd671e5a2008-05-19 14:53:33 +1000479 return -1;
480 }
481 }
482
483 return optind;
484}
485
486static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100487is_dir(char *path)
488{
489 struct stat sb;
490
491 /* XXX: report errors? */
492 if (stat(path, &sb) == -1)
493 return(0);
494
Darren Tucker1e80e402006-09-21 12:59:33 +1000495 return(S_ISDIR(sb.st_mode));
Damien Miller20e1fab2004-02-18 14:30:55 +1100496}
497
498static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100499remote_is_dir(struct sftp_conn *conn, char *path)
500{
501 Attrib *a;
502
503 /* XXX: report errors? */
504 if ((a = do_stat(conn, path, 1)) == NULL)
505 return(0);
506 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
507 return(0);
Darren Tucker1e80e402006-09-21 12:59:33 +1000508 return(S_ISDIR(a->perm));
Damien Miller20e1fab2004-02-18 14:30:55 +1100509}
510
Darren Tucker1b0dd172009-10-07 08:37:48 +1100511/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
Damien Miller20e1fab2004-02-18 14:30:55 +1100512static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100513pathname_is_dir(char *pathname)
514{
515 size_t l = strlen(pathname);
516
517 return l > 0 && pathname[l - 1] == '/';
518}
519
520static int
521process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
522 int pflag, int rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100523{
524 char *abs_src = NULL;
525 char *abs_dst = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100526 glob_t g;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100527 char *filename, *tmp=NULL;
528 int i, err = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100529
530 abs_src = xstrdup(src);
531 abs_src = make_absolute(abs_src, pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +1100532 memset(&g, 0, sizeof(g));
Darren Tucker1b0dd172009-10-07 08:37:48 +1100533
Damien Miller20e1fab2004-02-18 14:30:55 +1100534 debug3("Looking up %s", abs_src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100535 if (remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100536 error("File \"%s\" not found.", abs_src);
537 err = -1;
538 goto out;
539 }
540
Darren Tucker1b0dd172009-10-07 08:37:48 +1100541 /*
542 * If multiple matches then dst must be a directory or
543 * unspecified.
544 */
545 if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
546 error("Multiple source paths, but destination "
547 "\"%s\" is not a directory", dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100548 err = -1;
549 goto out;
550 }
551
Darren Tuckercdf547a2004-05-24 10:12:19 +1000552 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100553 tmp = xstrdup(g.gl_pathv[i]);
554 if ((filename = basename(tmp)) == NULL) {
555 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000556 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100557 err = -1;
558 goto out;
559 }
560
561 if (g.gl_matchc == 1 && dst) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100562 if (is_dir(dst)) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100563 abs_dst = path_append(dst, filename);
564 } else {
Damien Miller20e1fab2004-02-18 14:30:55 +1100565 abs_dst = xstrdup(dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100566 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100567 } else if (dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100568 abs_dst = path_append(dst, filename);
569 } else {
570 abs_dst = xstrdup(filename);
571 }
Darren Tuckera627d422013-06-02 07:31:17 +1000572 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100573
Damien Miller9303e652013-04-23 15:22:40 +1000574 if (!quiet)
575 printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100576 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
577 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
578 pflag || global_pflag, 1) == -1)
579 err = -1;
580 } else {
581 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
582 pflag || global_pflag) == -1)
583 err = -1;
584 }
Darren Tuckera627d422013-06-02 07:31:17 +1000585 free(abs_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100586 abs_dst = NULL;
587 }
588
589out:
Darren Tuckera627d422013-06-02 07:31:17 +1000590 free(abs_src);
Damien Miller20e1fab2004-02-18 14:30:55 +1100591 globfree(&g);
592 return(err);
593}
594
595static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100596process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
597 int pflag, int rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100598{
599 char *tmp_dst = NULL;
600 char *abs_dst = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100601 char *tmp = NULL, *filename = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100602 glob_t g;
603 int err = 0;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100604 int i, dst_is_dir = 1;
Damien Milleraec5cf82008-02-10 22:26:24 +1100605 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100606
607 if (dst) {
608 tmp_dst = xstrdup(dst);
609 tmp_dst = make_absolute(tmp_dst, pwd);
610 }
611
612 memset(&g, 0, sizeof(g));
613 debug3("Looking up %s", src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100614 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100615 error("File \"%s\" not found.", src);
616 err = -1;
617 goto out;
618 }
619
Darren Tucker1b0dd172009-10-07 08:37:48 +1100620 /* If we aren't fetching to pwd then stash this status for later */
621 if (tmp_dst != NULL)
622 dst_is_dir = remote_is_dir(conn, tmp_dst);
623
Damien Miller20e1fab2004-02-18 14:30:55 +1100624 /* If multiple matches, dst may be directory or unspecified */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100625 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
626 error("Multiple paths match, but destination "
627 "\"%s\" is not a directory", tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100628 err = -1;
629 goto out;
630 }
631
Darren Tuckercdf547a2004-05-24 10:12:19 +1000632 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Milleraec5cf82008-02-10 22:26:24 +1100633 if (stat(g.gl_pathv[i], &sb) == -1) {
634 err = -1;
635 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
636 continue;
637 }
Darren Tucker1b0dd172009-10-07 08:37:48 +1100638
639 tmp = xstrdup(g.gl_pathv[i]);
640 if ((filename = basename(tmp)) == NULL) {
641 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000642 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100643 err = -1;
644 goto out;
645 }
646
647 if (g.gl_matchc == 1 && tmp_dst) {
648 /* If directory specified, append filename */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100649 if (dst_is_dir)
650 abs_dst = path_append(tmp_dst, filename);
651 else
Damien Miller20e1fab2004-02-18 14:30:55 +1100652 abs_dst = xstrdup(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100653 } else if (tmp_dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100654 abs_dst = path_append(tmp_dst, filename);
655 } else {
656 abs_dst = make_absolute(xstrdup(filename), pwd);
657 }
Darren Tuckera627d422013-06-02 07:31:17 +1000658 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100659
Damien Miller9303e652013-04-23 15:22:40 +1000660 if (!quiet)
661 printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100662 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
663 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
664 pflag || global_pflag, 1) == -1)
665 err = -1;
666 } else {
667 if (do_upload(conn, g.gl_pathv[i], abs_dst,
668 pflag || global_pflag) == -1)
669 err = -1;
670 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100671 }
672
673out:
Darren Tuckera627d422013-06-02 07:31:17 +1000674 free(abs_dst);
675 free(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100676 globfree(&g);
677 return(err);
678}
679
680static int
681sdirent_comp(const void *aa, const void *bb)
682{
683 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
684 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000685 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100686
Darren Tuckerb9123452004-06-22 13:06:45 +1000687#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000688 if (sort_flag & LS_NAME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000689 return (rmul * strcmp(a->filename, b->filename));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000690 else if (sort_flag & LS_TIME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000691 return (rmul * NCMP(a->a.mtime, b->a.mtime));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000692 else if (sort_flag & LS_SIZE_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000693 return (rmul * NCMP(a->a.size, b->a.size));
694
695 fatal("Unknown ls sort type");
Damien Miller20e1fab2004-02-18 14:30:55 +1100696}
697
698/* sftp ls.1 replacement for directories */
699static int
700do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
701{
Damien Millereccb9de2005-06-17 12:59:34 +1000702 int n;
703 u_int c = 1, colspace = 0, columns = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100704 SFTP_DIRENT **d;
705
706 if ((n = do_readdir(conn, path, &d)) != 0)
707 return (n);
708
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000709 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000710 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100711 struct winsize ws;
712 char *tmp;
713
714 /* Count entries for sort and find longest filename */
Darren Tucker9a526452004-06-22 13:09:55 +1000715 for (n = 0; d[n] != NULL; n++) {
716 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
717 m = MAX(m, strlen(d[n]->filename));
718 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100719
720 /* Add any subpath that also needs to be counted */
721 tmp = path_strip(path, strip_path);
722 m += strlen(tmp);
Darren Tuckera627d422013-06-02 07:31:17 +1000723 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100724
725 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
726 width = ws.ws_col;
727
728 columns = width / (m + 2);
729 columns = MAX(columns, 1);
730 colspace = width / columns;
731 colspace = MIN(colspace, width);
732 }
733
Darren Tuckerb9123452004-06-22 13:06:45 +1000734 if (lflag & SORT_FLAGS) {
Damien Miller653b93b2005-11-05 15:15:23 +1100735 for (n = 0; d[n] != NULL; n++)
736 ; /* count entries */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000737 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
Darren Tuckerb9123452004-06-22 13:06:45 +1000738 qsort(d, n, sizeof(*d), sdirent_comp);
739 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100740
Darren Tuckercdf547a2004-05-24 10:12:19 +1000741 for (n = 0; d[n] != NULL && !interrupted; n++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100742 char *tmp, *fname;
743
Darren Tucker9a526452004-06-22 13:09:55 +1000744 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
745 continue;
746
Damien Miller20e1fab2004-02-18 14:30:55 +1100747 tmp = path_append(path, d[n]->filename);
748 fname = path_strip(tmp, strip_path);
Darren Tuckera627d422013-06-02 07:31:17 +1000749 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100750
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000751 if (lflag & LS_LONG_VIEW) {
Darren Tucker2901e2d2010-01-13 22:44:06 +1100752 if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000753 char *lname;
754 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100755
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000756 memset(&sb, 0, sizeof(sb));
757 attrib_to_stat(&d[n]->a, &sb);
Darren Tucker2901e2d2010-01-13 22:44:06 +1100758 lname = ls_file(fname, &sb, 1,
759 (lflag & LS_SI_UNITS));
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000760 printf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000761 free(lname);
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000762 } else
763 printf("%s\n", d[n]->longname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100764 } else {
765 printf("%-*s", colspace, fname);
766 if (c >= columns) {
767 printf("\n");
768 c = 1;
769 } else
770 c++;
771 }
772
Darren Tuckera627d422013-06-02 07:31:17 +1000773 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100774 }
775
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000776 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100777 printf("\n");
778
779 free_sftp_dirents(d);
780 return (0);
781}
782
783/* sftp ls.1 replacement which handles path globs */
784static int
785do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
786 int lflag)
787{
Damien Millera6e121a2010-10-07 21:39:17 +1100788 char *fname, *lname;
Damien Miller68e2e562010-10-07 21:39:55 +1100789 glob_t g;
790 int err;
791 struct winsize ws;
792 u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100793
794 memset(&g, 0, sizeof(g));
795
Damien Millera6e121a2010-10-07 21:39:17 +1100796 if (remote_glob(conn, path,
Damien Millerd7be70d2011-09-22 21:43:06 +1000797 GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
798 NULL, &g) ||
Damien Millera6e121a2010-10-07 21:39:17 +1100799 (g.gl_pathc && !g.gl_matchc)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100800 if (g.gl_pathc)
801 globfree(&g);
Damien Miller20e1fab2004-02-18 14:30:55 +1100802 error("Can't ls: \"%s\" not found", path);
Damien Millera6e121a2010-10-07 21:39:17 +1100803 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100804 }
805
Darren Tuckercdf547a2004-05-24 10:12:19 +1000806 if (interrupted)
807 goto out;
808
Damien Miller20e1fab2004-02-18 14:30:55 +1100809 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100810 * If the glob returns a single match and it is a directory,
811 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100812 */
Damien Millera6e121a2010-10-07 21:39:17 +1100813 if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
814 S_ISDIR(g.gl_statv[0]->st_mode)) {
815 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
816 globfree(&g);
817 return err;
Damien Miller20e1fab2004-02-18 14:30:55 +1100818 }
819
Damien Miller68e2e562010-10-07 21:39:55 +1100820 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
821 width = ws.ws_col;
Damien Miller20e1fab2004-02-18 14:30:55 +1100822
Damien Miller68e2e562010-10-07 21:39:55 +1100823 if (!(lflag & LS_SHORT_VIEW)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100824 /* Count entries for sort and find longest filename */
825 for (i = 0; g.gl_pathv[i]; i++)
826 m = MAX(m, strlen(g.gl_pathv[i]));
827
Damien Miller20e1fab2004-02-18 14:30:55 +1100828 columns = width / (m + 2);
829 columns = MAX(columns, 1);
830 colspace = width / columns;
831 }
832
Damien Millerea858292012-06-30 08:33:32 +1000833 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100834 fname = path_strip(g.gl_pathv[i], strip_path);
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000835 if (lflag & LS_LONG_VIEW) {
Damien Millera6e121a2010-10-07 21:39:17 +1100836 if (g.gl_statv[i] == NULL) {
837 error("no stat information for %s", fname);
838 continue;
839 }
840 lname = ls_file(fname, g.gl_statv[i], 1,
841 (lflag & LS_SI_UNITS));
Damien Miller20e1fab2004-02-18 14:30:55 +1100842 printf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000843 free(lname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100844 } else {
845 printf("%-*s", colspace, fname);
846 if (c >= columns) {
847 printf("\n");
848 c = 1;
849 } else
850 c++;
851 }
Darren Tuckera627d422013-06-02 07:31:17 +1000852 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100853 }
854
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000855 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100856 printf("\n");
857
Darren Tuckercdf547a2004-05-24 10:12:19 +1000858 out:
Damien Miller20e1fab2004-02-18 14:30:55 +1100859 if (g.gl_pathc)
860 globfree(&g);
861
Damien Millera6e121a2010-10-07 21:39:17 +1100862 return 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100863}
864
Damien Millerd671e5a2008-05-19 14:53:33 +1000865static int
866do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
867{
Darren Tucker7b598892008-06-09 22:49:36 +1000868 struct sftp_statvfs st;
Damien Millerd671e5a2008-05-19 14:53:33 +1000869 char s_used[FMT_SCALED_STRSIZE];
870 char s_avail[FMT_SCALED_STRSIZE];
871 char s_root[FMT_SCALED_STRSIZE];
872 char s_total[FMT_SCALED_STRSIZE];
Darren Tuckerb5082e92010-01-08 18:51:47 +1100873 unsigned long long ffree;
Damien Millerd671e5a2008-05-19 14:53:33 +1000874
875 if (do_statvfs(conn, path, &st, 1) == -1)
876 return -1;
877 if (iflag) {
Darren Tuckerb5082e92010-01-08 18:51:47 +1100878 ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
Damien Millerd671e5a2008-05-19 14:53:33 +1000879 printf(" Inodes Used Avail "
880 "(root) %%Capacity\n");
881 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
882 (unsigned long long)st.f_files,
883 (unsigned long long)(st.f_files - st.f_ffree),
884 (unsigned long long)st.f_favail,
Darren Tuckerb5082e92010-01-08 18:51:47 +1100885 (unsigned long long)st.f_ffree, ffree);
Damien Millerd671e5a2008-05-19 14:53:33 +1000886 } else if (hflag) {
887 strlcpy(s_used, "error", sizeof(s_used));
888 strlcpy(s_avail, "error", sizeof(s_avail));
889 strlcpy(s_root, "error", sizeof(s_root));
890 strlcpy(s_total, "error", sizeof(s_total));
891 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
892 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
893 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
894 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
895 printf(" Size Used Avail (root) %%Capacity\n");
896 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
897 s_total, s_used, s_avail, s_root,
898 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
899 st.f_blocks));
900 } else {
901 printf(" Size Used Avail "
902 "(root) %%Capacity\n");
903 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
904 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
905 (unsigned long long)(st.f_frsize *
906 (st.f_blocks - st.f_bfree) / 1024),
907 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
908 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
909 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
910 st.f_blocks));
911 }
912 return 0;
913}
914
Damien Miller1cbc2922007-10-26 14:27:45 +1000915/*
916 * Undo escaping of glob sequences in place. Used to undo extra escaping
917 * applied in makeargv() when the string is destined for a function that
918 * does not glob it.
919 */
920static void
921undo_glob_escape(char *s)
922{
923 size_t i, j;
924
925 for (i = j = 0;;) {
926 if (s[i] == '\0') {
927 s[j] = '\0';
928 return;
929 }
930 if (s[i] != '\\') {
931 s[j++] = s[i++];
932 continue;
933 }
934 /* s[i] == '\\' */
935 ++i;
936 switch (s[i]) {
937 case '?':
938 case '[':
939 case '*':
940 case '\\':
941 s[j++] = s[i++];
942 break;
943 case '\0':
944 s[j++] = '\\';
945 s[j] = '\0';
946 return;
947 default:
948 s[j++] = '\\';
949 s[j++] = s[i++];
950 break;
951 }
952 }
953}
954
955/*
956 * Split a string into an argument vector using sh(1)-style quoting,
957 * comment and escaping rules, but with some tweaks to handle glob(3)
958 * wildcards.
Darren Tucker909d8582010-01-08 19:02:40 +1100959 * The "sloppy" flag allows for recovery from missing terminating quote, for
960 * use in parsing incomplete commandlines during tab autocompletion.
961 *
Damien Miller1cbc2922007-10-26 14:27:45 +1000962 * Returns NULL on error or a NULL-terminated array of arguments.
Darren Tucker909d8582010-01-08 19:02:40 +1100963 *
964 * If "lastquote" is not NULL, the quoting character used for the last
965 * argument is placed in *lastquote ("\0", "'" or "\"").
966 *
967 * If "terminated" is not NULL, *terminated will be set to 1 when the
968 * last argument's quote has been properly terminated or 0 otherwise.
969 * This parameter is only of use if "sloppy" is set.
Damien Miller1cbc2922007-10-26 14:27:45 +1000970 */
971#define MAXARGS 128
972#define MAXARGLEN 8192
973static char **
Darren Tucker909d8582010-01-08 19:02:40 +1100974makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
975 u_int *terminated)
Damien Miller1cbc2922007-10-26 14:27:45 +1000976{
977 int argc, quot;
978 size_t i, j;
979 static char argvs[MAXARGLEN];
980 static char *argv[MAXARGS + 1];
981 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
982
983 *argcp = argc = 0;
984 if (strlen(arg) > sizeof(argvs) - 1) {
985 args_too_longs:
986 error("string too long");
987 return NULL;
988 }
Darren Tucker909d8582010-01-08 19:02:40 +1100989 if (terminated != NULL)
990 *terminated = 1;
991 if (lastquote != NULL)
992 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +1000993 state = MA_START;
994 i = j = 0;
995 for (;;) {
Damien Miller07daed52012-10-31 08:57:55 +1100996 if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){
Darren Tucker063018d2012-10-05 10:43:58 +1000997 error("Too many arguments.");
998 return NULL;
999 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001000 if (isspace(arg[i])) {
1001 if (state == MA_UNQUOTED) {
1002 /* Terminate current argument */
1003 argvs[j++] = '\0';
1004 argc++;
1005 state = MA_START;
1006 } else if (state != MA_START)
1007 argvs[j++] = arg[i];
1008 } else if (arg[i] == '"' || arg[i] == '\'') {
1009 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1010 if (state == MA_START) {
1011 argv[argc] = argvs + j;
1012 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +11001013 if (lastquote != NULL)
1014 *lastquote = arg[i];
Damien Miller1cbc2922007-10-26 14:27:45 +10001015 } else if (state == MA_UNQUOTED)
1016 state = q;
1017 else if (state == q)
1018 state = MA_UNQUOTED;
1019 else
1020 argvs[j++] = arg[i];
1021 } else if (arg[i] == '\\') {
1022 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1023 quot = state == MA_SQUOTE ? '\'' : '"';
1024 /* Unescape quote we are in */
1025 /* XXX support \n and friends? */
1026 if (arg[i + 1] == quot) {
1027 i++;
1028 argvs[j++] = arg[i];
1029 } else if (arg[i + 1] == '?' ||
1030 arg[i + 1] == '[' || arg[i + 1] == '*') {
1031 /*
1032 * Special case for sftp: append
1033 * double-escaped glob sequence -
1034 * glob will undo one level of
1035 * escaping. NB. string can grow here.
1036 */
1037 if (j >= sizeof(argvs) - 5)
1038 goto args_too_longs;
1039 argvs[j++] = '\\';
1040 argvs[j++] = arg[i++];
1041 argvs[j++] = '\\';
1042 argvs[j++] = arg[i];
1043 } else {
1044 argvs[j++] = arg[i++];
1045 argvs[j++] = arg[i];
1046 }
1047 } else {
1048 if (state == MA_START) {
1049 argv[argc] = argvs + j;
1050 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001051 if (lastquote != NULL)
1052 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001053 }
1054 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1055 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1056 /*
1057 * Special case for sftp: append
1058 * escaped glob sequence -
1059 * glob will undo one level of
1060 * escaping.
1061 */
1062 argvs[j++] = arg[i++];
1063 argvs[j++] = arg[i];
1064 } else {
1065 /* Unescape everything */
1066 /* XXX support \n and friends? */
1067 i++;
1068 argvs[j++] = arg[i];
1069 }
1070 }
1071 } else if (arg[i] == '#') {
1072 if (state == MA_SQUOTE || state == MA_DQUOTE)
1073 argvs[j++] = arg[i];
1074 else
1075 goto string_done;
1076 } else if (arg[i] == '\0') {
1077 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001078 if (sloppy) {
1079 state = MA_UNQUOTED;
1080 if (terminated != NULL)
1081 *terminated = 0;
1082 goto string_done;
1083 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001084 error("Unterminated quoted argument");
1085 return NULL;
1086 }
1087 string_done:
1088 if (state == MA_UNQUOTED) {
1089 argvs[j++] = '\0';
1090 argc++;
1091 }
1092 break;
1093 } else {
1094 if (state == MA_START) {
1095 argv[argc] = argvs + j;
1096 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001097 if (lastquote != NULL)
1098 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001099 }
1100 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1101 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1102 /*
1103 * Special case for sftp: escape quoted
1104 * glob(3) wildcards. NB. string can grow
1105 * here.
1106 */
1107 if (j >= sizeof(argvs) - 3)
1108 goto args_too_longs;
1109 argvs[j++] = '\\';
1110 argvs[j++] = arg[i];
1111 } else
1112 argvs[j++] = arg[i];
1113 }
1114 i++;
1115 }
1116 *argcp = argc;
1117 return argv;
1118}
1119
Damien Miller20e1fab2004-02-18 14:30:55 +11001120static int
Darren Tucker909d8582010-01-08 19:02:40 +11001121parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag,
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001122 int *hflag, int *sflag, unsigned long *n_arg, char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001123{
1124 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001125 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001126 int base = 0;
1127 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001128 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001129
1130 /* Skip leading whitespace */
1131 cp = cp + strspn(cp, WHITESPACE);
1132
Damien Miller20e1fab2004-02-18 14:30:55 +11001133 /* Check for leading '-' (disable error processing) */
1134 *iflag = 0;
1135 if (*cp == '-') {
1136 *iflag = 1;
1137 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001138 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001139 }
1140
Darren Tucker70cc0922010-01-09 22:28:03 +11001141 /* Ignore blank lines and lines which begin with comment '#' char */
1142 if (*cp == '\0' || *cp == '#')
1143 return (0);
1144
Darren Tucker909d8582010-01-08 19:02:40 +11001145 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001146 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001147
Damien Miller1cbc2922007-10-26 14:27:45 +10001148 /* Figure out which command we have */
1149 for (i = 0; cmds[i].c != NULL; i++) {
Damien Millerd6d9fa02013-02-12 11:02:46 +11001150 if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001151 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001152 }
1153 cmdnum = cmds[i].n;
1154 cmd = cmds[i].c;
1155
1156 /* Special case */
1157 if (*cp == '!') {
1158 cp++;
1159 cmdnum = I_SHELL;
1160 } else if (cmdnum == -1) {
1161 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001162 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001163 }
1164
1165 /* Get arguments and parse flags */
Darren Tucker1b0dd172009-10-07 08:37:48 +11001166 *lflag = *pflag = *rflag = *hflag = *n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001167 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001168 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001169 switch (cmdnum) {
1170 case I_GET:
1171 case I_PUT:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001172 if ((optidx = parse_getput_flags(cmd, argv, argc,
1173 pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001174 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001175 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001176 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001177 error("You must specify at least one path after a "
1178 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001179 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001180 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001181 *path1 = xstrdup(argv[optidx]);
1182 /* Get second pathname (optional) */
1183 if (argc - optidx > 1) {
1184 *path2 = xstrdup(argv[optidx + 1]);
1185 /* Destination is not globbed */
1186 undo_glob_escape(*path2);
1187 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001188 break;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001189 case I_LINK:
1190 if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1191 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001192 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001193 case I_RENAME:
Damien Miller1cbc2922007-10-26 14:27:45 +10001194 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001195 error("You must specify two paths after a %s "
1196 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001197 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001198 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001199 *path1 = xstrdup(argv[optidx]);
1200 *path2 = xstrdup(argv[optidx + 1]);
1201 /* Paths are not globbed */
1202 undo_glob_escape(*path1);
1203 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001204 break;
1205 case I_RM:
1206 case I_MKDIR:
1207 case I_RMDIR:
1208 case I_CHDIR:
1209 case I_LCHDIR:
1210 case I_LMKDIR:
1211 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001212 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001213 error("You must specify a path after a %s command.",
1214 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001215 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001216 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001217 *path1 = xstrdup(argv[optidx]);
1218 /* Only "rm" globs */
1219 if (cmdnum != I_RM)
1220 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001221 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001222 case I_DF:
1223 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1224 iflag)) == -1)
1225 return -1;
1226 /* Default to current directory if no path specified */
1227 if (argc - optidx < 1)
1228 *path1 = NULL;
1229 else {
1230 *path1 = xstrdup(argv[optidx]);
1231 undo_glob_escape(*path1);
1232 }
1233 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001234 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001235 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001236 return(-1);
1237 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001238 if (argc - optidx > 0)
1239 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001240 break;
1241 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001242 /* Skip ls command and following whitespace */
1243 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001244 case I_SHELL:
1245 /* Uses the rest of the line */
1246 break;
1247 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001248 case I_CHMOD:
1249 base = 8;
1250 case I_CHOWN:
1251 case I_CHGRP:
1252 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001253 if (argc - optidx < 1)
1254 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001255 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001256 l = strtol(argv[optidx], &cp2, base);
1257 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1258 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1259 l < 0) {
1260 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001261 error("You must supply a numeric argument "
1262 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001263 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001264 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001265 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001266 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001267 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001268 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001269 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001270 error("You must specify a path after a %s command.",
1271 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001272 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001273 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001274 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001275 break;
1276 case I_QUIT:
1277 case I_PWD:
1278 case I_LPWD:
1279 case I_HELP:
1280 case I_VERSION:
1281 case I_PROGRESS:
1282 break;
1283 default:
1284 fatal("Command not implemented");
1285 }
1286
1287 *cpp = cp;
1288 return(cmdnum);
1289}
1290
1291static int
1292parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1293 int err_abort)
1294{
1295 char *path1, *path2, *tmp;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001296 int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, sflag = 0;
1297 int cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001298 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001299 Attrib a, *aa;
1300 char path_buf[MAXPATHLEN];
1301 int err = 0;
1302 glob_t g;
1303
1304 path1 = path2 = NULL;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001305 cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag,
1306 &sflag, &n_arg, &path1, &path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001307
1308 if (iflag != 0)
1309 err_abort = 0;
1310
1311 memset(&g, 0, sizeof(g));
1312
1313 /* Perform command */
1314 switch (cmdnum) {
1315 case 0:
1316 /* Blank line */
1317 break;
1318 case -1:
1319 /* Unrecognized command */
1320 err = -1;
1321 break;
1322 case I_GET:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001323 err = process_get(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001324 break;
1325 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001326 err = process_put(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001327 break;
1328 case I_RENAME:
1329 path1 = make_absolute(path1, *pwd);
1330 path2 = make_absolute(path2, *pwd);
1331 err = do_rename(conn, path1, path2);
1332 break;
1333 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001334 sflag = 1;
1335 case I_LINK:
1336 path1 = make_absolute(path1, *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001337 path2 = make_absolute(path2, *pwd);
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001338 err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001339 break;
1340 case I_RM:
1341 path1 = make_absolute(path1, *pwd);
1342 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001343 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001344 if (!quiet)
1345 printf("Removing %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001346 err = do_rm(conn, g.gl_pathv[i]);
1347 if (err != 0 && err_abort)
1348 break;
1349 }
1350 break;
1351 case I_MKDIR:
1352 path1 = make_absolute(path1, *pwd);
1353 attrib_clear(&a);
1354 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1355 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001356 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001357 break;
1358 case I_RMDIR:
1359 path1 = make_absolute(path1, *pwd);
1360 err = do_rmdir(conn, path1);
1361 break;
1362 case I_CHDIR:
1363 path1 = make_absolute(path1, *pwd);
1364 if ((tmp = do_realpath(conn, path1)) == NULL) {
1365 err = 1;
1366 break;
1367 }
1368 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
Darren Tuckera627d422013-06-02 07:31:17 +10001369 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001370 err = 1;
1371 break;
1372 }
1373 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1374 error("Can't change directory: Can't check target");
Darren Tuckera627d422013-06-02 07:31:17 +10001375 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001376 err = 1;
1377 break;
1378 }
1379 if (!S_ISDIR(aa->perm)) {
1380 error("Can't change directory: \"%s\" is not "
1381 "a directory", tmp);
Darren Tuckera627d422013-06-02 07:31:17 +10001382 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001383 err = 1;
1384 break;
1385 }
Darren Tuckera627d422013-06-02 07:31:17 +10001386 free(*pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001387 *pwd = tmp;
1388 break;
1389 case I_LS:
1390 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001391 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001392 break;
1393 }
1394
1395 /* Strip pwd off beginning of non-absolute paths */
1396 tmp = NULL;
1397 if (*path1 != '/')
1398 tmp = *pwd;
1399
1400 path1 = make_absolute(path1, *pwd);
1401 err = do_globbed_ls(conn, path1, tmp, lflag);
1402 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001403 case I_DF:
1404 /* Default to current directory if no path specified */
1405 if (path1 == NULL)
1406 path1 = xstrdup(*pwd);
1407 path1 = make_absolute(path1, *pwd);
1408 err = do_df(conn, path1, hflag, iflag);
1409 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001410 case I_LCHDIR:
1411 if (chdir(path1) == -1) {
1412 error("Couldn't change local directory to "
1413 "\"%s\": %s", path1, strerror(errno));
1414 err = 1;
1415 }
1416 break;
1417 case I_LMKDIR:
1418 if (mkdir(path1, 0777) == -1) {
1419 error("Couldn't create local directory "
1420 "\"%s\": %s", path1, strerror(errno));
1421 err = 1;
1422 }
1423 break;
1424 case I_LLS:
1425 local_do_ls(cmd);
1426 break;
1427 case I_SHELL:
1428 local_do_shell(cmd);
1429 break;
1430 case I_LUMASK:
1431 umask(n_arg);
1432 printf("Local umask: %03lo\n", n_arg);
1433 break;
1434 case I_CHMOD:
1435 path1 = make_absolute(path1, *pwd);
1436 attrib_clear(&a);
1437 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1438 a.perm = n_arg;
1439 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001440 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001441 if (!quiet)
1442 printf("Changing mode on %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001443 err = do_setstat(conn, g.gl_pathv[i], &a);
1444 if (err != 0 && err_abort)
1445 break;
1446 }
1447 break;
1448 case I_CHOWN:
1449 case I_CHGRP:
1450 path1 = make_absolute(path1, *pwd);
1451 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001452 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001453 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001454 if (err_abort) {
1455 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001456 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001457 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001458 continue;
1459 }
1460 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1461 error("Can't get current ownership of "
1462 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001463 if (err_abort) {
1464 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001465 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001466 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001467 continue;
1468 }
1469 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1470 if (cmdnum == I_CHOWN) {
Damien Miller9303e652013-04-23 15:22:40 +10001471 if (!quiet)
1472 printf("Changing owner on %s\n",
1473 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001474 aa->uid = n_arg;
1475 } else {
Damien Miller9303e652013-04-23 15:22:40 +10001476 if (!quiet)
1477 printf("Changing group on %s\n",
1478 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001479 aa->gid = n_arg;
1480 }
1481 err = do_setstat(conn, g.gl_pathv[i], aa);
1482 if (err != 0 && err_abort)
1483 break;
1484 }
1485 break;
1486 case I_PWD:
1487 printf("Remote working directory: %s\n", *pwd);
1488 break;
1489 case I_LPWD:
1490 if (!getcwd(path_buf, sizeof(path_buf))) {
1491 error("Couldn't get local cwd: %s", strerror(errno));
1492 err = -1;
1493 break;
1494 }
1495 printf("Local working directory: %s\n", path_buf);
1496 break;
1497 case I_QUIT:
1498 /* Processed below */
1499 break;
1500 case I_HELP:
1501 help();
1502 break;
1503 case I_VERSION:
1504 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1505 break;
1506 case I_PROGRESS:
1507 showprogress = !showprogress;
1508 if (showprogress)
1509 printf("Progress meter enabled\n");
1510 else
1511 printf("Progress meter disabled\n");
1512 break;
1513 default:
1514 fatal("%d is not implemented", cmdnum);
1515 }
1516
1517 if (g.gl_pathc)
1518 globfree(&g);
Darren Tuckera627d422013-06-02 07:31:17 +10001519 free(path1);
1520 free(path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001521
1522 /* If an unignored error occurs in batch mode we should abort. */
1523 if (err_abort && err != 0)
1524 return (-1);
1525 else if (cmdnum == I_QUIT)
1526 return (1);
1527
1528 return (0);
1529}
1530
Darren Tucker2d963d82004-11-07 20:04:10 +11001531#ifdef USE_LIBEDIT
1532static char *
1533prompt(EditLine *el)
1534{
1535 return ("sftp> ");
1536}
Darren Tucker2d963d82004-11-07 20:04:10 +11001537
Darren Tucker909d8582010-01-08 19:02:40 +11001538/* Display entries in 'list' after skipping the first 'len' chars */
1539static void
1540complete_display(char **list, u_int len)
1541{
1542 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1543 struct winsize ws;
1544 char *tmp;
1545
1546 /* Count entries for sort and find longest */
1547 for (y = 0; list[y]; y++)
1548 m = MAX(m, strlen(list[y]));
1549
1550 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1551 width = ws.ws_col;
1552
1553 m = m > len ? m - len : 0;
1554 columns = width / (m + 2);
1555 columns = MAX(columns, 1);
1556 colspace = width / columns;
1557 colspace = MIN(colspace, width);
1558
1559 printf("\n");
1560 m = 1;
1561 for (y = 0; list[y]; y++) {
1562 llen = strlen(list[y]);
1563 tmp = llen > len ? list[y] + len : "";
1564 printf("%-*s", colspace, tmp);
1565 if (m >= columns) {
1566 printf("\n");
1567 m = 1;
1568 } else
1569 m++;
1570 }
1571 printf("\n");
1572}
1573
1574/*
1575 * Given a "list" of words that begin with a common prefix of "word",
1576 * attempt to find an autocompletion to extends "word" by the next
1577 * characters common to all entries in "list".
1578 */
1579static char *
1580complete_ambiguous(const char *word, char **list, size_t count)
1581{
1582 if (word == NULL)
1583 return NULL;
1584
1585 if (count > 0) {
1586 u_int y, matchlen = strlen(list[0]);
1587
1588 /* Find length of common stem */
1589 for (y = 1; list[y]; y++) {
1590 u_int x;
1591
1592 for (x = 0; x < matchlen; x++)
1593 if (list[0][x] != list[y][x])
1594 break;
1595
1596 matchlen = x;
1597 }
1598
1599 if (matchlen > strlen(word)) {
1600 char *tmp = xstrdup(list[0]);
1601
Darren Tucker340d1682010-01-09 08:54:31 +11001602 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001603 return tmp;
1604 }
1605 }
1606
1607 return xstrdup(word);
1608}
1609
1610/* Autocomplete a sftp command */
1611static int
1612complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1613 int terminated)
1614{
1615 u_int y, count = 0, cmdlen, tmplen;
1616 char *tmp, **list, argterm[3];
1617 const LineInfo *lf;
1618
1619 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1620
1621 /* No command specified: display all available commands */
1622 if (cmd == NULL) {
1623 for (y = 0; cmds[y].c; y++)
1624 list[count++] = xstrdup(cmds[y].c);
1625
1626 list[count] = NULL;
1627 complete_display(list, 0);
1628
1629 for (y = 0; list[y] != NULL; y++)
Darren Tuckera627d422013-06-02 07:31:17 +10001630 free(list[y]);
1631 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001632 return count;
1633 }
1634
1635 /* Prepare subset of commands that start with "cmd" */
1636 cmdlen = strlen(cmd);
1637 for (y = 0; cmds[y].c; y++) {
1638 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
1639 list[count++] = xstrdup(cmds[y].c);
1640 }
1641 list[count] = NULL;
1642
Damien Miller47d81152011-11-25 13:53:48 +11001643 if (count == 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10001644 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001645 return 0;
Damien Miller47d81152011-11-25 13:53:48 +11001646 }
Darren Tucker909d8582010-01-08 19:02:40 +11001647
1648 /* Complete ambigious command */
1649 tmp = complete_ambiguous(cmd, list, count);
1650 if (count > 1)
1651 complete_display(list, 0);
1652
1653 for (y = 0; list[y]; y++)
Darren Tuckera627d422013-06-02 07:31:17 +10001654 free(list[y]);
1655 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001656
1657 if (tmp != NULL) {
1658 tmplen = strlen(tmp);
1659 cmdlen = strlen(cmd);
1660 /* If cmd may be extended then do so */
1661 if (tmplen > cmdlen)
1662 if (el_insertstr(el, tmp + cmdlen) == -1)
1663 fatal("el_insertstr failed.");
1664 lf = el_line(el);
1665 /* Terminate argument cleanly */
1666 if (count == 1) {
1667 y = 0;
1668 if (!terminated)
1669 argterm[y++] = quote;
1670 if (lastarg || *(lf->cursor) != ' ')
1671 argterm[y++] = ' ';
1672 argterm[y] = '\0';
1673 if (y > 0 && el_insertstr(el, argterm) == -1)
1674 fatal("el_insertstr failed.");
1675 }
Darren Tuckera627d422013-06-02 07:31:17 +10001676 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001677 }
1678
1679 return count;
1680}
1681
1682/*
1683 * Determine whether a particular sftp command's arguments (if any)
1684 * represent local or remote files.
1685 */
1686static int
1687complete_is_remote(char *cmd) {
1688 int i;
1689
1690 if (cmd == NULL)
1691 return -1;
1692
1693 for (i = 0; cmds[i].c; i++) {
1694 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
1695 return cmds[i].t;
1696 }
1697
1698 return -1;
1699}
1700
1701/* Autocomplete a filename "file" */
1702static int
1703complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1704 char *file, int remote, int lastarg, char quote, int terminated)
1705{
1706 glob_t g;
Darren Tuckerea647212013-06-06 08:19:09 +10001707 char *tmp, *tmp2, ins[8];
Darren Tucker17146d32012-10-05 10:46:16 +10001708 u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
Darren Tuckerea647212013-06-06 08:19:09 +10001709 int clen;
Darren Tucker909d8582010-01-08 19:02:40 +11001710 const LineInfo *lf;
1711
1712 /* Glob from "file" location */
1713 if (file == NULL)
1714 tmp = xstrdup("*");
1715 else
1716 xasprintf(&tmp, "%s*", file);
1717
Darren Tucker191fcc62012-10-05 10:45:01 +10001718 /* Check if the path is absolute. */
1719 isabs = tmp[0] == '/';
1720
Darren Tucker909d8582010-01-08 19:02:40 +11001721 memset(&g, 0, sizeof(g));
1722 if (remote != LOCAL) {
1723 tmp = make_absolute(tmp, remote_path);
1724 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1725 } else
1726 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1727
1728 /* Determine length of pwd so we can trim completion display */
1729 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1730 /* Terminate counting on first unescaped glob metacharacter */
1731 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1732 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1733 hadglob = 1;
1734 break;
1735 }
1736 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1737 tmplen++;
1738 if (tmp[tmplen] == '/')
1739 pwdlen = tmplen + 1; /* track last seen '/' */
1740 }
Darren Tuckera627d422013-06-02 07:31:17 +10001741 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001742
1743 if (g.gl_matchc == 0)
1744 goto out;
1745
1746 if (g.gl_matchc > 1)
1747 complete_display(g.gl_pathv, pwdlen);
1748
1749 tmp = NULL;
1750 /* Don't try to extend globs */
1751 if (file == NULL || hadglob)
1752 goto out;
1753
1754 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
Darren Tucker191fcc62012-10-05 10:45:01 +10001755 tmp = path_strip(tmp2, isabs ? NULL : remote_path);
Darren Tuckera627d422013-06-02 07:31:17 +10001756 free(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001757
1758 if (tmp == NULL)
1759 goto out;
1760
1761 tmplen = strlen(tmp);
1762 filelen = strlen(file);
1763
Darren Tucker17146d32012-10-05 10:46:16 +10001764 /* Count the number of escaped characters in the input string. */
1765 cesc = isesc = 0;
1766 for (i = 0; i < filelen; i++) {
1767 if (!isesc && file[i] == '\\' && i + 1 < filelen){
1768 isesc = 1;
1769 cesc++;
1770 } else
1771 isesc = 0;
1772 }
1773
1774 if (tmplen > (filelen - cesc)) {
1775 tmp2 = tmp + filelen - cesc;
Darren Tucker909d8582010-01-08 19:02:40 +11001776 len = strlen(tmp2);
1777 /* quote argument on way out */
Darren Tuckerea647212013-06-06 08:19:09 +10001778 for (i = 0; i < len; i += clen) {
1779 if ((clen = mblen(tmp2 + i, len - i)) < 0 ||
1780 (size_t)clen > sizeof(ins) - 2)
1781 fatal("invalid multibyte character");
Darren Tucker909d8582010-01-08 19:02:40 +11001782 ins[0] = '\\';
Darren Tuckerea647212013-06-06 08:19:09 +10001783 memcpy(ins + 1, tmp2 + i, clen);
1784 ins[clen + 1] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001785 switch (tmp2[i]) {
1786 case '\'':
1787 case '"':
1788 case '\\':
1789 case '\t':
Darren Tuckerd78739a2010-10-24 10:56:32 +11001790 case '[':
Darren Tucker909d8582010-01-08 19:02:40 +11001791 case ' ':
Darren Tucker17146d32012-10-05 10:46:16 +10001792 case '#':
1793 case '*':
Darren Tucker909d8582010-01-08 19:02:40 +11001794 if (quote == '\0' || tmp2[i] == quote) {
1795 if (el_insertstr(el, ins) == -1)
1796 fatal("el_insertstr "
1797 "failed.");
1798 break;
1799 }
1800 /* FALLTHROUGH */
1801 default:
1802 if (el_insertstr(el, ins + 1) == -1)
1803 fatal("el_insertstr failed.");
1804 break;
1805 }
1806 }
1807 }
1808
1809 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11001810 if (g.gl_matchc == 1) {
1811 i = 0;
1812 if (!terminated)
1813 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11001814 if (*(lf->cursor - 1) != '/' &&
1815 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11001816 ins[i++] = ' ';
1817 ins[i] = '\0';
1818 if (i > 0 && el_insertstr(el, ins) == -1)
1819 fatal("el_insertstr failed.");
1820 }
Darren Tuckera627d422013-06-02 07:31:17 +10001821 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001822
1823 out:
1824 globfree(&g);
1825 return g.gl_matchc;
1826}
1827
1828/* tab-completion hook function, called via libedit */
1829static unsigned char
1830complete(EditLine *el, int ch)
1831{
1832 char **argv, *line, quote;
Damien Miller746d1a62013-07-18 16:13:02 +10001833 int argc, carg;
1834 u_int cursor, len, terminated, ret = CC_ERROR;
Darren Tucker909d8582010-01-08 19:02:40 +11001835 const LineInfo *lf;
1836 struct complete_ctx *complete_ctx;
1837
1838 lf = el_line(el);
1839 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1840 fatal("%s: el_get failed", __func__);
1841
1842 /* Figure out which argument the cursor points to */
1843 cursor = lf->cursor - lf->buffer;
1844 line = (char *)xmalloc(cursor + 1);
1845 memcpy(line, lf->buffer, cursor);
1846 line[cursor] = '\0';
1847 argv = makeargv(line, &carg, 1, &quote, &terminated);
Darren Tuckera627d422013-06-02 07:31:17 +10001848 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001849
1850 /* Get all the arguments on the line */
1851 len = lf->lastchar - lf->buffer;
1852 line = (char *)xmalloc(len + 1);
1853 memcpy(line, lf->buffer, len);
1854 line[len] = '\0';
1855 argv = makeargv(line, &argc, 1, NULL, NULL);
1856
1857 /* Ensure cursor is at EOL or a argument boundary */
1858 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1859 line[cursor] != '\n') {
Darren Tuckera627d422013-06-02 07:31:17 +10001860 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001861 return ret;
1862 }
1863
1864 if (carg == 0) {
1865 /* Show all available commands */
1866 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1867 ret = CC_REDISPLAY;
1868 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1869 /* Handle the command parsing */
1870 if (complete_cmd_parse(el, argv[0], argc == carg,
1871 quote, terminated) != 0)
1872 ret = CC_REDISPLAY;
1873 } else if (carg >= 1) {
1874 /* Handle file parsing */
1875 int remote = complete_is_remote(argv[0]);
1876 char *filematch = NULL;
1877
1878 if (carg > 1 && line[cursor-1] != ' ')
1879 filematch = argv[carg - 1];
1880
1881 if (remote != 0 &&
1882 complete_match(el, complete_ctx->conn,
1883 *complete_ctx->remote_pathp, filematch,
1884 remote, carg == argc, quote, terminated) != 0)
1885 ret = CC_REDISPLAY;
1886 }
1887
Darren Tuckera627d422013-06-02 07:31:17 +10001888 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001889 return ret;
1890}
Darren Tuckere67f7db2010-01-08 19:50:02 +11001891#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11001892
Damien Miller20e1fab2004-02-18 14:30:55 +11001893int
Darren Tucker21063192010-01-08 17:10:36 +11001894interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001895{
Darren Tucker909d8582010-01-08 19:02:40 +11001896 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11001897 char *dir = NULL;
1898 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10001899 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11001900 EditLine *el = NULL;
1901#ifdef USE_LIBEDIT
1902 History *hl = NULL;
1903 HistEvent hev;
1904 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11001905 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11001906
1907 if (!batchmode && isatty(STDIN_FILENO)) {
1908 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1909 fatal("Couldn't initialise editline");
1910 if ((hl = history_init()) == NULL)
1911 fatal("Couldn't initialise editline history");
1912 history(hl, &hev, H_SETSIZE, 100);
1913 el_set(el, EL_HIST, history, hl);
1914
1915 el_set(el, EL_PROMPT, prompt);
1916 el_set(el, EL_EDITOR, "emacs");
1917 el_set(el, EL_TERMINAL, NULL);
1918 el_set(el, EL_SIGNAL, 1);
1919 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11001920
1921 /* Tab Completion */
1922 el_set(el, EL_ADDFN, "ftp-complete",
Darren Tuckerd78739a2010-10-24 10:56:32 +11001923 "Context sensitive argument completion", complete);
Darren Tucker909d8582010-01-08 19:02:40 +11001924 complete_ctx.conn = conn;
1925 complete_ctx.remote_pathp = &remote_path;
1926 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
1927 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11001928 }
1929#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001930
Darren Tucker909d8582010-01-08 19:02:40 +11001931 remote_path = do_realpath(conn, ".");
1932 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11001933 fatal("Need cwd");
1934
1935 if (file1 != NULL) {
1936 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11001937 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11001938
1939 if (remote_is_dir(conn, dir) && file2 == NULL) {
Damien Miller9303e652013-04-23 15:22:40 +10001940 if (!quiet)
1941 printf("Changing to: %s\n", dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11001942 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001943 if (parse_dispatch_command(conn, cmd,
1944 &remote_path, 1) != 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10001945 free(dir);
1946 free(remote_path);
1947 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001948 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11001949 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001950 } else {
Darren Tucker0af24052012-10-05 10:41:25 +10001951 /* XXX this is wrong wrt quoting */
Damien Miller20e1fab2004-02-18 14:30:55 +11001952 if (file2 == NULL)
1953 snprintf(cmd, sizeof cmd, "get %s", dir);
1954 else
1955 snprintf(cmd, sizeof cmd, "get %s %s", dir,
1956 file2);
1957
Darren Tucker909d8582010-01-08 19:02:40 +11001958 err = parse_dispatch_command(conn, cmd,
1959 &remote_path, 1);
Darren Tuckera627d422013-06-02 07:31:17 +10001960 free(dir);
1961 free(remote_path);
1962 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001963 return (err);
1964 }
Darren Tuckera627d422013-06-02 07:31:17 +10001965 free(dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11001966 }
1967
Damien Miller37294fb2005-07-17 17:18:49 +10001968 setlinebuf(stdout);
1969 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11001970
Damien Miller0e2c1022005-08-12 22:16:22 +10001971 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11001972 err = 0;
1973 for (;;) {
1974 char *cp;
1975
Darren Tuckercdf547a2004-05-24 10:12:19 +10001976 signal(SIGINT, SIG_IGN);
1977
Darren Tucker2d963d82004-11-07 20:04:10 +11001978 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001979 if (interactive)
1980 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11001981 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001982 if (interactive)
1983 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11001984 break;
1985 }
Damien Miller0e2c1022005-08-12 22:16:22 +10001986 if (!interactive) { /* Echo command */
1987 printf("sftp> %s", cmd);
1988 if (strlen(cmd) > 0 &&
1989 cmd[strlen(cmd) - 1] != '\n')
1990 printf("\n");
1991 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001992 } else {
1993#ifdef USE_LIBEDIT
1994 const char *line;
1995 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001996
Darren Tucker909d8582010-01-08 19:02:40 +11001997 if ((line = el_gets(el, &count)) == NULL ||
1998 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001999 printf("\n");
2000 break;
2001 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002002 history(hl, &hev, H_ENTER, line);
2003 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
2004 fprintf(stderr, "Error: input line too long\n");
2005 continue;
2006 }
2007#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002008 }
2009
Damien Miller20e1fab2004-02-18 14:30:55 +11002010 cp = strrchr(cmd, '\n');
2011 if (cp)
2012 *cp = '\0';
2013
Darren Tuckercdf547a2004-05-24 10:12:19 +10002014 /* Handle user interrupts gracefully during commands */
2015 interrupted = 0;
2016 signal(SIGINT, cmd_interrupt);
2017
Darren Tucker909d8582010-01-08 19:02:40 +11002018 err = parse_dispatch_command(conn, cmd, &remote_path,
2019 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11002020 if (err != 0)
2021 break;
2022 }
Darren Tuckera627d422013-06-02 07:31:17 +10002023 free(remote_path);
2024 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002025
Tim Rice027e8b12005-08-15 14:52:50 -07002026#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10002027 if (el != NULL)
2028 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07002029#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10002030
Damien Miller20e1fab2004-02-18 14:30:55 +11002031 /* err == 1 signifies normal "quit" exit */
2032 return (err >= 0 ? 0 : -1);
2033}
Damien Miller62d57f62003-01-10 21:43:24 +11002034
Ben Lindstrombba81212001-06-25 05:01:22 +00002035static void
Damien Millercc685c12003-06-04 22:51:38 +10002036connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11002037{
2038 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002039
Damien Miller33804262001-02-04 23:20:18 +11002040#ifdef USE_PIPES
2041 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002042
Damien Miller33804262001-02-04 23:20:18 +11002043 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2044 fatal("pipe: %s", strerror(errno));
2045 *in = pin[0];
2046 *out = pout[1];
2047 c_in = pout[0];
2048 c_out = pin[1];
2049#else /* USE_PIPES */
2050 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002051
Damien Miller33804262001-02-04 23:20:18 +11002052 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2053 fatal("socketpair: %s", strerror(errno));
2054 *in = *out = inout[0];
2055 c_in = c_out = inout[1];
2056#endif /* USE_PIPES */
2057
Damien Millercc685c12003-06-04 22:51:38 +10002058 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002059 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002060 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002061 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2062 (dup2(c_out, STDOUT_FILENO) == -1)) {
2063 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002064 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002065 }
2066 close(*in);
2067 close(*out);
2068 close(c_in);
2069 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002070
2071 /*
2072 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002073 * ignore SIGINT if we want to gracefully abort commands,
2074 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002075 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2076 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002077 */
2078 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002079 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002080 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002081 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002082 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002083 }
2084
Damien Millercc685c12003-06-04 22:51:38 +10002085 signal(SIGTERM, killchild);
2086 signal(SIGINT, killchild);
2087 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11002088 close(c_in);
2089 close(c_out);
2090}
2091
Ben Lindstrombba81212001-06-25 05:01:22 +00002092static void
Damien Miller33804262001-02-04 23:20:18 +11002093usage(void)
2094{
Damien Miller025e01c2002-02-08 22:06:29 +11002095 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002096
Ben Lindstrom1e243242001-09-18 05:38:44 +00002097 fprintf(stderr,
Darren Tucker1b0dd172009-10-07 08:37:48 +11002098 "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002099 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002100 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002101 " [-o ssh_option] [-P port] [-R num_requests] "
2102 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002103 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002104 " %s [user@]host[:file ...]\n"
2105 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002106 " %s -b batchfile [user@]host\n",
2107 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002108 exit(1);
2109}
2110
Kevin Stevesef4eea92001-02-05 12:42:17 +00002111int
Damien Miller33804262001-02-04 23:20:18 +11002112main(int argc, char **argv)
2113{
Damien Miller956f3fb2003-01-10 21:40:00 +11002114 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002115 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002116 int debug_level = 0, sshver = 2;
2117 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002118 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002119 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002120 LogLevel ll = SYSLOG_LEVEL_INFO;
2121 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002122 extern int optind;
2123 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002124 struct sftp_conn *conn;
2125 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2126 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002127 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002128
Darren Tuckerce321d82005-10-03 18:11:24 +10002129 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2130 sanitise_stdfd();
Darren Tuckerea647212013-06-06 08:19:09 +10002131 setlocale(LC_CTYPE, "");
Darren Tuckerce321d82005-10-03 18:11:24 +10002132
Damien Miller59d3d5b2003-08-22 09:34:41 +10002133 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002134 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002135 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002136 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002137 addargs(&args, "-oForwardX11 no");
2138 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002139 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002140 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002141
Ben Lindstrom387c4722001-05-08 20:27:25 +00002142 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002143 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002144
Darren Tucker282b4022009-10-07 08:23:06 +11002145 while ((ch = getopt(argc, argv,
Damien Miller65e42f82010-09-24 22:15:11 +10002146 "1246hpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002147 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002148 /* Passed through to ssh(1) */
2149 case '4':
2150 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002151 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002152 addargs(&args, "-%c", ch);
2153 break;
2154 /* Passed through to ssh(1) with argument */
2155 case 'F':
2156 case 'c':
2157 case 'i':
2158 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002159 addargs(&args, "-%c", ch);
2160 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002161 break;
2162 case 'q':
Damien Miller9303e652013-04-23 15:22:40 +10002163 ll = SYSLOG_LEVEL_ERROR;
2164 quiet = 1;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002165 showprogress = 0;
2166 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002167 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002168 case 'P':
2169 addargs(&args, "-oPort %s", optarg);
2170 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002171 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002172 if (debug_level < 3) {
2173 addargs(&args, "-v");
2174 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2175 }
2176 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002177 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002178 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002179 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002180 if (sftp_server == NULL)
2181 sftp_server = _PATH_SFTP_SERVER;
2182 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002183 case '2':
2184 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002185 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002186 case 'B':
2187 copy_buffer_len = strtol(optarg, &cp, 10);
2188 if (copy_buffer_len == 0 || *cp != '\0')
2189 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002190 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002191 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002192 if (batchmode)
2193 fatal("Batch file already specified.");
2194
2195 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002196 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002197 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002198 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002199 showprogress = 0;
Damien Miller9303e652013-04-23 15:22:40 +10002200 quiet = batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002201 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002202 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002203 case 'p':
2204 global_pflag = 1;
2205 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002206 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002207 sftp_direct = optarg;
2208 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002209 case 'l':
2210 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2211 &errstr);
2212 if (errstr != NULL)
2213 usage();
2214 limit_kbps *= 1024; /* kbps */
2215 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002216 case 'r':
2217 global_rflag = 1;
2218 break;
Damien Miller16a13332002-02-13 14:03:56 +11002219 case 'R':
2220 num_requests = strtol(optarg, &cp, 10);
2221 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002222 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002223 optarg);
2224 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002225 case 's':
2226 sftp_server = optarg;
2227 break;
2228 case 'S':
2229 ssh_program = optarg;
2230 replacearg(&args, 0, "%s", ssh_program);
2231 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002232 case 'h':
2233 default:
Damien Miller33804262001-02-04 23:20:18 +11002234 usage();
2235 }
2236 }
2237
Damien Millerc0f27d82004-03-08 23:12:19 +11002238 if (!isatty(STDERR_FILENO))
2239 showprogress = 0;
2240
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002241 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2242
Damien Millerd14ee1e2002-02-05 12:27:31 +11002243 if (sftp_direct == NULL) {
2244 if (optind == argc || argc > (optind + 2))
2245 usage();
Damien Miller33804262001-02-04 23:20:18 +11002246
Damien Millerd14ee1e2002-02-05 12:27:31 +11002247 userhost = xstrdup(argv[optind]);
2248 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002249
Ben Lindstromc276c122002-12-23 02:14:51 +00002250 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002251 host = userhost;
2252 else {
2253 *host++ = '\0';
2254 if (!userhost[0]) {
2255 fprintf(stderr, "Missing username\n");
2256 usage();
2257 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002258 addargs(&args, "-l");
2259 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002260 }
2261
Damien Millerec692032004-01-27 21:22:00 +11002262 if ((cp = colon(host)) != NULL) {
2263 *cp++ = '\0';
2264 file1 = cp;
2265 }
2266
Damien Millerd14ee1e2002-02-05 12:27:31 +11002267 host = cleanhostname(host);
2268 if (!*host) {
2269 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002270 usage();
2271 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002272
Damien Millerd14ee1e2002-02-05 12:27:31 +11002273 addargs(&args, "-oProtocol %d", sshver);
2274
2275 /* no subsystem if the server-spec contains a '/' */
2276 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2277 addargs(&args, "-s");
2278
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002279 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002280 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002281 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002282 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002283
Damien Millercc685c12003-06-04 22:51:38 +10002284 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002285 } else {
2286 args.list = NULL;
2287 addargs(&args, "sftp-server");
2288
Damien Millercc685c12003-06-04 22:51:38 +10002289 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002290 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002291 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002292
Damien Miller65e42f82010-09-24 22:15:11 +10002293 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002294 if (conn == NULL)
2295 fatal("Couldn't initialise connection to server");
2296
Damien Miller9303e652013-04-23 15:22:40 +10002297 if (!quiet) {
Darren Tucker21063192010-01-08 17:10:36 +11002298 if (sftp_direct == NULL)
2299 fprintf(stderr, "Connected to %s.\n", host);
2300 else
2301 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2302 }
2303
2304 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002305
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002306#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002307 shutdown(in, SHUT_RDWR);
2308 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002309#endif
2310
Damien Miller33804262001-02-04 23:20:18 +11002311 close(in);
2312 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002313 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002314 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002315
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002316 while (waitpid(sshpid, NULL, 0) == -1)
2317 if (errno != EINTR)
2318 fatal("Couldn't wait for ssh process: %s",
2319 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002320
Damien Miller956f3fb2003-01-10 21:40:00 +11002321 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002322}