blob: 5c03f89d799a408ff5100abc1b43f363af8b38d0 [file] [log] [blame]
Damien Millerd7be70d2011-09-22 21:43:06 +10001/* $OpenBSD: sftp.c,v 1.133 2011/09/22 06:29:03 djm Exp $ */
Damien Miller33804262001-02-04 23:20:18 +11002/*
Damien Miller4e60ed72004-02-17 17:07:59 +11003 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
Damien Miller33804262001-02-04 23:20:18 +11004 *
Damien Miller4e60ed72004-02-17 17:07:59 +11005 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
Damien Miller33804262001-02-04 23:20:18 +11008 *
Damien Miller4e60ed72004-02-17 17:07:59 +11009 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Damien Miller33804262001-02-04 23:20:18 +110016 */
17
18#include "includes.h"
19
Damien Miller9cf6d072006-03-15 11:29:24 +110020#include <sys/types.h>
Damien Millerd7834352006-08-05 12:39:39 +100021#include <sys/ioctl.h>
Damien Millerf17883e2006-03-15 11:45:54 +110022#ifdef HAVE_SYS_STAT_H
23# include <sys/stat.h>
24#endif
Damien Miller8dbffe72006-08-05 11:02:17 +100025#include <sys/param.h>
Damien Millere3b60b52006-07-10 21:08:03 +100026#include <sys/socket.h>
Damien Miller9cf6d072006-03-15 11:29:24 +110027#include <sys/wait.h>
Darren Tucker5b2e2ba2008-06-08 09:25:28 +100028#ifdef HAVE_SYS_STATVFS_H
Damien Millerd671e5a2008-05-19 14:53:33 +100029#include <sys/statvfs.h>
Darren Tucker5b2e2ba2008-06-08 09:25:28 +100030#endif
Darren Tucker2d963d82004-11-07 20:04:10 +110031
Damien Miller1cbc2922007-10-26 14:27:45 +100032#include <ctype.h>
Darren Tucker39972492006-07-12 22:22:46 +100033#include <errno.h>
34
Damien Miller03e20032006-03-15 11:16:59 +110035#ifdef HAVE_PATHS_H
Damien Millera9263d02006-03-15 11:18:26 +110036# include <paths.h>
Damien Miller03e20032006-03-15 11:16:59 +110037#endif
Darren Tucker1b0dd172009-10-07 08:37:48 +110038#ifdef HAVE_LIBGEN_H
39#include <libgen.h>
40#endif
Darren 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
1634 if (count == 0)
1635 return 0;
1636
1637 /* Complete ambigious command */
1638 tmp = complete_ambiguous(cmd, list, count);
1639 if (count > 1)
1640 complete_display(list, 0);
1641
1642 for (y = 0; list[y]; y++)
1643 xfree(list[y]);
1644 xfree(list);
1645
1646 if (tmp != NULL) {
1647 tmplen = strlen(tmp);
1648 cmdlen = strlen(cmd);
1649 /* If cmd may be extended then do so */
1650 if (tmplen > cmdlen)
1651 if (el_insertstr(el, tmp + cmdlen) == -1)
1652 fatal("el_insertstr failed.");
1653 lf = el_line(el);
1654 /* Terminate argument cleanly */
1655 if (count == 1) {
1656 y = 0;
1657 if (!terminated)
1658 argterm[y++] = quote;
1659 if (lastarg || *(lf->cursor) != ' ')
1660 argterm[y++] = ' ';
1661 argterm[y] = '\0';
1662 if (y > 0 && el_insertstr(el, argterm) == -1)
1663 fatal("el_insertstr failed.");
1664 }
1665 xfree(tmp);
1666 }
1667
1668 return count;
1669}
1670
1671/*
1672 * Determine whether a particular sftp command's arguments (if any)
1673 * represent local or remote files.
1674 */
1675static int
1676complete_is_remote(char *cmd) {
1677 int i;
1678
1679 if (cmd == NULL)
1680 return -1;
1681
1682 for (i = 0; cmds[i].c; i++) {
1683 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
1684 return cmds[i].t;
1685 }
1686
1687 return -1;
1688}
1689
1690/* Autocomplete a filename "file" */
1691static int
1692complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1693 char *file, int remote, int lastarg, char quote, int terminated)
1694{
1695 glob_t g;
1696 char *tmp, *tmp2, ins[3];
1697 u_int i, hadglob, pwdlen, len, tmplen, filelen;
1698 const LineInfo *lf;
1699
1700 /* Glob from "file" location */
1701 if (file == NULL)
1702 tmp = xstrdup("*");
1703 else
1704 xasprintf(&tmp, "%s*", file);
1705
1706 memset(&g, 0, sizeof(g));
1707 if (remote != LOCAL) {
1708 tmp = make_absolute(tmp, remote_path);
1709 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1710 } else
1711 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1712
1713 /* Determine length of pwd so we can trim completion display */
1714 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1715 /* Terminate counting on first unescaped glob metacharacter */
1716 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1717 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1718 hadglob = 1;
1719 break;
1720 }
1721 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1722 tmplen++;
1723 if (tmp[tmplen] == '/')
1724 pwdlen = tmplen + 1; /* track last seen '/' */
1725 }
1726 xfree(tmp);
1727
1728 if (g.gl_matchc == 0)
1729 goto out;
1730
1731 if (g.gl_matchc > 1)
1732 complete_display(g.gl_pathv, pwdlen);
1733
1734 tmp = NULL;
1735 /* Don't try to extend globs */
1736 if (file == NULL || hadglob)
1737 goto out;
1738
1739 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
1740 tmp = path_strip(tmp2, remote_path);
1741 xfree(tmp2);
1742
1743 if (tmp == NULL)
1744 goto out;
1745
1746 tmplen = strlen(tmp);
1747 filelen = strlen(file);
1748
1749 if (tmplen > filelen) {
1750 tmp2 = tmp + filelen;
1751 len = strlen(tmp2);
1752 /* quote argument on way out */
1753 for (i = 0; i < len; i++) {
1754 ins[0] = '\\';
1755 ins[1] = tmp2[i];
1756 ins[2] = '\0';
1757 switch (tmp2[i]) {
1758 case '\'':
1759 case '"':
1760 case '\\':
1761 case '\t':
Darren Tuckerd78739a2010-10-24 10:56:32 +11001762 case '[':
Darren Tucker909d8582010-01-08 19:02:40 +11001763 case ' ':
1764 if (quote == '\0' || tmp2[i] == quote) {
1765 if (el_insertstr(el, ins) == -1)
1766 fatal("el_insertstr "
1767 "failed.");
1768 break;
1769 }
1770 /* FALLTHROUGH */
1771 default:
1772 if (el_insertstr(el, ins + 1) == -1)
1773 fatal("el_insertstr failed.");
1774 break;
1775 }
1776 }
1777 }
1778
1779 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11001780 if (g.gl_matchc == 1) {
1781 i = 0;
1782 if (!terminated)
1783 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11001784 if (*(lf->cursor - 1) != '/' &&
1785 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11001786 ins[i++] = ' ';
1787 ins[i] = '\0';
1788 if (i > 0 && el_insertstr(el, ins) == -1)
1789 fatal("el_insertstr failed.");
1790 }
1791 xfree(tmp);
1792
1793 out:
1794 globfree(&g);
1795 return g.gl_matchc;
1796}
1797
1798/* tab-completion hook function, called via libedit */
1799static unsigned char
1800complete(EditLine *el, int ch)
1801{
1802 char **argv, *line, quote;
1803 u_int argc, carg, cursor, len, terminated, ret = CC_ERROR;
1804 const LineInfo *lf;
1805 struct complete_ctx *complete_ctx;
1806
1807 lf = el_line(el);
1808 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1809 fatal("%s: el_get failed", __func__);
1810
1811 /* Figure out which argument the cursor points to */
1812 cursor = lf->cursor - lf->buffer;
1813 line = (char *)xmalloc(cursor + 1);
1814 memcpy(line, lf->buffer, cursor);
1815 line[cursor] = '\0';
1816 argv = makeargv(line, &carg, 1, &quote, &terminated);
1817 xfree(line);
1818
1819 /* Get all the arguments on the line */
1820 len = lf->lastchar - lf->buffer;
1821 line = (char *)xmalloc(len + 1);
1822 memcpy(line, lf->buffer, len);
1823 line[len] = '\0';
1824 argv = makeargv(line, &argc, 1, NULL, NULL);
1825
1826 /* Ensure cursor is at EOL or a argument boundary */
1827 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1828 line[cursor] != '\n') {
1829 xfree(line);
1830 return ret;
1831 }
1832
1833 if (carg == 0) {
1834 /* Show all available commands */
1835 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1836 ret = CC_REDISPLAY;
1837 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1838 /* Handle the command parsing */
1839 if (complete_cmd_parse(el, argv[0], argc == carg,
1840 quote, terminated) != 0)
1841 ret = CC_REDISPLAY;
1842 } else if (carg >= 1) {
1843 /* Handle file parsing */
1844 int remote = complete_is_remote(argv[0]);
1845 char *filematch = NULL;
1846
1847 if (carg > 1 && line[cursor-1] != ' ')
1848 filematch = argv[carg - 1];
1849
1850 if (remote != 0 &&
1851 complete_match(el, complete_ctx->conn,
1852 *complete_ctx->remote_pathp, filematch,
1853 remote, carg == argc, quote, terminated) != 0)
1854 ret = CC_REDISPLAY;
1855 }
1856
1857 xfree(line);
1858 return ret;
1859}
Darren Tuckere67f7db2010-01-08 19:50:02 +11001860#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11001861
Damien Miller20e1fab2004-02-18 14:30:55 +11001862int
Darren Tucker21063192010-01-08 17:10:36 +11001863interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001864{
Darren Tucker909d8582010-01-08 19:02:40 +11001865 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11001866 char *dir = NULL;
1867 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10001868 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11001869 EditLine *el = NULL;
1870#ifdef USE_LIBEDIT
1871 History *hl = NULL;
1872 HistEvent hev;
1873 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11001874 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11001875
1876 if (!batchmode && isatty(STDIN_FILENO)) {
1877 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1878 fatal("Couldn't initialise editline");
1879 if ((hl = history_init()) == NULL)
1880 fatal("Couldn't initialise editline history");
1881 history(hl, &hev, H_SETSIZE, 100);
1882 el_set(el, EL_HIST, history, hl);
1883
1884 el_set(el, EL_PROMPT, prompt);
1885 el_set(el, EL_EDITOR, "emacs");
1886 el_set(el, EL_TERMINAL, NULL);
1887 el_set(el, EL_SIGNAL, 1);
1888 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11001889
1890 /* Tab Completion */
1891 el_set(el, EL_ADDFN, "ftp-complete",
Darren Tuckerd78739a2010-10-24 10:56:32 +11001892 "Context sensitive argument completion", complete);
Darren Tucker909d8582010-01-08 19:02:40 +11001893 complete_ctx.conn = conn;
1894 complete_ctx.remote_pathp = &remote_path;
1895 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
1896 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11001897 }
1898#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001899
Darren Tucker909d8582010-01-08 19:02:40 +11001900 remote_path = do_realpath(conn, ".");
1901 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11001902 fatal("Need cwd");
1903
1904 if (file1 != NULL) {
1905 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11001906 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11001907
1908 if (remote_is_dir(conn, dir) && file2 == NULL) {
1909 printf("Changing to: %s\n", dir);
1910 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001911 if (parse_dispatch_command(conn, cmd,
1912 &remote_path, 1) != 0) {
Darren Tuckercd516ef2004-12-06 22:43:43 +11001913 xfree(dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001914 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001915 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001916 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11001917 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001918 } else {
1919 if (file2 == NULL)
1920 snprintf(cmd, sizeof cmd, "get %s", dir);
1921 else
1922 snprintf(cmd, sizeof cmd, "get %s %s", dir,
1923 file2);
1924
Darren Tucker909d8582010-01-08 19:02:40 +11001925 err = parse_dispatch_command(conn, cmd,
1926 &remote_path, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001927 xfree(dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001928 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001929 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001930 return (err);
1931 }
1932 xfree(dir);
1933 }
1934
Darren Tucker93e7e8f2005-08-23 08:06:55 +10001935#if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF)
Damien Miller20e1fab2004-02-18 14:30:55 +11001936 setvbuf(stdout, NULL, _IOLBF, 0);
1937 setvbuf(infile, NULL, _IOLBF, 0);
1938#else
Damien Miller37294fb2005-07-17 17:18:49 +10001939 setlinebuf(stdout);
1940 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11001941#endif
1942
Damien Miller0e2c1022005-08-12 22:16:22 +10001943 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11001944 err = 0;
1945 for (;;) {
1946 char *cp;
1947
Darren Tuckercdf547a2004-05-24 10:12:19 +10001948 signal(SIGINT, SIG_IGN);
1949
Darren Tucker2d963d82004-11-07 20:04:10 +11001950 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001951 if (interactive)
1952 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11001953 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001954 if (interactive)
1955 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11001956 break;
1957 }
Damien Miller0e2c1022005-08-12 22:16:22 +10001958 if (!interactive) { /* Echo command */
1959 printf("sftp> %s", cmd);
1960 if (strlen(cmd) > 0 &&
1961 cmd[strlen(cmd) - 1] != '\n')
1962 printf("\n");
1963 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001964 } else {
1965#ifdef USE_LIBEDIT
1966 const char *line;
1967 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001968
Darren Tucker909d8582010-01-08 19:02:40 +11001969 if ((line = el_gets(el, &count)) == NULL ||
1970 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001971 printf("\n");
1972 break;
1973 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001974 history(hl, &hev, H_ENTER, line);
1975 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
1976 fprintf(stderr, "Error: input line too long\n");
1977 continue;
1978 }
1979#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001980 }
1981
Damien Miller20e1fab2004-02-18 14:30:55 +11001982 cp = strrchr(cmd, '\n');
1983 if (cp)
1984 *cp = '\0';
1985
Darren Tuckercdf547a2004-05-24 10:12:19 +10001986 /* Handle user interrupts gracefully during commands */
1987 interrupted = 0;
1988 signal(SIGINT, cmd_interrupt);
1989
Darren Tucker909d8582010-01-08 19:02:40 +11001990 err = parse_dispatch_command(conn, cmd, &remote_path,
1991 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11001992 if (err != 0)
1993 break;
1994 }
Darren Tucker909d8582010-01-08 19:02:40 +11001995 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001996 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001997
Tim Rice027e8b12005-08-15 14:52:50 -07001998#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10001999 if (el != NULL)
2000 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07002001#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10002002
Damien Miller20e1fab2004-02-18 14:30:55 +11002003 /* err == 1 signifies normal "quit" exit */
2004 return (err >= 0 ? 0 : -1);
2005}
Damien Miller62d57f62003-01-10 21:43:24 +11002006
Ben Lindstrombba81212001-06-25 05:01:22 +00002007static void
Damien Millercc685c12003-06-04 22:51:38 +10002008connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11002009{
2010 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002011
Damien Miller33804262001-02-04 23:20:18 +11002012#ifdef USE_PIPES
2013 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002014
Damien Miller33804262001-02-04 23:20:18 +11002015 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2016 fatal("pipe: %s", strerror(errno));
2017 *in = pin[0];
2018 *out = pout[1];
2019 c_in = pout[0];
2020 c_out = pin[1];
2021#else /* USE_PIPES */
2022 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002023
Damien Miller33804262001-02-04 23:20:18 +11002024 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2025 fatal("socketpair: %s", strerror(errno));
2026 *in = *out = inout[0];
2027 c_in = c_out = inout[1];
2028#endif /* USE_PIPES */
2029
Damien Millercc685c12003-06-04 22:51:38 +10002030 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002031 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002032 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002033 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2034 (dup2(c_out, STDOUT_FILENO) == -1)) {
2035 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002036 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002037 }
2038 close(*in);
2039 close(*out);
2040 close(c_in);
2041 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002042
2043 /*
2044 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002045 * ignore SIGINT if we want to gracefully abort commands,
2046 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002047 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2048 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002049 */
2050 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002051 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002052 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002053 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002054 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002055 }
2056
Damien Millercc685c12003-06-04 22:51:38 +10002057 signal(SIGTERM, killchild);
2058 signal(SIGINT, killchild);
2059 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11002060 close(c_in);
2061 close(c_out);
2062}
2063
Ben Lindstrombba81212001-06-25 05:01:22 +00002064static void
Damien Miller33804262001-02-04 23:20:18 +11002065usage(void)
2066{
Damien Miller025e01c2002-02-08 22:06:29 +11002067 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002068
Ben Lindstrom1e243242001-09-18 05:38:44 +00002069 fprintf(stderr,
Darren Tucker1b0dd172009-10-07 08:37:48 +11002070 "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002071 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002072 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002073 " [-o ssh_option] [-P port] [-R num_requests] "
2074 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002075 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002076 " %s [user@]host[:file ...]\n"
2077 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002078 " %s -b batchfile [user@]host\n",
2079 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002080 exit(1);
2081}
2082
Kevin Stevesef4eea92001-02-05 12:42:17 +00002083int
Damien Miller33804262001-02-04 23:20:18 +11002084main(int argc, char **argv)
2085{
Damien Miller956f3fb2003-01-10 21:40:00 +11002086 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002087 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002088 int debug_level = 0, sshver = 2;
2089 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002090 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002091 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002092 LogLevel ll = SYSLOG_LEVEL_INFO;
2093 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002094 extern int optind;
2095 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002096 struct sftp_conn *conn;
2097 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2098 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002099 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002100
Darren Tuckerce321d82005-10-03 18:11:24 +10002101 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2102 sanitise_stdfd();
2103
Damien Miller59d3d5b2003-08-22 09:34:41 +10002104 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002105 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002106 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002107 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002108 addargs(&args, "-oForwardX11 no");
2109 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002110 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002111 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002112
Ben Lindstrom387c4722001-05-08 20:27:25 +00002113 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002114 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002115
Darren Tucker282b4022009-10-07 08:23:06 +11002116 while ((ch = getopt(argc, argv,
Damien Miller65e42f82010-09-24 22:15:11 +10002117 "1246hpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002118 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002119 /* Passed through to ssh(1) */
2120 case '4':
2121 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002122 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002123 addargs(&args, "-%c", ch);
2124 break;
2125 /* Passed through to ssh(1) with argument */
2126 case 'F':
2127 case 'c':
2128 case 'i':
2129 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002130 addargs(&args, "-%c", ch);
2131 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002132 break;
2133 case 'q':
2134 showprogress = 0;
2135 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002136 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002137 case 'P':
2138 addargs(&args, "-oPort %s", optarg);
2139 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002140 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002141 if (debug_level < 3) {
2142 addargs(&args, "-v");
2143 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2144 }
2145 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002146 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002147 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002148 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002149 if (sftp_server == NULL)
2150 sftp_server = _PATH_SFTP_SERVER;
2151 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002152 case '2':
2153 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002154 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002155 case 'B':
2156 copy_buffer_len = strtol(optarg, &cp, 10);
2157 if (copy_buffer_len == 0 || *cp != '\0')
2158 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002159 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002160 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002161 if (batchmode)
2162 fatal("Batch file already specified.");
2163
2164 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002165 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002166 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002167 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002168 showprogress = 0;
Damien Miller44f75c12004-01-21 10:58:47 +11002169 batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002170 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002171 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002172 case 'p':
2173 global_pflag = 1;
2174 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002175 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002176 sftp_direct = optarg;
2177 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002178 case 'l':
2179 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2180 &errstr);
2181 if (errstr != NULL)
2182 usage();
2183 limit_kbps *= 1024; /* kbps */
2184 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002185 case 'r':
2186 global_rflag = 1;
2187 break;
Damien Miller16a13332002-02-13 14:03:56 +11002188 case 'R':
2189 num_requests = strtol(optarg, &cp, 10);
2190 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002191 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002192 optarg);
2193 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002194 case 's':
2195 sftp_server = optarg;
2196 break;
2197 case 'S':
2198 ssh_program = optarg;
2199 replacearg(&args, 0, "%s", ssh_program);
2200 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002201 case 'h':
2202 default:
Damien Miller33804262001-02-04 23:20:18 +11002203 usage();
2204 }
2205 }
2206
Damien Millerc0f27d82004-03-08 23:12:19 +11002207 if (!isatty(STDERR_FILENO))
2208 showprogress = 0;
2209
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002210 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2211
Damien Millerd14ee1e2002-02-05 12:27:31 +11002212 if (sftp_direct == NULL) {
2213 if (optind == argc || argc > (optind + 2))
2214 usage();
Damien Miller33804262001-02-04 23:20:18 +11002215
Damien Millerd14ee1e2002-02-05 12:27:31 +11002216 userhost = xstrdup(argv[optind]);
2217 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002218
Ben Lindstromc276c122002-12-23 02:14:51 +00002219 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002220 host = userhost;
2221 else {
2222 *host++ = '\0';
2223 if (!userhost[0]) {
2224 fprintf(stderr, "Missing username\n");
2225 usage();
2226 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002227 addargs(&args, "-l");
2228 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002229 }
2230
Damien Millerec692032004-01-27 21:22:00 +11002231 if ((cp = colon(host)) != NULL) {
2232 *cp++ = '\0';
2233 file1 = cp;
2234 }
2235
Damien Millerd14ee1e2002-02-05 12:27:31 +11002236 host = cleanhostname(host);
2237 if (!*host) {
2238 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002239 usage();
2240 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002241
Damien Millerd14ee1e2002-02-05 12:27:31 +11002242 addargs(&args, "-oProtocol %d", sshver);
2243
2244 /* no subsystem if the server-spec contains a '/' */
2245 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2246 addargs(&args, "-s");
2247
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002248 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002249 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002250 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002251 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002252
Damien Millercc685c12003-06-04 22:51:38 +10002253 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002254 } else {
2255 args.list = NULL;
2256 addargs(&args, "sftp-server");
2257
Damien Millercc685c12003-06-04 22:51:38 +10002258 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002259 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002260 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002261
Damien Miller65e42f82010-09-24 22:15:11 +10002262 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002263 if (conn == NULL)
2264 fatal("Couldn't initialise connection to server");
2265
2266 if (!batchmode) {
2267 if (sftp_direct == NULL)
2268 fprintf(stderr, "Connected to %s.\n", host);
2269 else
2270 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2271 }
2272
2273 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002274
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002275#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002276 shutdown(in, SHUT_RDWR);
2277 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002278#endif
2279
Damien Miller33804262001-02-04 23:20:18 +11002280 close(in);
2281 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002282 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002283 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002284
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002285 while (waitpid(sshpid, NULL, 0) == -1)
2286 if (errno != EINTR)
2287 fatal("Couldn't wait for ssh process: %s",
2288 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002289
Damien Miller956f3fb2003-01-10 21:40:00 +11002290 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002291}