blob: 0c90fd1e40f2cbbc5b67588101cda73d53e724c5 [file] [log] [blame]
Darren Tuckerea647212013-06-06 08:19:09 +10001/* $OpenBSD: sftp.c,v 1.146 2013/06/04 20:42:36 dtucker 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 Tuckerea647212013-06-06 08:19:09 +100041#include <locale.h>
Darren Tucker2d963d82004-11-07 20:04:10 +110042#ifdef USE_LIBEDIT
43#include <histedit.h>
44#else
45typedef void EditLine;
46#endif
Damien Miller6ff3cad2006-03-15 11:52:09 +110047#include <signal.h>
Damien Millere7a1e5c2006-08-05 11:34:19 +100048#include <stdlib.h>
Damien Millera7a73ee2006-08-05 11:37:59 +100049#include <stdio.h>
Damien Millere3476ed2006-07-24 14:13:33 +100050#include <string.h>
Damien Millere6b3b612006-07-24 14:01:23 +100051#include <unistd.h>
Damien Millerd7834352006-08-05 12:39:39 +100052#include <stdarg.h>
Damien Miller33804262001-02-04 23:20:18 +110053
Damien Millera7058ec2008-05-20 08:57:06 +100054#ifdef HAVE_UTIL_H
55# include <util.h>
56#endif
57
Damien Miller33804262001-02-04 23:20:18 +110058#include "xmalloc.h"
59#include "log.h"
60#include "pathnames.h"
Ben Lindstrom4529b702001-05-03 23:39:53 +000061#include "misc.h"
Damien Miller33804262001-02-04 23:20:18 +110062
63#include "sftp.h"
Damien Millerd7834352006-08-05 12:39:39 +100064#include "buffer.h"
Damien Miller33804262001-02-04 23:20:18 +110065#include "sftp-common.h"
66#include "sftp-client.h"
Damien Millerd7d46bb2004-02-18 14:11:13 +110067
Darren Tucker21063192010-01-08 17:10:36 +110068#define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */
69#define DEFAULT_NUM_REQUESTS 64 /* # concurrent outstanding requests */
70
Damien Miller20e1fab2004-02-18 14:30:55 +110071/* File to read commands from */
72FILE* infile;
73
74/* Are we in batchfile mode? */
75int batchmode = 0;
76
Damien Miller20e1fab2004-02-18 14:30:55 +110077/* PID of ssh transport process */
78static pid_t sshpid = -1;
79
Damien Miller9303e652013-04-23 15:22:40 +100080/* Suppress diagnositic messages */
81int quiet = 0;
82
Damien Miller20e1fab2004-02-18 14:30:55 +110083/* This is set to 0 if the progressmeter is not desired. */
Damien Millerc0f27d82004-03-08 23:12:19 +110084int showprogress = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +110085
Darren Tucker1b0dd172009-10-07 08:37:48 +110086/* When this option is set, we always recursively download/upload directories */
87int global_rflag = 0;
88
89/* When this option is set, the file transfers will always preserve times */
90int global_pflag = 0;
91
Darren Tuckercdf547a2004-05-24 10:12:19 +100092/* SIGINT received during command processing */
93volatile sig_atomic_t interrupted = 0;
94
Darren Tuckerb9123452004-06-22 13:06:45 +100095/* I wish qsort() took a separate ctx for the comparison function...*/
96int sort_flag;
97
Darren Tucker909d8582010-01-08 19:02:40 +110098/* Context used for commandline completion */
99struct complete_ctx {
100 struct sftp_conn *conn;
101 char **remote_pathp;
102};
103
Damien Miller20e1fab2004-02-18 14:30:55 +1100104int remote_glob(struct sftp_conn *, const char *, int,
105 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
Damien Miller33804262001-02-04 23:20:18 +1100106
Kevin Steves12888d12001-03-05 19:50:57 +0000107extern char *__progname;
Kevin Steves12888d12001-03-05 19:50:57 +0000108
Damien Miller20e1fab2004-02-18 14:30:55 +1100109/* Separators for interactive commands */
110#define WHITESPACE " \t\r\n"
Damien Millerd7686fd2001-02-10 00:40:03 +1100111
Darren Tuckerb9123452004-06-22 13:06:45 +1000112/* ls flags */
Darren Tucker2901e2d2010-01-13 22:44:06 +1100113#define LS_LONG_VIEW 0x0001 /* Full view ala ls -l */
114#define LS_SHORT_VIEW 0x0002 /* Single row view ala ls -1 */
115#define LS_NUMERIC_VIEW 0x0004 /* Long view with numeric uid/gid */
116#define LS_NAME_SORT 0x0008 /* Sort by name (default) */
117#define LS_TIME_SORT 0x0010 /* Sort by mtime */
118#define LS_SIZE_SORT 0x0020 /* Sort by file size */
119#define LS_REVERSE_SORT 0x0040 /* Reverse sort order */
120#define LS_SHOW_ALL 0x0080 /* Don't skip filenames starting with '.' */
121#define LS_SI_UNITS 0x0100 /* Display sizes as K, M, G, etc. */
Darren Tuckerb9123452004-06-22 13:06:45 +1000122
Darren Tucker2901e2d2010-01-13 22:44:06 +1100123#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS)
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000124#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
Damien Miller20e1fab2004-02-18 14:30:55 +1100125
126/* Commands for interactive mode */
127#define I_CHDIR 1
128#define I_CHGRP 2
129#define I_CHMOD 3
130#define I_CHOWN 4
Damien Millerd671e5a2008-05-19 14:53:33 +1000131#define I_DF 24
Damien Miller20e1fab2004-02-18 14:30:55 +1100132#define I_GET 5
133#define I_HELP 6
134#define I_LCHDIR 7
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100135#define I_LINK 25
Damien Miller20e1fab2004-02-18 14:30:55 +1100136#define I_LLS 8
137#define I_LMKDIR 9
138#define I_LPWD 10
139#define I_LS 11
140#define I_LUMASK 12
141#define I_MKDIR 13
142#define I_PUT 14
143#define I_PWD 15
144#define I_QUIT 16
145#define I_RENAME 17
146#define I_RM 18
147#define I_RMDIR 19
148#define I_SHELL 20
149#define I_SYMLINK 21
150#define I_VERSION 22
151#define I_PROGRESS 23
152
153struct CMD {
154 const char *c;
155 const int n;
Darren Tucker909d8582010-01-08 19:02:40 +1100156 const int t;
Damien Miller20e1fab2004-02-18 14:30:55 +1100157};
158
Darren Tucker909d8582010-01-08 19:02:40 +1100159/* Type of completion */
160#define NOARGS 0
161#define REMOTE 1
162#define LOCAL 2
163
Damien Miller20e1fab2004-02-18 14:30:55 +1100164static const struct CMD cmds[] = {
Darren Tucker909d8582010-01-08 19:02:40 +1100165 { "bye", I_QUIT, NOARGS },
166 { "cd", I_CHDIR, REMOTE },
167 { "chdir", I_CHDIR, REMOTE },
168 { "chgrp", I_CHGRP, REMOTE },
169 { "chmod", I_CHMOD, REMOTE },
170 { "chown", I_CHOWN, REMOTE },
171 { "df", I_DF, REMOTE },
172 { "dir", I_LS, REMOTE },
173 { "exit", I_QUIT, NOARGS },
174 { "get", I_GET, REMOTE },
175 { "help", I_HELP, NOARGS },
176 { "lcd", I_LCHDIR, LOCAL },
177 { "lchdir", I_LCHDIR, LOCAL },
178 { "lls", I_LLS, LOCAL },
179 { "lmkdir", I_LMKDIR, LOCAL },
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100180 { "ln", I_LINK, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100181 { "lpwd", I_LPWD, LOCAL },
182 { "ls", I_LS, REMOTE },
183 { "lumask", I_LUMASK, NOARGS },
184 { "mkdir", I_MKDIR, REMOTE },
Damien Miller099fc162010-05-10 11:56:50 +1000185 { "mget", I_GET, REMOTE },
186 { "mput", I_PUT, LOCAL },
Darren Tucker909d8582010-01-08 19:02:40 +1100187 { "progress", I_PROGRESS, NOARGS },
188 { "put", I_PUT, LOCAL },
189 { "pwd", I_PWD, REMOTE },
190 { "quit", I_QUIT, NOARGS },
191 { "rename", I_RENAME, REMOTE },
192 { "rm", I_RM, REMOTE },
193 { "rmdir", I_RMDIR, REMOTE },
194 { "symlink", I_SYMLINK, REMOTE },
195 { "version", I_VERSION, NOARGS },
196 { "!", I_SHELL, NOARGS },
197 { "?", I_HELP, NOARGS },
198 { NULL, -1, -1 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100199};
200
Darren Tucker21063192010-01-08 17:10:36 +1100201int interactive_loop(struct sftp_conn *, char *file1, char *file2);
Damien Miller20e1fab2004-02-18 14:30:55 +1100202
Damien Millerb6c85fc2007-01-05 16:30:41 +1100203/* ARGSUSED */
Damien Miller20e1fab2004-02-18 14:30:55 +1100204static void
Darren Tuckercdf547a2004-05-24 10:12:19 +1000205killchild(int signo)
206{
Darren Tuckerba66df82005-01-24 21:57:40 +1100207 if (sshpid > 1) {
Darren Tuckercdf547a2004-05-24 10:12:19 +1000208 kill(sshpid, SIGTERM);
Darren Tuckerba66df82005-01-24 21:57:40 +1100209 waitpid(sshpid, NULL, 0);
210 }
Darren Tuckercdf547a2004-05-24 10:12:19 +1000211
212 _exit(1);
213}
214
Damien Millerb6c85fc2007-01-05 16:30:41 +1100215/* ARGSUSED */
Darren Tuckercdf547a2004-05-24 10:12:19 +1000216static void
217cmd_interrupt(int signo)
218{
219 const char msg[] = "\rInterrupt \n";
Darren Tuckere2f189a2004-12-06 22:45:53 +1100220 int olderrno = errno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000221
Darren Tuckerdbee3082013-05-16 20:32:29 +1000222 (void)write(STDERR_FILENO, msg, sizeof(msg) - 1);
Darren Tuckercdf547a2004-05-24 10:12:19 +1000223 interrupted = 1;
Darren Tuckere2f189a2004-12-06 22:45:53 +1100224 errno = olderrno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000225}
226
227static void
Damien Miller20e1fab2004-02-18 14:30:55 +1100228help(void)
229{
Damien Miller62fd18a2009-01-28 16:14:09 +1100230 printf("Available commands:\n"
231 "bye Quit sftp\n"
232 "cd path Change remote directory to 'path'\n"
233 "chgrp grp path Change group of file 'path' to 'grp'\n"
234 "chmod mode path Change permissions of file 'path' to 'mode'\n"
235 "chown own path Change owner of file 'path' to 'own'\n"
236 "df [-hi] [path] Display statistics for current directory or\n"
237 " filesystem containing 'path'\n"
238 "exit Quit sftp\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100239 "get [-Ppr] remote [local] Download file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100240 "help Display this help text\n"
241 "lcd path Change local directory to 'path'\n"
242 "lls [ls-options [path]] Display local directory listing\n"
243 "lmkdir path Create local directory\n"
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100244 "ln [-s] oldpath newpath Link remote file (-s for symlink)\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100245 "lpwd Print local working directory\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100246 "ls [-1afhlnrSt] [path] Display remote directory listing\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100247 "lumask umask Set local umask to 'umask'\n"
248 "mkdir path Create remote directory\n"
249 "progress Toggle display of progress meter\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100250 "put [-Ppr] local [remote] Upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100251 "pwd Display remote working directory\n"
252 "quit Quit sftp\n"
253 "rename oldpath newpath Rename remote file\n"
254 "rm path Delete remote file\n"
255 "rmdir path Remove remote directory\n"
256 "symlink oldpath newpath Symlink remote file\n"
257 "version Show SFTP version\n"
258 "!command Execute 'command' in local shell\n"
259 "! Escape to local shell\n"
260 "? Synonym for help\n");
Damien Miller20e1fab2004-02-18 14:30:55 +1100261}
262
263static void
264local_do_shell(const char *args)
265{
266 int status;
267 char *shell;
268 pid_t pid;
269
270 if (!*args)
271 args = NULL;
272
Damien Miller38d9a962010-10-07 22:07:11 +1100273 if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
Damien Miller20e1fab2004-02-18 14:30:55 +1100274 shell = _PATH_BSHELL;
275
276 if ((pid = fork()) == -1)
277 fatal("Couldn't fork: %s", strerror(errno));
278
279 if (pid == 0) {
280 /* XXX: child has pipe fds to ssh subproc open - issue? */
281 if (args) {
282 debug3("Executing %s -c \"%s\"", shell, args);
283 execl(shell, shell, "-c", args, (char *)NULL);
284 } else {
285 debug3("Executing %s", shell);
286 execl(shell, shell, (char *)NULL);
287 }
288 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
289 strerror(errno));
290 _exit(1);
291 }
292 while (waitpid(pid, &status, 0) == -1)
293 if (errno != EINTR)
294 fatal("Couldn't wait for child: %s", strerror(errno));
295 if (!WIFEXITED(status))
Damien Miller55b04f12006-03-26 14:23:17 +1100296 error("Shell exited abnormally");
Damien Miller20e1fab2004-02-18 14:30:55 +1100297 else if (WEXITSTATUS(status))
298 error("Shell exited with status %d", WEXITSTATUS(status));
299}
300
301static void
302local_do_ls(const char *args)
303{
304 if (!args || !*args)
305 local_do_shell(_PATH_LS);
306 else {
307 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
308 char *buf = xmalloc(len);
309
310 /* XXX: quoting - rip quoting code from ftp? */
311 snprintf(buf, len, _PATH_LS " %s", args);
312 local_do_shell(buf);
Darren Tuckera627d422013-06-02 07:31:17 +1000313 free(buf);
Damien Miller20e1fab2004-02-18 14:30:55 +1100314 }
315}
316
317/* Strip one path (usually the pwd) from the start of another */
318static char *
319path_strip(char *path, char *strip)
320{
321 size_t len;
322
323 if (strip == NULL)
324 return (xstrdup(path));
325
326 len = strlen(strip);
Darren Tuckere2f189a2004-12-06 22:45:53 +1100327 if (strncmp(path, strip, len) == 0) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100328 if (strip[len - 1] != '/' && path[len] == '/')
329 len++;
330 return (xstrdup(path + len));
331 }
332
333 return (xstrdup(path));
334}
335
336static char *
Damien Miller20e1fab2004-02-18 14:30:55 +1100337make_absolute(char *p, char *pwd)
338{
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000339 char *abs_str;
Damien Miller20e1fab2004-02-18 14:30:55 +1100340
341 /* Derelativise */
342 if (p && p[0] != '/') {
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000343 abs_str = path_append(pwd, p);
Darren Tuckera627d422013-06-02 07:31:17 +1000344 free(p);
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000345 return(abs_str);
Damien Miller20e1fab2004-02-18 14:30:55 +1100346 } else
347 return(p);
348}
349
350static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100351parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag,
352 int *rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100353{
Damien Millerf184bcf2008-06-29 22:45:13 +1000354 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000355 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100356
Damien Miller1cbc2922007-10-26 14:27:45 +1000357 optind = optreset = 1;
358 opterr = 0;
359
Darren Tucker1b0dd172009-10-07 08:37:48 +1100360 *rflag = *pflag = 0;
361 while ((ch = getopt(argc, argv, "PpRr")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000362 switch (ch) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100363 case 'p':
364 case 'P':
365 *pflag = 1;
366 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100367 case 'r':
368 case 'R':
369 *rflag = 1;
370 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100371 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000372 error("%s: Invalid flag -%c", cmd, optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000373 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100374 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100375 }
376
Damien Miller1cbc2922007-10-26 14:27:45 +1000377 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100378}
379
380static int
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100381parse_link_flags(const char *cmd, char **argv, int argc, int *sflag)
382{
383 extern int opterr, optind, optopt, optreset;
384 int ch;
385
386 optind = optreset = 1;
387 opterr = 0;
388
389 *sflag = 0;
390 while ((ch = getopt(argc, argv, "s")) != -1) {
391 switch (ch) {
392 case 's':
393 *sflag = 1;
394 break;
395 default:
396 error("%s: Invalid flag -%c", cmd, optopt);
397 return -1;
398 }
399 }
400
401 return optind;
402}
403
404static int
Damien Miller1cbc2922007-10-26 14:27:45 +1000405parse_ls_flags(char **argv, int argc, int *lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100406{
Damien Millerf184bcf2008-06-29 22:45:13 +1000407 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000408 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100409
Damien Miller1cbc2922007-10-26 14:27:45 +1000410 optind = optreset = 1;
411 opterr = 0;
412
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000413 *lflag = LS_NAME_SORT;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100414 while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000415 switch (ch) {
416 case '1':
417 *lflag &= ~VIEW_FLAGS;
418 *lflag |= LS_SHORT_VIEW;
419 break;
420 case 'S':
421 *lflag &= ~SORT_FLAGS;
422 *lflag |= LS_SIZE_SORT;
423 break;
424 case 'a':
425 *lflag |= LS_SHOW_ALL;
426 break;
427 case 'f':
428 *lflag &= ~SORT_FLAGS;
429 break;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100430 case 'h':
431 *lflag |= LS_SI_UNITS;
432 break;
Damien Miller1cbc2922007-10-26 14:27:45 +1000433 case 'l':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100434 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000435 *lflag |= LS_LONG_VIEW;
436 break;
437 case 'n':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100438 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000439 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
440 break;
441 case 'r':
442 *lflag |= LS_REVERSE_SORT;
443 break;
444 case 't':
445 *lflag &= ~SORT_FLAGS;
446 *lflag |= LS_TIME_SORT;
447 break;
448 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000449 error("ls: Invalid flag -%c", optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000450 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100451 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100452 }
453
Damien Miller1cbc2922007-10-26 14:27:45 +1000454 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100455}
456
457static int
Damien Millerd671e5a2008-05-19 14:53:33 +1000458parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
459{
Damien Millerf184bcf2008-06-29 22:45:13 +1000460 extern int opterr, optind, optopt, optreset;
Damien Millerd671e5a2008-05-19 14:53:33 +1000461 int ch;
462
463 optind = optreset = 1;
464 opterr = 0;
465
466 *hflag = *iflag = 0;
467 while ((ch = getopt(argc, argv, "hi")) != -1) {
468 switch (ch) {
469 case 'h':
470 *hflag = 1;
471 break;
472 case 'i':
473 *iflag = 1;
474 break;
475 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000476 error("%s: Invalid flag -%c", cmd, optopt);
Damien Millerd671e5a2008-05-19 14:53:33 +1000477 return -1;
478 }
479 }
480
481 return optind;
482}
483
484static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100485is_dir(char *path)
486{
487 struct stat sb;
488
489 /* XXX: report errors? */
490 if (stat(path, &sb) == -1)
491 return(0);
492
Darren Tucker1e80e402006-09-21 12:59:33 +1000493 return(S_ISDIR(sb.st_mode));
Damien Miller20e1fab2004-02-18 14:30:55 +1100494}
495
496static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100497remote_is_dir(struct sftp_conn *conn, char *path)
498{
499 Attrib *a;
500
501 /* XXX: report errors? */
502 if ((a = do_stat(conn, path, 1)) == NULL)
503 return(0);
504 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
505 return(0);
Darren Tucker1e80e402006-09-21 12:59:33 +1000506 return(S_ISDIR(a->perm));
Damien Miller20e1fab2004-02-18 14:30:55 +1100507}
508
Darren Tucker1b0dd172009-10-07 08:37:48 +1100509/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
Damien Miller20e1fab2004-02-18 14:30:55 +1100510static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100511pathname_is_dir(char *pathname)
512{
513 size_t l = strlen(pathname);
514
515 return l > 0 && pathname[l - 1] == '/';
516}
517
518static int
519process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
520 int pflag, int rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100521{
522 char *abs_src = NULL;
523 char *abs_dst = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100524 glob_t g;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100525 char *filename, *tmp=NULL;
526 int i, err = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100527
528 abs_src = xstrdup(src);
529 abs_src = make_absolute(abs_src, pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +1100530 memset(&g, 0, sizeof(g));
Darren Tucker1b0dd172009-10-07 08:37:48 +1100531
Damien Miller20e1fab2004-02-18 14:30:55 +1100532 debug3("Looking up %s", abs_src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100533 if (remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100534 error("File \"%s\" not found.", abs_src);
535 err = -1;
536 goto out;
537 }
538
Darren Tucker1b0dd172009-10-07 08:37:48 +1100539 /*
540 * If multiple matches then dst must be a directory or
541 * unspecified.
542 */
543 if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
544 error("Multiple source paths, but destination "
545 "\"%s\" is not a directory", dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100546 err = -1;
547 goto out;
548 }
549
Darren Tuckercdf547a2004-05-24 10:12:19 +1000550 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100551 tmp = xstrdup(g.gl_pathv[i]);
552 if ((filename = basename(tmp)) == NULL) {
553 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000554 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100555 err = -1;
556 goto out;
557 }
558
559 if (g.gl_matchc == 1 && dst) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100560 if (is_dir(dst)) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100561 abs_dst = path_append(dst, filename);
562 } else {
Damien Miller20e1fab2004-02-18 14:30:55 +1100563 abs_dst = xstrdup(dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100564 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100565 } else if (dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100566 abs_dst = path_append(dst, filename);
567 } else {
568 abs_dst = xstrdup(filename);
569 }
Darren Tuckera627d422013-06-02 07:31:17 +1000570 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100571
Damien Miller9303e652013-04-23 15:22:40 +1000572 if (!quiet)
573 printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100574 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
575 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
576 pflag || global_pflag, 1) == -1)
577 err = -1;
578 } else {
579 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
580 pflag || global_pflag) == -1)
581 err = -1;
582 }
Darren Tuckera627d422013-06-02 07:31:17 +1000583 free(abs_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100584 abs_dst = NULL;
585 }
586
587out:
Darren Tuckera627d422013-06-02 07:31:17 +1000588 free(abs_src);
Damien Miller20e1fab2004-02-18 14:30:55 +1100589 globfree(&g);
590 return(err);
591}
592
593static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100594process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
595 int pflag, int rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100596{
597 char *tmp_dst = NULL;
598 char *abs_dst = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100599 char *tmp = NULL, *filename = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100600 glob_t g;
601 int err = 0;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100602 int i, dst_is_dir = 1;
Damien Milleraec5cf82008-02-10 22:26:24 +1100603 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100604
605 if (dst) {
606 tmp_dst = xstrdup(dst);
607 tmp_dst = make_absolute(tmp_dst, pwd);
608 }
609
610 memset(&g, 0, sizeof(g));
611 debug3("Looking up %s", src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100612 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100613 error("File \"%s\" not found.", src);
614 err = -1;
615 goto out;
616 }
617
Darren Tucker1b0dd172009-10-07 08:37:48 +1100618 /* If we aren't fetching to pwd then stash this status for later */
619 if (tmp_dst != NULL)
620 dst_is_dir = remote_is_dir(conn, tmp_dst);
621
Damien Miller20e1fab2004-02-18 14:30:55 +1100622 /* If multiple matches, dst may be directory or unspecified */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100623 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
624 error("Multiple paths match, but destination "
625 "\"%s\" is not a directory", tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100626 err = -1;
627 goto out;
628 }
629
Darren Tuckercdf547a2004-05-24 10:12:19 +1000630 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Milleraec5cf82008-02-10 22:26:24 +1100631 if (stat(g.gl_pathv[i], &sb) == -1) {
632 err = -1;
633 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
634 continue;
635 }
Darren Tucker1b0dd172009-10-07 08:37:48 +1100636
637 tmp = xstrdup(g.gl_pathv[i]);
638 if ((filename = basename(tmp)) == NULL) {
639 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000640 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100641 err = -1;
642 goto out;
643 }
644
645 if (g.gl_matchc == 1 && tmp_dst) {
646 /* If directory specified, append filename */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100647 if (dst_is_dir)
648 abs_dst = path_append(tmp_dst, filename);
649 else
Damien Miller20e1fab2004-02-18 14:30:55 +1100650 abs_dst = xstrdup(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100651 } else if (tmp_dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100652 abs_dst = path_append(tmp_dst, filename);
653 } else {
654 abs_dst = make_absolute(xstrdup(filename), pwd);
655 }
Darren Tuckera627d422013-06-02 07:31:17 +1000656 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100657
Damien Miller9303e652013-04-23 15:22:40 +1000658 if (!quiet)
659 printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100660 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
661 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
662 pflag || global_pflag, 1) == -1)
663 err = -1;
664 } else {
665 if (do_upload(conn, g.gl_pathv[i], abs_dst,
666 pflag || global_pflag) == -1)
667 err = -1;
668 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100669 }
670
671out:
Darren Tuckera627d422013-06-02 07:31:17 +1000672 free(abs_dst);
673 free(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100674 globfree(&g);
675 return(err);
676}
677
678static int
679sdirent_comp(const void *aa, const void *bb)
680{
681 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
682 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000683 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100684
Darren Tuckerb9123452004-06-22 13:06:45 +1000685#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000686 if (sort_flag & LS_NAME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000687 return (rmul * strcmp(a->filename, b->filename));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000688 else if (sort_flag & LS_TIME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000689 return (rmul * NCMP(a->a.mtime, b->a.mtime));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000690 else if (sort_flag & LS_SIZE_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000691 return (rmul * NCMP(a->a.size, b->a.size));
692
693 fatal("Unknown ls sort type");
Damien Miller20e1fab2004-02-18 14:30:55 +1100694}
695
696/* sftp ls.1 replacement for directories */
697static int
698do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
699{
Damien Millereccb9de2005-06-17 12:59:34 +1000700 int n;
701 u_int c = 1, colspace = 0, columns = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100702 SFTP_DIRENT **d;
703
704 if ((n = do_readdir(conn, path, &d)) != 0)
705 return (n);
706
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000707 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000708 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100709 struct winsize ws;
710 char *tmp;
711
712 /* Count entries for sort and find longest filename */
Darren Tucker9a526452004-06-22 13:09:55 +1000713 for (n = 0; d[n] != NULL; n++) {
714 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
715 m = MAX(m, strlen(d[n]->filename));
716 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100717
718 /* Add any subpath that also needs to be counted */
719 tmp = path_strip(path, strip_path);
720 m += strlen(tmp);
Darren Tuckera627d422013-06-02 07:31:17 +1000721 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100722
723 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
724 width = ws.ws_col;
725
726 columns = width / (m + 2);
727 columns = MAX(columns, 1);
728 colspace = width / columns;
729 colspace = MIN(colspace, width);
730 }
731
Darren Tuckerb9123452004-06-22 13:06:45 +1000732 if (lflag & SORT_FLAGS) {
Damien Miller653b93b2005-11-05 15:15:23 +1100733 for (n = 0; d[n] != NULL; n++)
734 ; /* count entries */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000735 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
Darren Tuckerb9123452004-06-22 13:06:45 +1000736 qsort(d, n, sizeof(*d), sdirent_comp);
737 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100738
Darren Tuckercdf547a2004-05-24 10:12:19 +1000739 for (n = 0; d[n] != NULL && !interrupted; n++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100740 char *tmp, *fname;
741
Darren Tucker9a526452004-06-22 13:09:55 +1000742 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
743 continue;
744
Damien Miller20e1fab2004-02-18 14:30:55 +1100745 tmp = path_append(path, d[n]->filename);
746 fname = path_strip(tmp, strip_path);
Darren Tuckera627d422013-06-02 07:31:17 +1000747 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100748
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000749 if (lflag & LS_LONG_VIEW) {
Darren Tucker2901e2d2010-01-13 22:44:06 +1100750 if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000751 char *lname;
752 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100753
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000754 memset(&sb, 0, sizeof(sb));
755 attrib_to_stat(&d[n]->a, &sb);
Darren Tucker2901e2d2010-01-13 22:44:06 +1100756 lname = ls_file(fname, &sb, 1,
757 (lflag & LS_SI_UNITS));
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000758 printf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000759 free(lname);
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000760 } else
761 printf("%s\n", d[n]->longname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100762 } else {
763 printf("%-*s", colspace, fname);
764 if (c >= columns) {
765 printf("\n");
766 c = 1;
767 } else
768 c++;
769 }
770
Darren Tuckera627d422013-06-02 07:31:17 +1000771 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100772 }
773
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000774 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100775 printf("\n");
776
777 free_sftp_dirents(d);
778 return (0);
779}
780
781/* sftp ls.1 replacement which handles path globs */
782static int
783do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
784 int lflag)
785{
Damien Millera6e121a2010-10-07 21:39:17 +1100786 char *fname, *lname;
Damien Miller68e2e562010-10-07 21:39:55 +1100787 glob_t g;
788 int err;
789 struct winsize ws;
790 u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100791
792 memset(&g, 0, sizeof(g));
793
Damien Millera6e121a2010-10-07 21:39:17 +1100794 if (remote_glob(conn, path,
Damien Millerd7be70d2011-09-22 21:43:06 +1000795 GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
796 NULL, &g) ||
Damien Millera6e121a2010-10-07 21:39:17 +1100797 (g.gl_pathc && !g.gl_matchc)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100798 if (g.gl_pathc)
799 globfree(&g);
Damien Miller20e1fab2004-02-18 14:30:55 +1100800 error("Can't ls: \"%s\" not found", path);
Damien Millera6e121a2010-10-07 21:39:17 +1100801 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100802 }
803
Darren Tuckercdf547a2004-05-24 10:12:19 +1000804 if (interrupted)
805 goto out;
806
Damien Miller20e1fab2004-02-18 14:30:55 +1100807 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100808 * If the glob returns a single match and it is a directory,
809 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100810 */
Damien Millera6e121a2010-10-07 21:39:17 +1100811 if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
812 S_ISDIR(g.gl_statv[0]->st_mode)) {
813 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
814 globfree(&g);
815 return err;
Damien Miller20e1fab2004-02-18 14:30:55 +1100816 }
817
Damien Miller68e2e562010-10-07 21:39:55 +1100818 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
819 width = ws.ws_col;
Damien Miller20e1fab2004-02-18 14:30:55 +1100820
Damien Miller68e2e562010-10-07 21:39:55 +1100821 if (!(lflag & LS_SHORT_VIEW)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100822 /* Count entries for sort and find longest filename */
823 for (i = 0; g.gl_pathv[i]; i++)
824 m = MAX(m, strlen(g.gl_pathv[i]));
825
Damien Miller20e1fab2004-02-18 14:30:55 +1100826 columns = width / (m + 2);
827 columns = MAX(columns, 1);
828 colspace = width / columns;
829 }
830
Damien Millerea858292012-06-30 08:33:32 +1000831 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100832 fname = path_strip(g.gl_pathv[i], strip_path);
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000833 if (lflag & LS_LONG_VIEW) {
Damien Millera6e121a2010-10-07 21:39:17 +1100834 if (g.gl_statv[i] == NULL) {
835 error("no stat information for %s", fname);
836 continue;
837 }
838 lname = ls_file(fname, g.gl_statv[i], 1,
839 (lflag & LS_SI_UNITS));
Damien Miller20e1fab2004-02-18 14:30:55 +1100840 printf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000841 free(lname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100842 } else {
843 printf("%-*s", colspace, fname);
844 if (c >= columns) {
845 printf("\n");
846 c = 1;
847 } else
848 c++;
849 }
Darren Tuckera627d422013-06-02 07:31:17 +1000850 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100851 }
852
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000853 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100854 printf("\n");
855
Darren Tuckercdf547a2004-05-24 10:12:19 +1000856 out:
Damien Miller20e1fab2004-02-18 14:30:55 +1100857 if (g.gl_pathc)
858 globfree(&g);
859
Damien Millera6e121a2010-10-07 21:39:17 +1100860 return 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100861}
862
Damien Millerd671e5a2008-05-19 14:53:33 +1000863static int
864do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
865{
Darren Tucker7b598892008-06-09 22:49:36 +1000866 struct sftp_statvfs st;
Damien Millerd671e5a2008-05-19 14:53:33 +1000867 char s_used[FMT_SCALED_STRSIZE];
868 char s_avail[FMT_SCALED_STRSIZE];
869 char s_root[FMT_SCALED_STRSIZE];
870 char s_total[FMT_SCALED_STRSIZE];
Darren Tuckerb5082e92010-01-08 18:51:47 +1100871 unsigned long long ffree;
Damien Millerd671e5a2008-05-19 14:53:33 +1000872
873 if (do_statvfs(conn, path, &st, 1) == -1)
874 return -1;
875 if (iflag) {
Darren Tuckerb5082e92010-01-08 18:51:47 +1100876 ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
Damien Millerd671e5a2008-05-19 14:53:33 +1000877 printf(" Inodes Used Avail "
878 "(root) %%Capacity\n");
879 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
880 (unsigned long long)st.f_files,
881 (unsigned long long)(st.f_files - st.f_ffree),
882 (unsigned long long)st.f_favail,
Darren Tuckerb5082e92010-01-08 18:51:47 +1100883 (unsigned long long)st.f_ffree, ffree);
Damien Millerd671e5a2008-05-19 14:53:33 +1000884 } else if (hflag) {
885 strlcpy(s_used, "error", sizeof(s_used));
886 strlcpy(s_avail, "error", sizeof(s_avail));
887 strlcpy(s_root, "error", sizeof(s_root));
888 strlcpy(s_total, "error", sizeof(s_total));
889 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
890 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
891 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
892 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
893 printf(" Size Used Avail (root) %%Capacity\n");
894 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
895 s_total, s_used, s_avail, s_root,
896 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
897 st.f_blocks));
898 } else {
899 printf(" Size Used Avail "
900 "(root) %%Capacity\n");
901 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
902 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
903 (unsigned long long)(st.f_frsize *
904 (st.f_blocks - st.f_bfree) / 1024),
905 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
906 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
907 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
908 st.f_blocks));
909 }
910 return 0;
911}
912
Damien Miller1cbc2922007-10-26 14:27:45 +1000913/*
914 * Undo escaping of glob sequences in place. Used to undo extra escaping
915 * applied in makeargv() when the string is destined for a function that
916 * does not glob it.
917 */
918static void
919undo_glob_escape(char *s)
920{
921 size_t i, j;
922
923 for (i = j = 0;;) {
924 if (s[i] == '\0') {
925 s[j] = '\0';
926 return;
927 }
928 if (s[i] != '\\') {
929 s[j++] = s[i++];
930 continue;
931 }
932 /* s[i] == '\\' */
933 ++i;
934 switch (s[i]) {
935 case '?':
936 case '[':
937 case '*':
938 case '\\':
939 s[j++] = s[i++];
940 break;
941 case '\0':
942 s[j++] = '\\';
943 s[j] = '\0';
944 return;
945 default:
946 s[j++] = '\\';
947 s[j++] = s[i++];
948 break;
949 }
950 }
951}
952
953/*
954 * Split a string into an argument vector using sh(1)-style quoting,
955 * comment and escaping rules, but with some tweaks to handle glob(3)
956 * wildcards.
Darren Tucker909d8582010-01-08 19:02:40 +1100957 * The "sloppy" flag allows for recovery from missing terminating quote, for
958 * use in parsing incomplete commandlines during tab autocompletion.
959 *
Damien Miller1cbc2922007-10-26 14:27:45 +1000960 * Returns NULL on error or a NULL-terminated array of arguments.
Darren Tucker909d8582010-01-08 19:02:40 +1100961 *
962 * If "lastquote" is not NULL, the quoting character used for the last
963 * argument is placed in *lastquote ("\0", "'" or "\"").
964 *
965 * If "terminated" is not NULL, *terminated will be set to 1 when the
966 * last argument's quote has been properly terminated or 0 otherwise.
967 * This parameter is only of use if "sloppy" is set.
Damien Miller1cbc2922007-10-26 14:27:45 +1000968 */
969#define MAXARGS 128
970#define MAXARGLEN 8192
971static char **
Darren Tucker909d8582010-01-08 19:02:40 +1100972makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
973 u_int *terminated)
Damien Miller1cbc2922007-10-26 14:27:45 +1000974{
975 int argc, quot;
976 size_t i, j;
977 static char argvs[MAXARGLEN];
978 static char *argv[MAXARGS + 1];
979 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
980
981 *argcp = argc = 0;
982 if (strlen(arg) > sizeof(argvs) - 1) {
983 args_too_longs:
984 error("string too long");
985 return NULL;
986 }
Darren Tucker909d8582010-01-08 19:02:40 +1100987 if (terminated != NULL)
988 *terminated = 1;
989 if (lastquote != NULL)
990 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +1000991 state = MA_START;
992 i = j = 0;
993 for (;;) {
Damien Miller07daed52012-10-31 08:57:55 +1100994 if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){
Darren Tucker063018d2012-10-05 10:43:58 +1000995 error("Too many arguments.");
996 return NULL;
997 }
Damien Miller1cbc2922007-10-26 14:27:45 +1000998 if (isspace(arg[i])) {
999 if (state == MA_UNQUOTED) {
1000 /* Terminate current argument */
1001 argvs[j++] = '\0';
1002 argc++;
1003 state = MA_START;
1004 } else if (state != MA_START)
1005 argvs[j++] = arg[i];
1006 } else if (arg[i] == '"' || arg[i] == '\'') {
1007 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1008 if (state == MA_START) {
1009 argv[argc] = argvs + j;
1010 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +11001011 if (lastquote != NULL)
1012 *lastquote = arg[i];
Damien Miller1cbc2922007-10-26 14:27:45 +10001013 } else if (state == MA_UNQUOTED)
1014 state = q;
1015 else if (state == q)
1016 state = MA_UNQUOTED;
1017 else
1018 argvs[j++] = arg[i];
1019 } else if (arg[i] == '\\') {
1020 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1021 quot = state == MA_SQUOTE ? '\'' : '"';
1022 /* Unescape quote we are in */
1023 /* XXX support \n and friends? */
1024 if (arg[i + 1] == quot) {
1025 i++;
1026 argvs[j++] = arg[i];
1027 } else if (arg[i + 1] == '?' ||
1028 arg[i + 1] == '[' || arg[i + 1] == '*') {
1029 /*
1030 * Special case for sftp: append
1031 * double-escaped glob sequence -
1032 * glob will undo one level of
1033 * escaping. NB. string can grow here.
1034 */
1035 if (j >= sizeof(argvs) - 5)
1036 goto args_too_longs;
1037 argvs[j++] = '\\';
1038 argvs[j++] = arg[i++];
1039 argvs[j++] = '\\';
1040 argvs[j++] = arg[i];
1041 } else {
1042 argvs[j++] = arg[i++];
1043 argvs[j++] = arg[i];
1044 }
1045 } else {
1046 if (state == MA_START) {
1047 argv[argc] = argvs + j;
1048 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001049 if (lastquote != NULL)
1050 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001051 }
1052 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1053 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1054 /*
1055 * Special case for sftp: append
1056 * escaped glob sequence -
1057 * glob will undo one level of
1058 * escaping.
1059 */
1060 argvs[j++] = arg[i++];
1061 argvs[j++] = arg[i];
1062 } else {
1063 /* Unescape everything */
1064 /* XXX support \n and friends? */
1065 i++;
1066 argvs[j++] = arg[i];
1067 }
1068 }
1069 } else if (arg[i] == '#') {
1070 if (state == MA_SQUOTE || state == MA_DQUOTE)
1071 argvs[j++] = arg[i];
1072 else
1073 goto string_done;
1074 } else if (arg[i] == '\0') {
1075 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001076 if (sloppy) {
1077 state = MA_UNQUOTED;
1078 if (terminated != NULL)
1079 *terminated = 0;
1080 goto string_done;
1081 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001082 error("Unterminated quoted argument");
1083 return NULL;
1084 }
1085 string_done:
1086 if (state == MA_UNQUOTED) {
1087 argvs[j++] = '\0';
1088 argc++;
1089 }
1090 break;
1091 } else {
1092 if (state == MA_START) {
1093 argv[argc] = argvs + j;
1094 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001095 if (lastquote != NULL)
1096 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001097 }
1098 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1099 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1100 /*
1101 * Special case for sftp: escape quoted
1102 * glob(3) wildcards. NB. string can grow
1103 * here.
1104 */
1105 if (j >= sizeof(argvs) - 3)
1106 goto args_too_longs;
1107 argvs[j++] = '\\';
1108 argvs[j++] = arg[i];
1109 } else
1110 argvs[j++] = arg[i];
1111 }
1112 i++;
1113 }
1114 *argcp = argc;
1115 return argv;
1116}
1117
Damien Miller20e1fab2004-02-18 14:30:55 +11001118static int
Darren Tucker909d8582010-01-08 19:02:40 +11001119parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag,
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001120 int *hflag, int *sflag, unsigned long *n_arg, char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001121{
1122 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001123 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001124 int base = 0;
1125 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001126 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001127
1128 /* Skip leading whitespace */
1129 cp = cp + strspn(cp, WHITESPACE);
1130
Damien Miller20e1fab2004-02-18 14:30:55 +11001131 /* Check for leading '-' (disable error processing) */
1132 *iflag = 0;
1133 if (*cp == '-') {
1134 *iflag = 1;
1135 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001136 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001137 }
1138
Darren Tucker70cc0922010-01-09 22:28:03 +11001139 /* Ignore blank lines and lines which begin with comment '#' char */
1140 if (*cp == '\0' || *cp == '#')
1141 return (0);
1142
Darren Tucker909d8582010-01-08 19:02:40 +11001143 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001144 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001145
Damien Miller1cbc2922007-10-26 14:27:45 +10001146 /* Figure out which command we have */
1147 for (i = 0; cmds[i].c != NULL; i++) {
Damien Millerd6d9fa02013-02-12 11:02:46 +11001148 if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001149 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001150 }
1151 cmdnum = cmds[i].n;
1152 cmd = cmds[i].c;
1153
1154 /* Special case */
1155 if (*cp == '!') {
1156 cp++;
1157 cmdnum = I_SHELL;
1158 } else if (cmdnum == -1) {
1159 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001160 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001161 }
1162
1163 /* Get arguments and parse flags */
Darren Tucker1b0dd172009-10-07 08:37:48 +11001164 *lflag = *pflag = *rflag = *hflag = *n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001165 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001166 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001167 switch (cmdnum) {
1168 case I_GET:
1169 case I_PUT:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001170 if ((optidx = parse_getput_flags(cmd, argv, argc,
1171 pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001172 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001173 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001174 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001175 error("You must specify at least one path after a "
1176 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001177 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001178 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001179 *path1 = xstrdup(argv[optidx]);
1180 /* Get second pathname (optional) */
1181 if (argc - optidx > 1) {
1182 *path2 = xstrdup(argv[optidx + 1]);
1183 /* Destination is not globbed */
1184 undo_glob_escape(*path2);
1185 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001186 break;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001187 case I_LINK:
1188 if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1189 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001190 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001191 case I_RENAME:
Damien Miller1cbc2922007-10-26 14:27:45 +10001192 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001193 error("You must specify two paths after a %s "
1194 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001195 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001196 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001197 *path1 = xstrdup(argv[optidx]);
1198 *path2 = xstrdup(argv[optidx + 1]);
1199 /* Paths are not globbed */
1200 undo_glob_escape(*path1);
1201 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001202 break;
1203 case I_RM:
1204 case I_MKDIR:
1205 case I_RMDIR:
1206 case I_CHDIR:
1207 case I_LCHDIR:
1208 case I_LMKDIR:
1209 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001210 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001211 error("You must specify a path after a %s command.",
1212 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001213 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001214 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001215 *path1 = xstrdup(argv[optidx]);
1216 /* Only "rm" globs */
1217 if (cmdnum != I_RM)
1218 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001219 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001220 case I_DF:
1221 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1222 iflag)) == -1)
1223 return -1;
1224 /* Default to current directory if no path specified */
1225 if (argc - optidx < 1)
1226 *path1 = NULL;
1227 else {
1228 *path1 = xstrdup(argv[optidx]);
1229 undo_glob_escape(*path1);
1230 }
1231 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001232 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001233 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001234 return(-1);
1235 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001236 if (argc - optidx > 0)
1237 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001238 break;
1239 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001240 /* Skip ls command and following whitespace */
1241 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001242 case I_SHELL:
1243 /* Uses the rest of the line */
1244 break;
1245 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001246 case I_CHMOD:
1247 base = 8;
1248 case I_CHOWN:
1249 case I_CHGRP:
1250 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001251 if (argc - optidx < 1)
1252 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001253 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001254 l = strtol(argv[optidx], &cp2, base);
1255 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1256 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1257 l < 0) {
1258 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001259 error("You must supply a numeric argument "
1260 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001261 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001262 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001263 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001264 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001265 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001266 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001267 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001268 error("You must specify a path after a %s command.",
1269 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001270 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001271 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001272 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001273 break;
1274 case I_QUIT:
1275 case I_PWD:
1276 case I_LPWD:
1277 case I_HELP:
1278 case I_VERSION:
1279 case I_PROGRESS:
1280 break;
1281 default:
1282 fatal("Command not implemented");
1283 }
1284
1285 *cpp = cp;
1286 return(cmdnum);
1287}
1288
1289static int
1290parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1291 int err_abort)
1292{
1293 char *path1, *path2, *tmp;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001294 int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, sflag = 0;
1295 int cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001296 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001297 Attrib a, *aa;
1298 char path_buf[MAXPATHLEN];
1299 int err = 0;
1300 glob_t g;
1301
1302 path1 = path2 = NULL;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001303 cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag,
1304 &sflag, &n_arg, &path1, &path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001305
1306 if (iflag != 0)
1307 err_abort = 0;
1308
1309 memset(&g, 0, sizeof(g));
1310
1311 /* Perform command */
1312 switch (cmdnum) {
1313 case 0:
1314 /* Blank line */
1315 break;
1316 case -1:
1317 /* Unrecognized command */
1318 err = -1;
1319 break;
1320 case I_GET:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001321 err = process_get(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001322 break;
1323 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001324 err = process_put(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001325 break;
1326 case I_RENAME:
1327 path1 = make_absolute(path1, *pwd);
1328 path2 = make_absolute(path2, *pwd);
1329 err = do_rename(conn, path1, path2);
1330 break;
1331 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001332 sflag = 1;
1333 case I_LINK:
1334 path1 = make_absolute(path1, *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001335 path2 = make_absolute(path2, *pwd);
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001336 err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001337 break;
1338 case I_RM:
1339 path1 = make_absolute(path1, *pwd);
1340 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001341 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001342 if (!quiet)
1343 printf("Removing %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001344 err = do_rm(conn, g.gl_pathv[i]);
1345 if (err != 0 && err_abort)
1346 break;
1347 }
1348 break;
1349 case I_MKDIR:
1350 path1 = make_absolute(path1, *pwd);
1351 attrib_clear(&a);
1352 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1353 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001354 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001355 break;
1356 case I_RMDIR:
1357 path1 = make_absolute(path1, *pwd);
1358 err = do_rmdir(conn, path1);
1359 break;
1360 case I_CHDIR:
1361 path1 = make_absolute(path1, *pwd);
1362 if ((tmp = do_realpath(conn, path1)) == NULL) {
1363 err = 1;
1364 break;
1365 }
1366 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
Darren Tuckera627d422013-06-02 07:31:17 +10001367 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001368 err = 1;
1369 break;
1370 }
1371 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1372 error("Can't change directory: Can't check target");
Darren Tuckera627d422013-06-02 07:31:17 +10001373 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001374 err = 1;
1375 break;
1376 }
1377 if (!S_ISDIR(aa->perm)) {
1378 error("Can't change directory: \"%s\" is not "
1379 "a directory", tmp);
Darren Tuckera627d422013-06-02 07:31:17 +10001380 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001381 err = 1;
1382 break;
1383 }
Darren Tuckera627d422013-06-02 07:31:17 +10001384 free(*pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001385 *pwd = tmp;
1386 break;
1387 case I_LS:
1388 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001389 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001390 break;
1391 }
1392
1393 /* Strip pwd off beginning of non-absolute paths */
1394 tmp = NULL;
1395 if (*path1 != '/')
1396 tmp = *pwd;
1397
1398 path1 = make_absolute(path1, *pwd);
1399 err = do_globbed_ls(conn, path1, tmp, lflag);
1400 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001401 case I_DF:
1402 /* Default to current directory if no path specified */
1403 if (path1 == NULL)
1404 path1 = xstrdup(*pwd);
1405 path1 = make_absolute(path1, *pwd);
1406 err = do_df(conn, path1, hflag, iflag);
1407 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001408 case I_LCHDIR:
1409 if (chdir(path1) == -1) {
1410 error("Couldn't change local directory to "
1411 "\"%s\": %s", path1, strerror(errno));
1412 err = 1;
1413 }
1414 break;
1415 case I_LMKDIR:
1416 if (mkdir(path1, 0777) == -1) {
1417 error("Couldn't create local directory "
1418 "\"%s\": %s", path1, strerror(errno));
1419 err = 1;
1420 }
1421 break;
1422 case I_LLS:
1423 local_do_ls(cmd);
1424 break;
1425 case I_SHELL:
1426 local_do_shell(cmd);
1427 break;
1428 case I_LUMASK:
1429 umask(n_arg);
1430 printf("Local umask: %03lo\n", n_arg);
1431 break;
1432 case I_CHMOD:
1433 path1 = make_absolute(path1, *pwd);
1434 attrib_clear(&a);
1435 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1436 a.perm = n_arg;
1437 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001438 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001439 if (!quiet)
1440 printf("Changing mode on %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001441 err = do_setstat(conn, g.gl_pathv[i], &a);
1442 if (err != 0 && err_abort)
1443 break;
1444 }
1445 break;
1446 case I_CHOWN:
1447 case I_CHGRP:
1448 path1 = make_absolute(path1, *pwd);
1449 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001450 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001451 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001452 if (err_abort) {
1453 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001454 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001455 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001456 continue;
1457 }
1458 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1459 error("Can't get current ownership of "
1460 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001461 if (err_abort) {
1462 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001463 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001464 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001465 continue;
1466 }
1467 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1468 if (cmdnum == I_CHOWN) {
Damien Miller9303e652013-04-23 15:22:40 +10001469 if (!quiet)
1470 printf("Changing owner on %s\n",
1471 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001472 aa->uid = n_arg;
1473 } else {
Damien Miller9303e652013-04-23 15:22:40 +10001474 if (!quiet)
1475 printf("Changing group on %s\n",
1476 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001477 aa->gid = n_arg;
1478 }
1479 err = do_setstat(conn, g.gl_pathv[i], aa);
1480 if (err != 0 && err_abort)
1481 break;
1482 }
1483 break;
1484 case I_PWD:
1485 printf("Remote working directory: %s\n", *pwd);
1486 break;
1487 case I_LPWD:
1488 if (!getcwd(path_buf, sizeof(path_buf))) {
1489 error("Couldn't get local cwd: %s", strerror(errno));
1490 err = -1;
1491 break;
1492 }
1493 printf("Local working directory: %s\n", path_buf);
1494 break;
1495 case I_QUIT:
1496 /* Processed below */
1497 break;
1498 case I_HELP:
1499 help();
1500 break;
1501 case I_VERSION:
1502 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1503 break;
1504 case I_PROGRESS:
1505 showprogress = !showprogress;
1506 if (showprogress)
1507 printf("Progress meter enabled\n");
1508 else
1509 printf("Progress meter disabled\n");
1510 break;
1511 default:
1512 fatal("%d is not implemented", cmdnum);
1513 }
1514
1515 if (g.gl_pathc)
1516 globfree(&g);
Darren Tuckera627d422013-06-02 07:31:17 +10001517 free(path1);
1518 free(path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001519
1520 /* If an unignored error occurs in batch mode we should abort. */
1521 if (err_abort && err != 0)
1522 return (-1);
1523 else if (cmdnum == I_QUIT)
1524 return (1);
1525
1526 return (0);
1527}
1528
Darren Tucker2d963d82004-11-07 20:04:10 +11001529#ifdef USE_LIBEDIT
1530static char *
1531prompt(EditLine *el)
1532{
1533 return ("sftp> ");
1534}
Darren Tucker2d963d82004-11-07 20:04:10 +11001535
Darren Tucker909d8582010-01-08 19:02:40 +11001536/* Display entries in 'list' after skipping the first 'len' chars */
1537static void
1538complete_display(char **list, u_int len)
1539{
1540 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1541 struct winsize ws;
1542 char *tmp;
1543
1544 /* Count entries for sort and find longest */
1545 for (y = 0; list[y]; y++)
1546 m = MAX(m, strlen(list[y]));
1547
1548 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1549 width = ws.ws_col;
1550
1551 m = m > len ? m - len : 0;
1552 columns = width / (m + 2);
1553 columns = MAX(columns, 1);
1554 colspace = width / columns;
1555 colspace = MIN(colspace, width);
1556
1557 printf("\n");
1558 m = 1;
1559 for (y = 0; list[y]; y++) {
1560 llen = strlen(list[y]);
1561 tmp = llen > len ? list[y] + len : "";
1562 printf("%-*s", colspace, tmp);
1563 if (m >= columns) {
1564 printf("\n");
1565 m = 1;
1566 } else
1567 m++;
1568 }
1569 printf("\n");
1570}
1571
1572/*
1573 * Given a "list" of words that begin with a common prefix of "word",
1574 * attempt to find an autocompletion to extends "word" by the next
1575 * characters common to all entries in "list".
1576 */
1577static char *
1578complete_ambiguous(const char *word, char **list, size_t count)
1579{
1580 if (word == NULL)
1581 return NULL;
1582
1583 if (count > 0) {
1584 u_int y, matchlen = strlen(list[0]);
1585
1586 /* Find length of common stem */
1587 for (y = 1; list[y]; y++) {
1588 u_int x;
1589
1590 for (x = 0; x < matchlen; x++)
1591 if (list[0][x] != list[y][x])
1592 break;
1593
1594 matchlen = x;
1595 }
1596
1597 if (matchlen > strlen(word)) {
1598 char *tmp = xstrdup(list[0]);
1599
Darren Tucker340d1682010-01-09 08:54:31 +11001600 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001601 return tmp;
1602 }
1603 }
1604
1605 return xstrdup(word);
1606}
1607
1608/* Autocomplete a sftp command */
1609static int
1610complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1611 int terminated)
1612{
1613 u_int y, count = 0, cmdlen, tmplen;
1614 char *tmp, **list, argterm[3];
1615 const LineInfo *lf;
1616
1617 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1618
1619 /* No command specified: display all available commands */
1620 if (cmd == NULL) {
1621 for (y = 0; cmds[y].c; y++)
1622 list[count++] = xstrdup(cmds[y].c);
1623
1624 list[count] = NULL;
1625 complete_display(list, 0);
1626
1627 for (y = 0; list[y] != NULL; y++)
Darren Tuckera627d422013-06-02 07:31:17 +10001628 free(list[y]);
1629 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001630 return count;
1631 }
1632
1633 /* Prepare subset of commands that start with "cmd" */
1634 cmdlen = strlen(cmd);
1635 for (y = 0; cmds[y].c; y++) {
1636 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
1637 list[count++] = xstrdup(cmds[y].c);
1638 }
1639 list[count] = NULL;
1640
Damien Miller47d81152011-11-25 13:53:48 +11001641 if (count == 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10001642 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001643 return 0;
Damien Miller47d81152011-11-25 13:53:48 +11001644 }
Darren Tucker909d8582010-01-08 19:02:40 +11001645
1646 /* Complete ambigious command */
1647 tmp = complete_ambiguous(cmd, list, count);
1648 if (count > 1)
1649 complete_display(list, 0);
1650
1651 for (y = 0; list[y]; y++)
Darren Tuckera627d422013-06-02 07:31:17 +10001652 free(list[y]);
1653 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001654
1655 if (tmp != NULL) {
1656 tmplen = strlen(tmp);
1657 cmdlen = strlen(cmd);
1658 /* If cmd may be extended then do so */
1659 if (tmplen > cmdlen)
1660 if (el_insertstr(el, tmp + cmdlen) == -1)
1661 fatal("el_insertstr failed.");
1662 lf = el_line(el);
1663 /* Terminate argument cleanly */
1664 if (count == 1) {
1665 y = 0;
1666 if (!terminated)
1667 argterm[y++] = quote;
1668 if (lastarg || *(lf->cursor) != ' ')
1669 argterm[y++] = ' ';
1670 argterm[y] = '\0';
1671 if (y > 0 && el_insertstr(el, argterm) == -1)
1672 fatal("el_insertstr failed.");
1673 }
Darren Tuckera627d422013-06-02 07:31:17 +10001674 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001675 }
1676
1677 return count;
1678}
1679
1680/*
1681 * Determine whether a particular sftp command's arguments (if any)
1682 * represent local or remote files.
1683 */
1684static int
1685complete_is_remote(char *cmd) {
1686 int i;
1687
1688 if (cmd == NULL)
1689 return -1;
1690
1691 for (i = 0; cmds[i].c; i++) {
1692 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
1693 return cmds[i].t;
1694 }
1695
1696 return -1;
1697}
1698
1699/* Autocomplete a filename "file" */
1700static int
1701complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1702 char *file, int remote, int lastarg, char quote, int terminated)
1703{
1704 glob_t g;
Darren Tuckerea647212013-06-06 08:19:09 +10001705 char *tmp, *tmp2, ins[8];
Darren Tucker17146d32012-10-05 10:46:16 +10001706 u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
Darren Tuckerea647212013-06-06 08:19:09 +10001707 int clen;
Darren Tucker909d8582010-01-08 19:02:40 +11001708 const LineInfo *lf;
1709
1710 /* Glob from "file" location */
1711 if (file == NULL)
1712 tmp = xstrdup("*");
1713 else
1714 xasprintf(&tmp, "%s*", file);
1715
Darren Tucker191fcc62012-10-05 10:45:01 +10001716 /* Check if the path is absolute. */
1717 isabs = tmp[0] == '/';
1718
Darren Tucker909d8582010-01-08 19:02:40 +11001719 memset(&g, 0, sizeof(g));
1720 if (remote != LOCAL) {
1721 tmp = make_absolute(tmp, remote_path);
1722 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1723 } else
1724 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1725
1726 /* Determine length of pwd so we can trim completion display */
1727 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1728 /* Terminate counting on first unescaped glob metacharacter */
1729 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1730 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1731 hadglob = 1;
1732 break;
1733 }
1734 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1735 tmplen++;
1736 if (tmp[tmplen] == '/')
1737 pwdlen = tmplen + 1; /* track last seen '/' */
1738 }
Darren Tuckera627d422013-06-02 07:31:17 +10001739 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001740
1741 if (g.gl_matchc == 0)
1742 goto out;
1743
1744 if (g.gl_matchc > 1)
1745 complete_display(g.gl_pathv, pwdlen);
1746
1747 tmp = NULL;
1748 /* Don't try to extend globs */
1749 if (file == NULL || hadglob)
1750 goto out;
1751
1752 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
Darren Tucker191fcc62012-10-05 10:45:01 +10001753 tmp = path_strip(tmp2, isabs ? NULL : remote_path);
Darren Tuckera627d422013-06-02 07:31:17 +10001754 free(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001755
1756 if (tmp == NULL)
1757 goto out;
1758
1759 tmplen = strlen(tmp);
1760 filelen = strlen(file);
1761
Darren Tucker17146d32012-10-05 10:46:16 +10001762 /* Count the number of escaped characters in the input string. */
1763 cesc = isesc = 0;
1764 for (i = 0; i < filelen; i++) {
1765 if (!isesc && file[i] == '\\' && i + 1 < filelen){
1766 isesc = 1;
1767 cesc++;
1768 } else
1769 isesc = 0;
1770 }
1771
1772 if (tmplen > (filelen - cesc)) {
1773 tmp2 = tmp + filelen - cesc;
Darren Tucker909d8582010-01-08 19:02:40 +11001774 len = strlen(tmp2);
1775 /* quote argument on way out */
Darren Tuckerea647212013-06-06 08:19:09 +10001776 for (i = 0; i < len; i += clen) {
1777 if ((clen = mblen(tmp2 + i, len - i)) < 0 ||
1778 (size_t)clen > sizeof(ins) - 2)
1779 fatal("invalid multibyte character");
Darren Tucker909d8582010-01-08 19:02:40 +11001780 ins[0] = '\\';
Darren Tuckerea647212013-06-06 08:19:09 +10001781 memcpy(ins + 1, tmp2 + i, clen);
1782 ins[clen + 1] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001783 switch (tmp2[i]) {
1784 case '\'':
1785 case '"':
1786 case '\\':
1787 case '\t':
Darren Tuckerd78739a2010-10-24 10:56:32 +11001788 case '[':
Darren Tucker909d8582010-01-08 19:02:40 +11001789 case ' ':
Darren Tucker17146d32012-10-05 10:46:16 +10001790 case '#':
1791 case '*':
Darren Tucker909d8582010-01-08 19:02:40 +11001792 if (quote == '\0' || tmp2[i] == quote) {
1793 if (el_insertstr(el, ins) == -1)
1794 fatal("el_insertstr "
1795 "failed.");
1796 break;
1797 }
1798 /* FALLTHROUGH */
1799 default:
1800 if (el_insertstr(el, ins + 1) == -1)
1801 fatal("el_insertstr failed.");
1802 break;
1803 }
1804 }
1805 }
1806
1807 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11001808 if (g.gl_matchc == 1) {
1809 i = 0;
1810 if (!terminated)
1811 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11001812 if (*(lf->cursor - 1) != '/' &&
1813 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11001814 ins[i++] = ' ';
1815 ins[i] = '\0';
1816 if (i > 0 && el_insertstr(el, ins) == -1)
1817 fatal("el_insertstr failed.");
1818 }
Darren Tuckera627d422013-06-02 07:31:17 +10001819 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001820
1821 out:
1822 globfree(&g);
1823 return g.gl_matchc;
1824}
1825
1826/* tab-completion hook function, called via libedit */
1827static unsigned char
1828complete(EditLine *el, int ch)
1829{
1830 char **argv, *line, quote;
1831 u_int argc, carg, cursor, len, terminated, ret = CC_ERROR;
1832 const LineInfo *lf;
1833 struct complete_ctx *complete_ctx;
1834
1835 lf = el_line(el);
1836 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1837 fatal("%s: el_get failed", __func__);
1838
1839 /* Figure out which argument the cursor points to */
1840 cursor = lf->cursor - lf->buffer;
1841 line = (char *)xmalloc(cursor + 1);
1842 memcpy(line, lf->buffer, cursor);
1843 line[cursor] = '\0';
1844 argv = makeargv(line, &carg, 1, &quote, &terminated);
Darren Tuckera627d422013-06-02 07:31:17 +10001845 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001846
1847 /* Get all the arguments on the line */
1848 len = lf->lastchar - lf->buffer;
1849 line = (char *)xmalloc(len + 1);
1850 memcpy(line, lf->buffer, len);
1851 line[len] = '\0';
1852 argv = makeargv(line, &argc, 1, NULL, NULL);
1853
1854 /* Ensure cursor is at EOL or a argument boundary */
1855 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1856 line[cursor] != '\n') {
Darren Tuckera627d422013-06-02 07:31:17 +10001857 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001858 return ret;
1859 }
1860
1861 if (carg == 0) {
1862 /* Show all available commands */
1863 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1864 ret = CC_REDISPLAY;
1865 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1866 /* Handle the command parsing */
1867 if (complete_cmd_parse(el, argv[0], argc == carg,
1868 quote, terminated) != 0)
1869 ret = CC_REDISPLAY;
1870 } else if (carg >= 1) {
1871 /* Handle file parsing */
1872 int remote = complete_is_remote(argv[0]);
1873 char *filematch = NULL;
1874
1875 if (carg > 1 && line[cursor-1] != ' ')
1876 filematch = argv[carg - 1];
1877
1878 if (remote != 0 &&
1879 complete_match(el, complete_ctx->conn,
1880 *complete_ctx->remote_pathp, filematch,
1881 remote, carg == argc, quote, terminated) != 0)
1882 ret = CC_REDISPLAY;
1883 }
1884
Darren Tuckera627d422013-06-02 07:31:17 +10001885 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001886 return ret;
1887}
Darren Tuckere67f7db2010-01-08 19:50:02 +11001888#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11001889
Damien Miller20e1fab2004-02-18 14:30:55 +11001890int
Darren Tucker21063192010-01-08 17:10:36 +11001891interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001892{
Darren Tucker909d8582010-01-08 19:02:40 +11001893 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11001894 char *dir = NULL;
1895 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10001896 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11001897 EditLine *el = NULL;
1898#ifdef USE_LIBEDIT
1899 History *hl = NULL;
1900 HistEvent hev;
1901 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11001902 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11001903
1904 if (!batchmode && isatty(STDIN_FILENO)) {
1905 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1906 fatal("Couldn't initialise editline");
1907 if ((hl = history_init()) == NULL)
1908 fatal("Couldn't initialise editline history");
1909 history(hl, &hev, H_SETSIZE, 100);
1910 el_set(el, EL_HIST, history, hl);
1911
1912 el_set(el, EL_PROMPT, prompt);
1913 el_set(el, EL_EDITOR, "emacs");
1914 el_set(el, EL_TERMINAL, NULL);
1915 el_set(el, EL_SIGNAL, 1);
1916 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11001917
1918 /* Tab Completion */
1919 el_set(el, EL_ADDFN, "ftp-complete",
Darren Tuckerd78739a2010-10-24 10:56:32 +11001920 "Context sensitive argument completion", complete);
Darren Tucker909d8582010-01-08 19:02:40 +11001921 complete_ctx.conn = conn;
1922 complete_ctx.remote_pathp = &remote_path;
1923 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
1924 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11001925 }
1926#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001927
Darren Tucker909d8582010-01-08 19:02:40 +11001928 remote_path = do_realpath(conn, ".");
1929 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11001930 fatal("Need cwd");
1931
1932 if (file1 != NULL) {
1933 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11001934 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11001935
1936 if (remote_is_dir(conn, dir) && file2 == NULL) {
Damien Miller9303e652013-04-23 15:22:40 +10001937 if (!quiet)
1938 printf("Changing to: %s\n", dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11001939 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001940 if (parse_dispatch_command(conn, cmd,
1941 &remote_path, 1) != 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10001942 free(dir);
1943 free(remote_path);
1944 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001945 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11001946 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001947 } else {
Darren Tucker0af24052012-10-05 10:41:25 +10001948 /* XXX this is wrong wrt quoting */
Damien Miller20e1fab2004-02-18 14:30:55 +11001949 if (file2 == NULL)
1950 snprintf(cmd, sizeof cmd, "get %s", dir);
1951 else
1952 snprintf(cmd, sizeof cmd, "get %s %s", dir,
1953 file2);
1954
Darren Tucker909d8582010-01-08 19:02:40 +11001955 err = parse_dispatch_command(conn, cmd,
1956 &remote_path, 1);
Darren Tuckera627d422013-06-02 07:31:17 +10001957 free(dir);
1958 free(remote_path);
1959 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001960 return (err);
1961 }
Darren Tuckera627d422013-06-02 07:31:17 +10001962 free(dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11001963 }
1964
Damien Miller37294fb2005-07-17 17:18:49 +10001965 setlinebuf(stdout);
1966 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11001967
Damien Miller0e2c1022005-08-12 22:16:22 +10001968 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11001969 err = 0;
1970 for (;;) {
1971 char *cp;
1972
Darren Tuckercdf547a2004-05-24 10:12:19 +10001973 signal(SIGINT, SIG_IGN);
1974
Darren Tucker2d963d82004-11-07 20:04:10 +11001975 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001976 if (interactive)
1977 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11001978 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001979 if (interactive)
1980 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11001981 break;
1982 }
Damien Miller0e2c1022005-08-12 22:16:22 +10001983 if (!interactive) { /* Echo command */
1984 printf("sftp> %s", cmd);
1985 if (strlen(cmd) > 0 &&
1986 cmd[strlen(cmd) - 1] != '\n')
1987 printf("\n");
1988 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001989 } else {
1990#ifdef USE_LIBEDIT
1991 const char *line;
1992 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001993
Darren Tucker909d8582010-01-08 19:02:40 +11001994 if ((line = el_gets(el, &count)) == NULL ||
1995 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001996 printf("\n");
1997 break;
1998 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001999 history(hl, &hev, H_ENTER, line);
2000 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
2001 fprintf(stderr, "Error: input line too long\n");
2002 continue;
2003 }
2004#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002005 }
2006
Damien Miller20e1fab2004-02-18 14:30:55 +11002007 cp = strrchr(cmd, '\n');
2008 if (cp)
2009 *cp = '\0';
2010
Darren Tuckercdf547a2004-05-24 10:12:19 +10002011 /* Handle user interrupts gracefully during commands */
2012 interrupted = 0;
2013 signal(SIGINT, cmd_interrupt);
2014
Darren Tucker909d8582010-01-08 19:02:40 +11002015 err = parse_dispatch_command(conn, cmd, &remote_path,
2016 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11002017 if (err != 0)
2018 break;
2019 }
Darren Tuckera627d422013-06-02 07:31:17 +10002020 free(remote_path);
2021 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002022
Tim Rice027e8b12005-08-15 14:52:50 -07002023#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10002024 if (el != NULL)
2025 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07002026#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10002027
Damien Miller20e1fab2004-02-18 14:30:55 +11002028 /* err == 1 signifies normal "quit" exit */
2029 return (err >= 0 ? 0 : -1);
2030}
Damien Miller62d57f62003-01-10 21:43:24 +11002031
Ben Lindstrombba81212001-06-25 05:01:22 +00002032static void
Damien Millercc685c12003-06-04 22:51:38 +10002033connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11002034{
2035 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002036
Damien Miller33804262001-02-04 23:20:18 +11002037#ifdef USE_PIPES
2038 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002039
Damien Miller33804262001-02-04 23:20:18 +11002040 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2041 fatal("pipe: %s", strerror(errno));
2042 *in = pin[0];
2043 *out = pout[1];
2044 c_in = pout[0];
2045 c_out = pin[1];
2046#else /* USE_PIPES */
2047 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002048
Damien Miller33804262001-02-04 23:20:18 +11002049 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2050 fatal("socketpair: %s", strerror(errno));
2051 *in = *out = inout[0];
2052 c_in = c_out = inout[1];
2053#endif /* USE_PIPES */
2054
Damien Millercc685c12003-06-04 22:51:38 +10002055 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002056 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002057 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002058 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2059 (dup2(c_out, STDOUT_FILENO) == -1)) {
2060 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002061 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002062 }
2063 close(*in);
2064 close(*out);
2065 close(c_in);
2066 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002067
2068 /*
2069 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002070 * ignore SIGINT if we want to gracefully abort commands,
2071 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002072 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2073 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002074 */
2075 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002076 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002077 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002078 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002079 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002080 }
2081
Damien Millercc685c12003-06-04 22:51:38 +10002082 signal(SIGTERM, killchild);
2083 signal(SIGINT, killchild);
2084 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11002085 close(c_in);
2086 close(c_out);
2087}
2088
Ben Lindstrombba81212001-06-25 05:01:22 +00002089static void
Damien Miller33804262001-02-04 23:20:18 +11002090usage(void)
2091{
Damien Miller025e01c2002-02-08 22:06:29 +11002092 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002093
Ben Lindstrom1e243242001-09-18 05:38:44 +00002094 fprintf(stderr,
Darren Tucker1b0dd172009-10-07 08:37:48 +11002095 "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002096 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002097 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002098 " [-o ssh_option] [-P port] [-R num_requests] "
2099 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002100 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002101 " %s [user@]host[:file ...]\n"
2102 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002103 " %s -b batchfile [user@]host\n",
2104 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002105 exit(1);
2106}
2107
Kevin Stevesef4eea92001-02-05 12:42:17 +00002108int
Damien Miller33804262001-02-04 23:20:18 +11002109main(int argc, char **argv)
2110{
Damien Miller956f3fb2003-01-10 21:40:00 +11002111 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002112 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002113 int debug_level = 0, sshver = 2;
2114 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002115 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002116 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002117 LogLevel ll = SYSLOG_LEVEL_INFO;
2118 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002119 extern int optind;
2120 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002121 struct sftp_conn *conn;
2122 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2123 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002124 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002125
Darren Tuckerce321d82005-10-03 18:11:24 +10002126 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2127 sanitise_stdfd();
Darren Tuckerea647212013-06-06 08:19:09 +10002128 setlocale(LC_CTYPE, "");
Darren Tuckerce321d82005-10-03 18:11:24 +10002129
Damien Miller59d3d5b2003-08-22 09:34:41 +10002130 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002131 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002132 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002133 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002134 addargs(&args, "-oForwardX11 no");
2135 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002136 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002137 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002138
Ben Lindstrom387c4722001-05-08 20:27:25 +00002139 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002140 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002141
Darren Tucker282b4022009-10-07 08:23:06 +11002142 while ((ch = getopt(argc, argv,
Damien Miller65e42f82010-09-24 22:15:11 +10002143 "1246hpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002144 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002145 /* Passed through to ssh(1) */
2146 case '4':
2147 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002148 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002149 addargs(&args, "-%c", ch);
2150 break;
2151 /* Passed through to ssh(1) with argument */
2152 case 'F':
2153 case 'c':
2154 case 'i':
2155 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002156 addargs(&args, "-%c", ch);
2157 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002158 break;
2159 case 'q':
Damien Miller9303e652013-04-23 15:22:40 +10002160 ll = SYSLOG_LEVEL_ERROR;
2161 quiet = 1;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002162 showprogress = 0;
2163 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002164 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002165 case 'P':
2166 addargs(&args, "-oPort %s", optarg);
2167 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002168 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002169 if (debug_level < 3) {
2170 addargs(&args, "-v");
2171 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2172 }
2173 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002174 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002175 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002176 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002177 if (sftp_server == NULL)
2178 sftp_server = _PATH_SFTP_SERVER;
2179 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002180 case '2':
2181 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002182 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002183 case 'B':
2184 copy_buffer_len = strtol(optarg, &cp, 10);
2185 if (copy_buffer_len == 0 || *cp != '\0')
2186 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002187 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002188 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002189 if (batchmode)
2190 fatal("Batch file already specified.");
2191
2192 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002193 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002194 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002195 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002196 showprogress = 0;
Damien Miller9303e652013-04-23 15:22:40 +10002197 quiet = batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002198 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002199 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002200 case 'p':
2201 global_pflag = 1;
2202 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002203 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002204 sftp_direct = optarg;
2205 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002206 case 'l':
2207 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2208 &errstr);
2209 if (errstr != NULL)
2210 usage();
2211 limit_kbps *= 1024; /* kbps */
2212 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002213 case 'r':
2214 global_rflag = 1;
2215 break;
Damien Miller16a13332002-02-13 14:03:56 +11002216 case 'R':
2217 num_requests = strtol(optarg, &cp, 10);
2218 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002219 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002220 optarg);
2221 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002222 case 's':
2223 sftp_server = optarg;
2224 break;
2225 case 'S':
2226 ssh_program = optarg;
2227 replacearg(&args, 0, "%s", ssh_program);
2228 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002229 case 'h':
2230 default:
Damien Miller33804262001-02-04 23:20:18 +11002231 usage();
2232 }
2233 }
2234
Damien Millerc0f27d82004-03-08 23:12:19 +11002235 if (!isatty(STDERR_FILENO))
2236 showprogress = 0;
2237
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002238 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2239
Damien Millerd14ee1e2002-02-05 12:27:31 +11002240 if (sftp_direct == NULL) {
2241 if (optind == argc || argc > (optind + 2))
2242 usage();
Damien Miller33804262001-02-04 23:20:18 +11002243
Damien Millerd14ee1e2002-02-05 12:27:31 +11002244 userhost = xstrdup(argv[optind]);
2245 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002246
Ben Lindstromc276c122002-12-23 02:14:51 +00002247 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002248 host = userhost;
2249 else {
2250 *host++ = '\0';
2251 if (!userhost[0]) {
2252 fprintf(stderr, "Missing username\n");
2253 usage();
2254 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002255 addargs(&args, "-l");
2256 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002257 }
2258
Damien Millerec692032004-01-27 21:22:00 +11002259 if ((cp = colon(host)) != NULL) {
2260 *cp++ = '\0';
2261 file1 = cp;
2262 }
2263
Damien Millerd14ee1e2002-02-05 12:27:31 +11002264 host = cleanhostname(host);
2265 if (!*host) {
2266 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002267 usage();
2268 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002269
Damien Millerd14ee1e2002-02-05 12:27:31 +11002270 addargs(&args, "-oProtocol %d", sshver);
2271
2272 /* no subsystem if the server-spec contains a '/' */
2273 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2274 addargs(&args, "-s");
2275
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002276 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002277 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002278 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002279 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002280
Damien Millercc685c12003-06-04 22:51:38 +10002281 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002282 } else {
2283 args.list = NULL;
2284 addargs(&args, "sftp-server");
2285
Damien Millercc685c12003-06-04 22:51:38 +10002286 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002287 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002288 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002289
Damien Miller65e42f82010-09-24 22:15:11 +10002290 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002291 if (conn == NULL)
2292 fatal("Couldn't initialise connection to server");
2293
Damien Miller9303e652013-04-23 15:22:40 +10002294 if (!quiet) {
Darren Tucker21063192010-01-08 17:10:36 +11002295 if (sftp_direct == NULL)
2296 fprintf(stderr, "Connected to %s.\n", host);
2297 else
2298 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2299 }
2300
2301 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002302
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002303#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002304 shutdown(in, SHUT_RDWR);
2305 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002306#endif
2307
Damien Miller33804262001-02-04 23:20:18 +11002308 close(in);
2309 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002310 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002311 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002312
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002313 while (waitpid(sshpid, NULL, 0) == -1)
2314 if (errno != EINTR)
2315 fatal("Couldn't wait for ssh process: %s",
2316 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002317
Damien Miller956f3fb2003-01-10 21:40:00 +11002318 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002319}