blob: a723fa6437ff70c32ade83ec14009efdec483c76 [file] [log] [blame]
Darren Tuckera627d422013-06-02 07:31:17 +10001/* $OpenBSD: sftp.c,v 1.145 2013/05/17 00:13:14 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
Damien Miller33804262001-02-04 23:20:18 +110057#include "xmalloc.h"
58#include "log.h"
59#include "pathnames.h"
Ben Lindstrom4529b702001-05-03 23:39:53 +000060#include "misc.h"
Damien Miller33804262001-02-04 23:20:18 +110061
62#include "sftp.h"
Damien Millerd7834352006-08-05 12:39:39 +100063#include "buffer.h"
Damien Miller33804262001-02-04 23:20:18 +110064#include "sftp-common.h"
65#include "sftp-client.h"
Damien Millerd7d46bb2004-02-18 14:11:13 +110066
Darren Tucker21063192010-01-08 17:10:36 +110067#define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */
68#define DEFAULT_NUM_REQUESTS 64 /* # concurrent outstanding requests */
69
Damien Miller20e1fab2004-02-18 14:30:55 +110070/* File to read commands from */
71FILE* infile;
72
73/* Are we in batchfile mode? */
74int batchmode = 0;
75
Damien Miller20e1fab2004-02-18 14:30:55 +110076/* PID of ssh transport process */
77static pid_t sshpid = -1;
78
Damien Miller9303e652013-04-23 15:22:40 +100079/* Suppress diagnositic messages */
80int quiet = 0;
81
Damien Miller20e1fab2004-02-18 14:30:55 +110082/* This is set to 0 if the progressmeter is not desired. */
Damien Millerc0f27d82004-03-08 23:12:19 +110083int showprogress = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +110084
Darren Tucker1b0dd172009-10-07 08:37:48 +110085/* When this option is set, we always recursively download/upload directories */
86int global_rflag = 0;
87
88/* When this option is set, the file transfers will always preserve times */
89int global_pflag = 0;
90
Darren Tuckercdf547a2004-05-24 10:12:19 +100091/* SIGINT received during command processing */
92volatile sig_atomic_t interrupted = 0;
93
Darren Tuckerb9123452004-06-22 13:06:45 +100094/* I wish qsort() took a separate ctx for the comparison function...*/
95int sort_flag;
96
Darren Tucker909d8582010-01-08 19:02:40 +110097/* Context used for commandline completion */
98struct complete_ctx {
99 struct sftp_conn *conn;
100 char **remote_pathp;
101};
102
Damien Miller20e1fab2004-02-18 14:30:55 +1100103int remote_glob(struct sftp_conn *, const char *, int,
104 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
Damien Miller33804262001-02-04 23:20:18 +1100105
Kevin Steves12888d12001-03-05 19:50:57 +0000106extern char *__progname;
Kevin Steves12888d12001-03-05 19:50:57 +0000107
Damien Miller20e1fab2004-02-18 14:30:55 +1100108/* Separators for interactive commands */
109#define WHITESPACE " \t\r\n"
Damien Millerd7686fd2001-02-10 00:40:03 +1100110
Darren Tuckerb9123452004-06-22 13:06:45 +1000111/* ls flags */
Darren Tucker2901e2d2010-01-13 22:44:06 +1100112#define LS_LONG_VIEW 0x0001 /* Full view ala ls -l */
113#define LS_SHORT_VIEW 0x0002 /* Single row view ala ls -1 */
114#define LS_NUMERIC_VIEW 0x0004 /* Long view with numeric uid/gid */
115#define LS_NAME_SORT 0x0008 /* Sort by name (default) */
116#define LS_TIME_SORT 0x0010 /* Sort by mtime */
117#define LS_SIZE_SORT 0x0020 /* Sort by file size */
118#define LS_REVERSE_SORT 0x0040 /* Reverse sort order */
119#define LS_SHOW_ALL 0x0080 /* Don't skip filenames starting with '.' */
120#define LS_SI_UNITS 0x0100 /* Display sizes as K, M, G, etc. */
Darren Tuckerb9123452004-06-22 13:06:45 +1000121
Darren Tucker2901e2d2010-01-13 22:44:06 +1100122#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS)
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000123#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
Damien Miller20e1fab2004-02-18 14:30:55 +1100124
125/* Commands for interactive mode */
126#define I_CHDIR 1
127#define I_CHGRP 2
128#define I_CHMOD 3
129#define I_CHOWN 4
Damien Millerd671e5a2008-05-19 14:53:33 +1000130#define I_DF 24
Damien Miller20e1fab2004-02-18 14:30:55 +1100131#define I_GET 5
132#define I_HELP 6
133#define I_LCHDIR 7
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100134#define I_LINK 25
Damien Miller20e1fab2004-02-18 14:30:55 +1100135#define I_LLS 8
136#define I_LMKDIR 9
137#define I_LPWD 10
138#define I_LS 11
139#define I_LUMASK 12
140#define I_MKDIR 13
141#define I_PUT 14
142#define I_PWD 15
143#define I_QUIT 16
144#define I_RENAME 17
145#define I_RM 18
146#define I_RMDIR 19
147#define I_SHELL 20
148#define I_SYMLINK 21
149#define I_VERSION 22
150#define I_PROGRESS 23
151
152struct CMD {
153 const char *c;
154 const int n;
Darren Tucker909d8582010-01-08 19:02:40 +1100155 const int t;
Damien Miller20e1fab2004-02-18 14:30:55 +1100156};
157
Darren Tucker909d8582010-01-08 19:02:40 +1100158/* Type of completion */
159#define NOARGS 0
160#define REMOTE 1
161#define LOCAL 2
162
Damien Miller20e1fab2004-02-18 14:30:55 +1100163static const struct CMD cmds[] = {
Darren Tucker909d8582010-01-08 19:02:40 +1100164 { "bye", I_QUIT, NOARGS },
165 { "cd", I_CHDIR, REMOTE },
166 { "chdir", I_CHDIR, REMOTE },
167 { "chgrp", I_CHGRP, REMOTE },
168 { "chmod", I_CHMOD, REMOTE },
169 { "chown", I_CHOWN, REMOTE },
170 { "df", I_DF, REMOTE },
171 { "dir", I_LS, REMOTE },
172 { "exit", I_QUIT, NOARGS },
173 { "get", I_GET, REMOTE },
174 { "help", I_HELP, NOARGS },
175 { "lcd", I_LCHDIR, LOCAL },
176 { "lchdir", I_LCHDIR, LOCAL },
177 { "lls", I_LLS, LOCAL },
178 { "lmkdir", I_LMKDIR, LOCAL },
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100179 { "ln", I_LINK, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100180 { "lpwd", I_LPWD, LOCAL },
181 { "ls", I_LS, REMOTE },
182 { "lumask", I_LUMASK, NOARGS },
183 { "mkdir", I_MKDIR, REMOTE },
Damien Miller099fc162010-05-10 11:56:50 +1000184 { "mget", I_GET, REMOTE },
185 { "mput", I_PUT, LOCAL },
Darren Tucker909d8582010-01-08 19:02:40 +1100186 { "progress", I_PROGRESS, NOARGS },
187 { "put", I_PUT, LOCAL },
188 { "pwd", I_PWD, REMOTE },
189 { "quit", I_QUIT, NOARGS },
190 { "rename", I_RENAME, REMOTE },
191 { "rm", I_RM, REMOTE },
192 { "rmdir", I_RMDIR, REMOTE },
193 { "symlink", I_SYMLINK, REMOTE },
194 { "version", I_VERSION, NOARGS },
195 { "!", I_SHELL, NOARGS },
196 { "?", I_HELP, NOARGS },
197 { NULL, -1, -1 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100198};
199
Darren Tucker21063192010-01-08 17:10:36 +1100200int interactive_loop(struct sftp_conn *, char *file1, char *file2);
Damien Miller20e1fab2004-02-18 14:30:55 +1100201
Damien Millerb6c85fc2007-01-05 16:30:41 +1100202/* ARGSUSED */
Damien Miller20e1fab2004-02-18 14:30:55 +1100203static void
Darren Tuckercdf547a2004-05-24 10:12:19 +1000204killchild(int signo)
205{
Darren Tuckerba66df82005-01-24 21:57:40 +1100206 if (sshpid > 1) {
Darren Tuckercdf547a2004-05-24 10:12:19 +1000207 kill(sshpid, SIGTERM);
Darren Tuckerba66df82005-01-24 21:57:40 +1100208 waitpid(sshpid, NULL, 0);
209 }
Darren Tuckercdf547a2004-05-24 10:12:19 +1000210
211 _exit(1);
212}
213
Damien Millerb6c85fc2007-01-05 16:30:41 +1100214/* ARGSUSED */
Darren Tuckercdf547a2004-05-24 10:12:19 +1000215static void
216cmd_interrupt(int signo)
217{
218 const char msg[] = "\rInterrupt \n";
Darren Tuckere2f189a2004-12-06 22:45:53 +1100219 int olderrno = errno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000220
Darren Tuckerdbee3082013-05-16 20:32:29 +1000221 (void)write(STDERR_FILENO, msg, sizeof(msg) - 1);
Darren Tuckercdf547a2004-05-24 10:12:19 +1000222 interrupted = 1;
Darren Tuckere2f189a2004-12-06 22:45:53 +1100223 errno = olderrno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000224}
225
226static void
Damien Miller20e1fab2004-02-18 14:30:55 +1100227help(void)
228{
Damien Miller62fd18a2009-01-28 16:14:09 +1100229 printf("Available commands:\n"
230 "bye Quit sftp\n"
231 "cd path Change remote directory to 'path'\n"
232 "chgrp grp path Change group of file 'path' to 'grp'\n"
233 "chmod mode path Change permissions of file 'path' to 'mode'\n"
234 "chown own path Change owner of file 'path' to 'own'\n"
235 "df [-hi] [path] Display statistics for current directory or\n"
236 " filesystem containing 'path'\n"
237 "exit Quit sftp\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100238 "get [-Ppr] remote [local] Download file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100239 "help Display this help text\n"
240 "lcd path Change local directory to 'path'\n"
241 "lls [ls-options [path]] Display local directory listing\n"
242 "lmkdir path Create local directory\n"
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100243 "ln [-s] oldpath newpath Link remote file (-s for symlink)\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100244 "lpwd Print local working directory\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100245 "ls [-1afhlnrSt] [path] Display remote directory listing\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100246 "lumask umask Set local umask to 'umask'\n"
247 "mkdir path Create remote directory\n"
248 "progress Toggle display of progress meter\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100249 "put [-Ppr] local [remote] Upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100250 "pwd Display remote working directory\n"
251 "quit Quit sftp\n"
252 "rename oldpath newpath Rename remote file\n"
253 "rm path Delete remote file\n"
254 "rmdir path Remove remote directory\n"
255 "symlink oldpath newpath Symlink remote file\n"
256 "version Show SFTP version\n"
257 "!command Execute 'command' in local shell\n"
258 "! Escape to local shell\n"
259 "? Synonym for help\n");
Damien Miller20e1fab2004-02-18 14:30:55 +1100260}
261
262static void
263local_do_shell(const char *args)
264{
265 int status;
266 char *shell;
267 pid_t pid;
268
269 if (!*args)
270 args = NULL;
271
Damien Miller38d9a962010-10-07 22:07:11 +1100272 if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
Damien Miller20e1fab2004-02-18 14:30:55 +1100273 shell = _PATH_BSHELL;
274
275 if ((pid = fork()) == -1)
276 fatal("Couldn't fork: %s", strerror(errno));
277
278 if (pid == 0) {
279 /* XXX: child has pipe fds to ssh subproc open - issue? */
280 if (args) {
281 debug3("Executing %s -c \"%s\"", shell, args);
282 execl(shell, shell, "-c", args, (char *)NULL);
283 } else {
284 debug3("Executing %s", shell);
285 execl(shell, shell, (char *)NULL);
286 }
287 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
288 strerror(errno));
289 _exit(1);
290 }
291 while (waitpid(pid, &status, 0) == -1)
292 if (errno != EINTR)
293 fatal("Couldn't wait for child: %s", strerror(errno));
294 if (!WIFEXITED(status))
Damien Miller55b04f12006-03-26 14:23:17 +1100295 error("Shell exited abnormally");
Damien Miller20e1fab2004-02-18 14:30:55 +1100296 else if (WEXITSTATUS(status))
297 error("Shell exited with status %d", WEXITSTATUS(status));
298}
299
300static void
301local_do_ls(const char *args)
302{
303 if (!args || !*args)
304 local_do_shell(_PATH_LS);
305 else {
306 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
307 char *buf = xmalloc(len);
308
309 /* XXX: quoting - rip quoting code from ftp? */
310 snprintf(buf, len, _PATH_LS " %s", args);
311 local_do_shell(buf);
Darren Tuckera627d422013-06-02 07:31:17 +1000312 free(buf);
Damien Miller20e1fab2004-02-18 14:30:55 +1100313 }
314}
315
316/* Strip one path (usually the pwd) from the start of another */
317static char *
318path_strip(char *path, char *strip)
319{
320 size_t len;
321
322 if (strip == NULL)
323 return (xstrdup(path));
324
325 len = strlen(strip);
Darren Tuckere2f189a2004-12-06 22:45:53 +1100326 if (strncmp(path, strip, len) == 0) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100327 if (strip[len - 1] != '/' && path[len] == '/')
328 len++;
329 return (xstrdup(path + len));
330 }
331
332 return (xstrdup(path));
333}
334
335static char *
Damien Miller20e1fab2004-02-18 14:30:55 +1100336make_absolute(char *p, char *pwd)
337{
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000338 char *abs_str;
Damien Miller20e1fab2004-02-18 14:30:55 +1100339
340 /* Derelativise */
341 if (p && p[0] != '/') {
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000342 abs_str = path_append(pwd, p);
Darren Tuckera627d422013-06-02 07:31:17 +1000343 free(p);
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000344 return(abs_str);
Damien Miller20e1fab2004-02-18 14:30:55 +1100345 } else
346 return(p);
347}
348
349static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100350parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag,
351 int *rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100352{
Damien Millerf184bcf2008-06-29 22:45:13 +1000353 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000354 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100355
Damien Miller1cbc2922007-10-26 14:27:45 +1000356 optind = optreset = 1;
357 opterr = 0;
358
Darren Tucker1b0dd172009-10-07 08:37:48 +1100359 *rflag = *pflag = 0;
360 while ((ch = getopt(argc, argv, "PpRr")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000361 switch (ch) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100362 case 'p':
363 case 'P':
364 *pflag = 1;
365 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100366 case 'r':
367 case 'R':
368 *rflag = 1;
369 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100370 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000371 error("%s: Invalid flag -%c", cmd, optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000372 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100373 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100374 }
375
Damien Miller1cbc2922007-10-26 14:27:45 +1000376 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100377}
378
379static int
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100380parse_link_flags(const char *cmd, char **argv, int argc, int *sflag)
381{
382 extern int opterr, optind, optopt, optreset;
383 int ch;
384
385 optind = optreset = 1;
386 opterr = 0;
387
388 *sflag = 0;
389 while ((ch = getopt(argc, argv, "s")) != -1) {
390 switch (ch) {
391 case 's':
392 *sflag = 1;
393 break;
394 default:
395 error("%s: Invalid flag -%c", cmd, optopt);
396 return -1;
397 }
398 }
399
400 return optind;
401}
402
403static int
Damien Miller1cbc2922007-10-26 14:27:45 +1000404parse_ls_flags(char **argv, int argc, int *lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100405{
Damien Millerf184bcf2008-06-29 22:45:13 +1000406 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000407 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100408
Damien Miller1cbc2922007-10-26 14:27:45 +1000409 optind = optreset = 1;
410 opterr = 0;
411
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000412 *lflag = LS_NAME_SORT;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100413 while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000414 switch (ch) {
415 case '1':
416 *lflag &= ~VIEW_FLAGS;
417 *lflag |= LS_SHORT_VIEW;
418 break;
419 case 'S':
420 *lflag &= ~SORT_FLAGS;
421 *lflag |= LS_SIZE_SORT;
422 break;
423 case 'a':
424 *lflag |= LS_SHOW_ALL;
425 break;
426 case 'f':
427 *lflag &= ~SORT_FLAGS;
428 break;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100429 case 'h':
430 *lflag |= LS_SI_UNITS;
431 break;
Damien Miller1cbc2922007-10-26 14:27:45 +1000432 case 'l':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100433 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000434 *lflag |= LS_LONG_VIEW;
435 break;
436 case 'n':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100437 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000438 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
439 break;
440 case 'r':
441 *lflag |= LS_REVERSE_SORT;
442 break;
443 case 't':
444 *lflag &= ~SORT_FLAGS;
445 *lflag |= LS_TIME_SORT;
446 break;
447 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000448 error("ls: Invalid flag -%c", optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000449 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100450 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100451 }
452
Damien Miller1cbc2922007-10-26 14:27:45 +1000453 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100454}
455
456static int
Damien Millerd671e5a2008-05-19 14:53:33 +1000457parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
458{
Damien Millerf184bcf2008-06-29 22:45:13 +1000459 extern int opterr, optind, optopt, optreset;
Damien Millerd671e5a2008-05-19 14:53:33 +1000460 int ch;
461
462 optind = optreset = 1;
463 opterr = 0;
464
465 *hflag = *iflag = 0;
466 while ((ch = getopt(argc, argv, "hi")) != -1) {
467 switch (ch) {
468 case 'h':
469 *hflag = 1;
470 break;
471 case 'i':
472 *iflag = 1;
473 break;
474 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000475 error("%s: Invalid flag -%c", cmd, optopt);
Damien Millerd671e5a2008-05-19 14:53:33 +1000476 return -1;
477 }
478 }
479
480 return optind;
481}
482
483static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100484is_dir(char *path)
485{
486 struct stat sb;
487
488 /* XXX: report errors? */
489 if (stat(path, &sb) == -1)
490 return(0);
491
Darren Tucker1e80e402006-09-21 12:59:33 +1000492 return(S_ISDIR(sb.st_mode));
Damien Miller20e1fab2004-02-18 14:30:55 +1100493}
494
495static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100496remote_is_dir(struct sftp_conn *conn, char *path)
497{
498 Attrib *a;
499
500 /* XXX: report errors? */
501 if ((a = do_stat(conn, path, 1)) == NULL)
502 return(0);
503 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
504 return(0);
Darren Tucker1e80e402006-09-21 12:59:33 +1000505 return(S_ISDIR(a->perm));
Damien Miller20e1fab2004-02-18 14:30:55 +1100506}
507
Darren Tucker1b0dd172009-10-07 08:37:48 +1100508/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
Damien Miller20e1fab2004-02-18 14:30:55 +1100509static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100510pathname_is_dir(char *pathname)
511{
512 size_t l = strlen(pathname);
513
514 return l > 0 && pathname[l - 1] == '/';
515}
516
517static int
518process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
519 int pflag, int rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100520{
521 char *abs_src = NULL;
522 char *abs_dst = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100523 glob_t g;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100524 char *filename, *tmp=NULL;
525 int i, err = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100526
527 abs_src = xstrdup(src);
528 abs_src = make_absolute(abs_src, pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +1100529 memset(&g, 0, sizeof(g));
Darren Tucker1b0dd172009-10-07 08:37:48 +1100530
Damien Miller20e1fab2004-02-18 14:30:55 +1100531 debug3("Looking up %s", abs_src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100532 if (remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100533 error("File \"%s\" not found.", abs_src);
534 err = -1;
535 goto out;
536 }
537
Darren Tucker1b0dd172009-10-07 08:37:48 +1100538 /*
539 * If multiple matches then dst must be a directory or
540 * unspecified.
541 */
542 if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
543 error("Multiple source paths, but destination "
544 "\"%s\" is not a directory", dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100545 err = -1;
546 goto out;
547 }
548
Darren Tuckercdf547a2004-05-24 10:12:19 +1000549 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100550 tmp = xstrdup(g.gl_pathv[i]);
551 if ((filename = basename(tmp)) == NULL) {
552 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000553 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100554 err = -1;
555 goto out;
556 }
557
558 if (g.gl_matchc == 1 && dst) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100559 if (is_dir(dst)) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100560 abs_dst = path_append(dst, filename);
561 } else {
Damien Miller20e1fab2004-02-18 14:30:55 +1100562 abs_dst = xstrdup(dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100563 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100564 } else if (dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100565 abs_dst = path_append(dst, filename);
566 } else {
567 abs_dst = xstrdup(filename);
568 }
Darren Tuckera627d422013-06-02 07:31:17 +1000569 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100570
Damien Miller9303e652013-04-23 15:22:40 +1000571 if (!quiet)
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 }
Darren Tuckera627d422013-06-02 07:31:17 +1000582 free(abs_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100583 abs_dst = NULL;
584 }
585
586out:
Darren Tuckera627d422013-06-02 07:31:17 +1000587 free(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));
Darren Tuckera627d422013-06-02 07:31:17 +1000639 free(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 }
Darren Tuckera627d422013-06-02 07:31:17 +1000655 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100656
Damien Miller9303e652013-04-23 15:22:40 +1000657 if (!quiet)
658 printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100659 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
660 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
661 pflag || global_pflag, 1) == -1)
662 err = -1;
663 } else {
664 if (do_upload(conn, g.gl_pathv[i], abs_dst,
665 pflag || global_pflag) == -1)
666 err = -1;
667 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100668 }
669
670out:
Darren Tuckera627d422013-06-02 07:31:17 +1000671 free(abs_dst);
672 free(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100673 globfree(&g);
674 return(err);
675}
676
677static int
678sdirent_comp(const void *aa, const void *bb)
679{
680 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
681 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000682 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100683
Darren Tuckerb9123452004-06-22 13:06:45 +1000684#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000685 if (sort_flag & LS_NAME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000686 return (rmul * strcmp(a->filename, b->filename));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000687 else if (sort_flag & LS_TIME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000688 return (rmul * NCMP(a->a.mtime, b->a.mtime));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000689 else if (sort_flag & LS_SIZE_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000690 return (rmul * NCMP(a->a.size, b->a.size));
691
692 fatal("Unknown ls sort type");
Damien Miller20e1fab2004-02-18 14:30:55 +1100693}
694
695/* sftp ls.1 replacement for directories */
696static int
697do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
698{
Damien Millereccb9de2005-06-17 12:59:34 +1000699 int n;
700 u_int c = 1, colspace = 0, columns = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100701 SFTP_DIRENT **d;
702
703 if ((n = do_readdir(conn, path, &d)) != 0)
704 return (n);
705
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000706 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000707 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100708 struct winsize ws;
709 char *tmp;
710
711 /* Count entries for sort and find longest filename */
Darren Tucker9a526452004-06-22 13:09:55 +1000712 for (n = 0; d[n] != NULL; n++) {
713 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
714 m = MAX(m, strlen(d[n]->filename));
715 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100716
717 /* Add any subpath that also needs to be counted */
718 tmp = path_strip(path, strip_path);
719 m += strlen(tmp);
Darren Tuckera627d422013-06-02 07:31:17 +1000720 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100721
722 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
723 width = ws.ws_col;
724
725 columns = width / (m + 2);
726 columns = MAX(columns, 1);
727 colspace = width / columns;
728 colspace = MIN(colspace, width);
729 }
730
Darren Tuckerb9123452004-06-22 13:06:45 +1000731 if (lflag & SORT_FLAGS) {
Damien Miller653b93b2005-11-05 15:15:23 +1100732 for (n = 0; d[n] != NULL; n++)
733 ; /* count entries */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000734 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
Darren Tuckerb9123452004-06-22 13:06:45 +1000735 qsort(d, n, sizeof(*d), sdirent_comp);
736 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100737
Darren Tuckercdf547a2004-05-24 10:12:19 +1000738 for (n = 0; d[n] != NULL && !interrupted; n++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100739 char *tmp, *fname;
740
Darren Tucker9a526452004-06-22 13:09:55 +1000741 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
742 continue;
743
Damien Miller20e1fab2004-02-18 14:30:55 +1100744 tmp = path_append(path, d[n]->filename);
745 fname = path_strip(tmp, strip_path);
Darren Tuckera627d422013-06-02 07:31:17 +1000746 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100747
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000748 if (lflag & LS_LONG_VIEW) {
Darren Tucker2901e2d2010-01-13 22:44:06 +1100749 if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000750 char *lname;
751 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100752
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000753 memset(&sb, 0, sizeof(sb));
754 attrib_to_stat(&d[n]->a, &sb);
Darren Tucker2901e2d2010-01-13 22:44:06 +1100755 lname = ls_file(fname, &sb, 1,
756 (lflag & LS_SI_UNITS));
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000757 printf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000758 free(lname);
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000759 } else
760 printf("%s\n", d[n]->longname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100761 } else {
762 printf("%-*s", colspace, fname);
763 if (c >= columns) {
764 printf("\n");
765 c = 1;
766 } else
767 c++;
768 }
769
Darren Tuckera627d422013-06-02 07:31:17 +1000770 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100771 }
772
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000773 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100774 printf("\n");
775
776 free_sftp_dirents(d);
777 return (0);
778}
779
780/* sftp ls.1 replacement which handles path globs */
781static int
782do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
783 int lflag)
784{
Damien Millera6e121a2010-10-07 21:39:17 +1100785 char *fname, *lname;
Damien Miller68e2e562010-10-07 21:39:55 +1100786 glob_t g;
787 int err;
788 struct winsize ws;
789 u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100790
791 memset(&g, 0, sizeof(g));
792
Damien Millera6e121a2010-10-07 21:39:17 +1100793 if (remote_glob(conn, path,
Damien Millerd7be70d2011-09-22 21:43:06 +1000794 GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
795 NULL, &g) ||
Damien Millera6e121a2010-10-07 21:39:17 +1100796 (g.gl_pathc && !g.gl_matchc)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100797 if (g.gl_pathc)
798 globfree(&g);
Damien Miller20e1fab2004-02-18 14:30:55 +1100799 error("Can't ls: \"%s\" not found", path);
Damien Millera6e121a2010-10-07 21:39:17 +1100800 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100801 }
802
Darren Tuckercdf547a2004-05-24 10:12:19 +1000803 if (interrupted)
804 goto out;
805
Damien Miller20e1fab2004-02-18 14:30:55 +1100806 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100807 * If the glob returns a single match and it is a directory,
808 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100809 */
Damien Millera6e121a2010-10-07 21:39:17 +1100810 if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
811 S_ISDIR(g.gl_statv[0]->st_mode)) {
812 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
813 globfree(&g);
814 return err;
Damien Miller20e1fab2004-02-18 14:30:55 +1100815 }
816
Damien Miller68e2e562010-10-07 21:39:55 +1100817 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
818 width = ws.ws_col;
Damien Miller20e1fab2004-02-18 14:30:55 +1100819
Damien Miller68e2e562010-10-07 21:39:55 +1100820 if (!(lflag & LS_SHORT_VIEW)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100821 /* Count entries for sort and find longest filename */
822 for (i = 0; g.gl_pathv[i]; i++)
823 m = MAX(m, strlen(g.gl_pathv[i]));
824
Damien Miller20e1fab2004-02-18 14:30:55 +1100825 columns = width / (m + 2);
826 columns = MAX(columns, 1);
827 colspace = width / columns;
828 }
829
Damien Millerea858292012-06-30 08:33:32 +1000830 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100831 fname = path_strip(g.gl_pathv[i], strip_path);
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000832 if (lflag & LS_LONG_VIEW) {
Damien Millera6e121a2010-10-07 21:39:17 +1100833 if (g.gl_statv[i] == NULL) {
834 error("no stat information for %s", fname);
835 continue;
836 }
837 lname = ls_file(fname, g.gl_statv[i], 1,
838 (lflag & LS_SI_UNITS));
Damien Miller20e1fab2004-02-18 14:30:55 +1100839 printf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000840 free(lname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100841 } else {
842 printf("%-*s", colspace, fname);
843 if (c >= columns) {
844 printf("\n");
845 c = 1;
846 } else
847 c++;
848 }
Darren Tuckera627d422013-06-02 07:31:17 +1000849 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100850 }
851
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000852 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100853 printf("\n");
854
Darren Tuckercdf547a2004-05-24 10:12:19 +1000855 out:
Damien Miller20e1fab2004-02-18 14:30:55 +1100856 if (g.gl_pathc)
857 globfree(&g);
858
Damien Millera6e121a2010-10-07 21:39:17 +1100859 return 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100860}
861
Damien Millerd671e5a2008-05-19 14:53:33 +1000862static int
863do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
864{
Darren Tucker7b598892008-06-09 22:49:36 +1000865 struct sftp_statvfs st;
Damien Millerd671e5a2008-05-19 14:53:33 +1000866 char s_used[FMT_SCALED_STRSIZE];
867 char s_avail[FMT_SCALED_STRSIZE];
868 char s_root[FMT_SCALED_STRSIZE];
869 char s_total[FMT_SCALED_STRSIZE];
Darren Tuckerb5082e92010-01-08 18:51:47 +1100870 unsigned long long ffree;
Damien Millerd671e5a2008-05-19 14:53:33 +1000871
872 if (do_statvfs(conn, path, &st, 1) == -1)
873 return -1;
874 if (iflag) {
Darren Tuckerb5082e92010-01-08 18:51:47 +1100875 ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
Damien Millerd671e5a2008-05-19 14:53:33 +1000876 printf(" Inodes Used Avail "
877 "(root) %%Capacity\n");
878 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
879 (unsigned long long)st.f_files,
880 (unsigned long long)(st.f_files - st.f_ffree),
881 (unsigned long long)st.f_favail,
Darren Tuckerb5082e92010-01-08 18:51:47 +1100882 (unsigned long long)st.f_ffree, ffree);
Damien Millerd671e5a2008-05-19 14:53:33 +1000883 } else if (hflag) {
884 strlcpy(s_used, "error", sizeof(s_used));
885 strlcpy(s_avail, "error", sizeof(s_avail));
886 strlcpy(s_root, "error", sizeof(s_root));
887 strlcpy(s_total, "error", sizeof(s_total));
888 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
889 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
890 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
891 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
892 printf(" Size Used Avail (root) %%Capacity\n");
893 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
894 s_total, s_used, s_avail, s_root,
895 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
896 st.f_blocks));
897 } else {
898 printf(" Size Used Avail "
899 "(root) %%Capacity\n");
900 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
901 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
902 (unsigned long long)(st.f_frsize *
903 (st.f_blocks - st.f_bfree) / 1024),
904 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
905 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
906 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
907 st.f_blocks));
908 }
909 return 0;
910}
911
Damien Miller1cbc2922007-10-26 14:27:45 +1000912/*
913 * Undo escaping of glob sequences in place. Used to undo extra escaping
914 * applied in makeargv() when the string is destined for a function that
915 * does not glob it.
916 */
917static void
918undo_glob_escape(char *s)
919{
920 size_t i, j;
921
922 for (i = j = 0;;) {
923 if (s[i] == '\0') {
924 s[j] = '\0';
925 return;
926 }
927 if (s[i] != '\\') {
928 s[j++] = s[i++];
929 continue;
930 }
931 /* s[i] == '\\' */
932 ++i;
933 switch (s[i]) {
934 case '?':
935 case '[':
936 case '*':
937 case '\\':
938 s[j++] = s[i++];
939 break;
940 case '\0':
941 s[j++] = '\\';
942 s[j] = '\0';
943 return;
944 default:
945 s[j++] = '\\';
946 s[j++] = s[i++];
947 break;
948 }
949 }
950}
951
952/*
953 * Split a string into an argument vector using sh(1)-style quoting,
954 * comment and escaping rules, but with some tweaks to handle glob(3)
955 * wildcards.
Darren Tucker909d8582010-01-08 19:02:40 +1100956 * The "sloppy" flag allows for recovery from missing terminating quote, for
957 * use in parsing incomplete commandlines during tab autocompletion.
958 *
Damien Miller1cbc2922007-10-26 14:27:45 +1000959 * Returns NULL on error or a NULL-terminated array of arguments.
Darren Tucker909d8582010-01-08 19:02:40 +1100960 *
961 * If "lastquote" is not NULL, the quoting character used for the last
962 * argument is placed in *lastquote ("\0", "'" or "\"").
963 *
964 * If "terminated" is not NULL, *terminated will be set to 1 when the
965 * last argument's quote has been properly terminated or 0 otherwise.
966 * This parameter is only of use if "sloppy" is set.
Damien Miller1cbc2922007-10-26 14:27:45 +1000967 */
968#define MAXARGS 128
969#define MAXARGLEN 8192
970static char **
Darren Tucker909d8582010-01-08 19:02:40 +1100971makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
972 u_int *terminated)
Damien Miller1cbc2922007-10-26 14:27:45 +1000973{
974 int argc, quot;
975 size_t i, j;
976 static char argvs[MAXARGLEN];
977 static char *argv[MAXARGS + 1];
978 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
979
980 *argcp = argc = 0;
981 if (strlen(arg) > sizeof(argvs) - 1) {
982 args_too_longs:
983 error("string too long");
984 return NULL;
985 }
Darren Tucker909d8582010-01-08 19:02:40 +1100986 if (terminated != NULL)
987 *terminated = 1;
988 if (lastquote != NULL)
989 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +1000990 state = MA_START;
991 i = j = 0;
992 for (;;) {
Damien Miller07daed52012-10-31 08:57:55 +1100993 if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){
Darren Tucker063018d2012-10-05 10:43:58 +1000994 error("Too many arguments.");
995 return NULL;
996 }
Damien Miller1cbc2922007-10-26 14:27:45 +1000997 if (isspace(arg[i])) {
998 if (state == MA_UNQUOTED) {
999 /* Terminate current argument */
1000 argvs[j++] = '\0';
1001 argc++;
1002 state = MA_START;
1003 } else if (state != MA_START)
1004 argvs[j++] = arg[i];
1005 } else if (arg[i] == '"' || arg[i] == '\'') {
1006 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1007 if (state == MA_START) {
1008 argv[argc] = argvs + j;
1009 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +11001010 if (lastquote != NULL)
1011 *lastquote = arg[i];
Damien Miller1cbc2922007-10-26 14:27:45 +10001012 } else if (state == MA_UNQUOTED)
1013 state = q;
1014 else if (state == q)
1015 state = MA_UNQUOTED;
1016 else
1017 argvs[j++] = arg[i];
1018 } else if (arg[i] == '\\') {
1019 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1020 quot = state == MA_SQUOTE ? '\'' : '"';
1021 /* Unescape quote we are in */
1022 /* XXX support \n and friends? */
1023 if (arg[i + 1] == quot) {
1024 i++;
1025 argvs[j++] = arg[i];
1026 } else if (arg[i + 1] == '?' ||
1027 arg[i + 1] == '[' || arg[i + 1] == '*') {
1028 /*
1029 * Special case for sftp: append
1030 * double-escaped glob sequence -
1031 * glob will undo one level of
1032 * escaping. NB. string can grow here.
1033 */
1034 if (j >= sizeof(argvs) - 5)
1035 goto args_too_longs;
1036 argvs[j++] = '\\';
1037 argvs[j++] = arg[i++];
1038 argvs[j++] = '\\';
1039 argvs[j++] = arg[i];
1040 } else {
1041 argvs[j++] = arg[i++];
1042 argvs[j++] = arg[i];
1043 }
1044 } else {
1045 if (state == MA_START) {
1046 argv[argc] = argvs + j;
1047 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001048 if (lastquote != NULL)
1049 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001050 }
1051 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1052 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1053 /*
1054 * Special case for sftp: append
1055 * escaped glob sequence -
1056 * glob will undo one level of
1057 * escaping.
1058 */
1059 argvs[j++] = arg[i++];
1060 argvs[j++] = arg[i];
1061 } else {
1062 /* Unescape everything */
1063 /* XXX support \n and friends? */
1064 i++;
1065 argvs[j++] = arg[i];
1066 }
1067 }
1068 } else if (arg[i] == '#') {
1069 if (state == MA_SQUOTE || state == MA_DQUOTE)
1070 argvs[j++] = arg[i];
1071 else
1072 goto string_done;
1073 } else if (arg[i] == '\0') {
1074 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001075 if (sloppy) {
1076 state = MA_UNQUOTED;
1077 if (terminated != NULL)
1078 *terminated = 0;
1079 goto string_done;
1080 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001081 error("Unterminated quoted argument");
1082 return NULL;
1083 }
1084 string_done:
1085 if (state == MA_UNQUOTED) {
1086 argvs[j++] = '\0';
1087 argc++;
1088 }
1089 break;
1090 } else {
1091 if (state == MA_START) {
1092 argv[argc] = argvs + j;
1093 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001094 if (lastquote != NULL)
1095 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001096 }
1097 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1098 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1099 /*
1100 * Special case for sftp: escape quoted
1101 * glob(3) wildcards. NB. string can grow
1102 * here.
1103 */
1104 if (j >= sizeof(argvs) - 3)
1105 goto args_too_longs;
1106 argvs[j++] = '\\';
1107 argvs[j++] = arg[i];
1108 } else
1109 argvs[j++] = arg[i];
1110 }
1111 i++;
1112 }
1113 *argcp = argc;
1114 return argv;
1115}
1116
Damien Miller20e1fab2004-02-18 14:30:55 +11001117static int
Darren Tucker909d8582010-01-08 19:02:40 +11001118parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag,
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001119 int *hflag, int *sflag, unsigned long *n_arg, char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001120{
1121 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001122 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001123 int base = 0;
1124 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001125 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001126
1127 /* Skip leading whitespace */
1128 cp = cp + strspn(cp, WHITESPACE);
1129
Damien Miller20e1fab2004-02-18 14:30:55 +11001130 /* Check for leading '-' (disable error processing) */
1131 *iflag = 0;
1132 if (*cp == '-') {
1133 *iflag = 1;
1134 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001135 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001136 }
1137
Darren Tucker70cc0922010-01-09 22:28:03 +11001138 /* Ignore blank lines and lines which begin with comment '#' char */
1139 if (*cp == '\0' || *cp == '#')
1140 return (0);
1141
Darren Tucker909d8582010-01-08 19:02:40 +11001142 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001143 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001144
Damien Miller1cbc2922007-10-26 14:27:45 +10001145 /* Figure out which command we have */
1146 for (i = 0; cmds[i].c != NULL; i++) {
Damien Millerd6d9fa02013-02-12 11:02:46 +11001147 if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001148 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001149 }
1150 cmdnum = cmds[i].n;
1151 cmd = cmds[i].c;
1152
1153 /* Special case */
1154 if (*cp == '!') {
1155 cp++;
1156 cmdnum = I_SHELL;
1157 } else if (cmdnum == -1) {
1158 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001159 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001160 }
1161
1162 /* Get arguments and parse flags */
Darren Tucker1b0dd172009-10-07 08:37:48 +11001163 *lflag = *pflag = *rflag = *hflag = *n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001164 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001165 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001166 switch (cmdnum) {
1167 case I_GET:
1168 case I_PUT:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001169 if ((optidx = parse_getput_flags(cmd, argv, argc,
1170 pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001171 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001172 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001173 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001174 error("You must specify at least one path after a "
1175 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001176 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001177 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001178 *path1 = xstrdup(argv[optidx]);
1179 /* Get second pathname (optional) */
1180 if (argc - optidx > 1) {
1181 *path2 = xstrdup(argv[optidx + 1]);
1182 /* Destination is not globbed */
1183 undo_glob_escape(*path2);
1184 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001185 break;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001186 case I_LINK:
1187 if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1188 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001189 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001190 case I_RENAME:
Damien Miller1cbc2922007-10-26 14:27:45 +10001191 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001192 error("You must specify two paths after a %s "
1193 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001194 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001195 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001196 *path1 = xstrdup(argv[optidx]);
1197 *path2 = xstrdup(argv[optidx + 1]);
1198 /* Paths are not globbed */
1199 undo_glob_escape(*path1);
1200 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001201 break;
1202 case I_RM:
1203 case I_MKDIR:
1204 case I_RMDIR:
1205 case I_CHDIR:
1206 case I_LCHDIR:
1207 case I_LMKDIR:
1208 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001209 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001210 error("You must specify a path after a %s command.",
1211 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001212 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001213 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001214 *path1 = xstrdup(argv[optidx]);
1215 /* Only "rm" globs */
1216 if (cmdnum != I_RM)
1217 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001218 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001219 case I_DF:
1220 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1221 iflag)) == -1)
1222 return -1;
1223 /* Default to current directory if no path specified */
1224 if (argc - optidx < 1)
1225 *path1 = NULL;
1226 else {
1227 *path1 = xstrdup(argv[optidx]);
1228 undo_glob_escape(*path1);
1229 }
1230 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001231 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001232 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001233 return(-1);
1234 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001235 if (argc - optidx > 0)
1236 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001237 break;
1238 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001239 /* Skip ls command and following whitespace */
1240 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001241 case I_SHELL:
1242 /* Uses the rest of the line */
1243 break;
1244 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001245 case I_CHMOD:
1246 base = 8;
1247 case I_CHOWN:
1248 case I_CHGRP:
1249 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001250 if (argc - optidx < 1)
1251 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001252 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001253 l = strtol(argv[optidx], &cp2, base);
1254 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1255 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1256 l < 0) {
1257 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001258 error("You must supply a numeric argument "
1259 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001260 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001261 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001262 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001263 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001264 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001265 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001266 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001267 error("You must specify a path after a %s command.",
1268 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001269 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001270 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001271 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001272 break;
1273 case I_QUIT:
1274 case I_PWD:
1275 case I_LPWD:
1276 case I_HELP:
1277 case I_VERSION:
1278 case I_PROGRESS:
1279 break;
1280 default:
1281 fatal("Command not implemented");
1282 }
1283
1284 *cpp = cp;
1285 return(cmdnum);
1286}
1287
1288static int
1289parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1290 int err_abort)
1291{
1292 char *path1, *path2, *tmp;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001293 int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, sflag = 0;
1294 int cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001295 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001296 Attrib a, *aa;
1297 char path_buf[MAXPATHLEN];
1298 int err = 0;
1299 glob_t g;
1300
1301 path1 = path2 = NULL;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001302 cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag,
1303 &sflag, &n_arg, &path1, &path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001304
1305 if (iflag != 0)
1306 err_abort = 0;
1307
1308 memset(&g, 0, sizeof(g));
1309
1310 /* Perform command */
1311 switch (cmdnum) {
1312 case 0:
1313 /* Blank line */
1314 break;
1315 case -1:
1316 /* Unrecognized command */
1317 err = -1;
1318 break;
1319 case I_GET:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001320 err = process_get(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001321 break;
1322 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001323 err = process_put(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001324 break;
1325 case I_RENAME:
1326 path1 = make_absolute(path1, *pwd);
1327 path2 = make_absolute(path2, *pwd);
1328 err = do_rename(conn, path1, path2);
1329 break;
1330 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001331 sflag = 1;
1332 case I_LINK:
1333 path1 = make_absolute(path1, *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001334 path2 = make_absolute(path2, *pwd);
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001335 err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001336 break;
1337 case I_RM:
1338 path1 = make_absolute(path1, *pwd);
1339 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001340 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001341 if (!quiet)
1342 printf("Removing %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001343 err = do_rm(conn, g.gl_pathv[i]);
1344 if (err != 0 && err_abort)
1345 break;
1346 }
1347 break;
1348 case I_MKDIR:
1349 path1 = make_absolute(path1, *pwd);
1350 attrib_clear(&a);
1351 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1352 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001353 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001354 break;
1355 case I_RMDIR:
1356 path1 = make_absolute(path1, *pwd);
1357 err = do_rmdir(conn, path1);
1358 break;
1359 case I_CHDIR:
1360 path1 = make_absolute(path1, *pwd);
1361 if ((tmp = do_realpath(conn, path1)) == NULL) {
1362 err = 1;
1363 break;
1364 }
1365 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
Darren Tuckera627d422013-06-02 07:31:17 +10001366 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001367 err = 1;
1368 break;
1369 }
1370 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1371 error("Can't change directory: Can't check target");
Darren Tuckera627d422013-06-02 07:31:17 +10001372 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001373 err = 1;
1374 break;
1375 }
1376 if (!S_ISDIR(aa->perm)) {
1377 error("Can't change directory: \"%s\" is not "
1378 "a directory", tmp);
Darren Tuckera627d422013-06-02 07:31:17 +10001379 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001380 err = 1;
1381 break;
1382 }
Darren Tuckera627d422013-06-02 07:31:17 +10001383 free(*pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001384 *pwd = tmp;
1385 break;
1386 case I_LS:
1387 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001388 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001389 break;
1390 }
1391
1392 /* Strip pwd off beginning of non-absolute paths */
1393 tmp = NULL;
1394 if (*path1 != '/')
1395 tmp = *pwd;
1396
1397 path1 = make_absolute(path1, *pwd);
1398 err = do_globbed_ls(conn, path1, tmp, lflag);
1399 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001400 case I_DF:
1401 /* Default to current directory if no path specified */
1402 if (path1 == NULL)
1403 path1 = xstrdup(*pwd);
1404 path1 = make_absolute(path1, *pwd);
1405 err = do_df(conn, path1, hflag, iflag);
1406 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001407 case I_LCHDIR:
1408 if (chdir(path1) == -1) {
1409 error("Couldn't change local directory to "
1410 "\"%s\": %s", path1, strerror(errno));
1411 err = 1;
1412 }
1413 break;
1414 case I_LMKDIR:
1415 if (mkdir(path1, 0777) == -1) {
1416 error("Couldn't create local directory "
1417 "\"%s\": %s", path1, strerror(errno));
1418 err = 1;
1419 }
1420 break;
1421 case I_LLS:
1422 local_do_ls(cmd);
1423 break;
1424 case I_SHELL:
1425 local_do_shell(cmd);
1426 break;
1427 case I_LUMASK:
1428 umask(n_arg);
1429 printf("Local umask: %03lo\n", n_arg);
1430 break;
1431 case I_CHMOD:
1432 path1 = make_absolute(path1, *pwd);
1433 attrib_clear(&a);
1434 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1435 a.perm = n_arg;
1436 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001437 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001438 if (!quiet)
1439 printf("Changing mode on %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001440 err = do_setstat(conn, g.gl_pathv[i], &a);
1441 if (err != 0 && err_abort)
1442 break;
1443 }
1444 break;
1445 case I_CHOWN:
1446 case I_CHGRP:
1447 path1 = make_absolute(path1, *pwd);
1448 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001449 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001450 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001451 if (err_abort) {
1452 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001453 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001454 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001455 continue;
1456 }
1457 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1458 error("Can't get current ownership of "
1459 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001460 if (err_abort) {
1461 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001462 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001463 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001464 continue;
1465 }
1466 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1467 if (cmdnum == I_CHOWN) {
Damien Miller9303e652013-04-23 15:22:40 +10001468 if (!quiet)
1469 printf("Changing owner on %s\n",
1470 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001471 aa->uid = n_arg;
1472 } else {
Damien Miller9303e652013-04-23 15:22:40 +10001473 if (!quiet)
1474 printf("Changing group on %s\n",
1475 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001476 aa->gid = n_arg;
1477 }
1478 err = do_setstat(conn, g.gl_pathv[i], aa);
1479 if (err != 0 && err_abort)
1480 break;
1481 }
1482 break;
1483 case I_PWD:
1484 printf("Remote working directory: %s\n", *pwd);
1485 break;
1486 case I_LPWD:
1487 if (!getcwd(path_buf, sizeof(path_buf))) {
1488 error("Couldn't get local cwd: %s", strerror(errno));
1489 err = -1;
1490 break;
1491 }
1492 printf("Local working directory: %s\n", path_buf);
1493 break;
1494 case I_QUIT:
1495 /* Processed below */
1496 break;
1497 case I_HELP:
1498 help();
1499 break;
1500 case I_VERSION:
1501 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1502 break;
1503 case I_PROGRESS:
1504 showprogress = !showprogress;
1505 if (showprogress)
1506 printf("Progress meter enabled\n");
1507 else
1508 printf("Progress meter disabled\n");
1509 break;
1510 default:
1511 fatal("%d is not implemented", cmdnum);
1512 }
1513
1514 if (g.gl_pathc)
1515 globfree(&g);
Darren Tuckera627d422013-06-02 07:31:17 +10001516 free(path1);
1517 free(path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001518
1519 /* If an unignored error occurs in batch mode we should abort. */
1520 if (err_abort && err != 0)
1521 return (-1);
1522 else if (cmdnum == I_QUIT)
1523 return (1);
1524
1525 return (0);
1526}
1527
Darren Tucker2d963d82004-11-07 20:04:10 +11001528#ifdef USE_LIBEDIT
1529static char *
1530prompt(EditLine *el)
1531{
1532 return ("sftp> ");
1533}
Darren Tucker2d963d82004-11-07 20:04:10 +11001534
Darren Tucker909d8582010-01-08 19:02:40 +11001535/* Display entries in 'list' after skipping the first 'len' chars */
1536static void
1537complete_display(char **list, u_int len)
1538{
1539 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1540 struct winsize ws;
1541 char *tmp;
1542
1543 /* Count entries for sort and find longest */
1544 for (y = 0; list[y]; y++)
1545 m = MAX(m, strlen(list[y]));
1546
1547 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1548 width = ws.ws_col;
1549
1550 m = m > len ? m - len : 0;
1551 columns = width / (m + 2);
1552 columns = MAX(columns, 1);
1553 colspace = width / columns;
1554 colspace = MIN(colspace, width);
1555
1556 printf("\n");
1557 m = 1;
1558 for (y = 0; list[y]; y++) {
1559 llen = strlen(list[y]);
1560 tmp = llen > len ? list[y] + len : "";
1561 printf("%-*s", colspace, tmp);
1562 if (m >= columns) {
1563 printf("\n");
1564 m = 1;
1565 } else
1566 m++;
1567 }
1568 printf("\n");
1569}
1570
1571/*
1572 * Given a "list" of words that begin with a common prefix of "word",
1573 * attempt to find an autocompletion to extends "word" by the next
1574 * characters common to all entries in "list".
1575 */
1576static char *
1577complete_ambiguous(const char *word, char **list, size_t count)
1578{
1579 if (word == NULL)
1580 return NULL;
1581
1582 if (count > 0) {
1583 u_int y, matchlen = strlen(list[0]);
1584
1585 /* Find length of common stem */
1586 for (y = 1; list[y]; y++) {
1587 u_int x;
1588
1589 for (x = 0; x < matchlen; x++)
1590 if (list[0][x] != list[y][x])
1591 break;
1592
1593 matchlen = x;
1594 }
1595
1596 if (matchlen > strlen(word)) {
1597 char *tmp = xstrdup(list[0]);
1598
Darren Tucker340d1682010-01-09 08:54:31 +11001599 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001600 return tmp;
1601 }
1602 }
1603
1604 return xstrdup(word);
1605}
1606
1607/* Autocomplete a sftp command */
1608static int
1609complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1610 int terminated)
1611{
1612 u_int y, count = 0, cmdlen, tmplen;
1613 char *tmp, **list, argterm[3];
1614 const LineInfo *lf;
1615
1616 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1617
1618 /* No command specified: display all available commands */
1619 if (cmd == NULL) {
1620 for (y = 0; cmds[y].c; y++)
1621 list[count++] = xstrdup(cmds[y].c);
1622
1623 list[count] = NULL;
1624 complete_display(list, 0);
1625
1626 for (y = 0; list[y] != NULL; y++)
Darren Tuckera627d422013-06-02 07:31:17 +10001627 free(list[y]);
1628 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001629 return count;
1630 }
1631
1632 /* Prepare subset of commands that start with "cmd" */
1633 cmdlen = strlen(cmd);
1634 for (y = 0; cmds[y].c; y++) {
1635 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
1636 list[count++] = xstrdup(cmds[y].c);
1637 }
1638 list[count] = NULL;
1639
Damien Miller47d81152011-11-25 13:53:48 +11001640 if (count == 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10001641 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001642 return 0;
Damien Miller47d81152011-11-25 13:53:48 +11001643 }
Darren Tucker909d8582010-01-08 19:02:40 +11001644
1645 /* Complete ambigious command */
1646 tmp = complete_ambiguous(cmd, list, count);
1647 if (count > 1)
1648 complete_display(list, 0);
1649
1650 for (y = 0; list[y]; y++)
Darren Tuckera627d422013-06-02 07:31:17 +10001651 free(list[y]);
1652 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001653
1654 if (tmp != NULL) {
1655 tmplen = strlen(tmp);
1656 cmdlen = strlen(cmd);
1657 /* If cmd may be extended then do so */
1658 if (tmplen > cmdlen)
1659 if (el_insertstr(el, tmp + cmdlen) == -1)
1660 fatal("el_insertstr failed.");
1661 lf = el_line(el);
1662 /* Terminate argument cleanly */
1663 if (count == 1) {
1664 y = 0;
1665 if (!terminated)
1666 argterm[y++] = quote;
1667 if (lastarg || *(lf->cursor) != ' ')
1668 argterm[y++] = ' ';
1669 argterm[y] = '\0';
1670 if (y > 0 && el_insertstr(el, argterm) == -1)
1671 fatal("el_insertstr failed.");
1672 }
Darren Tuckera627d422013-06-02 07:31:17 +10001673 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001674 }
1675
1676 return count;
1677}
1678
1679/*
1680 * Determine whether a particular sftp command's arguments (if any)
1681 * represent local or remote files.
1682 */
1683static int
1684complete_is_remote(char *cmd) {
1685 int i;
1686
1687 if (cmd == NULL)
1688 return -1;
1689
1690 for (i = 0; cmds[i].c; i++) {
1691 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
1692 return cmds[i].t;
1693 }
1694
1695 return -1;
1696}
1697
1698/* Autocomplete a filename "file" */
1699static int
1700complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1701 char *file, int remote, int lastarg, char quote, int terminated)
1702{
1703 glob_t g;
1704 char *tmp, *tmp2, ins[3];
Darren Tucker17146d32012-10-05 10:46:16 +10001705 u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
Darren Tucker909d8582010-01-08 19:02:40 +11001706 const LineInfo *lf;
1707
1708 /* Glob from "file" location */
1709 if (file == NULL)
1710 tmp = xstrdup("*");
1711 else
1712 xasprintf(&tmp, "%s*", file);
1713
Darren Tucker191fcc62012-10-05 10:45:01 +10001714 /* Check if the path is absolute. */
1715 isabs = tmp[0] == '/';
1716
Darren Tucker909d8582010-01-08 19:02:40 +11001717 memset(&g, 0, sizeof(g));
1718 if (remote != LOCAL) {
1719 tmp = make_absolute(tmp, remote_path);
1720 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1721 } else
1722 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1723
1724 /* Determine length of pwd so we can trim completion display */
1725 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1726 /* Terminate counting on first unescaped glob metacharacter */
1727 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1728 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1729 hadglob = 1;
1730 break;
1731 }
1732 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1733 tmplen++;
1734 if (tmp[tmplen] == '/')
1735 pwdlen = tmplen + 1; /* track last seen '/' */
1736 }
Darren Tuckera627d422013-06-02 07:31:17 +10001737 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001738
1739 if (g.gl_matchc == 0)
1740 goto out;
1741
1742 if (g.gl_matchc > 1)
1743 complete_display(g.gl_pathv, pwdlen);
1744
1745 tmp = NULL;
1746 /* Don't try to extend globs */
1747 if (file == NULL || hadglob)
1748 goto out;
1749
1750 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
Darren Tucker191fcc62012-10-05 10:45:01 +10001751 tmp = path_strip(tmp2, isabs ? NULL : remote_path);
Darren Tuckera627d422013-06-02 07:31:17 +10001752 free(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001753
1754 if (tmp == NULL)
1755 goto out;
1756
1757 tmplen = strlen(tmp);
1758 filelen = strlen(file);
1759
Darren Tucker17146d32012-10-05 10:46:16 +10001760 /* Count the number of escaped characters in the input string. */
1761 cesc = isesc = 0;
1762 for (i = 0; i < filelen; i++) {
1763 if (!isesc && file[i] == '\\' && i + 1 < filelen){
1764 isesc = 1;
1765 cesc++;
1766 } else
1767 isesc = 0;
1768 }
1769
1770 if (tmplen > (filelen - cesc)) {
1771 tmp2 = tmp + filelen - cesc;
Darren Tucker909d8582010-01-08 19:02:40 +11001772 len = strlen(tmp2);
1773 /* quote argument on way out */
1774 for (i = 0; i < len; i++) {
1775 ins[0] = '\\';
1776 ins[1] = tmp2[i];
1777 ins[2] = '\0';
1778 switch (tmp2[i]) {
1779 case '\'':
1780 case '"':
1781 case '\\':
1782 case '\t':
Darren Tuckerd78739a2010-10-24 10:56:32 +11001783 case '[':
Darren Tucker909d8582010-01-08 19:02:40 +11001784 case ' ':
Darren Tucker17146d32012-10-05 10:46:16 +10001785 case '#':
1786 case '*':
Darren Tucker909d8582010-01-08 19:02:40 +11001787 if (quote == '\0' || tmp2[i] == quote) {
1788 if (el_insertstr(el, ins) == -1)
1789 fatal("el_insertstr "
1790 "failed.");
1791 break;
1792 }
1793 /* FALLTHROUGH */
1794 default:
1795 if (el_insertstr(el, ins + 1) == -1)
1796 fatal("el_insertstr failed.");
1797 break;
1798 }
1799 }
1800 }
1801
1802 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11001803 if (g.gl_matchc == 1) {
1804 i = 0;
1805 if (!terminated)
1806 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11001807 if (*(lf->cursor - 1) != '/' &&
1808 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11001809 ins[i++] = ' ';
1810 ins[i] = '\0';
1811 if (i > 0 && el_insertstr(el, ins) == -1)
1812 fatal("el_insertstr failed.");
1813 }
Darren Tuckera627d422013-06-02 07:31:17 +10001814 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001815
1816 out:
1817 globfree(&g);
1818 return g.gl_matchc;
1819}
1820
1821/* tab-completion hook function, called via libedit */
1822static unsigned char
1823complete(EditLine *el, int ch)
1824{
1825 char **argv, *line, quote;
1826 u_int argc, carg, cursor, len, terminated, ret = CC_ERROR;
1827 const LineInfo *lf;
1828 struct complete_ctx *complete_ctx;
1829
1830 lf = el_line(el);
1831 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1832 fatal("%s: el_get failed", __func__);
1833
1834 /* Figure out which argument the cursor points to */
1835 cursor = lf->cursor - lf->buffer;
1836 line = (char *)xmalloc(cursor + 1);
1837 memcpy(line, lf->buffer, cursor);
1838 line[cursor] = '\0';
1839 argv = makeargv(line, &carg, 1, &quote, &terminated);
Darren Tuckera627d422013-06-02 07:31:17 +10001840 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001841
1842 /* Get all the arguments on the line */
1843 len = lf->lastchar - lf->buffer;
1844 line = (char *)xmalloc(len + 1);
1845 memcpy(line, lf->buffer, len);
1846 line[len] = '\0';
1847 argv = makeargv(line, &argc, 1, NULL, NULL);
1848
1849 /* Ensure cursor is at EOL or a argument boundary */
1850 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1851 line[cursor] != '\n') {
Darren Tuckera627d422013-06-02 07:31:17 +10001852 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001853 return ret;
1854 }
1855
1856 if (carg == 0) {
1857 /* Show all available commands */
1858 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1859 ret = CC_REDISPLAY;
1860 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1861 /* Handle the command parsing */
1862 if (complete_cmd_parse(el, argv[0], argc == carg,
1863 quote, terminated) != 0)
1864 ret = CC_REDISPLAY;
1865 } else if (carg >= 1) {
1866 /* Handle file parsing */
1867 int remote = complete_is_remote(argv[0]);
1868 char *filematch = NULL;
1869
1870 if (carg > 1 && line[cursor-1] != ' ')
1871 filematch = argv[carg - 1];
1872
1873 if (remote != 0 &&
1874 complete_match(el, complete_ctx->conn,
1875 *complete_ctx->remote_pathp, filematch,
1876 remote, carg == argc, quote, terminated) != 0)
1877 ret = CC_REDISPLAY;
1878 }
1879
Darren Tuckera627d422013-06-02 07:31:17 +10001880 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001881 return ret;
1882}
Darren Tuckere67f7db2010-01-08 19:50:02 +11001883#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11001884
Damien Miller20e1fab2004-02-18 14:30:55 +11001885int
Darren Tucker21063192010-01-08 17:10:36 +11001886interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001887{
Darren Tucker909d8582010-01-08 19:02:40 +11001888 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11001889 char *dir = NULL;
1890 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10001891 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11001892 EditLine *el = NULL;
1893#ifdef USE_LIBEDIT
1894 History *hl = NULL;
1895 HistEvent hev;
1896 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11001897 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11001898
1899 if (!batchmode && isatty(STDIN_FILENO)) {
1900 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1901 fatal("Couldn't initialise editline");
1902 if ((hl = history_init()) == NULL)
1903 fatal("Couldn't initialise editline history");
1904 history(hl, &hev, H_SETSIZE, 100);
1905 el_set(el, EL_HIST, history, hl);
1906
1907 el_set(el, EL_PROMPT, prompt);
1908 el_set(el, EL_EDITOR, "emacs");
1909 el_set(el, EL_TERMINAL, NULL);
1910 el_set(el, EL_SIGNAL, 1);
1911 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11001912
1913 /* Tab Completion */
1914 el_set(el, EL_ADDFN, "ftp-complete",
Darren Tuckerd78739a2010-10-24 10:56:32 +11001915 "Context sensitive argument completion", complete);
Darren Tucker909d8582010-01-08 19:02:40 +11001916 complete_ctx.conn = conn;
1917 complete_ctx.remote_pathp = &remote_path;
1918 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
1919 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11001920 }
1921#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001922
Darren Tucker909d8582010-01-08 19:02:40 +11001923 remote_path = do_realpath(conn, ".");
1924 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11001925 fatal("Need cwd");
1926
1927 if (file1 != NULL) {
1928 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11001929 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11001930
1931 if (remote_is_dir(conn, dir) && file2 == NULL) {
Damien Miller9303e652013-04-23 15:22:40 +10001932 if (!quiet)
1933 printf("Changing to: %s\n", dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11001934 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001935 if (parse_dispatch_command(conn, cmd,
1936 &remote_path, 1) != 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10001937 free(dir);
1938 free(remote_path);
1939 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001940 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11001941 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001942 } else {
Darren Tucker0af24052012-10-05 10:41:25 +10001943 /* XXX this is wrong wrt quoting */
Damien Miller20e1fab2004-02-18 14:30:55 +11001944 if (file2 == NULL)
1945 snprintf(cmd, sizeof cmd, "get %s", dir);
1946 else
1947 snprintf(cmd, sizeof cmd, "get %s %s", dir,
1948 file2);
1949
Darren Tucker909d8582010-01-08 19:02:40 +11001950 err = parse_dispatch_command(conn, cmd,
1951 &remote_path, 1);
Darren Tuckera627d422013-06-02 07:31:17 +10001952 free(dir);
1953 free(remote_path);
1954 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001955 return (err);
1956 }
Darren Tuckera627d422013-06-02 07:31:17 +10001957 free(dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11001958 }
1959
Damien Miller37294fb2005-07-17 17:18:49 +10001960 setlinebuf(stdout);
1961 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11001962
Damien Miller0e2c1022005-08-12 22:16:22 +10001963 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11001964 err = 0;
1965 for (;;) {
1966 char *cp;
1967
Darren Tuckercdf547a2004-05-24 10:12:19 +10001968 signal(SIGINT, SIG_IGN);
1969
Darren Tucker2d963d82004-11-07 20:04:10 +11001970 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001971 if (interactive)
1972 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11001973 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001974 if (interactive)
1975 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11001976 break;
1977 }
Damien Miller0e2c1022005-08-12 22:16:22 +10001978 if (!interactive) { /* Echo command */
1979 printf("sftp> %s", cmd);
1980 if (strlen(cmd) > 0 &&
1981 cmd[strlen(cmd) - 1] != '\n')
1982 printf("\n");
1983 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001984 } else {
1985#ifdef USE_LIBEDIT
1986 const char *line;
1987 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001988
Darren Tucker909d8582010-01-08 19:02:40 +11001989 if ((line = el_gets(el, &count)) == NULL ||
1990 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001991 printf("\n");
1992 break;
1993 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001994 history(hl, &hev, H_ENTER, line);
1995 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
1996 fprintf(stderr, "Error: input line too long\n");
1997 continue;
1998 }
1999#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002000 }
2001
Damien Miller20e1fab2004-02-18 14:30:55 +11002002 cp = strrchr(cmd, '\n');
2003 if (cp)
2004 *cp = '\0';
2005
Darren Tuckercdf547a2004-05-24 10:12:19 +10002006 /* Handle user interrupts gracefully during commands */
2007 interrupted = 0;
2008 signal(SIGINT, cmd_interrupt);
2009
Darren Tucker909d8582010-01-08 19:02:40 +11002010 err = parse_dispatch_command(conn, cmd, &remote_path,
2011 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11002012 if (err != 0)
2013 break;
2014 }
Darren Tuckera627d422013-06-02 07:31:17 +10002015 free(remote_path);
2016 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002017
Tim Rice027e8b12005-08-15 14:52:50 -07002018#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10002019 if (el != NULL)
2020 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07002021#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10002022
Damien Miller20e1fab2004-02-18 14:30:55 +11002023 /* err == 1 signifies normal "quit" exit */
2024 return (err >= 0 ? 0 : -1);
2025}
Damien Miller62d57f62003-01-10 21:43:24 +11002026
Ben Lindstrombba81212001-06-25 05:01:22 +00002027static void
Damien Millercc685c12003-06-04 22:51:38 +10002028connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11002029{
2030 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002031
Damien Miller33804262001-02-04 23:20:18 +11002032#ifdef USE_PIPES
2033 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002034
Damien Miller33804262001-02-04 23:20:18 +11002035 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2036 fatal("pipe: %s", strerror(errno));
2037 *in = pin[0];
2038 *out = pout[1];
2039 c_in = pout[0];
2040 c_out = pin[1];
2041#else /* USE_PIPES */
2042 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002043
Damien Miller33804262001-02-04 23:20:18 +11002044 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2045 fatal("socketpair: %s", strerror(errno));
2046 *in = *out = inout[0];
2047 c_in = c_out = inout[1];
2048#endif /* USE_PIPES */
2049
Damien Millercc685c12003-06-04 22:51:38 +10002050 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002051 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002052 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002053 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2054 (dup2(c_out, STDOUT_FILENO) == -1)) {
2055 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002056 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002057 }
2058 close(*in);
2059 close(*out);
2060 close(c_in);
2061 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002062
2063 /*
2064 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002065 * ignore SIGINT if we want to gracefully abort commands,
2066 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002067 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2068 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002069 */
2070 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002071 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002072 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002073 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002074 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002075 }
2076
Damien Millercc685c12003-06-04 22:51:38 +10002077 signal(SIGTERM, killchild);
2078 signal(SIGINT, killchild);
2079 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11002080 close(c_in);
2081 close(c_out);
2082}
2083
Ben Lindstrombba81212001-06-25 05:01:22 +00002084static void
Damien Miller33804262001-02-04 23:20:18 +11002085usage(void)
2086{
Damien Miller025e01c2002-02-08 22:06:29 +11002087 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002088
Ben Lindstrom1e243242001-09-18 05:38:44 +00002089 fprintf(stderr,
Darren Tucker1b0dd172009-10-07 08:37:48 +11002090 "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002091 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002092 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002093 " [-o ssh_option] [-P port] [-R num_requests] "
2094 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002095 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002096 " %s [user@]host[:file ...]\n"
2097 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002098 " %s -b batchfile [user@]host\n",
2099 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002100 exit(1);
2101}
2102
Kevin Stevesef4eea92001-02-05 12:42:17 +00002103int
Damien Miller33804262001-02-04 23:20:18 +11002104main(int argc, char **argv)
2105{
Damien Miller956f3fb2003-01-10 21:40:00 +11002106 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002107 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002108 int debug_level = 0, sshver = 2;
2109 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002110 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002111 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002112 LogLevel ll = SYSLOG_LEVEL_INFO;
2113 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002114 extern int optind;
2115 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002116 struct sftp_conn *conn;
2117 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2118 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002119 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002120
Darren Tuckerce321d82005-10-03 18:11:24 +10002121 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2122 sanitise_stdfd();
2123
Damien Miller59d3d5b2003-08-22 09:34:41 +10002124 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002125 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002126 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002127 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002128 addargs(&args, "-oForwardX11 no");
2129 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002130 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002131 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002132
Ben Lindstrom387c4722001-05-08 20:27:25 +00002133 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002134 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002135
Darren Tucker282b4022009-10-07 08:23:06 +11002136 while ((ch = getopt(argc, argv,
Damien Miller65e42f82010-09-24 22:15:11 +10002137 "1246hpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002138 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002139 /* Passed through to ssh(1) */
2140 case '4':
2141 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002142 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002143 addargs(&args, "-%c", ch);
2144 break;
2145 /* Passed through to ssh(1) with argument */
2146 case 'F':
2147 case 'c':
2148 case 'i':
2149 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002150 addargs(&args, "-%c", ch);
2151 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002152 break;
2153 case 'q':
Damien Miller9303e652013-04-23 15:22:40 +10002154 ll = SYSLOG_LEVEL_ERROR;
2155 quiet = 1;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002156 showprogress = 0;
2157 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002158 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002159 case 'P':
2160 addargs(&args, "-oPort %s", optarg);
2161 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002162 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002163 if (debug_level < 3) {
2164 addargs(&args, "-v");
2165 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2166 }
2167 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002168 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002169 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002170 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002171 if (sftp_server == NULL)
2172 sftp_server = _PATH_SFTP_SERVER;
2173 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002174 case '2':
2175 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002176 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002177 case 'B':
2178 copy_buffer_len = strtol(optarg, &cp, 10);
2179 if (copy_buffer_len == 0 || *cp != '\0')
2180 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002181 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002182 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002183 if (batchmode)
2184 fatal("Batch file already specified.");
2185
2186 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002187 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002188 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002189 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002190 showprogress = 0;
Damien Miller9303e652013-04-23 15:22:40 +10002191 quiet = batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002192 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002193 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002194 case 'p':
2195 global_pflag = 1;
2196 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002197 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002198 sftp_direct = optarg;
2199 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002200 case 'l':
2201 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2202 &errstr);
2203 if (errstr != NULL)
2204 usage();
2205 limit_kbps *= 1024; /* kbps */
2206 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002207 case 'r':
2208 global_rflag = 1;
2209 break;
Damien Miller16a13332002-02-13 14:03:56 +11002210 case 'R':
2211 num_requests = strtol(optarg, &cp, 10);
2212 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002213 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002214 optarg);
2215 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002216 case 's':
2217 sftp_server = optarg;
2218 break;
2219 case 'S':
2220 ssh_program = optarg;
2221 replacearg(&args, 0, "%s", ssh_program);
2222 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002223 case 'h':
2224 default:
Damien Miller33804262001-02-04 23:20:18 +11002225 usage();
2226 }
2227 }
2228
Damien Millerc0f27d82004-03-08 23:12:19 +11002229 if (!isatty(STDERR_FILENO))
2230 showprogress = 0;
2231
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002232 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2233
Damien Millerd14ee1e2002-02-05 12:27:31 +11002234 if (sftp_direct == NULL) {
2235 if (optind == argc || argc > (optind + 2))
2236 usage();
Damien Miller33804262001-02-04 23:20:18 +11002237
Damien Millerd14ee1e2002-02-05 12:27:31 +11002238 userhost = xstrdup(argv[optind]);
2239 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002240
Ben Lindstromc276c122002-12-23 02:14:51 +00002241 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002242 host = userhost;
2243 else {
2244 *host++ = '\0';
2245 if (!userhost[0]) {
2246 fprintf(stderr, "Missing username\n");
2247 usage();
2248 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002249 addargs(&args, "-l");
2250 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002251 }
2252
Damien Millerec692032004-01-27 21:22:00 +11002253 if ((cp = colon(host)) != NULL) {
2254 *cp++ = '\0';
2255 file1 = cp;
2256 }
2257
Damien Millerd14ee1e2002-02-05 12:27:31 +11002258 host = cleanhostname(host);
2259 if (!*host) {
2260 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002261 usage();
2262 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002263
Damien Millerd14ee1e2002-02-05 12:27:31 +11002264 addargs(&args, "-oProtocol %d", sshver);
2265
2266 /* no subsystem if the server-spec contains a '/' */
2267 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2268 addargs(&args, "-s");
2269
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002270 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002271 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002272 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002273 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002274
Damien Millercc685c12003-06-04 22:51:38 +10002275 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002276 } else {
2277 args.list = NULL;
2278 addargs(&args, "sftp-server");
2279
Damien Millercc685c12003-06-04 22:51:38 +10002280 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002281 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002282 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002283
Damien Miller65e42f82010-09-24 22:15:11 +10002284 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002285 if (conn == NULL)
2286 fatal("Couldn't initialise connection to server");
2287
Damien Miller9303e652013-04-23 15:22:40 +10002288 if (!quiet) {
Darren Tucker21063192010-01-08 17:10:36 +11002289 if (sftp_direct == NULL)
2290 fprintf(stderr, "Connected to %s.\n", host);
2291 else
2292 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2293 }
2294
2295 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002296
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002297#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002298 shutdown(in, SHUT_RDWR);
2299 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002300#endif
2301
Damien Miller33804262001-02-04 23:20:18 +11002302 close(in);
2303 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002304 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002305 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002306
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002307 while (waitpid(sshpid, NULL, 0) == -1)
2308 if (errno != EINTR)
2309 fatal("Couldn't wait for ssh process: %s",
2310 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002311
Damien Miller956f3fb2003-01-10 21:40:00 +11002312 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002313}