blob: da7fbab38e0530cf5d050d1a6b438b841e48a22f [file] [log] [blame]
Damien Miller47d81152011-11-25 13:53:48 +11001/* $OpenBSD: sftp.c,v 1.134 2011/11/16 12:24:28 oga 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 Tucker2d963d82004-11-07 20:04:10 +110041#ifdef USE_LIBEDIT
42#include <histedit.h>
43#else
44typedef void EditLine;
45#endif
Damien Miller6ff3cad2006-03-15 11:52:09 +110046#include <signal.h>
Damien Millere7a1e5c2006-08-05 11:34:19 +100047#include <stdlib.h>
Damien Millera7a73ee2006-08-05 11:37:59 +100048#include <stdio.h>
Damien Millere3476ed2006-07-24 14:13:33 +100049#include <string.h>
Damien Millere6b3b612006-07-24 14:01:23 +100050#include <unistd.h>
Damien Millerd7834352006-08-05 12:39:39 +100051#include <stdarg.h>
Damien Miller33804262001-02-04 23:20:18 +110052
Damien Millera7058ec2008-05-20 08:57:06 +100053#ifdef HAVE_UTIL_H
54# include <util.h>
55#endif
56
57#ifdef HAVE_LIBUTIL_H
58# include <libutil.h>
59#endif
60
Damien Miller33804262001-02-04 23:20:18 +110061#include "xmalloc.h"
62#include "log.h"
63#include "pathnames.h"
Ben Lindstrom4529b702001-05-03 23:39:53 +000064#include "misc.h"
Damien Miller33804262001-02-04 23:20:18 +110065
66#include "sftp.h"
Damien Millerd7834352006-08-05 12:39:39 +100067#include "buffer.h"
Damien Miller33804262001-02-04 23:20:18 +110068#include "sftp-common.h"
69#include "sftp-client.h"
Damien Millerd7d46bb2004-02-18 14:11:13 +110070
Darren Tucker21063192010-01-08 17:10:36 +110071#define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */
72#define DEFAULT_NUM_REQUESTS 64 /* # concurrent outstanding requests */
73
Damien Miller20e1fab2004-02-18 14:30:55 +110074/* File to read commands from */
75FILE* infile;
76
77/* Are we in batchfile mode? */
78int batchmode = 0;
79
Damien Miller20e1fab2004-02-18 14:30:55 +110080/* PID of ssh transport process */
81static pid_t sshpid = -1;
82
83/* 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
222 write(STDERR_FILENO, msg, sizeof(msg) - 1);
223 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);
313 xfree(buf);
314 }
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);
Damien Miller20e1fab2004-02-18 14:30:55 +1100344 xfree(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));
554 xfree(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 }
570 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100571
572 printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100573 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
574 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
575 pflag || global_pflag, 1) == -1)
576 err = -1;
577 } else {
578 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
579 pflag || global_pflag) == -1)
580 err = -1;
581 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100582 xfree(abs_dst);
583 abs_dst = NULL;
584 }
585
586out:
587 xfree(abs_src);
Damien Miller20e1fab2004-02-18 14:30:55 +1100588 globfree(&g);
589 return(err);
590}
591
592static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100593process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
594 int pflag, int rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100595{
596 char *tmp_dst = NULL;
597 char *abs_dst = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100598 char *tmp = NULL, *filename = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100599 glob_t g;
600 int err = 0;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100601 int i, dst_is_dir = 1;
Damien Milleraec5cf82008-02-10 22:26:24 +1100602 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100603
604 if (dst) {
605 tmp_dst = xstrdup(dst);
606 tmp_dst = make_absolute(tmp_dst, pwd);
607 }
608
609 memset(&g, 0, sizeof(g));
610 debug3("Looking up %s", src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100611 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100612 error("File \"%s\" not found.", src);
613 err = -1;
614 goto out;
615 }
616
Darren Tucker1b0dd172009-10-07 08:37:48 +1100617 /* If we aren't fetching to pwd then stash this status for later */
618 if (tmp_dst != NULL)
619 dst_is_dir = remote_is_dir(conn, tmp_dst);
620
Damien Miller20e1fab2004-02-18 14:30:55 +1100621 /* If multiple matches, dst may be directory or unspecified */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100622 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
623 error("Multiple paths match, but destination "
624 "\"%s\" is not a directory", tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100625 err = -1;
626 goto out;
627 }
628
Darren Tuckercdf547a2004-05-24 10:12:19 +1000629 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Milleraec5cf82008-02-10 22:26:24 +1100630 if (stat(g.gl_pathv[i], &sb) == -1) {
631 err = -1;
632 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
633 continue;
634 }
Darren Tucker1b0dd172009-10-07 08:37:48 +1100635
636 tmp = xstrdup(g.gl_pathv[i]);
637 if ((filename = basename(tmp)) == NULL) {
638 error("basename %s: %s", tmp, strerror(errno));
639 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100640 err = -1;
641 goto out;
642 }
643
644 if (g.gl_matchc == 1 && tmp_dst) {
645 /* If directory specified, append filename */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100646 if (dst_is_dir)
647 abs_dst = path_append(tmp_dst, filename);
648 else
Damien Miller20e1fab2004-02-18 14:30:55 +1100649 abs_dst = xstrdup(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100650 } else if (tmp_dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100651 abs_dst = path_append(tmp_dst, filename);
652 } else {
653 abs_dst = make_absolute(xstrdup(filename), pwd);
654 }
655 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100656
657 printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100658 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
659 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
660 pflag || global_pflag, 1) == -1)
661 err = -1;
662 } else {
663 if (do_upload(conn, g.gl_pathv[i], abs_dst,
664 pflag || global_pflag) == -1)
665 err = -1;
666 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100667 }
668
669out:
670 if (abs_dst)
671 xfree(abs_dst);
672 if (tmp_dst)
673 xfree(tmp_dst);
674 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);
721 xfree(tmp);
722
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);
747 xfree(tmp);
748
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);
759 xfree(lname);
760 } 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
771 xfree(fname);
772 }
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{
Darren Tucker596dcfa2004-12-11 13:37:22 +1100786 Attrib *a = NULL;
Damien Millera6e121a2010-10-07 21:39:17 +1100787 char *fname, *lname;
Damien Miller68e2e562010-10-07 21:39:55 +1100788 glob_t g;
789 int err;
790 struct winsize ws;
791 u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100792
793 memset(&g, 0, sizeof(g));
794
Damien Millera6e121a2010-10-07 21:39:17 +1100795 if (remote_glob(conn, path,
Damien Millerd7be70d2011-09-22 21:43:06 +1000796 GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
797 NULL, &g) ||
Damien Millera6e121a2010-10-07 21:39:17 +1100798 (g.gl_pathc && !g.gl_matchc)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100799 if (g.gl_pathc)
800 globfree(&g);
Damien Miller20e1fab2004-02-18 14:30:55 +1100801 error("Can't ls: \"%s\" not found", path);
Damien Millera6e121a2010-10-07 21:39:17 +1100802 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100803 }
804
Darren Tuckercdf547a2004-05-24 10:12:19 +1000805 if (interrupted)
806 goto out;
807
Damien Miller20e1fab2004-02-18 14:30:55 +1100808 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100809 * If the glob returns a single match and it is a directory,
810 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100811 */
Damien Millera6e121a2010-10-07 21:39:17 +1100812 if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
813 S_ISDIR(g.gl_statv[0]->st_mode)) {
814 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
815 globfree(&g);
816 return err;
Damien Miller20e1fab2004-02-18 14:30:55 +1100817 }
818
Damien Miller68e2e562010-10-07 21:39:55 +1100819 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
820 width = ws.ws_col;
Damien Miller20e1fab2004-02-18 14:30:55 +1100821
Damien Miller68e2e562010-10-07 21:39:55 +1100822 if (!(lflag & LS_SHORT_VIEW)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100823 /* Count entries for sort and find longest filename */
824 for (i = 0; g.gl_pathv[i]; i++)
825 m = MAX(m, strlen(g.gl_pathv[i]));
826
Damien Miller20e1fab2004-02-18 14:30:55 +1100827 columns = width / (m + 2);
828 columns = MAX(columns, 1);
829 colspace = width / columns;
830 }
831
Darren Tucker596dcfa2004-12-11 13:37:22 +1100832 for (i = 0; g.gl_pathv[i] && !interrupted; i++, a = NULL) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100833 fname = path_strip(g.gl_pathv[i], strip_path);
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000834 if (lflag & LS_LONG_VIEW) {
Damien Millera6e121a2010-10-07 21:39:17 +1100835 if (g.gl_statv[i] == NULL) {
836 error("no stat information for %s", fname);
837 continue;
838 }
839 lname = ls_file(fname, g.gl_statv[i], 1,
840 (lflag & LS_SI_UNITS));
Damien Miller20e1fab2004-02-18 14:30:55 +1100841 printf("%s\n", lname);
842 xfree(lname);
843 } else {
844 printf("%-*s", colspace, fname);
845 if (c >= columns) {
846 printf("\n");
847 c = 1;
848 } else
849 c++;
850 }
851 xfree(fname);
852 }
853
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000854 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100855 printf("\n");
856
Darren Tuckercdf547a2004-05-24 10:12:19 +1000857 out:
Damien Miller20e1fab2004-02-18 14:30:55 +1100858 if (g.gl_pathc)
859 globfree(&g);
860
Damien Millera6e121a2010-10-07 21:39:17 +1100861 return 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100862}
863
Damien Millerd671e5a2008-05-19 14:53:33 +1000864static int
865do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
866{
Darren Tucker7b598892008-06-09 22:49:36 +1000867 struct sftp_statvfs st;
Damien Millerd671e5a2008-05-19 14:53:33 +1000868 char s_used[FMT_SCALED_STRSIZE];
869 char s_avail[FMT_SCALED_STRSIZE];
870 char s_root[FMT_SCALED_STRSIZE];
871 char s_total[FMT_SCALED_STRSIZE];
Darren Tuckerb5082e92010-01-08 18:51:47 +1100872 unsigned long long ffree;
Damien Millerd671e5a2008-05-19 14:53:33 +1000873
874 if (do_statvfs(conn, path, &st, 1) == -1)
875 return -1;
876 if (iflag) {
Darren Tuckerb5082e92010-01-08 18:51:47 +1100877 ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
Damien Millerd671e5a2008-05-19 14:53:33 +1000878 printf(" Inodes Used Avail "
879 "(root) %%Capacity\n");
880 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
881 (unsigned long long)st.f_files,
882 (unsigned long long)(st.f_files - st.f_ffree),
883 (unsigned long long)st.f_favail,
Darren Tuckerb5082e92010-01-08 18:51:47 +1100884 (unsigned long long)st.f_ffree, ffree);
Damien Millerd671e5a2008-05-19 14:53:33 +1000885 } else if (hflag) {
886 strlcpy(s_used, "error", sizeof(s_used));
887 strlcpy(s_avail, "error", sizeof(s_avail));
888 strlcpy(s_root, "error", sizeof(s_root));
889 strlcpy(s_total, "error", sizeof(s_total));
890 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
891 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
892 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
893 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
894 printf(" Size Used Avail (root) %%Capacity\n");
895 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
896 s_total, s_used, s_avail, s_root,
897 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
898 st.f_blocks));
899 } else {
900 printf(" Size Used Avail "
901 "(root) %%Capacity\n");
902 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
903 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
904 (unsigned long long)(st.f_frsize *
905 (st.f_blocks - st.f_bfree) / 1024),
906 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
907 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
908 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
909 st.f_blocks));
910 }
911 return 0;
912}
913
Damien Miller1cbc2922007-10-26 14:27:45 +1000914/*
915 * Undo escaping of glob sequences in place. Used to undo extra escaping
916 * applied in makeargv() when the string is destined for a function that
917 * does not glob it.
918 */
919static void
920undo_glob_escape(char *s)
921{
922 size_t i, j;
923
924 for (i = j = 0;;) {
925 if (s[i] == '\0') {
926 s[j] = '\0';
927 return;
928 }
929 if (s[i] != '\\') {
930 s[j++] = s[i++];
931 continue;
932 }
933 /* s[i] == '\\' */
934 ++i;
935 switch (s[i]) {
936 case '?':
937 case '[':
938 case '*':
939 case '\\':
940 s[j++] = s[i++];
941 break;
942 case '\0':
943 s[j++] = '\\';
944 s[j] = '\0';
945 return;
946 default:
947 s[j++] = '\\';
948 s[j++] = s[i++];
949 break;
950 }
951 }
952}
953
954/*
955 * Split a string into an argument vector using sh(1)-style quoting,
956 * comment and escaping rules, but with some tweaks to handle glob(3)
957 * wildcards.
Darren Tucker909d8582010-01-08 19:02:40 +1100958 * The "sloppy" flag allows for recovery from missing terminating quote, for
959 * use in parsing incomplete commandlines during tab autocompletion.
960 *
Damien Miller1cbc2922007-10-26 14:27:45 +1000961 * Returns NULL on error or a NULL-terminated array of arguments.
Darren Tucker909d8582010-01-08 19:02:40 +1100962 *
963 * If "lastquote" is not NULL, the quoting character used for the last
964 * argument is placed in *lastquote ("\0", "'" or "\"").
965 *
966 * If "terminated" is not NULL, *terminated will be set to 1 when the
967 * last argument's quote has been properly terminated or 0 otherwise.
968 * This parameter is only of use if "sloppy" is set.
Damien Miller1cbc2922007-10-26 14:27:45 +1000969 */
970#define MAXARGS 128
971#define MAXARGLEN 8192
972static char **
Darren Tucker909d8582010-01-08 19:02:40 +1100973makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
974 u_int *terminated)
Damien Miller1cbc2922007-10-26 14:27:45 +1000975{
976 int argc, quot;
977 size_t i, j;
978 static char argvs[MAXARGLEN];
979 static char *argv[MAXARGS + 1];
980 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
981
982 *argcp = argc = 0;
983 if (strlen(arg) > sizeof(argvs) - 1) {
984 args_too_longs:
985 error("string too long");
986 return NULL;
987 }
Darren Tucker909d8582010-01-08 19:02:40 +1100988 if (terminated != NULL)
989 *terminated = 1;
990 if (lastquote != NULL)
991 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +1000992 state = MA_START;
993 i = j = 0;
994 for (;;) {
995 if (isspace(arg[i])) {
996 if (state == MA_UNQUOTED) {
997 /* Terminate current argument */
998 argvs[j++] = '\0';
999 argc++;
1000 state = MA_START;
1001 } else if (state != MA_START)
1002 argvs[j++] = arg[i];
1003 } else if (arg[i] == '"' || arg[i] == '\'') {
1004 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1005 if (state == MA_START) {
1006 argv[argc] = argvs + j;
1007 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +11001008 if (lastquote != NULL)
1009 *lastquote = arg[i];
Damien Miller1cbc2922007-10-26 14:27:45 +10001010 } else if (state == MA_UNQUOTED)
1011 state = q;
1012 else if (state == q)
1013 state = MA_UNQUOTED;
1014 else
1015 argvs[j++] = arg[i];
1016 } else if (arg[i] == '\\') {
1017 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1018 quot = state == MA_SQUOTE ? '\'' : '"';
1019 /* Unescape quote we are in */
1020 /* XXX support \n and friends? */
1021 if (arg[i + 1] == quot) {
1022 i++;
1023 argvs[j++] = arg[i];
1024 } else if (arg[i + 1] == '?' ||
1025 arg[i + 1] == '[' || arg[i + 1] == '*') {
1026 /*
1027 * Special case for sftp: append
1028 * double-escaped glob sequence -
1029 * glob will undo one level of
1030 * escaping. NB. string can grow here.
1031 */
1032 if (j >= sizeof(argvs) - 5)
1033 goto args_too_longs;
1034 argvs[j++] = '\\';
1035 argvs[j++] = arg[i++];
1036 argvs[j++] = '\\';
1037 argvs[j++] = arg[i];
1038 } else {
1039 argvs[j++] = arg[i++];
1040 argvs[j++] = arg[i];
1041 }
1042 } else {
1043 if (state == MA_START) {
1044 argv[argc] = argvs + j;
1045 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001046 if (lastquote != NULL)
1047 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001048 }
1049 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1050 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1051 /*
1052 * Special case for sftp: append
1053 * escaped glob sequence -
1054 * glob will undo one level of
1055 * escaping.
1056 */
1057 argvs[j++] = arg[i++];
1058 argvs[j++] = arg[i];
1059 } else {
1060 /* Unescape everything */
1061 /* XXX support \n and friends? */
1062 i++;
1063 argvs[j++] = arg[i];
1064 }
1065 }
1066 } else if (arg[i] == '#') {
1067 if (state == MA_SQUOTE || state == MA_DQUOTE)
1068 argvs[j++] = arg[i];
1069 else
1070 goto string_done;
1071 } else if (arg[i] == '\0') {
1072 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001073 if (sloppy) {
1074 state = MA_UNQUOTED;
1075 if (terminated != NULL)
1076 *terminated = 0;
1077 goto string_done;
1078 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001079 error("Unterminated quoted argument");
1080 return NULL;
1081 }
1082 string_done:
1083 if (state == MA_UNQUOTED) {
1084 argvs[j++] = '\0';
1085 argc++;
1086 }
1087 break;
1088 } else {
1089 if (state == MA_START) {
1090 argv[argc] = argvs + j;
1091 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001092 if (lastquote != NULL)
1093 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001094 }
1095 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1096 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1097 /*
1098 * Special case for sftp: escape quoted
1099 * glob(3) wildcards. NB. string can grow
1100 * here.
1101 */
1102 if (j >= sizeof(argvs) - 3)
1103 goto args_too_longs;
1104 argvs[j++] = '\\';
1105 argvs[j++] = arg[i];
1106 } else
1107 argvs[j++] = arg[i];
1108 }
1109 i++;
1110 }
1111 *argcp = argc;
1112 return argv;
1113}
1114
Damien Miller20e1fab2004-02-18 14:30:55 +11001115static int
Darren Tucker909d8582010-01-08 19:02:40 +11001116parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag,
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001117 int *hflag, int *sflag, unsigned long *n_arg, char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001118{
1119 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001120 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001121 int base = 0;
1122 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001123 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001124
1125 /* Skip leading whitespace */
1126 cp = cp + strspn(cp, WHITESPACE);
1127
Damien Miller20e1fab2004-02-18 14:30:55 +11001128 /* Check for leading '-' (disable error processing) */
1129 *iflag = 0;
1130 if (*cp == '-') {
1131 *iflag = 1;
1132 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001133 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001134 }
1135
Darren Tucker70cc0922010-01-09 22:28:03 +11001136 /* Ignore blank lines and lines which begin with comment '#' char */
1137 if (*cp == '\0' || *cp == '#')
1138 return (0);
1139
Darren Tucker909d8582010-01-08 19:02:40 +11001140 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001141 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001142
Damien Miller1cbc2922007-10-26 14:27:45 +10001143 /* Figure out which command we have */
1144 for (i = 0; cmds[i].c != NULL; i++) {
1145 if (strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001146 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001147 }
1148 cmdnum = cmds[i].n;
1149 cmd = cmds[i].c;
1150
1151 /* Special case */
1152 if (*cp == '!') {
1153 cp++;
1154 cmdnum = I_SHELL;
1155 } else if (cmdnum == -1) {
1156 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001157 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001158 }
1159
1160 /* Get arguments and parse flags */
Darren Tucker1b0dd172009-10-07 08:37:48 +11001161 *lflag = *pflag = *rflag = *hflag = *n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001162 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001163 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001164 switch (cmdnum) {
1165 case I_GET:
1166 case I_PUT:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001167 if ((optidx = parse_getput_flags(cmd, argv, argc,
1168 pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001169 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001170 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001171 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001172 error("You must specify at least one path after a "
1173 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001174 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001175 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001176 *path1 = xstrdup(argv[optidx]);
1177 /* Get second pathname (optional) */
1178 if (argc - optidx > 1) {
1179 *path2 = xstrdup(argv[optidx + 1]);
1180 /* Destination is not globbed */
1181 undo_glob_escape(*path2);
1182 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001183 break;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001184 case I_LINK:
1185 if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1186 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001187 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001188 case I_RENAME:
Damien Miller1cbc2922007-10-26 14:27:45 +10001189 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001190 error("You must specify two paths after a %s "
1191 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001192 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001193 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001194 *path1 = xstrdup(argv[optidx]);
1195 *path2 = xstrdup(argv[optidx + 1]);
1196 /* Paths are not globbed */
1197 undo_glob_escape(*path1);
1198 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001199 break;
1200 case I_RM:
1201 case I_MKDIR:
1202 case I_RMDIR:
1203 case I_CHDIR:
1204 case I_LCHDIR:
1205 case I_LMKDIR:
1206 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001207 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001208 error("You must specify a path after a %s command.",
1209 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001210 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001211 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001212 *path1 = xstrdup(argv[optidx]);
1213 /* Only "rm" globs */
1214 if (cmdnum != I_RM)
1215 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001216 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001217 case I_DF:
1218 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1219 iflag)) == -1)
1220 return -1;
1221 /* Default to current directory if no path specified */
1222 if (argc - optidx < 1)
1223 *path1 = NULL;
1224 else {
1225 *path1 = xstrdup(argv[optidx]);
1226 undo_glob_escape(*path1);
1227 }
1228 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001229 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001230 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001231 return(-1);
1232 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001233 if (argc - optidx > 0)
1234 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001235 break;
1236 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001237 /* Skip ls command and following whitespace */
1238 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001239 case I_SHELL:
1240 /* Uses the rest of the line */
1241 break;
1242 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001243 case I_CHMOD:
1244 base = 8;
1245 case I_CHOWN:
1246 case I_CHGRP:
1247 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001248 if (argc - optidx < 1)
1249 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001250 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001251 l = strtol(argv[optidx], &cp2, base);
1252 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1253 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1254 l < 0) {
1255 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001256 error("You must supply a numeric argument "
1257 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001258 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001259 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001260 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001261 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001262 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001263 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001264 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001265 error("You must specify a path after a %s command.",
1266 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001267 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001268 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001269 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001270 break;
1271 case I_QUIT:
1272 case I_PWD:
1273 case I_LPWD:
1274 case I_HELP:
1275 case I_VERSION:
1276 case I_PROGRESS:
1277 break;
1278 default:
1279 fatal("Command not implemented");
1280 }
1281
1282 *cpp = cp;
1283 return(cmdnum);
1284}
1285
1286static int
1287parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1288 int err_abort)
1289{
1290 char *path1, *path2, *tmp;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001291 int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, sflag = 0;
1292 int cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001293 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001294 Attrib a, *aa;
1295 char path_buf[MAXPATHLEN];
1296 int err = 0;
1297 glob_t g;
1298
1299 path1 = path2 = NULL;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001300 cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag,
1301 &sflag, &n_arg, &path1, &path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001302
1303 if (iflag != 0)
1304 err_abort = 0;
1305
1306 memset(&g, 0, sizeof(g));
1307
1308 /* Perform command */
1309 switch (cmdnum) {
1310 case 0:
1311 /* Blank line */
1312 break;
1313 case -1:
1314 /* Unrecognized command */
1315 err = -1;
1316 break;
1317 case I_GET:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001318 err = process_get(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001319 break;
1320 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001321 err = process_put(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001322 break;
1323 case I_RENAME:
1324 path1 = make_absolute(path1, *pwd);
1325 path2 = make_absolute(path2, *pwd);
1326 err = do_rename(conn, path1, path2);
1327 break;
1328 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001329 sflag = 1;
1330 case I_LINK:
1331 path1 = make_absolute(path1, *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001332 path2 = make_absolute(path2, *pwd);
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001333 err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001334 break;
1335 case I_RM:
1336 path1 = make_absolute(path1, *pwd);
1337 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001338 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001339 printf("Removing %s\n", g.gl_pathv[i]);
1340 err = do_rm(conn, g.gl_pathv[i]);
1341 if (err != 0 && err_abort)
1342 break;
1343 }
1344 break;
1345 case I_MKDIR:
1346 path1 = make_absolute(path1, *pwd);
1347 attrib_clear(&a);
1348 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1349 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001350 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001351 break;
1352 case I_RMDIR:
1353 path1 = make_absolute(path1, *pwd);
1354 err = do_rmdir(conn, path1);
1355 break;
1356 case I_CHDIR:
1357 path1 = make_absolute(path1, *pwd);
1358 if ((tmp = do_realpath(conn, path1)) == NULL) {
1359 err = 1;
1360 break;
1361 }
1362 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1363 xfree(tmp);
1364 err = 1;
1365 break;
1366 }
1367 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1368 error("Can't change directory: Can't check target");
1369 xfree(tmp);
1370 err = 1;
1371 break;
1372 }
1373 if (!S_ISDIR(aa->perm)) {
1374 error("Can't change directory: \"%s\" is not "
1375 "a directory", tmp);
1376 xfree(tmp);
1377 err = 1;
1378 break;
1379 }
1380 xfree(*pwd);
1381 *pwd = tmp;
1382 break;
1383 case I_LS:
1384 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001385 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001386 break;
1387 }
1388
1389 /* Strip pwd off beginning of non-absolute paths */
1390 tmp = NULL;
1391 if (*path1 != '/')
1392 tmp = *pwd;
1393
1394 path1 = make_absolute(path1, *pwd);
1395 err = do_globbed_ls(conn, path1, tmp, lflag);
1396 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001397 case I_DF:
1398 /* Default to current directory if no path specified */
1399 if (path1 == NULL)
1400 path1 = xstrdup(*pwd);
1401 path1 = make_absolute(path1, *pwd);
1402 err = do_df(conn, path1, hflag, iflag);
1403 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001404 case I_LCHDIR:
1405 if (chdir(path1) == -1) {
1406 error("Couldn't change local directory to "
1407 "\"%s\": %s", path1, strerror(errno));
1408 err = 1;
1409 }
1410 break;
1411 case I_LMKDIR:
1412 if (mkdir(path1, 0777) == -1) {
1413 error("Couldn't create local directory "
1414 "\"%s\": %s", path1, strerror(errno));
1415 err = 1;
1416 }
1417 break;
1418 case I_LLS:
1419 local_do_ls(cmd);
1420 break;
1421 case I_SHELL:
1422 local_do_shell(cmd);
1423 break;
1424 case I_LUMASK:
1425 umask(n_arg);
1426 printf("Local umask: %03lo\n", n_arg);
1427 break;
1428 case I_CHMOD:
1429 path1 = make_absolute(path1, *pwd);
1430 attrib_clear(&a);
1431 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1432 a.perm = n_arg;
1433 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001434 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001435 printf("Changing mode on %s\n", g.gl_pathv[i]);
1436 err = do_setstat(conn, g.gl_pathv[i], &a);
1437 if (err != 0 && err_abort)
1438 break;
1439 }
1440 break;
1441 case I_CHOWN:
1442 case I_CHGRP:
1443 path1 = make_absolute(path1, *pwd);
1444 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001445 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001446 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001447 if (err_abort) {
1448 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001449 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001450 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001451 continue;
1452 }
1453 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1454 error("Can't get current ownership of "
1455 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001456 if (err_abort) {
1457 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001458 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001459 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001460 continue;
1461 }
1462 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1463 if (cmdnum == I_CHOWN) {
1464 printf("Changing owner on %s\n", g.gl_pathv[i]);
1465 aa->uid = n_arg;
1466 } else {
1467 printf("Changing group on %s\n", g.gl_pathv[i]);
1468 aa->gid = n_arg;
1469 }
1470 err = do_setstat(conn, g.gl_pathv[i], aa);
1471 if (err != 0 && err_abort)
1472 break;
1473 }
1474 break;
1475 case I_PWD:
1476 printf("Remote working directory: %s\n", *pwd);
1477 break;
1478 case I_LPWD:
1479 if (!getcwd(path_buf, sizeof(path_buf))) {
1480 error("Couldn't get local cwd: %s", strerror(errno));
1481 err = -1;
1482 break;
1483 }
1484 printf("Local working directory: %s\n", path_buf);
1485 break;
1486 case I_QUIT:
1487 /* Processed below */
1488 break;
1489 case I_HELP:
1490 help();
1491 break;
1492 case I_VERSION:
1493 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1494 break;
1495 case I_PROGRESS:
1496 showprogress = !showprogress;
1497 if (showprogress)
1498 printf("Progress meter enabled\n");
1499 else
1500 printf("Progress meter disabled\n");
1501 break;
1502 default:
1503 fatal("%d is not implemented", cmdnum);
1504 }
1505
1506 if (g.gl_pathc)
1507 globfree(&g);
1508 if (path1)
1509 xfree(path1);
1510 if (path2)
1511 xfree(path2);
1512
1513 /* If an unignored error occurs in batch mode we should abort. */
1514 if (err_abort && err != 0)
1515 return (-1);
1516 else if (cmdnum == I_QUIT)
1517 return (1);
1518
1519 return (0);
1520}
1521
Darren Tucker2d963d82004-11-07 20:04:10 +11001522#ifdef USE_LIBEDIT
1523static char *
1524prompt(EditLine *el)
1525{
1526 return ("sftp> ");
1527}
Darren Tucker2d963d82004-11-07 20:04:10 +11001528
Darren Tucker909d8582010-01-08 19:02:40 +11001529/* Display entries in 'list' after skipping the first 'len' chars */
1530static void
1531complete_display(char **list, u_int len)
1532{
1533 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1534 struct winsize ws;
1535 char *tmp;
1536
1537 /* Count entries for sort and find longest */
1538 for (y = 0; list[y]; y++)
1539 m = MAX(m, strlen(list[y]));
1540
1541 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1542 width = ws.ws_col;
1543
1544 m = m > len ? m - len : 0;
1545 columns = width / (m + 2);
1546 columns = MAX(columns, 1);
1547 colspace = width / columns;
1548 colspace = MIN(colspace, width);
1549
1550 printf("\n");
1551 m = 1;
1552 for (y = 0; list[y]; y++) {
1553 llen = strlen(list[y]);
1554 tmp = llen > len ? list[y] + len : "";
1555 printf("%-*s", colspace, tmp);
1556 if (m >= columns) {
1557 printf("\n");
1558 m = 1;
1559 } else
1560 m++;
1561 }
1562 printf("\n");
1563}
1564
1565/*
1566 * Given a "list" of words that begin with a common prefix of "word",
1567 * attempt to find an autocompletion to extends "word" by the next
1568 * characters common to all entries in "list".
1569 */
1570static char *
1571complete_ambiguous(const char *word, char **list, size_t count)
1572{
1573 if (word == NULL)
1574 return NULL;
1575
1576 if (count > 0) {
1577 u_int y, matchlen = strlen(list[0]);
1578
1579 /* Find length of common stem */
1580 for (y = 1; list[y]; y++) {
1581 u_int x;
1582
1583 for (x = 0; x < matchlen; x++)
1584 if (list[0][x] != list[y][x])
1585 break;
1586
1587 matchlen = x;
1588 }
1589
1590 if (matchlen > strlen(word)) {
1591 char *tmp = xstrdup(list[0]);
1592
Darren Tucker340d1682010-01-09 08:54:31 +11001593 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001594 return tmp;
1595 }
1596 }
1597
1598 return xstrdup(word);
1599}
1600
1601/* Autocomplete a sftp command */
1602static int
1603complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1604 int terminated)
1605{
1606 u_int y, count = 0, cmdlen, tmplen;
1607 char *tmp, **list, argterm[3];
1608 const LineInfo *lf;
1609
1610 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1611
1612 /* No command specified: display all available commands */
1613 if (cmd == NULL) {
1614 for (y = 0; cmds[y].c; y++)
1615 list[count++] = xstrdup(cmds[y].c);
1616
1617 list[count] = NULL;
1618 complete_display(list, 0);
1619
1620 for (y = 0; list[y] != NULL; y++)
1621 xfree(list[y]);
1622 xfree(list);
1623 return count;
1624 }
1625
1626 /* Prepare subset of commands that start with "cmd" */
1627 cmdlen = strlen(cmd);
1628 for (y = 0; cmds[y].c; y++) {
1629 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
1630 list[count++] = xstrdup(cmds[y].c);
1631 }
1632 list[count] = NULL;
1633
Damien Miller47d81152011-11-25 13:53:48 +11001634 if (count == 0) {
1635 xfree(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001636 return 0;
Damien Miller47d81152011-11-25 13:53:48 +11001637 }
Darren Tucker909d8582010-01-08 19:02:40 +11001638
1639 /* Complete ambigious command */
1640 tmp = complete_ambiguous(cmd, list, count);
1641 if (count > 1)
1642 complete_display(list, 0);
1643
1644 for (y = 0; list[y]; y++)
1645 xfree(list[y]);
1646 xfree(list);
1647
1648 if (tmp != NULL) {
1649 tmplen = strlen(tmp);
1650 cmdlen = strlen(cmd);
1651 /* If cmd may be extended then do so */
1652 if (tmplen > cmdlen)
1653 if (el_insertstr(el, tmp + cmdlen) == -1)
1654 fatal("el_insertstr failed.");
1655 lf = el_line(el);
1656 /* Terminate argument cleanly */
1657 if (count == 1) {
1658 y = 0;
1659 if (!terminated)
1660 argterm[y++] = quote;
1661 if (lastarg || *(lf->cursor) != ' ')
1662 argterm[y++] = ' ';
1663 argterm[y] = '\0';
1664 if (y > 0 && el_insertstr(el, argterm) == -1)
1665 fatal("el_insertstr failed.");
1666 }
1667 xfree(tmp);
1668 }
1669
1670 return count;
1671}
1672
1673/*
1674 * Determine whether a particular sftp command's arguments (if any)
1675 * represent local or remote files.
1676 */
1677static int
1678complete_is_remote(char *cmd) {
1679 int i;
1680
1681 if (cmd == NULL)
1682 return -1;
1683
1684 for (i = 0; cmds[i].c; i++) {
1685 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
1686 return cmds[i].t;
1687 }
1688
1689 return -1;
1690}
1691
1692/* Autocomplete a filename "file" */
1693static int
1694complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1695 char *file, int remote, int lastarg, char quote, int terminated)
1696{
1697 glob_t g;
1698 char *tmp, *tmp2, ins[3];
1699 u_int i, hadglob, pwdlen, len, tmplen, filelen;
1700 const LineInfo *lf;
1701
1702 /* Glob from "file" location */
1703 if (file == NULL)
1704 tmp = xstrdup("*");
1705 else
1706 xasprintf(&tmp, "%s*", file);
1707
1708 memset(&g, 0, sizeof(g));
1709 if (remote != LOCAL) {
1710 tmp = make_absolute(tmp, remote_path);
1711 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1712 } else
1713 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1714
1715 /* Determine length of pwd so we can trim completion display */
1716 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1717 /* Terminate counting on first unescaped glob metacharacter */
1718 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1719 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1720 hadglob = 1;
1721 break;
1722 }
1723 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1724 tmplen++;
1725 if (tmp[tmplen] == '/')
1726 pwdlen = tmplen + 1; /* track last seen '/' */
1727 }
1728 xfree(tmp);
1729
1730 if (g.gl_matchc == 0)
1731 goto out;
1732
1733 if (g.gl_matchc > 1)
1734 complete_display(g.gl_pathv, pwdlen);
1735
1736 tmp = NULL;
1737 /* Don't try to extend globs */
1738 if (file == NULL || hadglob)
1739 goto out;
1740
1741 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
1742 tmp = path_strip(tmp2, remote_path);
1743 xfree(tmp2);
1744
1745 if (tmp == NULL)
1746 goto out;
1747
1748 tmplen = strlen(tmp);
1749 filelen = strlen(file);
1750
1751 if (tmplen > filelen) {
1752 tmp2 = tmp + filelen;
1753 len = strlen(tmp2);
1754 /* quote argument on way out */
1755 for (i = 0; i < len; i++) {
1756 ins[0] = '\\';
1757 ins[1] = tmp2[i];
1758 ins[2] = '\0';
1759 switch (tmp2[i]) {
1760 case '\'':
1761 case '"':
1762 case '\\':
1763 case '\t':
Darren Tuckerd78739a2010-10-24 10:56:32 +11001764 case '[':
Darren Tucker909d8582010-01-08 19:02:40 +11001765 case ' ':
1766 if (quote == '\0' || tmp2[i] == quote) {
1767 if (el_insertstr(el, ins) == -1)
1768 fatal("el_insertstr "
1769 "failed.");
1770 break;
1771 }
1772 /* FALLTHROUGH */
1773 default:
1774 if (el_insertstr(el, ins + 1) == -1)
1775 fatal("el_insertstr failed.");
1776 break;
1777 }
1778 }
1779 }
1780
1781 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11001782 if (g.gl_matchc == 1) {
1783 i = 0;
1784 if (!terminated)
1785 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11001786 if (*(lf->cursor - 1) != '/' &&
1787 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11001788 ins[i++] = ' ';
1789 ins[i] = '\0';
1790 if (i > 0 && el_insertstr(el, ins) == -1)
1791 fatal("el_insertstr failed.");
1792 }
1793 xfree(tmp);
1794
1795 out:
1796 globfree(&g);
1797 return g.gl_matchc;
1798}
1799
1800/* tab-completion hook function, called via libedit */
1801static unsigned char
1802complete(EditLine *el, int ch)
1803{
1804 char **argv, *line, quote;
1805 u_int argc, carg, cursor, len, terminated, ret = CC_ERROR;
1806 const LineInfo *lf;
1807 struct complete_ctx *complete_ctx;
1808
1809 lf = el_line(el);
1810 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1811 fatal("%s: el_get failed", __func__);
1812
1813 /* Figure out which argument the cursor points to */
1814 cursor = lf->cursor - lf->buffer;
1815 line = (char *)xmalloc(cursor + 1);
1816 memcpy(line, lf->buffer, cursor);
1817 line[cursor] = '\0';
1818 argv = makeargv(line, &carg, 1, &quote, &terminated);
1819 xfree(line);
1820
1821 /* Get all the arguments on the line */
1822 len = lf->lastchar - lf->buffer;
1823 line = (char *)xmalloc(len + 1);
1824 memcpy(line, lf->buffer, len);
1825 line[len] = '\0';
1826 argv = makeargv(line, &argc, 1, NULL, NULL);
1827
1828 /* Ensure cursor is at EOL or a argument boundary */
1829 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1830 line[cursor] != '\n') {
1831 xfree(line);
1832 return ret;
1833 }
1834
1835 if (carg == 0) {
1836 /* Show all available commands */
1837 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1838 ret = CC_REDISPLAY;
1839 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1840 /* Handle the command parsing */
1841 if (complete_cmd_parse(el, argv[0], argc == carg,
1842 quote, terminated) != 0)
1843 ret = CC_REDISPLAY;
1844 } else if (carg >= 1) {
1845 /* Handle file parsing */
1846 int remote = complete_is_remote(argv[0]);
1847 char *filematch = NULL;
1848
1849 if (carg > 1 && line[cursor-1] != ' ')
1850 filematch = argv[carg - 1];
1851
1852 if (remote != 0 &&
1853 complete_match(el, complete_ctx->conn,
1854 *complete_ctx->remote_pathp, filematch,
1855 remote, carg == argc, quote, terminated) != 0)
1856 ret = CC_REDISPLAY;
1857 }
1858
1859 xfree(line);
1860 return ret;
1861}
Darren Tuckere67f7db2010-01-08 19:50:02 +11001862#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11001863
Damien Miller20e1fab2004-02-18 14:30:55 +11001864int
Darren Tucker21063192010-01-08 17:10:36 +11001865interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001866{
Darren Tucker909d8582010-01-08 19:02:40 +11001867 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11001868 char *dir = NULL;
1869 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10001870 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11001871 EditLine *el = NULL;
1872#ifdef USE_LIBEDIT
1873 History *hl = NULL;
1874 HistEvent hev;
1875 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11001876 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11001877
1878 if (!batchmode && isatty(STDIN_FILENO)) {
1879 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1880 fatal("Couldn't initialise editline");
1881 if ((hl = history_init()) == NULL)
1882 fatal("Couldn't initialise editline history");
1883 history(hl, &hev, H_SETSIZE, 100);
1884 el_set(el, EL_HIST, history, hl);
1885
1886 el_set(el, EL_PROMPT, prompt);
1887 el_set(el, EL_EDITOR, "emacs");
1888 el_set(el, EL_TERMINAL, NULL);
1889 el_set(el, EL_SIGNAL, 1);
1890 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11001891
1892 /* Tab Completion */
1893 el_set(el, EL_ADDFN, "ftp-complete",
Darren Tuckerd78739a2010-10-24 10:56:32 +11001894 "Context sensitive argument completion", complete);
Darren Tucker909d8582010-01-08 19:02:40 +11001895 complete_ctx.conn = conn;
1896 complete_ctx.remote_pathp = &remote_path;
1897 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
1898 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11001899 }
1900#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001901
Darren Tucker909d8582010-01-08 19:02:40 +11001902 remote_path = do_realpath(conn, ".");
1903 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11001904 fatal("Need cwd");
1905
1906 if (file1 != NULL) {
1907 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11001908 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11001909
1910 if (remote_is_dir(conn, dir) && file2 == NULL) {
1911 printf("Changing to: %s\n", dir);
1912 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001913 if (parse_dispatch_command(conn, cmd,
1914 &remote_path, 1) != 0) {
Darren Tuckercd516ef2004-12-06 22:43:43 +11001915 xfree(dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001916 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001917 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001918 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11001919 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001920 } else {
1921 if (file2 == NULL)
1922 snprintf(cmd, sizeof cmd, "get %s", dir);
1923 else
1924 snprintf(cmd, sizeof cmd, "get %s %s", dir,
1925 file2);
1926
Darren Tucker909d8582010-01-08 19:02:40 +11001927 err = parse_dispatch_command(conn, cmd,
1928 &remote_path, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001929 xfree(dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001930 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001931 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001932 return (err);
1933 }
1934 xfree(dir);
1935 }
1936
Darren Tucker93e7e8f2005-08-23 08:06:55 +10001937#if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF)
Damien Miller20e1fab2004-02-18 14:30:55 +11001938 setvbuf(stdout, NULL, _IOLBF, 0);
1939 setvbuf(infile, NULL, _IOLBF, 0);
1940#else
Damien Miller37294fb2005-07-17 17:18:49 +10001941 setlinebuf(stdout);
1942 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11001943#endif
1944
Damien Miller0e2c1022005-08-12 22:16:22 +10001945 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11001946 err = 0;
1947 for (;;) {
1948 char *cp;
1949
Darren Tuckercdf547a2004-05-24 10:12:19 +10001950 signal(SIGINT, SIG_IGN);
1951
Darren Tucker2d963d82004-11-07 20:04:10 +11001952 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001953 if (interactive)
1954 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11001955 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001956 if (interactive)
1957 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11001958 break;
1959 }
Damien Miller0e2c1022005-08-12 22:16:22 +10001960 if (!interactive) { /* Echo command */
1961 printf("sftp> %s", cmd);
1962 if (strlen(cmd) > 0 &&
1963 cmd[strlen(cmd) - 1] != '\n')
1964 printf("\n");
1965 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001966 } else {
1967#ifdef USE_LIBEDIT
1968 const char *line;
1969 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001970
Darren Tucker909d8582010-01-08 19:02:40 +11001971 if ((line = el_gets(el, &count)) == NULL ||
1972 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001973 printf("\n");
1974 break;
1975 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001976 history(hl, &hev, H_ENTER, line);
1977 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
1978 fprintf(stderr, "Error: input line too long\n");
1979 continue;
1980 }
1981#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001982 }
1983
Damien Miller20e1fab2004-02-18 14:30:55 +11001984 cp = strrchr(cmd, '\n');
1985 if (cp)
1986 *cp = '\0';
1987
Darren Tuckercdf547a2004-05-24 10:12:19 +10001988 /* Handle user interrupts gracefully during commands */
1989 interrupted = 0;
1990 signal(SIGINT, cmd_interrupt);
1991
Darren Tucker909d8582010-01-08 19:02:40 +11001992 err = parse_dispatch_command(conn, cmd, &remote_path,
1993 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11001994 if (err != 0)
1995 break;
1996 }
Darren Tucker909d8582010-01-08 19:02:40 +11001997 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001998 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001999
Tim Rice027e8b12005-08-15 14:52:50 -07002000#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10002001 if (el != NULL)
2002 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07002003#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10002004
Damien Miller20e1fab2004-02-18 14:30:55 +11002005 /* err == 1 signifies normal "quit" exit */
2006 return (err >= 0 ? 0 : -1);
2007}
Damien Miller62d57f62003-01-10 21:43:24 +11002008
Ben Lindstrombba81212001-06-25 05:01:22 +00002009static void
Damien Millercc685c12003-06-04 22:51:38 +10002010connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11002011{
2012 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002013
Damien Miller33804262001-02-04 23:20:18 +11002014#ifdef USE_PIPES
2015 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002016
Damien Miller33804262001-02-04 23:20:18 +11002017 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2018 fatal("pipe: %s", strerror(errno));
2019 *in = pin[0];
2020 *out = pout[1];
2021 c_in = pout[0];
2022 c_out = pin[1];
2023#else /* USE_PIPES */
2024 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002025
Damien Miller33804262001-02-04 23:20:18 +11002026 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2027 fatal("socketpair: %s", strerror(errno));
2028 *in = *out = inout[0];
2029 c_in = c_out = inout[1];
2030#endif /* USE_PIPES */
2031
Damien Millercc685c12003-06-04 22:51:38 +10002032 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002033 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002034 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002035 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2036 (dup2(c_out, STDOUT_FILENO) == -1)) {
2037 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002038 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002039 }
2040 close(*in);
2041 close(*out);
2042 close(c_in);
2043 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002044
2045 /*
2046 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002047 * ignore SIGINT if we want to gracefully abort commands,
2048 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002049 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2050 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002051 */
2052 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002053 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002054 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002055 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002056 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002057 }
2058
Damien Millercc685c12003-06-04 22:51:38 +10002059 signal(SIGTERM, killchild);
2060 signal(SIGINT, killchild);
2061 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11002062 close(c_in);
2063 close(c_out);
2064}
2065
Ben Lindstrombba81212001-06-25 05:01:22 +00002066static void
Damien Miller33804262001-02-04 23:20:18 +11002067usage(void)
2068{
Damien Miller025e01c2002-02-08 22:06:29 +11002069 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002070
Ben Lindstrom1e243242001-09-18 05:38:44 +00002071 fprintf(stderr,
Darren Tucker1b0dd172009-10-07 08:37:48 +11002072 "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002073 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002074 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002075 " [-o ssh_option] [-P port] [-R num_requests] "
2076 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002077 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002078 " %s [user@]host[:file ...]\n"
2079 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002080 " %s -b batchfile [user@]host\n",
2081 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002082 exit(1);
2083}
2084
Kevin Stevesef4eea92001-02-05 12:42:17 +00002085int
Damien Miller33804262001-02-04 23:20:18 +11002086main(int argc, char **argv)
2087{
Damien Miller956f3fb2003-01-10 21:40:00 +11002088 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002089 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002090 int debug_level = 0, sshver = 2;
2091 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002092 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002093 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002094 LogLevel ll = SYSLOG_LEVEL_INFO;
2095 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002096 extern int optind;
2097 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002098 struct sftp_conn *conn;
2099 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2100 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002101 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002102
Darren Tuckerce321d82005-10-03 18:11:24 +10002103 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2104 sanitise_stdfd();
2105
Damien Miller59d3d5b2003-08-22 09:34:41 +10002106 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002107 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002108 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002109 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002110 addargs(&args, "-oForwardX11 no");
2111 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002112 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002113 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002114
Ben Lindstrom387c4722001-05-08 20:27:25 +00002115 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002116 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002117
Darren Tucker282b4022009-10-07 08:23:06 +11002118 while ((ch = getopt(argc, argv,
Damien Miller65e42f82010-09-24 22:15:11 +10002119 "1246hpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002120 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002121 /* Passed through to ssh(1) */
2122 case '4':
2123 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002124 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002125 addargs(&args, "-%c", ch);
2126 break;
2127 /* Passed through to ssh(1) with argument */
2128 case 'F':
2129 case 'c':
2130 case 'i':
2131 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002132 addargs(&args, "-%c", ch);
2133 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002134 break;
2135 case 'q':
2136 showprogress = 0;
2137 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002138 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002139 case 'P':
2140 addargs(&args, "-oPort %s", optarg);
2141 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002142 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002143 if (debug_level < 3) {
2144 addargs(&args, "-v");
2145 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2146 }
2147 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002148 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002149 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002150 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002151 if (sftp_server == NULL)
2152 sftp_server = _PATH_SFTP_SERVER;
2153 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002154 case '2':
2155 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002156 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002157 case 'B':
2158 copy_buffer_len = strtol(optarg, &cp, 10);
2159 if (copy_buffer_len == 0 || *cp != '\0')
2160 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002161 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002162 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002163 if (batchmode)
2164 fatal("Batch file already specified.");
2165
2166 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002167 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002168 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002169 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002170 showprogress = 0;
Damien Miller44f75c12004-01-21 10:58:47 +11002171 batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002172 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002173 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002174 case 'p':
2175 global_pflag = 1;
2176 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002177 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002178 sftp_direct = optarg;
2179 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002180 case 'l':
2181 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2182 &errstr);
2183 if (errstr != NULL)
2184 usage();
2185 limit_kbps *= 1024; /* kbps */
2186 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002187 case 'r':
2188 global_rflag = 1;
2189 break;
Damien Miller16a13332002-02-13 14:03:56 +11002190 case 'R':
2191 num_requests = strtol(optarg, &cp, 10);
2192 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002193 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002194 optarg);
2195 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002196 case 's':
2197 sftp_server = optarg;
2198 break;
2199 case 'S':
2200 ssh_program = optarg;
2201 replacearg(&args, 0, "%s", ssh_program);
2202 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002203 case 'h':
2204 default:
Damien Miller33804262001-02-04 23:20:18 +11002205 usage();
2206 }
2207 }
2208
Damien Millerc0f27d82004-03-08 23:12:19 +11002209 if (!isatty(STDERR_FILENO))
2210 showprogress = 0;
2211
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002212 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2213
Damien Millerd14ee1e2002-02-05 12:27:31 +11002214 if (sftp_direct == NULL) {
2215 if (optind == argc || argc > (optind + 2))
2216 usage();
Damien Miller33804262001-02-04 23:20:18 +11002217
Damien Millerd14ee1e2002-02-05 12:27:31 +11002218 userhost = xstrdup(argv[optind]);
2219 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002220
Ben Lindstromc276c122002-12-23 02:14:51 +00002221 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002222 host = userhost;
2223 else {
2224 *host++ = '\0';
2225 if (!userhost[0]) {
2226 fprintf(stderr, "Missing username\n");
2227 usage();
2228 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002229 addargs(&args, "-l");
2230 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002231 }
2232
Damien Millerec692032004-01-27 21:22:00 +11002233 if ((cp = colon(host)) != NULL) {
2234 *cp++ = '\0';
2235 file1 = cp;
2236 }
2237
Damien Millerd14ee1e2002-02-05 12:27:31 +11002238 host = cleanhostname(host);
2239 if (!*host) {
2240 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002241 usage();
2242 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002243
Damien Millerd14ee1e2002-02-05 12:27:31 +11002244 addargs(&args, "-oProtocol %d", sshver);
2245
2246 /* no subsystem if the server-spec contains a '/' */
2247 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2248 addargs(&args, "-s");
2249
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002250 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002251 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002252 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002253 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002254
Damien Millercc685c12003-06-04 22:51:38 +10002255 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002256 } else {
2257 args.list = NULL;
2258 addargs(&args, "sftp-server");
2259
Damien Millercc685c12003-06-04 22:51:38 +10002260 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002261 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002262 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002263
Damien Miller65e42f82010-09-24 22:15:11 +10002264 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002265 if (conn == NULL)
2266 fatal("Couldn't initialise connection to server");
2267
2268 if (!batchmode) {
2269 if (sftp_direct == NULL)
2270 fprintf(stderr, "Connected to %s.\n", host);
2271 else
2272 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2273 }
2274
2275 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002276
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002277#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002278 shutdown(in, SHUT_RDWR);
2279 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002280#endif
2281
Damien Miller33804262001-02-04 23:20:18 +11002282 close(in);
2283 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002284 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002285 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002286
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002287 while (waitpid(sshpid, NULL, 0) == -1)
2288 if (errno != EINTR)
2289 fatal("Couldn't wait for ssh process: %s",
2290 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002291
Damien Miller956f3fb2003-01-10 21:40:00 +11002292 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002293}