blob: 12c4958d907515102e52b948b6c740f91265c0f8 [file] [log] [blame]
Darren Tuckerdbee3082013-05-16 20:32:29 +10001/* $OpenBSD: sftp.c,v 1.144 2013/05/16 09:08:41 dtucker Exp $ */
Damien Miller33804262001-02-04 23:20:18 +11002/*
Damien Miller4e60ed72004-02-17 17:07:59 +11003 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
Damien Miller33804262001-02-04 23:20:18 +11004 *
Damien Miller4e60ed72004-02-17 17:07:59 +11005 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
Damien Miller33804262001-02-04 23:20:18 +11008 *
Damien Miller4e60ed72004-02-17 17:07:59 +11009 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Damien Miller33804262001-02-04 23:20:18 +110016 */
17
18#include "includes.h"
19
Damien Miller9cf6d072006-03-15 11:29:24 +110020#include <sys/types.h>
Damien Millerd7834352006-08-05 12:39:39 +100021#include <sys/ioctl.h>
Damien Millerf17883e2006-03-15 11:45:54 +110022#ifdef HAVE_SYS_STAT_H
23# include <sys/stat.h>
24#endif
Damien Miller8dbffe72006-08-05 11:02:17 +100025#include <sys/param.h>
Damien Millere3b60b52006-07-10 21:08:03 +100026#include <sys/socket.h>
Damien Miller9cf6d072006-03-15 11:29:24 +110027#include <sys/wait.h>
Darren Tucker5b2e2ba2008-06-08 09:25:28 +100028#ifdef HAVE_SYS_STATVFS_H
Damien Millerd671e5a2008-05-19 14:53:33 +100029#include <sys/statvfs.h>
Darren Tucker5b2e2ba2008-06-08 09:25:28 +100030#endif
Darren Tucker2d963d82004-11-07 20:04:10 +110031
Damien Miller1cbc2922007-10-26 14:27:45 +100032#include <ctype.h>
Darren Tucker39972492006-07-12 22:22:46 +100033#include <errno.h>
34
Damien Miller03e20032006-03-15 11:16:59 +110035#ifdef HAVE_PATHS_H
Damien Millera9263d02006-03-15 11:18:26 +110036# include <paths.h>
Damien Miller03e20032006-03-15 11:16:59 +110037#endif
Darren Tucker1b0dd172009-10-07 08:37:48 +110038#ifdef HAVE_LIBGEN_H
39#include <libgen.h>
40#endif
Darren 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);
312 xfree(buf);
313 }
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);
Damien Miller20e1fab2004-02-18 14:30:55 +1100343 xfree(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));
553 xfree(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 }
569 xfree(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 }
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
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:
671 if (abs_dst)
672 xfree(abs_dst);
673 if (tmp_dst)
674 xfree(tmp_dst);
675 globfree(&g);
676 return(err);
677}
678
679static int
680sdirent_comp(const void *aa, const void *bb)
681{
682 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
683 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000684 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100685
Darren Tuckerb9123452004-06-22 13:06:45 +1000686#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000687 if (sort_flag & LS_NAME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000688 return (rmul * strcmp(a->filename, b->filename));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000689 else if (sort_flag & LS_TIME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000690 return (rmul * NCMP(a->a.mtime, b->a.mtime));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000691 else if (sort_flag & LS_SIZE_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000692 return (rmul * NCMP(a->a.size, b->a.size));
693
694 fatal("Unknown ls sort type");
Damien Miller20e1fab2004-02-18 14:30:55 +1100695}
696
697/* sftp ls.1 replacement for directories */
698static int
699do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
700{
Damien Millereccb9de2005-06-17 12:59:34 +1000701 int n;
702 u_int c = 1, colspace = 0, columns = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100703 SFTP_DIRENT **d;
704
705 if ((n = do_readdir(conn, path, &d)) != 0)
706 return (n);
707
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000708 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000709 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100710 struct winsize ws;
711 char *tmp;
712
713 /* Count entries for sort and find longest filename */
Darren Tucker9a526452004-06-22 13:09:55 +1000714 for (n = 0; d[n] != NULL; n++) {
715 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
716 m = MAX(m, strlen(d[n]->filename));
717 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100718
719 /* Add any subpath that also needs to be counted */
720 tmp = path_strip(path, strip_path);
721 m += strlen(tmp);
722 xfree(tmp);
723
724 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
725 width = ws.ws_col;
726
727 columns = width / (m + 2);
728 columns = MAX(columns, 1);
729 colspace = width / columns;
730 colspace = MIN(colspace, width);
731 }
732
Darren Tuckerb9123452004-06-22 13:06:45 +1000733 if (lflag & SORT_FLAGS) {
Damien Miller653b93b2005-11-05 15:15:23 +1100734 for (n = 0; d[n] != NULL; n++)
735 ; /* count entries */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000736 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
Darren Tuckerb9123452004-06-22 13:06:45 +1000737 qsort(d, n, sizeof(*d), sdirent_comp);
738 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100739
Darren Tuckercdf547a2004-05-24 10:12:19 +1000740 for (n = 0; d[n] != NULL && !interrupted; n++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100741 char *tmp, *fname;
742
Darren Tucker9a526452004-06-22 13:09:55 +1000743 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
744 continue;
745
Damien Miller20e1fab2004-02-18 14:30:55 +1100746 tmp = path_append(path, d[n]->filename);
747 fname = path_strip(tmp, strip_path);
748 xfree(tmp);
749
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000750 if (lflag & LS_LONG_VIEW) {
Darren Tucker2901e2d2010-01-13 22:44:06 +1100751 if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000752 char *lname;
753 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100754
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000755 memset(&sb, 0, sizeof(sb));
756 attrib_to_stat(&d[n]->a, &sb);
Darren Tucker2901e2d2010-01-13 22:44:06 +1100757 lname = ls_file(fname, &sb, 1,
758 (lflag & LS_SI_UNITS));
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000759 printf("%s\n", lname);
760 xfree(lname);
761 } else
762 printf("%s\n", d[n]->longname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100763 } else {
764 printf("%-*s", colspace, fname);
765 if (c >= columns) {
766 printf("\n");
767 c = 1;
768 } else
769 c++;
770 }
771
772 xfree(fname);
773 }
774
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000775 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100776 printf("\n");
777
778 free_sftp_dirents(d);
779 return (0);
780}
781
782/* sftp ls.1 replacement which handles path globs */
783static int
784do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
785 int lflag)
786{
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
Damien Millerea858292012-06-30 08:33:32 +1000832 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
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 (;;) {
Damien Miller07daed52012-10-31 08:57:55 +1100995 if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){
Darren Tucker063018d2012-10-05 10:43:58 +1000996 error("Too many arguments.");
997 return NULL;
998 }
Damien Miller1cbc2922007-10-26 14:27:45 +1000999 if (isspace(arg[i])) {
1000 if (state == MA_UNQUOTED) {
1001 /* Terminate current argument */
1002 argvs[j++] = '\0';
1003 argc++;
1004 state = MA_START;
1005 } else if (state != MA_START)
1006 argvs[j++] = arg[i];
1007 } else if (arg[i] == '"' || arg[i] == '\'') {
1008 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1009 if (state == MA_START) {
1010 argv[argc] = argvs + j;
1011 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +11001012 if (lastquote != NULL)
1013 *lastquote = arg[i];
Damien Miller1cbc2922007-10-26 14:27:45 +10001014 } else if (state == MA_UNQUOTED)
1015 state = q;
1016 else if (state == q)
1017 state = MA_UNQUOTED;
1018 else
1019 argvs[j++] = arg[i];
1020 } else if (arg[i] == '\\') {
1021 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1022 quot = state == MA_SQUOTE ? '\'' : '"';
1023 /* Unescape quote we are in */
1024 /* XXX support \n and friends? */
1025 if (arg[i + 1] == quot) {
1026 i++;
1027 argvs[j++] = arg[i];
1028 } else if (arg[i + 1] == '?' ||
1029 arg[i + 1] == '[' || arg[i + 1] == '*') {
1030 /*
1031 * Special case for sftp: append
1032 * double-escaped glob sequence -
1033 * glob will undo one level of
1034 * escaping. NB. string can grow here.
1035 */
1036 if (j >= sizeof(argvs) - 5)
1037 goto args_too_longs;
1038 argvs[j++] = '\\';
1039 argvs[j++] = arg[i++];
1040 argvs[j++] = '\\';
1041 argvs[j++] = arg[i];
1042 } else {
1043 argvs[j++] = arg[i++];
1044 argvs[j++] = arg[i];
1045 }
1046 } else {
1047 if (state == MA_START) {
1048 argv[argc] = argvs + j;
1049 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001050 if (lastquote != NULL)
1051 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001052 }
1053 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1054 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1055 /*
1056 * Special case for sftp: append
1057 * escaped glob sequence -
1058 * glob will undo one level of
1059 * escaping.
1060 */
1061 argvs[j++] = arg[i++];
1062 argvs[j++] = arg[i];
1063 } else {
1064 /* Unescape everything */
1065 /* XXX support \n and friends? */
1066 i++;
1067 argvs[j++] = arg[i];
1068 }
1069 }
1070 } else if (arg[i] == '#') {
1071 if (state == MA_SQUOTE || state == MA_DQUOTE)
1072 argvs[j++] = arg[i];
1073 else
1074 goto string_done;
1075 } else if (arg[i] == '\0') {
1076 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001077 if (sloppy) {
1078 state = MA_UNQUOTED;
1079 if (terminated != NULL)
1080 *terminated = 0;
1081 goto string_done;
1082 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001083 error("Unterminated quoted argument");
1084 return NULL;
1085 }
1086 string_done:
1087 if (state == MA_UNQUOTED) {
1088 argvs[j++] = '\0';
1089 argc++;
1090 }
1091 break;
1092 } else {
1093 if (state == MA_START) {
1094 argv[argc] = argvs + j;
1095 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001096 if (lastquote != NULL)
1097 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001098 }
1099 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1100 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1101 /*
1102 * Special case for sftp: escape quoted
1103 * glob(3) wildcards. NB. string can grow
1104 * here.
1105 */
1106 if (j >= sizeof(argvs) - 3)
1107 goto args_too_longs;
1108 argvs[j++] = '\\';
1109 argvs[j++] = arg[i];
1110 } else
1111 argvs[j++] = arg[i];
1112 }
1113 i++;
1114 }
1115 *argcp = argc;
1116 return argv;
1117}
1118
Damien Miller20e1fab2004-02-18 14:30:55 +11001119static int
Darren Tucker909d8582010-01-08 19:02:40 +11001120parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag,
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001121 int *hflag, int *sflag, unsigned long *n_arg, char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001122{
1123 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001124 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001125 int base = 0;
1126 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001127 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001128
1129 /* Skip leading whitespace */
1130 cp = cp + strspn(cp, WHITESPACE);
1131
Damien Miller20e1fab2004-02-18 14:30:55 +11001132 /* Check for leading '-' (disable error processing) */
1133 *iflag = 0;
1134 if (*cp == '-') {
1135 *iflag = 1;
1136 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001137 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001138 }
1139
Darren Tucker70cc0922010-01-09 22:28:03 +11001140 /* Ignore blank lines and lines which begin with comment '#' char */
1141 if (*cp == '\0' || *cp == '#')
1142 return (0);
1143
Darren Tucker909d8582010-01-08 19:02:40 +11001144 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001145 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001146
Damien Miller1cbc2922007-10-26 14:27:45 +10001147 /* Figure out which command we have */
1148 for (i = 0; cmds[i].c != NULL; i++) {
Damien Millerd6d9fa02013-02-12 11:02:46 +11001149 if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001150 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001151 }
1152 cmdnum = cmds[i].n;
1153 cmd = cmds[i].c;
1154
1155 /* Special case */
1156 if (*cp == '!') {
1157 cp++;
1158 cmdnum = I_SHELL;
1159 } else if (cmdnum == -1) {
1160 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001161 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001162 }
1163
1164 /* Get arguments and parse flags */
Darren Tucker1b0dd172009-10-07 08:37:48 +11001165 *lflag = *pflag = *rflag = *hflag = *n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001166 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001167 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001168 switch (cmdnum) {
1169 case I_GET:
1170 case I_PUT:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001171 if ((optidx = parse_getput_flags(cmd, argv, argc,
1172 pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001173 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001174 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001175 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001176 error("You must specify at least one path after a "
1177 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001178 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001179 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001180 *path1 = xstrdup(argv[optidx]);
1181 /* Get second pathname (optional) */
1182 if (argc - optidx > 1) {
1183 *path2 = xstrdup(argv[optidx + 1]);
1184 /* Destination is not globbed */
1185 undo_glob_escape(*path2);
1186 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001187 break;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001188 case I_LINK:
1189 if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1190 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001191 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001192 case I_RENAME:
Damien Miller1cbc2922007-10-26 14:27:45 +10001193 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001194 error("You must specify two paths after a %s "
1195 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001196 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001197 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001198 *path1 = xstrdup(argv[optidx]);
1199 *path2 = xstrdup(argv[optidx + 1]);
1200 /* Paths are not globbed */
1201 undo_glob_escape(*path1);
1202 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001203 break;
1204 case I_RM:
1205 case I_MKDIR:
1206 case I_RMDIR:
1207 case I_CHDIR:
1208 case I_LCHDIR:
1209 case I_LMKDIR:
1210 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001211 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001212 error("You must specify a path after a %s command.",
1213 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001214 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001215 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001216 *path1 = xstrdup(argv[optidx]);
1217 /* Only "rm" globs */
1218 if (cmdnum != I_RM)
1219 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001220 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001221 case I_DF:
1222 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1223 iflag)) == -1)
1224 return -1;
1225 /* Default to current directory if no path specified */
1226 if (argc - optidx < 1)
1227 *path1 = NULL;
1228 else {
1229 *path1 = xstrdup(argv[optidx]);
1230 undo_glob_escape(*path1);
1231 }
1232 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001233 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001234 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001235 return(-1);
1236 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001237 if (argc - optidx > 0)
1238 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001239 break;
1240 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001241 /* Skip ls command and following whitespace */
1242 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001243 case I_SHELL:
1244 /* Uses the rest of the line */
1245 break;
1246 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001247 case I_CHMOD:
1248 base = 8;
1249 case I_CHOWN:
1250 case I_CHGRP:
1251 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001252 if (argc - optidx < 1)
1253 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001254 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001255 l = strtol(argv[optidx], &cp2, base);
1256 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1257 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1258 l < 0) {
1259 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001260 error("You must supply a numeric argument "
1261 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001262 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001263 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001264 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001265 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001266 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001267 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001268 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001269 error("You must specify a path after a %s command.",
1270 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001271 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001272 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001273 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001274 break;
1275 case I_QUIT:
1276 case I_PWD:
1277 case I_LPWD:
1278 case I_HELP:
1279 case I_VERSION:
1280 case I_PROGRESS:
1281 break;
1282 default:
1283 fatal("Command not implemented");
1284 }
1285
1286 *cpp = cp;
1287 return(cmdnum);
1288}
1289
1290static int
1291parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1292 int err_abort)
1293{
1294 char *path1, *path2, *tmp;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001295 int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, sflag = 0;
1296 int cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001297 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001298 Attrib a, *aa;
1299 char path_buf[MAXPATHLEN];
1300 int err = 0;
1301 glob_t g;
1302
1303 path1 = path2 = NULL;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001304 cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag,
1305 &sflag, &n_arg, &path1, &path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001306
1307 if (iflag != 0)
1308 err_abort = 0;
1309
1310 memset(&g, 0, sizeof(g));
1311
1312 /* Perform command */
1313 switch (cmdnum) {
1314 case 0:
1315 /* Blank line */
1316 break;
1317 case -1:
1318 /* Unrecognized command */
1319 err = -1;
1320 break;
1321 case I_GET:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001322 err = process_get(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001323 break;
1324 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001325 err = process_put(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001326 break;
1327 case I_RENAME:
1328 path1 = make_absolute(path1, *pwd);
1329 path2 = make_absolute(path2, *pwd);
1330 err = do_rename(conn, path1, path2);
1331 break;
1332 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001333 sflag = 1;
1334 case I_LINK:
1335 path1 = make_absolute(path1, *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001336 path2 = make_absolute(path2, *pwd);
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001337 err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001338 break;
1339 case I_RM:
1340 path1 = make_absolute(path1, *pwd);
1341 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001342 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001343 if (!quiet)
1344 printf("Removing %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001345 err = do_rm(conn, g.gl_pathv[i]);
1346 if (err != 0 && err_abort)
1347 break;
1348 }
1349 break;
1350 case I_MKDIR:
1351 path1 = make_absolute(path1, *pwd);
1352 attrib_clear(&a);
1353 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1354 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001355 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001356 break;
1357 case I_RMDIR:
1358 path1 = make_absolute(path1, *pwd);
1359 err = do_rmdir(conn, path1);
1360 break;
1361 case I_CHDIR:
1362 path1 = make_absolute(path1, *pwd);
1363 if ((tmp = do_realpath(conn, path1)) == NULL) {
1364 err = 1;
1365 break;
1366 }
1367 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1368 xfree(tmp);
1369 err = 1;
1370 break;
1371 }
1372 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1373 error("Can't change directory: Can't check target");
1374 xfree(tmp);
1375 err = 1;
1376 break;
1377 }
1378 if (!S_ISDIR(aa->perm)) {
1379 error("Can't change directory: \"%s\" is not "
1380 "a directory", tmp);
1381 xfree(tmp);
1382 err = 1;
1383 break;
1384 }
1385 xfree(*pwd);
1386 *pwd = tmp;
1387 break;
1388 case I_LS:
1389 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001390 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001391 break;
1392 }
1393
1394 /* Strip pwd off beginning of non-absolute paths */
1395 tmp = NULL;
1396 if (*path1 != '/')
1397 tmp = *pwd;
1398
1399 path1 = make_absolute(path1, *pwd);
1400 err = do_globbed_ls(conn, path1, tmp, lflag);
1401 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001402 case I_DF:
1403 /* Default to current directory if no path specified */
1404 if (path1 == NULL)
1405 path1 = xstrdup(*pwd);
1406 path1 = make_absolute(path1, *pwd);
1407 err = do_df(conn, path1, hflag, iflag);
1408 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001409 case I_LCHDIR:
1410 if (chdir(path1) == -1) {
1411 error("Couldn't change local directory to "
1412 "\"%s\": %s", path1, strerror(errno));
1413 err = 1;
1414 }
1415 break;
1416 case I_LMKDIR:
1417 if (mkdir(path1, 0777) == -1) {
1418 error("Couldn't create local directory "
1419 "\"%s\": %s", path1, strerror(errno));
1420 err = 1;
1421 }
1422 break;
1423 case I_LLS:
1424 local_do_ls(cmd);
1425 break;
1426 case I_SHELL:
1427 local_do_shell(cmd);
1428 break;
1429 case I_LUMASK:
1430 umask(n_arg);
1431 printf("Local umask: %03lo\n", n_arg);
1432 break;
1433 case I_CHMOD:
1434 path1 = make_absolute(path1, *pwd);
1435 attrib_clear(&a);
1436 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1437 a.perm = n_arg;
1438 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001439 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001440 if (!quiet)
1441 printf("Changing mode on %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001442 err = do_setstat(conn, g.gl_pathv[i], &a);
1443 if (err != 0 && err_abort)
1444 break;
1445 }
1446 break;
1447 case I_CHOWN:
1448 case I_CHGRP:
1449 path1 = make_absolute(path1, *pwd);
1450 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001451 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001452 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001453 if (err_abort) {
1454 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001455 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001456 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001457 continue;
1458 }
1459 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1460 error("Can't get current ownership of "
1461 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001462 if (err_abort) {
1463 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001464 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001465 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001466 continue;
1467 }
1468 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1469 if (cmdnum == I_CHOWN) {
Damien Miller9303e652013-04-23 15:22:40 +10001470 if (!quiet)
1471 printf("Changing owner on %s\n",
1472 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001473 aa->uid = n_arg;
1474 } else {
Damien Miller9303e652013-04-23 15:22:40 +10001475 if (!quiet)
1476 printf("Changing group on %s\n",
1477 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001478 aa->gid = n_arg;
1479 }
1480 err = do_setstat(conn, g.gl_pathv[i], aa);
1481 if (err != 0 && err_abort)
1482 break;
1483 }
1484 break;
1485 case I_PWD:
1486 printf("Remote working directory: %s\n", *pwd);
1487 break;
1488 case I_LPWD:
1489 if (!getcwd(path_buf, sizeof(path_buf))) {
1490 error("Couldn't get local cwd: %s", strerror(errno));
1491 err = -1;
1492 break;
1493 }
1494 printf("Local working directory: %s\n", path_buf);
1495 break;
1496 case I_QUIT:
1497 /* Processed below */
1498 break;
1499 case I_HELP:
1500 help();
1501 break;
1502 case I_VERSION:
1503 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1504 break;
1505 case I_PROGRESS:
1506 showprogress = !showprogress;
1507 if (showprogress)
1508 printf("Progress meter enabled\n");
1509 else
1510 printf("Progress meter disabled\n");
1511 break;
1512 default:
1513 fatal("%d is not implemented", cmdnum);
1514 }
1515
1516 if (g.gl_pathc)
1517 globfree(&g);
1518 if (path1)
1519 xfree(path1);
1520 if (path2)
1521 xfree(path2);
1522
1523 /* If an unignored error occurs in batch mode we should abort. */
1524 if (err_abort && err != 0)
1525 return (-1);
1526 else if (cmdnum == I_QUIT)
1527 return (1);
1528
1529 return (0);
1530}
1531
Darren Tucker2d963d82004-11-07 20:04:10 +11001532#ifdef USE_LIBEDIT
1533static char *
1534prompt(EditLine *el)
1535{
1536 return ("sftp> ");
1537}
Darren Tucker2d963d82004-11-07 20:04:10 +11001538
Darren Tucker909d8582010-01-08 19:02:40 +11001539/* Display entries in 'list' after skipping the first 'len' chars */
1540static void
1541complete_display(char **list, u_int len)
1542{
1543 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1544 struct winsize ws;
1545 char *tmp;
1546
1547 /* Count entries for sort and find longest */
1548 for (y = 0; list[y]; y++)
1549 m = MAX(m, strlen(list[y]));
1550
1551 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1552 width = ws.ws_col;
1553
1554 m = m > len ? m - len : 0;
1555 columns = width / (m + 2);
1556 columns = MAX(columns, 1);
1557 colspace = width / columns;
1558 colspace = MIN(colspace, width);
1559
1560 printf("\n");
1561 m = 1;
1562 for (y = 0; list[y]; y++) {
1563 llen = strlen(list[y]);
1564 tmp = llen > len ? list[y] + len : "";
1565 printf("%-*s", colspace, tmp);
1566 if (m >= columns) {
1567 printf("\n");
1568 m = 1;
1569 } else
1570 m++;
1571 }
1572 printf("\n");
1573}
1574
1575/*
1576 * Given a "list" of words that begin with a common prefix of "word",
1577 * attempt to find an autocompletion to extends "word" by the next
1578 * characters common to all entries in "list".
1579 */
1580static char *
1581complete_ambiguous(const char *word, char **list, size_t count)
1582{
1583 if (word == NULL)
1584 return NULL;
1585
1586 if (count > 0) {
1587 u_int y, matchlen = strlen(list[0]);
1588
1589 /* Find length of common stem */
1590 for (y = 1; list[y]; y++) {
1591 u_int x;
1592
1593 for (x = 0; x < matchlen; x++)
1594 if (list[0][x] != list[y][x])
1595 break;
1596
1597 matchlen = x;
1598 }
1599
1600 if (matchlen > strlen(word)) {
1601 char *tmp = xstrdup(list[0]);
1602
Darren Tucker340d1682010-01-09 08:54:31 +11001603 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001604 return tmp;
1605 }
1606 }
1607
1608 return xstrdup(word);
1609}
1610
1611/* Autocomplete a sftp command */
1612static int
1613complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1614 int terminated)
1615{
1616 u_int y, count = 0, cmdlen, tmplen;
1617 char *tmp, **list, argterm[3];
1618 const LineInfo *lf;
1619
1620 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1621
1622 /* No command specified: display all available commands */
1623 if (cmd == NULL) {
1624 for (y = 0; cmds[y].c; y++)
1625 list[count++] = xstrdup(cmds[y].c);
1626
1627 list[count] = NULL;
1628 complete_display(list, 0);
1629
1630 for (y = 0; list[y] != NULL; y++)
1631 xfree(list[y]);
1632 xfree(list);
1633 return count;
1634 }
1635
1636 /* Prepare subset of commands that start with "cmd" */
1637 cmdlen = strlen(cmd);
1638 for (y = 0; cmds[y].c; y++) {
1639 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
1640 list[count++] = xstrdup(cmds[y].c);
1641 }
1642 list[count] = NULL;
1643
Damien Miller47d81152011-11-25 13:53:48 +11001644 if (count == 0) {
1645 xfree(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001646 return 0;
Damien Miller47d81152011-11-25 13:53:48 +11001647 }
Darren Tucker909d8582010-01-08 19:02:40 +11001648
1649 /* Complete ambigious command */
1650 tmp = complete_ambiguous(cmd, list, count);
1651 if (count > 1)
1652 complete_display(list, 0);
1653
1654 for (y = 0; list[y]; y++)
1655 xfree(list[y]);
1656 xfree(list);
1657
1658 if (tmp != NULL) {
1659 tmplen = strlen(tmp);
1660 cmdlen = strlen(cmd);
1661 /* If cmd may be extended then do so */
1662 if (tmplen > cmdlen)
1663 if (el_insertstr(el, tmp + cmdlen) == -1)
1664 fatal("el_insertstr failed.");
1665 lf = el_line(el);
1666 /* Terminate argument cleanly */
1667 if (count == 1) {
1668 y = 0;
1669 if (!terminated)
1670 argterm[y++] = quote;
1671 if (lastarg || *(lf->cursor) != ' ')
1672 argterm[y++] = ' ';
1673 argterm[y] = '\0';
1674 if (y > 0 && el_insertstr(el, argterm) == -1)
1675 fatal("el_insertstr failed.");
1676 }
1677 xfree(tmp);
1678 }
1679
1680 return count;
1681}
1682
1683/*
1684 * Determine whether a particular sftp command's arguments (if any)
1685 * represent local or remote files.
1686 */
1687static int
1688complete_is_remote(char *cmd) {
1689 int i;
1690
1691 if (cmd == NULL)
1692 return -1;
1693
1694 for (i = 0; cmds[i].c; i++) {
1695 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
1696 return cmds[i].t;
1697 }
1698
1699 return -1;
1700}
1701
1702/* Autocomplete a filename "file" */
1703static int
1704complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1705 char *file, int remote, int lastarg, char quote, int terminated)
1706{
1707 glob_t g;
1708 char *tmp, *tmp2, ins[3];
Darren Tucker17146d32012-10-05 10:46:16 +10001709 u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
Darren Tucker909d8582010-01-08 19:02:40 +11001710 const LineInfo *lf;
1711
1712 /* Glob from "file" location */
1713 if (file == NULL)
1714 tmp = xstrdup("*");
1715 else
1716 xasprintf(&tmp, "%s*", file);
1717
Darren Tucker191fcc62012-10-05 10:45:01 +10001718 /* Check if the path is absolute. */
1719 isabs = tmp[0] == '/';
1720
Darren Tucker909d8582010-01-08 19:02:40 +11001721 memset(&g, 0, sizeof(g));
1722 if (remote != LOCAL) {
1723 tmp = make_absolute(tmp, remote_path);
1724 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1725 } else
1726 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1727
1728 /* Determine length of pwd so we can trim completion display */
1729 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1730 /* Terminate counting on first unescaped glob metacharacter */
1731 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1732 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1733 hadglob = 1;
1734 break;
1735 }
1736 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1737 tmplen++;
1738 if (tmp[tmplen] == '/')
1739 pwdlen = tmplen + 1; /* track last seen '/' */
1740 }
1741 xfree(tmp);
1742
1743 if (g.gl_matchc == 0)
1744 goto out;
1745
1746 if (g.gl_matchc > 1)
1747 complete_display(g.gl_pathv, pwdlen);
1748
1749 tmp = NULL;
1750 /* Don't try to extend globs */
1751 if (file == NULL || hadglob)
1752 goto out;
1753
1754 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
Darren Tucker191fcc62012-10-05 10:45:01 +10001755 tmp = path_strip(tmp2, isabs ? NULL : remote_path);
Darren Tucker909d8582010-01-08 19:02:40 +11001756 xfree(tmp2);
1757
1758 if (tmp == NULL)
1759 goto out;
1760
1761 tmplen = strlen(tmp);
1762 filelen = strlen(file);
1763
Darren Tucker17146d32012-10-05 10:46:16 +10001764 /* Count the number of escaped characters in the input string. */
1765 cesc = isesc = 0;
1766 for (i = 0; i < filelen; i++) {
1767 if (!isesc && file[i] == '\\' && i + 1 < filelen){
1768 isesc = 1;
1769 cesc++;
1770 } else
1771 isesc = 0;
1772 }
1773
1774 if (tmplen > (filelen - cesc)) {
1775 tmp2 = tmp + filelen - cesc;
Darren Tucker909d8582010-01-08 19:02:40 +11001776 len = strlen(tmp2);
1777 /* quote argument on way out */
1778 for (i = 0; i < len; i++) {
1779 ins[0] = '\\';
1780 ins[1] = tmp2[i];
1781 ins[2] = '\0';
1782 switch (tmp2[i]) {
1783 case '\'':
1784 case '"':
1785 case '\\':
1786 case '\t':
Darren Tuckerd78739a2010-10-24 10:56:32 +11001787 case '[':
Darren Tucker909d8582010-01-08 19:02:40 +11001788 case ' ':
Darren Tucker17146d32012-10-05 10:46:16 +10001789 case '#':
1790 case '*':
Darren Tucker909d8582010-01-08 19:02:40 +11001791 if (quote == '\0' || tmp2[i] == quote) {
1792 if (el_insertstr(el, ins) == -1)
1793 fatal("el_insertstr "
1794 "failed.");
1795 break;
1796 }
1797 /* FALLTHROUGH */
1798 default:
1799 if (el_insertstr(el, ins + 1) == -1)
1800 fatal("el_insertstr failed.");
1801 break;
1802 }
1803 }
1804 }
1805
1806 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11001807 if (g.gl_matchc == 1) {
1808 i = 0;
1809 if (!terminated)
1810 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11001811 if (*(lf->cursor - 1) != '/' &&
1812 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11001813 ins[i++] = ' ';
1814 ins[i] = '\0';
1815 if (i > 0 && el_insertstr(el, ins) == -1)
1816 fatal("el_insertstr failed.");
1817 }
1818 xfree(tmp);
1819
1820 out:
1821 globfree(&g);
1822 return g.gl_matchc;
1823}
1824
1825/* tab-completion hook function, called via libedit */
1826static unsigned char
1827complete(EditLine *el, int ch)
1828{
1829 char **argv, *line, quote;
1830 u_int argc, carg, cursor, len, terminated, ret = CC_ERROR;
1831 const LineInfo *lf;
1832 struct complete_ctx *complete_ctx;
1833
1834 lf = el_line(el);
1835 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1836 fatal("%s: el_get failed", __func__);
1837
1838 /* Figure out which argument the cursor points to */
1839 cursor = lf->cursor - lf->buffer;
1840 line = (char *)xmalloc(cursor + 1);
1841 memcpy(line, lf->buffer, cursor);
1842 line[cursor] = '\0';
1843 argv = makeargv(line, &carg, 1, &quote, &terminated);
1844 xfree(line);
1845
1846 /* Get all the arguments on the line */
1847 len = lf->lastchar - lf->buffer;
1848 line = (char *)xmalloc(len + 1);
1849 memcpy(line, lf->buffer, len);
1850 line[len] = '\0';
1851 argv = makeargv(line, &argc, 1, NULL, NULL);
1852
1853 /* Ensure cursor is at EOL or a argument boundary */
1854 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1855 line[cursor] != '\n') {
1856 xfree(line);
1857 return ret;
1858 }
1859
1860 if (carg == 0) {
1861 /* Show all available commands */
1862 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1863 ret = CC_REDISPLAY;
1864 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1865 /* Handle the command parsing */
1866 if (complete_cmd_parse(el, argv[0], argc == carg,
1867 quote, terminated) != 0)
1868 ret = CC_REDISPLAY;
1869 } else if (carg >= 1) {
1870 /* Handle file parsing */
1871 int remote = complete_is_remote(argv[0]);
1872 char *filematch = NULL;
1873
1874 if (carg > 1 && line[cursor-1] != ' ')
1875 filematch = argv[carg - 1];
1876
1877 if (remote != 0 &&
1878 complete_match(el, complete_ctx->conn,
1879 *complete_ctx->remote_pathp, filematch,
1880 remote, carg == argc, quote, terminated) != 0)
1881 ret = CC_REDISPLAY;
1882 }
1883
1884 xfree(line);
1885 return ret;
1886}
Darren Tuckere67f7db2010-01-08 19:50:02 +11001887#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11001888
Damien Miller20e1fab2004-02-18 14:30:55 +11001889int
Darren Tucker21063192010-01-08 17:10:36 +11001890interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001891{
Darren Tucker909d8582010-01-08 19:02:40 +11001892 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11001893 char *dir = NULL;
1894 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10001895 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11001896 EditLine *el = NULL;
1897#ifdef USE_LIBEDIT
1898 History *hl = NULL;
1899 HistEvent hev;
1900 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11001901 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11001902
1903 if (!batchmode && isatty(STDIN_FILENO)) {
1904 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1905 fatal("Couldn't initialise editline");
1906 if ((hl = history_init()) == NULL)
1907 fatal("Couldn't initialise editline history");
1908 history(hl, &hev, H_SETSIZE, 100);
1909 el_set(el, EL_HIST, history, hl);
1910
1911 el_set(el, EL_PROMPT, prompt);
1912 el_set(el, EL_EDITOR, "emacs");
1913 el_set(el, EL_TERMINAL, NULL);
1914 el_set(el, EL_SIGNAL, 1);
1915 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11001916
1917 /* Tab Completion */
1918 el_set(el, EL_ADDFN, "ftp-complete",
Darren Tuckerd78739a2010-10-24 10:56:32 +11001919 "Context sensitive argument completion", complete);
Darren Tucker909d8582010-01-08 19:02:40 +11001920 complete_ctx.conn = conn;
1921 complete_ctx.remote_pathp = &remote_path;
1922 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
1923 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11001924 }
1925#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001926
Darren Tucker909d8582010-01-08 19:02:40 +11001927 remote_path = do_realpath(conn, ".");
1928 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11001929 fatal("Need cwd");
1930
1931 if (file1 != NULL) {
1932 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11001933 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11001934
1935 if (remote_is_dir(conn, dir) && file2 == NULL) {
Damien Miller9303e652013-04-23 15:22:40 +10001936 if (!quiet)
1937 printf("Changing to: %s\n", dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11001938 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001939 if (parse_dispatch_command(conn, cmd,
1940 &remote_path, 1) != 0) {
Darren Tuckercd516ef2004-12-06 22:43:43 +11001941 xfree(dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001942 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001943 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001944 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11001945 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001946 } else {
Darren Tucker0af24052012-10-05 10:41:25 +10001947 /* XXX this is wrong wrt quoting */
Damien Miller20e1fab2004-02-18 14:30:55 +11001948 if (file2 == NULL)
1949 snprintf(cmd, sizeof cmd, "get %s", dir);
1950 else
1951 snprintf(cmd, sizeof cmd, "get %s %s", dir,
1952 file2);
1953
Darren Tucker909d8582010-01-08 19:02:40 +11001954 err = parse_dispatch_command(conn, cmd,
1955 &remote_path, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001956 xfree(dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001957 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001958 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001959 return (err);
1960 }
1961 xfree(dir);
1962 }
1963
Damien Miller37294fb2005-07-17 17:18:49 +10001964 setlinebuf(stdout);
1965 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11001966
Damien Miller0e2c1022005-08-12 22:16:22 +10001967 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11001968 err = 0;
1969 for (;;) {
1970 char *cp;
1971
Darren Tuckercdf547a2004-05-24 10:12:19 +10001972 signal(SIGINT, SIG_IGN);
1973
Darren Tucker2d963d82004-11-07 20:04:10 +11001974 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001975 if (interactive)
1976 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11001977 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001978 if (interactive)
1979 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11001980 break;
1981 }
Damien Miller0e2c1022005-08-12 22:16:22 +10001982 if (!interactive) { /* Echo command */
1983 printf("sftp> %s", cmd);
1984 if (strlen(cmd) > 0 &&
1985 cmd[strlen(cmd) - 1] != '\n')
1986 printf("\n");
1987 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001988 } else {
1989#ifdef USE_LIBEDIT
1990 const char *line;
1991 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001992
Darren Tucker909d8582010-01-08 19:02:40 +11001993 if ((line = el_gets(el, &count)) == NULL ||
1994 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001995 printf("\n");
1996 break;
1997 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001998 history(hl, &hev, H_ENTER, line);
1999 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
2000 fprintf(stderr, "Error: input line too long\n");
2001 continue;
2002 }
2003#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002004 }
2005
Damien Miller20e1fab2004-02-18 14:30:55 +11002006 cp = strrchr(cmd, '\n');
2007 if (cp)
2008 *cp = '\0';
2009
Darren Tuckercdf547a2004-05-24 10:12:19 +10002010 /* Handle user interrupts gracefully during commands */
2011 interrupted = 0;
2012 signal(SIGINT, cmd_interrupt);
2013
Darren Tucker909d8582010-01-08 19:02:40 +11002014 err = parse_dispatch_command(conn, cmd, &remote_path,
2015 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11002016 if (err != 0)
2017 break;
2018 }
Darren Tucker909d8582010-01-08 19:02:40 +11002019 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11002020 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002021
Tim Rice027e8b12005-08-15 14:52:50 -07002022#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10002023 if (el != NULL)
2024 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07002025#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10002026
Damien Miller20e1fab2004-02-18 14:30:55 +11002027 /* err == 1 signifies normal "quit" exit */
2028 return (err >= 0 ? 0 : -1);
2029}
Damien Miller62d57f62003-01-10 21:43:24 +11002030
Ben Lindstrombba81212001-06-25 05:01:22 +00002031static void
Damien Millercc685c12003-06-04 22:51:38 +10002032connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11002033{
2034 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002035
Damien Miller33804262001-02-04 23:20:18 +11002036#ifdef USE_PIPES
2037 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002038
Damien Miller33804262001-02-04 23:20:18 +11002039 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2040 fatal("pipe: %s", strerror(errno));
2041 *in = pin[0];
2042 *out = pout[1];
2043 c_in = pout[0];
2044 c_out = pin[1];
2045#else /* USE_PIPES */
2046 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002047
Damien Miller33804262001-02-04 23:20:18 +11002048 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2049 fatal("socketpair: %s", strerror(errno));
2050 *in = *out = inout[0];
2051 c_in = c_out = inout[1];
2052#endif /* USE_PIPES */
2053
Damien Millercc685c12003-06-04 22:51:38 +10002054 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002055 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002056 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002057 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2058 (dup2(c_out, STDOUT_FILENO) == -1)) {
2059 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002060 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002061 }
2062 close(*in);
2063 close(*out);
2064 close(c_in);
2065 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002066
2067 /*
2068 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002069 * ignore SIGINT if we want to gracefully abort commands,
2070 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002071 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2072 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002073 */
2074 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002075 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002076 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002077 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002078 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002079 }
2080
Damien Millercc685c12003-06-04 22:51:38 +10002081 signal(SIGTERM, killchild);
2082 signal(SIGINT, killchild);
2083 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11002084 close(c_in);
2085 close(c_out);
2086}
2087
Ben Lindstrombba81212001-06-25 05:01:22 +00002088static void
Damien Miller33804262001-02-04 23:20:18 +11002089usage(void)
2090{
Damien Miller025e01c2002-02-08 22:06:29 +11002091 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002092
Ben Lindstrom1e243242001-09-18 05:38:44 +00002093 fprintf(stderr,
Darren Tucker1b0dd172009-10-07 08:37:48 +11002094 "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002095 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002096 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002097 " [-o ssh_option] [-P port] [-R num_requests] "
2098 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002099 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002100 " %s [user@]host[:file ...]\n"
2101 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002102 " %s -b batchfile [user@]host\n",
2103 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002104 exit(1);
2105}
2106
Kevin Stevesef4eea92001-02-05 12:42:17 +00002107int
Damien Miller33804262001-02-04 23:20:18 +11002108main(int argc, char **argv)
2109{
Damien Miller956f3fb2003-01-10 21:40:00 +11002110 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002111 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002112 int debug_level = 0, sshver = 2;
2113 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002114 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002115 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002116 LogLevel ll = SYSLOG_LEVEL_INFO;
2117 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002118 extern int optind;
2119 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002120 struct sftp_conn *conn;
2121 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2122 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002123 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002124
Darren Tuckerce321d82005-10-03 18:11:24 +10002125 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2126 sanitise_stdfd();
2127
Damien Miller59d3d5b2003-08-22 09:34:41 +10002128 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002129 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002130 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002131 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002132 addargs(&args, "-oForwardX11 no");
2133 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002134 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002135 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002136
Ben Lindstrom387c4722001-05-08 20:27:25 +00002137 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002138 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002139
Darren Tucker282b4022009-10-07 08:23:06 +11002140 while ((ch = getopt(argc, argv,
Damien Miller65e42f82010-09-24 22:15:11 +10002141 "1246hpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002142 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002143 /* Passed through to ssh(1) */
2144 case '4':
2145 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002146 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002147 addargs(&args, "-%c", ch);
2148 break;
2149 /* Passed through to ssh(1) with argument */
2150 case 'F':
2151 case 'c':
2152 case 'i':
2153 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002154 addargs(&args, "-%c", ch);
2155 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002156 break;
2157 case 'q':
Damien Miller9303e652013-04-23 15:22:40 +10002158 ll = SYSLOG_LEVEL_ERROR;
2159 quiet = 1;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002160 showprogress = 0;
2161 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002162 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002163 case 'P':
2164 addargs(&args, "-oPort %s", optarg);
2165 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002166 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002167 if (debug_level < 3) {
2168 addargs(&args, "-v");
2169 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2170 }
2171 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002172 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002173 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002174 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002175 if (sftp_server == NULL)
2176 sftp_server = _PATH_SFTP_SERVER;
2177 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002178 case '2':
2179 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002180 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002181 case 'B':
2182 copy_buffer_len = strtol(optarg, &cp, 10);
2183 if (copy_buffer_len == 0 || *cp != '\0')
2184 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002185 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002186 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002187 if (batchmode)
2188 fatal("Batch file already specified.");
2189
2190 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002191 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002192 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002193 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002194 showprogress = 0;
Damien Miller9303e652013-04-23 15:22:40 +10002195 quiet = batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002196 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002197 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002198 case 'p':
2199 global_pflag = 1;
2200 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002201 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002202 sftp_direct = optarg;
2203 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002204 case 'l':
2205 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2206 &errstr);
2207 if (errstr != NULL)
2208 usage();
2209 limit_kbps *= 1024; /* kbps */
2210 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002211 case 'r':
2212 global_rflag = 1;
2213 break;
Damien Miller16a13332002-02-13 14:03:56 +11002214 case 'R':
2215 num_requests = strtol(optarg, &cp, 10);
2216 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002217 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002218 optarg);
2219 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002220 case 's':
2221 sftp_server = optarg;
2222 break;
2223 case 'S':
2224 ssh_program = optarg;
2225 replacearg(&args, 0, "%s", ssh_program);
2226 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002227 case 'h':
2228 default:
Damien Miller33804262001-02-04 23:20:18 +11002229 usage();
2230 }
2231 }
2232
Damien Millerc0f27d82004-03-08 23:12:19 +11002233 if (!isatty(STDERR_FILENO))
2234 showprogress = 0;
2235
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002236 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2237
Damien Millerd14ee1e2002-02-05 12:27:31 +11002238 if (sftp_direct == NULL) {
2239 if (optind == argc || argc > (optind + 2))
2240 usage();
Damien Miller33804262001-02-04 23:20:18 +11002241
Damien Millerd14ee1e2002-02-05 12:27:31 +11002242 userhost = xstrdup(argv[optind]);
2243 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002244
Ben Lindstromc276c122002-12-23 02:14:51 +00002245 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002246 host = userhost;
2247 else {
2248 *host++ = '\0';
2249 if (!userhost[0]) {
2250 fprintf(stderr, "Missing username\n");
2251 usage();
2252 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002253 addargs(&args, "-l");
2254 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002255 }
2256
Damien Millerec692032004-01-27 21:22:00 +11002257 if ((cp = colon(host)) != NULL) {
2258 *cp++ = '\0';
2259 file1 = cp;
2260 }
2261
Damien Millerd14ee1e2002-02-05 12:27:31 +11002262 host = cleanhostname(host);
2263 if (!*host) {
2264 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002265 usage();
2266 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002267
Damien Millerd14ee1e2002-02-05 12:27:31 +11002268 addargs(&args, "-oProtocol %d", sshver);
2269
2270 /* no subsystem if the server-spec contains a '/' */
2271 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2272 addargs(&args, "-s");
2273
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002274 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002275 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002276 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002277 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002278
Damien Millercc685c12003-06-04 22:51:38 +10002279 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002280 } else {
2281 args.list = NULL;
2282 addargs(&args, "sftp-server");
2283
Damien Millercc685c12003-06-04 22:51:38 +10002284 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002285 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002286 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002287
Damien Miller65e42f82010-09-24 22:15:11 +10002288 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002289 if (conn == NULL)
2290 fatal("Couldn't initialise connection to server");
2291
Damien Miller9303e652013-04-23 15:22:40 +10002292 if (!quiet) {
Darren Tucker21063192010-01-08 17:10:36 +11002293 if (sftp_direct == NULL)
2294 fprintf(stderr, "Connected to %s.\n", host);
2295 else
2296 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2297 }
2298
2299 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002300
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002301#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002302 shutdown(in, SHUT_RDWR);
2303 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002304#endif
2305
Damien Miller33804262001-02-04 23:20:18 +11002306 close(in);
2307 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002308 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002309 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002310
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002311 while (waitpid(sshpid, NULL, 0) == -1)
2312 if (errno != EINTR)
2313 fatal("Couldn't wait for ssh process: %s",
2314 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002315
Damien Miller956f3fb2003-01-10 21:40:00 +11002316 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002317}