blob: ab667f5a55e0d9f483c27552daf31ff8fdb05dcf [file] [log] [blame]
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001/* $OpenBSD: sftp.c,v 1.132 2010/12/04 00:18:01 djm Exp $ */
Damien Miller33804262001-02-04 23:20:18 +11002/*
Damien Miller4e60ed72004-02-17 17:07:59 +11003 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
Damien Miller33804262001-02-04 23:20:18 +11004 *
Damien Miller4e60ed72004-02-17 17:07:59 +11005 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
Damien Miller33804262001-02-04 23:20:18 +11008 *
Damien Miller4e60ed72004-02-17 17:07:59 +11009 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Damien Miller33804262001-02-04 23:20:18 +110016 */
17
18#include "includes.h"
19
Damien Miller9cf6d072006-03-15 11:29:24 +110020#include <sys/types.h>
Damien Millerd7834352006-08-05 12:39:39 +100021#include <sys/ioctl.h>
Damien Millerf17883e2006-03-15 11:45:54 +110022#ifdef HAVE_SYS_STAT_H
23# include <sys/stat.h>
24#endif
Damien Miller8dbffe72006-08-05 11:02:17 +100025#include <sys/param.h>
Damien Millere3b60b52006-07-10 21:08:03 +100026#include <sys/socket.h>
Damien Miller9cf6d072006-03-15 11:29:24 +110027#include <sys/wait.h>
Darren Tucker5b2e2ba2008-06-08 09:25:28 +100028#ifdef HAVE_SYS_STATVFS_H
Damien Millerd671e5a2008-05-19 14:53:33 +100029#include <sys/statvfs.h>
Darren Tucker5b2e2ba2008-06-08 09:25:28 +100030#endif
Darren Tucker2d963d82004-11-07 20:04:10 +110031
Damien Miller1cbc2922007-10-26 14:27:45 +100032#include <ctype.h>
Darren Tucker39972492006-07-12 22:22:46 +100033#include <errno.h>
34
Damien Miller03e20032006-03-15 11:16:59 +110035#ifdef HAVE_PATHS_H
Damien Millera9263d02006-03-15 11:18:26 +110036# include <paths.h>
Damien Miller03e20032006-03-15 11:16:59 +110037#endif
Darren Tucker1b0dd172009-10-07 08:37:48 +110038#ifdef HAVE_LIBGEN_H
39#include <libgen.h>
40#endif
Darren Tucker2d963d82004-11-07 20:04:10 +110041#ifdef USE_LIBEDIT
42#include <histedit.h>
43#else
44typedef void EditLine;
45#endif
Damien Miller6ff3cad2006-03-15 11:52:09 +110046#include <signal.h>
Damien Millere7a1e5c2006-08-05 11:34:19 +100047#include <stdlib.h>
Damien Millera7a73ee2006-08-05 11:37:59 +100048#include <stdio.h>
Damien Millere3476ed2006-07-24 14:13:33 +100049#include <string.h>
Damien Millere6b3b612006-07-24 14:01:23 +100050#include <unistd.h>
Damien Millerd7834352006-08-05 12:39:39 +100051#include <stdarg.h>
Damien Miller33804262001-02-04 23:20:18 +110052
Damien Millera7058ec2008-05-20 08:57:06 +100053#ifdef HAVE_UTIL_H
54# include <util.h>
55#endif
56
57#ifdef HAVE_LIBUTIL_H
58# include <libutil.h>
59#endif
60
Damien Miller33804262001-02-04 23:20:18 +110061#include "xmalloc.h"
62#include "log.h"
63#include "pathnames.h"
Ben Lindstrom4529b702001-05-03 23:39:53 +000064#include "misc.h"
Damien Miller33804262001-02-04 23:20:18 +110065
66#include "sftp.h"
Damien Millerd7834352006-08-05 12:39:39 +100067#include "buffer.h"
Damien Miller33804262001-02-04 23:20:18 +110068#include "sftp-common.h"
69#include "sftp-client.h"
Damien Millerd7d46bb2004-02-18 14:11:13 +110070
Darren Tucker21063192010-01-08 17:10:36 +110071#define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */
72#define DEFAULT_NUM_REQUESTS 64 /* # concurrent outstanding requests */
73
Damien Miller20e1fab2004-02-18 14:30:55 +110074/* File to read commands from */
75FILE* infile;
76
77/* Are we in batchfile mode? */
78int batchmode = 0;
79
Damien Miller20e1fab2004-02-18 14:30:55 +110080/* PID of ssh transport process */
81static pid_t sshpid = -1;
82
83/* This is set to 0 if the progressmeter is not desired. */
Damien Millerc0f27d82004-03-08 23:12:19 +110084int showprogress = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +110085
Darren Tucker1b0dd172009-10-07 08:37:48 +110086/* When this option is set, we always recursively download/upload directories */
87int global_rflag = 0;
88
89/* When this option is set, the file transfers will always preserve times */
90int global_pflag = 0;
91
Darren Tuckercdf547a2004-05-24 10:12:19 +100092/* SIGINT received during command processing */
93volatile sig_atomic_t interrupted = 0;
94
Darren Tuckerb9123452004-06-22 13:06:45 +100095/* I wish qsort() took a separate ctx for the comparison function...*/
96int sort_flag;
97
Darren Tucker909d8582010-01-08 19:02:40 +110098/* Context used for commandline completion */
99struct complete_ctx {
100 struct sftp_conn *conn;
101 char **remote_pathp;
102};
103
Damien Miller20e1fab2004-02-18 14:30:55 +1100104int remote_glob(struct sftp_conn *, const char *, int,
105 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
Damien Miller33804262001-02-04 23:20:18 +1100106
Kevin Steves12888d12001-03-05 19:50:57 +0000107extern char *__progname;
Kevin Steves12888d12001-03-05 19:50:57 +0000108
Damien Miller20e1fab2004-02-18 14:30:55 +1100109/* Separators for interactive commands */
110#define WHITESPACE " \t\r\n"
Damien Millerd7686fd2001-02-10 00:40:03 +1100111
Darren Tuckerb9123452004-06-22 13:06:45 +1000112/* ls flags */
Darren Tucker2901e2d2010-01-13 22:44:06 +1100113#define LS_LONG_VIEW 0x0001 /* Full view ala ls -l */
114#define LS_SHORT_VIEW 0x0002 /* Single row view ala ls -1 */
115#define LS_NUMERIC_VIEW 0x0004 /* Long view with numeric uid/gid */
116#define LS_NAME_SORT 0x0008 /* Sort by name (default) */
117#define LS_TIME_SORT 0x0010 /* Sort by mtime */
118#define LS_SIZE_SORT 0x0020 /* Sort by file size */
119#define LS_REVERSE_SORT 0x0040 /* Reverse sort order */
120#define LS_SHOW_ALL 0x0080 /* Don't skip filenames starting with '.' */
121#define LS_SI_UNITS 0x0100 /* Display sizes as K, M, G, etc. */
Darren Tuckerb9123452004-06-22 13:06:45 +1000122
Darren Tucker2901e2d2010-01-13 22:44:06 +1100123#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS)
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000124#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
Damien Miller20e1fab2004-02-18 14:30:55 +1100125
126/* Commands for interactive mode */
127#define I_CHDIR 1
128#define I_CHGRP 2
129#define I_CHMOD 3
130#define I_CHOWN 4
Damien Millerd671e5a2008-05-19 14:53:33 +1000131#define I_DF 24
Damien Miller20e1fab2004-02-18 14:30:55 +1100132#define I_GET 5
133#define I_HELP 6
134#define I_LCHDIR 7
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100135#define I_LINK 25
Damien Miller20e1fab2004-02-18 14:30:55 +1100136#define I_LLS 8
137#define I_LMKDIR 9
138#define I_LPWD 10
139#define I_LS 11
140#define I_LUMASK 12
141#define I_MKDIR 13
142#define I_PUT 14
143#define I_PWD 15
144#define I_QUIT 16
145#define I_RENAME 17
146#define I_RM 18
147#define I_RMDIR 19
148#define I_SHELL 20
149#define I_SYMLINK 21
150#define I_VERSION 22
151#define I_PROGRESS 23
152
153struct CMD {
154 const char *c;
155 const int n;
Darren Tucker909d8582010-01-08 19:02:40 +1100156 const int t;
Damien Miller20e1fab2004-02-18 14:30:55 +1100157};
158
Darren Tucker909d8582010-01-08 19:02:40 +1100159/* Type of completion */
160#define NOARGS 0
161#define REMOTE 1
162#define LOCAL 2
163
Damien Miller20e1fab2004-02-18 14:30:55 +1100164static const struct CMD cmds[] = {
Darren Tucker909d8582010-01-08 19:02:40 +1100165 { "bye", I_QUIT, NOARGS },
166 { "cd", I_CHDIR, REMOTE },
167 { "chdir", I_CHDIR, REMOTE },
168 { "chgrp", I_CHGRP, REMOTE },
169 { "chmod", I_CHMOD, REMOTE },
170 { "chown", I_CHOWN, REMOTE },
171 { "df", I_DF, REMOTE },
172 { "dir", I_LS, REMOTE },
173 { "exit", I_QUIT, NOARGS },
174 { "get", I_GET, REMOTE },
175 { "help", I_HELP, NOARGS },
176 { "lcd", I_LCHDIR, LOCAL },
177 { "lchdir", I_LCHDIR, LOCAL },
178 { "lls", I_LLS, LOCAL },
179 { "lmkdir", I_LMKDIR, LOCAL },
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100180 { "ln", I_LINK, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100181 { "lpwd", I_LPWD, LOCAL },
182 { "ls", I_LS, REMOTE },
183 { "lumask", I_LUMASK, NOARGS },
184 { "mkdir", I_MKDIR, REMOTE },
Damien Miller099fc162010-05-10 11:56:50 +1000185 { "mget", I_GET, REMOTE },
186 { "mput", I_PUT, LOCAL },
Darren Tucker909d8582010-01-08 19:02:40 +1100187 { "progress", I_PROGRESS, NOARGS },
188 { "put", I_PUT, LOCAL },
189 { "pwd", I_PWD, REMOTE },
190 { "quit", I_QUIT, NOARGS },
191 { "rename", I_RENAME, REMOTE },
192 { "rm", I_RM, REMOTE },
193 { "rmdir", I_RMDIR, REMOTE },
194 { "symlink", I_SYMLINK, REMOTE },
195 { "version", I_VERSION, NOARGS },
196 { "!", I_SHELL, NOARGS },
197 { "?", I_HELP, NOARGS },
198 { NULL, -1, -1 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100199};
200
Darren Tucker21063192010-01-08 17:10:36 +1100201int interactive_loop(struct sftp_conn *, char *file1, char *file2);
Damien Miller20e1fab2004-02-18 14:30:55 +1100202
Damien Millerb6c85fc2007-01-05 16:30:41 +1100203/* ARGSUSED */
Damien Miller20e1fab2004-02-18 14:30:55 +1100204static void
Darren Tuckercdf547a2004-05-24 10:12:19 +1000205killchild(int signo)
206{
Darren Tuckerba66df82005-01-24 21:57:40 +1100207 if (sshpid > 1) {
Darren Tuckercdf547a2004-05-24 10:12:19 +1000208 kill(sshpid, SIGTERM);
Darren Tuckerba66df82005-01-24 21:57:40 +1100209 waitpid(sshpid, NULL, 0);
210 }
Darren Tuckercdf547a2004-05-24 10:12:19 +1000211
212 _exit(1);
213}
214
Damien Millerb6c85fc2007-01-05 16:30:41 +1100215/* ARGSUSED */
Darren Tuckercdf547a2004-05-24 10:12:19 +1000216static void
217cmd_interrupt(int signo)
218{
219 const char msg[] = "\rInterrupt \n";
Darren Tuckere2f189a2004-12-06 22:45:53 +1100220 int olderrno = errno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000221
222 write(STDERR_FILENO, msg, sizeof(msg) - 1);
223 interrupted = 1;
Darren Tuckere2f189a2004-12-06 22:45:53 +1100224 errno = olderrno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000225}
226
227static void
Damien Miller20e1fab2004-02-18 14:30:55 +1100228help(void)
229{
Damien Miller62fd18a2009-01-28 16:14:09 +1100230 printf("Available commands:\n"
231 "bye Quit sftp\n"
232 "cd path Change remote directory to 'path'\n"
233 "chgrp grp path Change group of file 'path' to 'grp'\n"
234 "chmod mode path Change permissions of file 'path' to 'mode'\n"
235 "chown own path Change owner of file 'path' to 'own'\n"
236 "df [-hi] [path] Display statistics for current directory or\n"
237 " filesystem containing 'path'\n"
238 "exit Quit sftp\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100239 "get [-Ppr] remote [local] Download file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100240 "help Display this help text\n"
241 "lcd path Change local directory to 'path'\n"
242 "lls [ls-options [path]] Display local directory listing\n"
243 "lmkdir path Create local directory\n"
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100244 "ln [-s] oldpath newpath Link remote file (-s for symlink)\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100245 "lpwd Print local working directory\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100246 "ls [-1afhlnrSt] [path] Display remote directory listing\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100247 "lumask umask Set local umask to 'umask'\n"
248 "mkdir path Create remote directory\n"
249 "progress Toggle display of progress meter\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100250 "put [-Ppr] local [remote] Upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100251 "pwd Display remote working directory\n"
252 "quit Quit sftp\n"
253 "rename oldpath newpath Rename remote file\n"
254 "rm path Delete remote file\n"
255 "rmdir path Remove remote directory\n"
256 "symlink oldpath newpath Symlink remote file\n"
257 "version Show SFTP version\n"
258 "!command Execute 'command' in local shell\n"
259 "! Escape to local shell\n"
260 "? Synonym for help\n");
Damien Miller20e1fab2004-02-18 14:30:55 +1100261}
262
263static void
264local_do_shell(const char *args)
265{
266 int status;
267 char *shell;
268 pid_t pid;
269
270 if (!*args)
271 args = NULL;
272
Damien Miller38d9a962010-10-07 22:07:11 +1100273 if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
Damien Miller20e1fab2004-02-18 14:30:55 +1100274 shell = _PATH_BSHELL;
275
276 if ((pid = fork()) == -1)
277 fatal("Couldn't fork: %s", strerror(errno));
278
279 if (pid == 0) {
280 /* XXX: child has pipe fds to ssh subproc open - issue? */
281 if (args) {
282 debug3("Executing %s -c \"%s\"", shell, args);
283 execl(shell, shell, "-c", args, (char *)NULL);
284 } else {
285 debug3("Executing %s", shell);
286 execl(shell, shell, (char *)NULL);
287 }
288 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
289 strerror(errno));
290 _exit(1);
291 }
292 while (waitpid(pid, &status, 0) == -1)
293 if (errno != EINTR)
294 fatal("Couldn't wait for child: %s", strerror(errno));
295 if (!WIFEXITED(status))
Damien Miller55b04f12006-03-26 14:23:17 +1100296 error("Shell exited abnormally");
Damien Miller20e1fab2004-02-18 14:30:55 +1100297 else if (WEXITSTATUS(status))
298 error("Shell exited with status %d", WEXITSTATUS(status));
299}
300
301static void
302local_do_ls(const char *args)
303{
304 if (!args || !*args)
305 local_do_shell(_PATH_LS);
306 else {
307 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
308 char *buf = xmalloc(len);
309
310 /* XXX: quoting - rip quoting code from ftp? */
311 snprintf(buf, len, _PATH_LS " %s", args);
312 local_do_shell(buf);
313 xfree(buf);
314 }
315}
316
317/* Strip one path (usually the pwd) from the start of another */
318static char *
319path_strip(char *path, char *strip)
320{
321 size_t len;
322
323 if (strip == NULL)
324 return (xstrdup(path));
325
326 len = strlen(strip);
Darren Tuckere2f189a2004-12-06 22:45:53 +1100327 if (strncmp(path, strip, len) == 0) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100328 if (strip[len - 1] != '/' && path[len] == '/')
329 len++;
330 return (xstrdup(path + len));
331 }
332
333 return (xstrdup(path));
334}
335
336static char *
Damien Miller20e1fab2004-02-18 14:30:55 +1100337make_absolute(char *p, char *pwd)
338{
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000339 char *abs_str;
Damien Miller20e1fab2004-02-18 14:30:55 +1100340
341 /* Derelativise */
342 if (p && p[0] != '/') {
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000343 abs_str = path_append(pwd, p);
Damien Miller20e1fab2004-02-18 14:30:55 +1100344 xfree(p);
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000345 return(abs_str);
Damien Miller20e1fab2004-02-18 14:30:55 +1100346 } else
347 return(p);
348}
349
350static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100351parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag,
352 int *rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100353{
Damien Millerf184bcf2008-06-29 22:45:13 +1000354 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000355 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100356
Damien Miller1cbc2922007-10-26 14:27:45 +1000357 optind = optreset = 1;
358 opterr = 0;
359
Darren Tucker1b0dd172009-10-07 08:37:48 +1100360 *rflag = *pflag = 0;
361 while ((ch = getopt(argc, argv, "PpRr")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000362 switch (ch) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100363 case 'p':
364 case 'P':
365 *pflag = 1;
366 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100367 case 'r':
368 case 'R':
369 *rflag = 1;
370 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100371 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000372 error("%s: Invalid flag -%c", cmd, optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000373 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100374 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100375 }
376
Damien Miller1cbc2922007-10-26 14:27:45 +1000377 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100378}
379
380static int
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100381parse_link_flags(const char *cmd, char **argv, int argc, int *sflag)
382{
383 extern int opterr, optind, optopt, optreset;
384 int ch;
385
386 optind = optreset = 1;
387 opterr = 0;
388
389 *sflag = 0;
390 while ((ch = getopt(argc, argv, "s")) != -1) {
391 switch (ch) {
392 case 's':
393 *sflag = 1;
394 break;
395 default:
396 error("%s: Invalid flag -%c", cmd, optopt);
397 return -1;
398 }
399 }
400
401 return optind;
402}
403
404static int
Damien Miller1cbc2922007-10-26 14:27:45 +1000405parse_ls_flags(char **argv, int argc, int *lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100406{
Damien Millerf184bcf2008-06-29 22:45:13 +1000407 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000408 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100409
Damien Miller1cbc2922007-10-26 14:27:45 +1000410 optind = optreset = 1;
411 opterr = 0;
412
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000413 *lflag = LS_NAME_SORT;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100414 while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000415 switch (ch) {
416 case '1':
417 *lflag &= ~VIEW_FLAGS;
418 *lflag |= LS_SHORT_VIEW;
419 break;
420 case 'S':
421 *lflag &= ~SORT_FLAGS;
422 *lflag |= LS_SIZE_SORT;
423 break;
424 case 'a':
425 *lflag |= LS_SHOW_ALL;
426 break;
427 case 'f':
428 *lflag &= ~SORT_FLAGS;
429 break;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100430 case 'h':
431 *lflag |= LS_SI_UNITS;
432 break;
Damien Miller1cbc2922007-10-26 14:27:45 +1000433 case 'l':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100434 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000435 *lflag |= LS_LONG_VIEW;
436 break;
437 case 'n':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100438 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000439 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
440 break;
441 case 'r':
442 *lflag |= LS_REVERSE_SORT;
443 break;
444 case 't':
445 *lflag &= ~SORT_FLAGS;
446 *lflag |= LS_TIME_SORT;
447 break;
448 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000449 error("ls: Invalid flag -%c", optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000450 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100451 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100452 }
453
Damien Miller1cbc2922007-10-26 14:27:45 +1000454 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100455}
456
457static int
Damien Millerd671e5a2008-05-19 14:53:33 +1000458parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
459{
Damien Millerf184bcf2008-06-29 22:45:13 +1000460 extern int opterr, optind, optopt, optreset;
Damien Millerd671e5a2008-05-19 14:53:33 +1000461 int ch;
462
463 optind = optreset = 1;
464 opterr = 0;
465
466 *hflag = *iflag = 0;
467 while ((ch = getopt(argc, argv, "hi")) != -1) {
468 switch (ch) {
469 case 'h':
470 *hflag = 1;
471 break;
472 case 'i':
473 *iflag = 1;
474 break;
475 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000476 error("%s: Invalid flag -%c", cmd, optopt);
Damien Millerd671e5a2008-05-19 14:53:33 +1000477 return -1;
478 }
479 }
480
481 return optind;
482}
483
484static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100485is_dir(char *path)
486{
487 struct stat sb;
488
489 /* XXX: report errors? */
490 if (stat(path, &sb) == -1)
491 return(0);
492
Darren Tucker1e80e402006-09-21 12:59:33 +1000493 return(S_ISDIR(sb.st_mode));
Damien Miller20e1fab2004-02-18 14:30:55 +1100494}
495
496static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100497remote_is_dir(struct sftp_conn *conn, char *path)
498{
499 Attrib *a;
500
501 /* XXX: report errors? */
502 if ((a = do_stat(conn, path, 1)) == NULL)
503 return(0);
504 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
505 return(0);
Darren Tucker1e80e402006-09-21 12:59:33 +1000506 return(S_ISDIR(a->perm));
Damien Miller20e1fab2004-02-18 14:30:55 +1100507}
508
Darren Tucker1b0dd172009-10-07 08:37:48 +1100509/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
Damien Miller20e1fab2004-02-18 14:30:55 +1100510static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100511pathname_is_dir(char *pathname)
512{
513 size_t l = strlen(pathname);
514
515 return l > 0 && pathname[l - 1] == '/';
516}
517
518static int
519process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
520 int pflag, int rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100521{
522 char *abs_src = NULL;
523 char *abs_dst = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100524 glob_t g;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100525 char *filename, *tmp=NULL;
526 int i, err = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100527
528 abs_src = xstrdup(src);
529 abs_src = make_absolute(abs_src, pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +1100530 memset(&g, 0, sizeof(g));
Darren Tucker1b0dd172009-10-07 08:37:48 +1100531
Damien Miller20e1fab2004-02-18 14:30:55 +1100532 debug3("Looking up %s", abs_src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100533 if (remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100534 error("File \"%s\" not found.", abs_src);
535 err = -1;
536 goto out;
537 }
538
Darren Tucker1b0dd172009-10-07 08:37:48 +1100539 /*
540 * If multiple matches then dst must be a directory or
541 * unspecified.
542 */
543 if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
544 error("Multiple source paths, but destination "
545 "\"%s\" is not a directory", dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100546 err = -1;
547 goto out;
548 }
549
Darren Tuckercdf547a2004-05-24 10:12:19 +1000550 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100551 tmp = xstrdup(g.gl_pathv[i]);
552 if ((filename = basename(tmp)) == NULL) {
553 error("basename %s: %s", tmp, strerror(errno));
554 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100555 err = -1;
556 goto out;
557 }
558
559 if (g.gl_matchc == 1 && dst) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100560 if (is_dir(dst)) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100561 abs_dst = path_append(dst, filename);
562 } else {
Damien Miller20e1fab2004-02-18 14:30:55 +1100563 abs_dst = xstrdup(dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100564 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100565 } else if (dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100566 abs_dst = path_append(dst, filename);
567 } else {
568 abs_dst = xstrdup(filename);
569 }
570 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100571
572 printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100573 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
574 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
575 pflag || global_pflag, 1) == -1)
576 err = -1;
577 } else {
578 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
579 pflag || global_pflag) == -1)
580 err = -1;
581 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100582 xfree(abs_dst);
583 abs_dst = NULL;
584 }
585
586out:
587 xfree(abs_src);
Damien Miller20e1fab2004-02-18 14:30:55 +1100588 globfree(&g);
589 return(err);
590}
591
592static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100593process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
594 int pflag, int rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100595{
596 char *tmp_dst = NULL;
597 char *abs_dst = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100598 char *tmp = NULL, *filename = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100599 glob_t g;
600 int err = 0;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100601 int i, dst_is_dir = 1;
Damien Milleraec5cf82008-02-10 22:26:24 +1100602 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100603
604 if (dst) {
605 tmp_dst = xstrdup(dst);
606 tmp_dst = make_absolute(tmp_dst, pwd);
607 }
608
609 memset(&g, 0, sizeof(g));
610 debug3("Looking up %s", src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100611 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100612 error("File \"%s\" not found.", src);
613 err = -1;
614 goto out;
615 }
616
Darren Tucker1b0dd172009-10-07 08:37:48 +1100617 /* If we aren't fetching to pwd then stash this status for later */
618 if (tmp_dst != NULL)
619 dst_is_dir = remote_is_dir(conn, tmp_dst);
620
Damien Miller20e1fab2004-02-18 14:30:55 +1100621 /* If multiple matches, dst may be directory or unspecified */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100622 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
623 error("Multiple paths match, but destination "
624 "\"%s\" is not a directory", tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100625 err = -1;
626 goto out;
627 }
628
Darren Tuckercdf547a2004-05-24 10:12:19 +1000629 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Milleraec5cf82008-02-10 22:26:24 +1100630 if (stat(g.gl_pathv[i], &sb) == -1) {
631 err = -1;
632 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
633 continue;
634 }
Darren Tucker1b0dd172009-10-07 08:37:48 +1100635
636 tmp = xstrdup(g.gl_pathv[i]);
637 if ((filename = basename(tmp)) == NULL) {
638 error("basename %s: %s", tmp, strerror(errno));
639 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100640 err = -1;
641 goto out;
642 }
643
644 if (g.gl_matchc == 1 && tmp_dst) {
645 /* If directory specified, append filename */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100646 if (dst_is_dir)
647 abs_dst = path_append(tmp_dst, filename);
648 else
Damien Miller20e1fab2004-02-18 14:30:55 +1100649 abs_dst = xstrdup(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100650 } else if (tmp_dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100651 abs_dst = path_append(tmp_dst, filename);
652 } else {
653 abs_dst = make_absolute(xstrdup(filename), pwd);
654 }
655 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100656
657 printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100658 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
659 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
660 pflag || global_pflag, 1) == -1)
661 err = -1;
662 } else {
663 if (do_upload(conn, g.gl_pathv[i], abs_dst,
664 pflag || global_pflag) == -1)
665 err = -1;
666 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100667 }
668
669out:
670 if (abs_dst)
671 xfree(abs_dst);
672 if (tmp_dst)
673 xfree(tmp_dst);
674 globfree(&g);
675 return(err);
676}
677
678static int
679sdirent_comp(const void *aa, const void *bb)
680{
681 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
682 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000683 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100684
Darren Tuckerb9123452004-06-22 13:06:45 +1000685#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000686 if (sort_flag & LS_NAME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000687 return (rmul * strcmp(a->filename, b->filename));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000688 else if (sort_flag & LS_TIME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000689 return (rmul * NCMP(a->a.mtime, b->a.mtime));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000690 else if (sort_flag & LS_SIZE_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000691 return (rmul * NCMP(a->a.size, b->a.size));
692
693 fatal("Unknown ls sort type");
Damien Miller20e1fab2004-02-18 14:30:55 +1100694}
695
696/* sftp ls.1 replacement for directories */
697static int
698do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
699{
Damien Millereccb9de2005-06-17 12:59:34 +1000700 int n;
701 u_int c = 1, colspace = 0, columns = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100702 SFTP_DIRENT **d;
703
704 if ((n = do_readdir(conn, path, &d)) != 0)
705 return (n);
706
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000707 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000708 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100709 struct winsize ws;
710 char *tmp;
711
712 /* Count entries for sort and find longest filename */
Darren Tucker9a526452004-06-22 13:09:55 +1000713 for (n = 0; d[n] != NULL; n++) {
714 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
715 m = MAX(m, strlen(d[n]->filename));
716 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100717
718 /* Add any subpath that also needs to be counted */
719 tmp = path_strip(path, strip_path);
720 m += strlen(tmp);
721 xfree(tmp);
722
723 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
724 width = ws.ws_col;
725
726 columns = width / (m + 2);
727 columns = MAX(columns, 1);
728 colspace = width / columns;
729 colspace = MIN(colspace, width);
730 }
731
Darren Tuckerb9123452004-06-22 13:06:45 +1000732 if (lflag & SORT_FLAGS) {
Damien Miller653b93b2005-11-05 15:15:23 +1100733 for (n = 0; d[n] != NULL; n++)
734 ; /* count entries */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000735 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
Darren Tuckerb9123452004-06-22 13:06:45 +1000736 qsort(d, n, sizeof(*d), sdirent_comp);
737 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100738
Darren Tuckercdf547a2004-05-24 10:12:19 +1000739 for (n = 0; d[n] != NULL && !interrupted; n++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100740 char *tmp, *fname;
741
Darren Tucker9a526452004-06-22 13:09:55 +1000742 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
743 continue;
744
Damien Miller20e1fab2004-02-18 14:30:55 +1100745 tmp = path_append(path, d[n]->filename);
746 fname = path_strip(tmp, strip_path);
747 xfree(tmp);
748
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000749 if (lflag & LS_LONG_VIEW) {
Darren Tucker2901e2d2010-01-13 22:44:06 +1100750 if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000751 char *lname;
752 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100753
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000754 memset(&sb, 0, sizeof(sb));
755 attrib_to_stat(&d[n]->a, &sb);
Darren Tucker2901e2d2010-01-13 22:44:06 +1100756 lname = ls_file(fname, &sb, 1,
757 (lflag & LS_SI_UNITS));
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000758 printf("%s\n", lname);
759 xfree(lname);
760 } else
761 printf("%s\n", d[n]->longname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100762 } else {
763 printf("%-*s", colspace, fname);
764 if (c >= columns) {
765 printf("\n");
766 c = 1;
767 } else
768 c++;
769 }
770
771 xfree(fname);
772 }
773
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000774 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100775 printf("\n");
776
777 free_sftp_dirents(d);
778 return (0);
779}
780
781/* sftp ls.1 replacement which handles path globs */
782static int
783do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
784 int lflag)
785{
Darren Tucker596dcfa2004-12-11 13:37:22 +1100786 Attrib *a = NULL;
Damien Millera6e121a2010-10-07 21:39:17 +1100787 char *fname, *lname;
Damien Miller68e2e562010-10-07 21:39:55 +1100788 glob_t g;
789 int err;
790 struct winsize ws;
791 u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100792
793 memset(&g, 0, sizeof(g));
794
Damien Millera6e121a2010-10-07 21:39:17 +1100795 if (remote_glob(conn, path,
796 GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT, NULL, &g) ||
797 (g.gl_pathc && !g.gl_matchc)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100798 if (g.gl_pathc)
799 globfree(&g);
Damien Miller20e1fab2004-02-18 14:30:55 +1100800 error("Can't ls: \"%s\" not found", path);
Damien Millera6e121a2010-10-07 21:39:17 +1100801 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100802 }
803
Darren Tuckercdf547a2004-05-24 10:12:19 +1000804 if (interrupted)
805 goto out;
806
Damien Miller20e1fab2004-02-18 14:30:55 +1100807 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100808 * If the glob returns a single match and it is a directory,
809 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100810 */
Damien Millera6e121a2010-10-07 21:39:17 +1100811 if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
812 S_ISDIR(g.gl_statv[0]->st_mode)) {
813 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
814 globfree(&g);
815 return err;
Damien Miller20e1fab2004-02-18 14:30:55 +1100816 }
817
Damien Miller68e2e562010-10-07 21:39:55 +1100818 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
819 width = ws.ws_col;
Damien Miller20e1fab2004-02-18 14:30:55 +1100820
Damien Miller68e2e562010-10-07 21:39:55 +1100821 if (!(lflag & LS_SHORT_VIEW)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100822 /* Count entries for sort and find longest filename */
823 for (i = 0; g.gl_pathv[i]; i++)
824 m = MAX(m, strlen(g.gl_pathv[i]));
825
Damien Miller20e1fab2004-02-18 14:30:55 +1100826 columns = width / (m + 2);
827 columns = MAX(columns, 1);
828 colspace = width / columns;
829 }
830
Darren Tucker596dcfa2004-12-11 13:37:22 +1100831 for (i = 0; g.gl_pathv[i] && !interrupted; i++, a = NULL) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100832 fname = path_strip(g.gl_pathv[i], strip_path);
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000833 if (lflag & LS_LONG_VIEW) {
Damien Millera6e121a2010-10-07 21:39:17 +1100834 if (g.gl_statv[i] == NULL) {
835 error("no stat information for %s", fname);
836 continue;
837 }
838 lname = ls_file(fname, g.gl_statv[i], 1,
839 (lflag & LS_SI_UNITS));
Damien Miller20e1fab2004-02-18 14:30:55 +1100840 printf("%s\n", lname);
841 xfree(lname);
842 } else {
843 printf("%-*s", colspace, fname);
844 if (c >= columns) {
845 printf("\n");
846 c = 1;
847 } else
848 c++;
849 }
850 xfree(fname);
851 }
852
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000853 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100854 printf("\n");
855
Darren Tuckercdf547a2004-05-24 10:12:19 +1000856 out:
Damien Miller20e1fab2004-02-18 14:30:55 +1100857 if (g.gl_pathc)
858 globfree(&g);
859
Damien Millera6e121a2010-10-07 21:39:17 +1100860 return 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100861}
862
Damien Millerd671e5a2008-05-19 14:53:33 +1000863static int
864do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
865{
Darren Tucker7b598892008-06-09 22:49:36 +1000866 struct sftp_statvfs st;
Damien Millerd671e5a2008-05-19 14:53:33 +1000867 char s_used[FMT_SCALED_STRSIZE];
868 char s_avail[FMT_SCALED_STRSIZE];
869 char s_root[FMT_SCALED_STRSIZE];
870 char s_total[FMT_SCALED_STRSIZE];
Darren Tuckerb5082e92010-01-08 18:51:47 +1100871 unsigned long long ffree;
Damien Millerd671e5a2008-05-19 14:53:33 +1000872
873 if (do_statvfs(conn, path, &st, 1) == -1)
874 return -1;
875 if (iflag) {
Darren Tuckerb5082e92010-01-08 18:51:47 +1100876 ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
Damien Millerd671e5a2008-05-19 14:53:33 +1000877 printf(" Inodes Used Avail "
878 "(root) %%Capacity\n");
879 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
880 (unsigned long long)st.f_files,
881 (unsigned long long)(st.f_files - st.f_ffree),
882 (unsigned long long)st.f_favail,
Darren Tuckerb5082e92010-01-08 18:51:47 +1100883 (unsigned long long)st.f_ffree, ffree);
Damien Millerd671e5a2008-05-19 14:53:33 +1000884 } else if (hflag) {
885 strlcpy(s_used, "error", sizeof(s_used));
886 strlcpy(s_avail, "error", sizeof(s_avail));
887 strlcpy(s_root, "error", sizeof(s_root));
888 strlcpy(s_total, "error", sizeof(s_total));
889 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
890 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
891 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
892 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
893 printf(" Size Used Avail (root) %%Capacity\n");
894 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
895 s_total, s_used, s_avail, s_root,
896 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
897 st.f_blocks));
898 } else {
899 printf(" Size Used Avail "
900 "(root) %%Capacity\n");
901 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
902 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
903 (unsigned long long)(st.f_frsize *
904 (st.f_blocks - st.f_bfree) / 1024),
905 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
906 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
907 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
908 st.f_blocks));
909 }
910 return 0;
911}
912
Damien Miller1cbc2922007-10-26 14:27:45 +1000913/*
914 * Undo escaping of glob sequences in place. Used to undo extra escaping
915 * applied in makeargv() when the string is destined for a function that
916 * does not glob it.
917 */
918static void
919undo_glob_escape(char *s)
920{
921 size_t i, j;
922
923 for (i = j = 0;;) {
924 if (s[i] == '\0') {
925 s[j] = '\0';
926 return;
927 }
928 if (s[i] != '\\') {
929 s[j++] = s[i++];
930 continue;
931 }
932 /* s[i] == '\\' */
933 ++i;
934 switch (s[i]) {
935 case '?':
936 case '[':
937 case '*':
938 case '\\':
939 s[j++] = s[i++];
940 break;
941 case '\0':
942 s[j++] = '\\';
943 s[j] = '\0';
944 return;
945 default:
946 s[j++] = '\\';
947 s[j++] = s[i++];
948 break;
949 }
950 }
951}
952
953/*
954 * Split a string into an argument vector using sh(1)-style quoting,
955 * comment and escaping rules, but with some tweaks to handle glob(3)
956 * wildcards.
Darren Tucker909d8582010-01-08 19:02:40 +1100957 * The "sloppy" flag allows for recovery from missing terminating quote, for
958 * use in parsing incomplete commandlines during tab autocompletion.
959 *
Damien Miller1cbc2922007-10-26 14:27:45 +1000960 * Returns NULL on error or a NULL-terminated array of arguments.
Darren Tucker909d8582010-01-08 19:02:40 +1100961 *
962 * If "lastquote" is not NULL, the quoting character used for the last
963 * argument is placed in *lastquote ("\0", "'" or "\"").
964 *
965 * If "terminated" is not NULL, *terminated will be set to 1 when the
966 * last argument's quote has been properly terminated or 0 otherwise.
967 * This parameter is only of use if "sloppy" is set.
Damien Miller1cbc2922007-10-26 14:27:45 +1000968 */
969#define MAXARGS 128
970#define MAXARGLEN 8192
971static char **
Darren Tucker909d8582010-01-08 19:02:40 +1100972makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
973 u_int *terminated)
Damien Miller1cbc2922007-10-26 14:27:45 +1000974{
975 int argc, quot;
976 size_t i, j;
977 static char argvs[MAXARGLEN];
978 static char *argv[MAXARGS + 1];
979 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
980
981 *argcp = argc = 0;
982 if (strlen(arg) > sizeof(argvs) - 1) {
983 args_too_longs:
984 error("string too long");
985 return NULL;
986 }
Darren Tucker909d8582010-01-08 19:02:40 +1100987 if (terminated != NULL)
988 *terminated = 1;
989 if (lastquote != NULL)
990 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +1000991 state = MA_START;
992 i = j = 0;
993 for (;;) {
994 if (isspace(arg[i])) {
995 if (state == MA_UNQUOTED) {
996 /* Terminate current argument */
997 argvs[j++] = '\0';
998 argc++;
999 state = MA_START;
1000 } else if (state != MA_START)
1001 argvs[j++] = arg[i];
1002 } else if (arg[i] == '"' || arg[i] == '\'') {
1003 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1004 if (state == MA_START) {
1005 argv[argc] = argvs + j;
1006 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +11001007 if (lastquote != NULL)
1008 *lastquote = arg[i];
Damien Miller1cbc2922007-10-26 14:27:45 +10001009 } else if (state == MA_UNQUOTED)
1010 state = q;
1011 else if (state == q)
1012 state = MA_UNQUOTED;
1013 else
1014 argvs[j++] = arg[i];
1015 } else if (arg[i] == '\\') {
1016 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1017 quot = state == MA_SQUOTE ? '\'' : '"';
1018 /* Unescape quote we are in */
1019 /* XXX support \n and friends? */
1020 if (arg[i + 1] == quot) {
1021 i++;
1022 argvs[j++] = arg[i];
1023 } else if (arg[i + 1] == '?' ||
1024 arg[i + 1] == '[' || arg[i + 1] == '*') {
1025 /*
1026 * Special case for sftp: append
1027 * double-escaped glob sequence -
1028 * glob will undo one level of
1029 * escaping. NB. string can grow here.
1030 */
1031 if (j >= sizeof(argvs) - 5)
1032 goto args_too_longs;
1033 argvs[j++] = '\\';
1034 argvs[j++] = arg[i++];
1035 argvs[j++] = '\\';
1036 argvs[j++] = arg[i];
1037 } else {
1038 argvs[j++] = arg[i++];
1039 argvs[j++] = arg[i];
1040 }
1041 } else {
1042 if (state == MA_START) {
1043 argv[argc] = argvs + j;
1044 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001045 if (lastquote != NULL)
1046 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001047 }
1048 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1049 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1050 /*
1051 * Special case for sftp: append
1052 * escaped glob sequence -
1053 * glob will undo one level of
1054 * escaping.
1055 */
1056 argvs[j++] = arg[i++];
1057 argvs[j++] = arg[i];
1058 } else {
1059 /* Unescape everything */
1060 /* XXX support \n and friends? */
1061 i++;
1062 argvs[j++] = arg[i];
1063 }
1064 }
1065 } else if (arg[i] == '#') {
1066 if (state == MA_SQUOTE || state == MA_DQUOTE)
1067 argvs[j++] = arg[i];
1068 else
1069 goto string_done;
1070 } else if (arg[i] == '\0') {
1071 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001072 if (sloppy) {
1073 state = MA_UNQUOTED;
1074 if (terminated != NULL)
1075 *terminated = 0;
1076 goto string_done;
1077 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001078 error("Unterminated quoted argument");
1079 return NULL;
1080 }
1081 string_done:
1082 if (state == MA_UNQUOTED) {
1083 argvs[j++] = '\0';
1084 argc++;
1085 }
1086 break;
1087 } else {
1088 if (state == MA_START) {
1089 argv[argc] = argvs + j;
1090 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001091 if (lastquote != NULL)
1092 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001093 }
1094 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1095 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1096 /*
1097 * Special case for sftp: escape quoted
1098 * glob(3) wildcards. NB. string can grow
1099 * here.
1100 */
1101 if (j >= sizeof(argvs) - 3)
1102 goto args_too_longs;
1103 argvs[j++] = '\\';
1104 argvs[j++] = arg[i];
1105 } else
1106 argvs[j++] = arg[i];
1107 }
1108 i++;
1109 }
1110 *argcp = argc;
1111 return argv;
1112}
1113
Damien Miller20e1fab2004-02-18 14:30:55 +11001114static int
Darren Tucker909d8582010-01-08 19:02:40 +11001115parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag,
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001116 int *hflag, int *sflag, unsigned long *n_arg, char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001117{
1118 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001119 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001120 int base = 0;
1121 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001122 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001123
1124 /* Skip leading whitespace */
1125 cp = cp + strspn(cp, WHITESPACE);
1126
Damien Miller20e1fab2004-02-18 14:30:55 +11001127 /* Check for leading '-' (disable error processing) */
1128 *iflag = 0;
1129 if (*cp == '-') {
1130 *iflag = 1;
1131 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001132 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001133 }
1134
Darren Tucker70cc0922010-01-09 22:28:03 +11001135 /* Ignore blank lines and lines which begin with comment '#' char */
1136 if (*cp == '\0' || *cp == '#')
1137 return (0);
1138
Darren Tucker909d8582010-01-08 19:02:40 +11001139 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001140 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001141
Damien Miller1cbc2922007-10-26 14:27:45 +10001142 /* Figure out which command we have */
1143 for (i = 0; cmds[i].c != NULL; i++) {
1144 if (strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001145 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001146 }
1147 cmdnum = cmds[i].n;
1148 cmd = cmds[i].c;
1149
1150 /* Special case */
1151 if (*cp == '!') {
1152 cp++;
1153 cmdnum = I_SHELL;
1154 } else if (cmdnum == -1) {
1155 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001156 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001157 }
1158
1159 /* Get arguments and parse flags */
Darren Tucker1b0dd172009-10-07 08:37:48 +11001160 *lflag = *pflag = *rflag = *hflag = *n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001161 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001162 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001163 switch (cmdnum) {
1164 case I_GET:
1165 case I_PUT:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001166 if ((optidx = parse_getput_flags(cmd, argv, argc,
1167 pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001168 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001169 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001170 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001171 error("You must specify at least one path after a "
1172 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001173 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001174 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001175 *path1 = xstrdup(argv[optidx]);
1176 /* Get second pathname (optional) */
1177 if (argc - optidx > 1) {
1178 *path2 = xstrdup(argv[optidx + 1]);
1179 /* Destination is not globbed */
1180 undo_glob_escape(*path2);
1181 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001182 break;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001183 case I_LINK:
1184 if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1185 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001186 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001187 case I_RENAME:
Damien Miller1cbc2922007-10-26 14:27:45 +10001188 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001189 error("You must specify two paths after a %s "
1190 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001191 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001192 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001193 *path1 = xstrdup(argv[optidx]);
1194 *path2 = xstrdup(argv[optidx + 1]);
1195 /* Paths are not globbed */
1196 undo_glob_escape(*path1);
1197 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001198 break;
1199 case I_RM:
1200 case I_MKDIR:
1201 case I_RMDIR:
1202 case I_CHDIR:
1203 case I_LCHDIR:
1204 case I_LMKDIR:
1205 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001206 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001207 error("You must specify a path after a %s command.",
1208 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001209 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001210 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001211 *path1 = xstrdup(argv[optidx]);
1212 /* Only "rm" globs */
1213 if (cmdnum != I_RM)
1214 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001215 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001216 case I_DF:
1217 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1218 iflag)) == -1)
1219 return -1;
1220 /* Default to current directory if no path specified */
1221 if (argc - optidx < 1)
1222 *path1 = NULL;
1223 else {
1224 *path1 = xstrdup(argv[optidx]);
1225 undo_glob_escape(*path1);
1226 }
1227 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001228 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001229 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001230 return(-1);
1231 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001232 if (argc - optidx > 0)
1233 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001234 break;
1235 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001236 /* Skip ls command and following whitespace */
1237 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001238 case I_SHELL:
1239 /* Uses the rest of the line */
1240 break;
1241 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001242 case I_CHMOD:
1243 base = 8;
1244 case I_CHOWN:
1245 case I_CHGRP:
1246 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001247 if (argc - optidx < 1)
1248 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001249 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001250 l = strtol(argv[optidx], &cp2, base);
1251 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1252 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1253 l < 0) {
1254 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001255 error("You must supply a numeric argument "
1256 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001257 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001258 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001259 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001260 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001261 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001262 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001263 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001264 error("You must specify a path after a %s command.",
1265 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001266 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001267 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001268 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001269 break;
1270 case I_QUIT:
1271 case I_PWD:
1272 case I_LPWD:
1273 case I_HELP:
1274 case I_VERSION:
1275 case I_PROGRESS:
1276 break;
1277 default:
1278 fatal("Command not implemented");
1279 }
1280
1281 *cpp = cp;
1282 return(cmdnum);
1283}
1284
1285static int
1286parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1287 int err_abort)
1288{
1289 char *path1, *path2, *tmp;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001290 int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, sflag = 0;
1291 int cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001292 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001293 Attrib a, *aa;
1294 char path_buf[MAXPATHLEN];
1295 int err = 0;
1296 glob_t g;
1297
1298 path1 = path2 = NULL;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001299 cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag,
1300 &sflag, &n_arg, &path1, &path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001301
1302 if (iflag != 0)
1303 err_abort = 0;
1304
1305 memset(&g, 0, sizeof(g));
1306
1307 /* Perform command */
1308 switch (cmdnum) {
1309 case 0:
1310 /* Blank line */
1311 break;
1312 case -1:
1313 /* Unrecognized command */
1314 err = -1;
1315 break;
1316 case I_GET:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001317 err = process_get(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001318 break;
1319 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001320 err = process_put(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001321 break;
1322 case I_RENAME:
1323 path1 = make_absolute(path1, *pwd);
1324 path2 = make_absolute(path2, *pwd);
1325 err = do_rename(conn, path1, path2);
1326 break;
1327 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001328 sflag = 1;
1329 case I_LINK:
1330 path1 = make_absolute(path1, *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001331 path2 = make_absolute(path2, *pwd);
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001332 err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001333 break;
1334 case I_RM:
1335 path1 = make_absolute(path1, *pwd);
1336 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001337 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001338 printf("Removing %s\n", g.gl_pathv[i]);
1339 err = do_rm(conn, g.gl_pathv[i]);
1340 if (err != 0 && err_abort)
1341 break;
1342 }
1343 break;
1344 case I_MKDIR:
1345 path1 = make_absolute(path1, *pwd);
1346 attrib_clear(&a);
1347 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1348 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001349 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001350 break;
1351 case I_RMDIR:
1352 path1 = make_absolute(path1, *pwd);
1353 err = do_rmdir(conn, path1);
1354 break;
1355 case I_CHDIR:
1356 path1 = make_absolute(path1, *pwd);
1357 if ((tmp = do_realpath(conn, path1)) == NULL) {
1358 err = 1;
1359 break;
1360 }
1361 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1362 xfree(tmp);
1363 err = 1;
1364 break;
1365 }
1366 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1367 error("Can't change directory: Can't check target");
1368 xfree(tmp);
1369 err = 1;
1370 break;
1371 }
1372 if (!S_ISDIR(aa->perm)) {
1373 error("Can't change directory: \"%s\" is not "
1374 "a directory", tmp);
1375 xfree(tmp);
1376 err = 1;
1377 break;
1378 }
1379 xfree(*pwd);
1380 *pwd = tmp;
1381 break;
1382 case I_LS:
1383 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001384 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001385 break;
1386 }
1387
1388 /* Strip pwd off beginning of non-absolute paths */
1389 tmp = NULL;
1390 if (*path1 != '/')
1391 tmp = *pwd;
1392
1393 path1 = make_absolute(path1, *pwd);
1394 err = do_globbed_ls(conn, path1, tmp, lflag);
1395 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001396 case I_DF:
1397 /* Default to current directory if no path specified */
1398 if (path1 == NULL)
1399 path1 = xstrdup(*pwd);
1400 path1 = make_absolute(path1, *pwd);
1401 err = do_df(conn, path1, hflag, iflag);
1402 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001403 case I_LCHDIR:
1404 if (chdir(path1) == -1) {
1405 error("Couldn't change local directory to "
1406 "\"%s\": %s", path1, strerror(errno));
1407 err = 1;
1408 }
1409 break;
1410 case I_LMKDIR:
1411 if (mkdir(path1, 0777) == -1) {
1412 error("Couldn't create local directory "
1413 "\"%s\": %s", path1, strerror(errno));
1414 err = 1;
1415 }
1416 break;
1417 case I_LLS:
1418 local_do_ls(cmd);
1419 break;
1420 case I_SHELL:
1421 local_do_shell(cmd);
1422 break;
1423 case I_LUMASK:
1424 umask(n_arg);
1425 printf("Local umask: %03lo\n", n_arg);
1426 break;
1427 case I_CHMOD:
1428 path1 = make_absolute(path1, *pwd);
1429 attrib_clear(&a);
1430 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1431 a.perm = n_arg;
1432 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001433 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001434 printf("Changing mode on %s\n", g.gl_pathv[i]);
1435 err = do_setstat(conn, g.gl_pathv[i], &a);
1436 if (err != 0 && err_abort)
1437 break;
1438 }
1439 break;
1440 case I_CHOWN:
1441 case I_CHGRP:
1442 path1 = make_absolute(path1, *pwd);
1443 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001444 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001445 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001446 if (err_abort) {
1447 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001448 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001449 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001450 continue;
1451 }
1452 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1453 error("Can't get current ownership of "
1454 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001455 if (err_abort) {
1456 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001457 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001458 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001459 continue;
1460 }
1461 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1462 if (cmdnum == I_CHOWN) {
1463 printf("Changing owner on %s\n", g.gl_pathv[i]);
1464 aa->uid = n_arg;
1465 } else {
1466 printf("Changing group on %s\n", g.gl_pathv[i]);
1467 aa->gid = n_arg;
1468 }
1469 err = do_setstat(conn, g.gl_pathv[i], aa);
1470 if (err != 0 && err_abort)
1471 break;
1472 }
1473 break;
1474 case I_PWD:
1475 printf("Remote working directory: %s\n", *pwd);
1476 break;
1477 case I_LPWD:
1478 if (!getcwd(path_buf, sizeof(path_buf))) {
1479 error("Couldn't get local cwd: %s", strerror(errno));
1480 err = -1;
1481 break;
1482 }
1483 printf("Local working directory: %s\n", path_buf);
1484 break;
1485 case I_QUIT:
1486 /* Processed below */
1487 break;
1488 case I_HELP:
1489 help();
1490 break;
1491 case I_VERSION:
1492 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1493 break;
1494 case I_PROGRESS:
1495 showprogress = !showprogress;
1496 if (showprogress)
1497 printf("Progress meter enabled\n");
1498 else
1499 printf("Progress meter disabled\n");
1500 break;
1501 default:
1502 fatal("%d is not implemented", cmdnum);
1503 }
1504
1505 if (g.gl_pathc)
1506 globfree(&g);
1507 if (path1)
1508 xfree(path1);
1509 if (path2)
1510 xfree(path2);
1511
1512 /* If an unignored error occurs in batch mode we should abort. */
1513 if (err_abort && err != 0)
1514 return (-1);
1515 else if (cmdnum == I_QUIT)
1516 return (1);
1517
1518 return (0);
1519}
1520
Darren Tucker2d963d82004-11-07 20:04:10 +11001521#ifdef USE_LIBEDIT
1522static char *
1523prompt(EditLine *el)
1524{
1525 return ("sftp> ");
1526}
Darren Tucker2d963d82004-11-07 20:04:10 +11001527
Darren Tucker909d8582010-01-08 19:02:40 +11001528/* Display entries in 'list' after skipping the first 'len' chars */
1529static void
1530complete_display(char **list, u_int len)
1531{
1532 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1533 struct winsize ws;
1534 char *tmp;
1535
1536 /* Count entries for sort and find longest */
1537 for (y = 0; list[y]; y++)
1538 m = MAX(m, strlen(list[y]));
1539
1540 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1541 width = ws.ws_col;
1542
1543 m = m > len ? m - len : 0;
1544 columns = width / (m + 2);
1545 columns = MAX(columns, 1);
1546 colspace = width / columns;
1547 colspace = MIN(colspace, width);
1548
1549 printf("\n");
1550 m = 1;
1551 for (y = 0; list[y]; y++) {
1552 llen = strlen(list[y]);
1553 tmp = llen > len ? list[y] + len : "";
1554 printf("%-*s", colspace, tmp);
1555 if (m >= columns) {
1556 printf("\n");
1557 m = 1;
1558 } else
1559 m++;
1560 }
1561 printf("\n");
1562}
1563
1564/*
1565 * Given a "list" of words that begin with a common prefix of "word",
1566 * attempt to find an autocompletion to extends "word" by the next
1567 * characters common to all entries in "list".
1568 */
1569static char *
1570complete_ambiguous(const char *word, char **list, size_t count)
1571{
1572 if (word == NULL)
1573 return NULL;
1574
1575 if (count > 0) {
1576 u_int y, matchlen = strlen(list[0]);
1577
1578 /* Find length of common stem */
1579 for (y = 1; list[y]; y++) {
1580 u_int x;
1581
1582 for (x = 0; x < matchlen; x++)
1583 if (list[0][x] != list[y][x])
1584 break;
1585
1586 matchlen = x;
1587 }
1588
1589 if (matchlen > strlen(word)) {
1590 char *tmp = xstrdup(list[0]);
1591
Darren Tucker340d1682010-01-09 08:54:31 +11001592 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001593 return tmp;
1594 }
1595 }
1596
1597 return xstrdup(word);
1598}
1599
1600/* Autocomplete a sftp command */
1601static int
1602complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1603 int terminated)
1604{
1605 u_int y, count = 0, cmdlen, tmplen;
1606 char *tmp, **list, argterm[3];
1607 const LineInfo *lf;
1608
1609 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1610
1611 /* No command specified: display all available commands */
1612 if (cmd == NULL) {
1613 for (y = 0; cmds[y].c; y++)
1614 list[count++] = xstrdup(cmds[y].c);
1615
1616 list[count] = NULL;
1617 complete_display(list, 0);
1618
1619 for (y = 0; list[y] != NULL; y++)
1620 xfree(list[y]);
1621 xfree(list);
1622 return count;
1623 }
1624
1625 /* Prepare subset of commands that start with "cmd" */
1626 cmdlen = strlen(cmd);
1627 for (y = 0; cmds[y].c; y++) {
1628 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
1629 list[count++] = xstrdup(cmds[y].c);
1630 }
1631 list[count] = NULL;
1632
1633 if (count == 0)
1634 return 0;
1635
1636 /* Complete ambigious command */
1637 tmp = complete_ambiguous(cmd, list, count);
1638 if (count > 1)
1639 complete_display(list, 0);
1640
1641 for (y = 0; list[y]; y++)
1642 xfree(list[y]);
1643 xfree(list);
1644
1645 if (tmp != NULL) {
1646 tmplen = strlen(tmp);
1647 cmdlen = strlen(cmd);
1648 /* If cmd may be extended then do so */
1649 if (tmplen > cmdlen)
1650 if (el_insertstr(el, tmp + cmdlen) == -1)
1651 fatal("el_insertstr failed.");
1652 lf = el_line(el);
1653 /* Terminate argument cleanly */
1654 if (count == 1) {
1655 y = 0;
1656 if (!terminated)
1657 argterm[y++] = quote;
1658 if (lastarg || *(lf->cursor) != ' ')
1659 argterm[y++] = ' ';
1660 argterm[y] = '\0';
1661 if (y > 0 && el_insertstr(el, argterm) == -1)
1662 fatal("el_insertstr failed.");
1663 }
1664 xfree(tmp);
1665 }
1666
1667 return count;
1668}
1669
1670/*
1671 * Determine whether a particular sftp command's arguments (if any)
1672 * represent local or remote files.
1673 */
1674static int
1675complete_is_remote(char *cmd) {
1676 int i;
1677
1678 if (cmd == NULL)
1679 return -1;
1680
1681 for (i = 0; cmds[i].c; i++) {
1682 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
1683 return cmds[i].t;
1684 }
1685
1686 return -1;
1687}
1688
1689/* Autocomplete a filename "file" */
1690static int
1691complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1692 char *file, int remote, int lastarg, char quote, int terminated)
1693{
1694 glob_t g;
1695 char *tmp, *tmp2, ins[3];
1696 u_int i, hadglob, pwdlen, len, tmplen, filelen;
1697 const LineInfo *lf;
1698
1699 /* Glob from "file" location */
1700 if (file == NULL)
1701 tmp = xstrdup("*");
1702 else
1703 xasprintf(&tmp, "%s*", file);
1704
1705 memset(&g, 0, sizeof(g));
1706 if (remote != LOCAL) {
1707 tmp = make_absolute(tmp, remote_path);
1708 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1709 } else
1710 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1711
1712 /* Determine length of pwd so we can trim completion display */
1713 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1714 /* Terminate counting on first unescaped glob metacharacter */
1715 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1716 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1717 hadglob = 1;
1718 break;
1719 }
1720 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1721 tmplen++;
1722 if (tmp[tmplen] == '/')
1723 pwdlen = tmplen + 1; /* track last seen '/' */
1724 }
1725 xfree(tmp);
1726
1727 if (g.gl_matchc == 0)
1728 goto out;
1729
1730 if (g.gl_matchc > 1)
1731 complete_display(g.gl_pathv, pwdlen);
1732
1733 tmp = NULL;
1734 /* Don't try to extend globs */
1735 if (file == NULL || hadglob)
1736 goto out;
1737
1738 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
1739 tmp = path_strip(tmp2, remote_path);
1740 xfree(tmp2);
1741
1742 if (tmp == NULL)
1743 goto out;
1744
1745 tmplen = strlen(tmp);
1746 filelen = strlen(file);
1747
1748 if (tmplen > filelen) {
1749 tmp2 = tmp + filelen;
1750 len = strlen(tmp2);
1751 /* quote argument on way out */
1752 for (i = 0; i < len; i++) {
1753 ins[0] = '\\';
1754 ins[1] = tmp2[i];
1755 ins[2] = '\0';
1756 switch (tmp2[i]) {
1757 case '\'':
1758 case '"':
1759 case '\\':
1760 case '\t':
Darren Tuckerd78739a2010-10-24 10:56:32 +11001761 case '[':
Darren Tucker909d8582010-01-08 19:02:40 +11001762 case ' ':
1763 if (quote == '\0' || tmp2[i] == quote) {
1764 if (el_insertstr(el, ins) == -1)
1765 fatal("el_insertstr "
1766 "failed.");
1767 break;
1768 }
1769 /* FALLTHROUGH */
1770 default:
1771 if (el_insertstr(el, ins + 1) == -1)
1772 fatal("el_insertstr failed.");
1773 break;
1774 }
1775 }
1776 }
1777
1778 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11001779 if (g.gl_matchc == 1) {
1780 i = 0;
1781 if (!terminated)
1782 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11001783 if (*(lf->cursor - 1) != '/' &&
1784 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11001785 ins[i++] = ' ';
1786 ins[i] = '\0';
1787 if (i > 0 && el_insertstr(el, ins) == -1)
1788 fatal("el_insertstr failed.");
1789 }
1790 xfree(tmp);
1791
1792 out:
1793 globfree(&g);
1794 return g.gl_matchc;
1795}
1796
1797/* tab-completion hook function, called via libedit */
1798static unsigned char
1799complete(EditLine *el, int ch)
1800{
1801 char **argv, *line, quote;
1802 u_int argc, carg, cursor, len, terminated, ret = CC_ERROR;
1803 const LineInfo *lf;
1804 struct complete_ctx *complete_ctx;
1805
1806 lf = el_line(el);
1807 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1808 fatal("%s: el_get failed", __func__);
1809
1810 /* Figure out which argument the cursor points to */
1811 cursor = lf->cursor - lf->buffer;
1812 line = (char *)xmalloc(cursor + 1);
1813 memcpy(line, lf->buffer, cursor);
1814 line[cursor] = '\0';
1815 argv = makeargv(line, &carg, 1, &quote, &terminated);
1816 xfree(line);
1817
1818 /* Get all the arguments on the line */
1819 len = lf->lastchar - lf->buffer;
1820 line = (char *)xmalloc(len + 1);
1821 memcpy(line, lf->buffer, len);
1822 line[len] = '\0';
1823 argv = makeargv(line, &argc, 1, NULL, NULL);
1824
1825 /* Ensure cursor is at EOL or a argument boundary */
1826 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1827 line[cursor] != '\n') {
1828 xfree(line);
1829 return ret;
1830 }
1831
1832 if (carg == 0) {
1833 /* Show all available commands */
1834 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1835 ret = CC_REDISPLAY;
1836 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1837 /* Handle the command parsing */
1838 if (complete_cmd_parse(el, argv[0], argc == carg,
1839 quote, terminated) != 0)
1840 ret = CC_REDISPLAY;
1841 } else if (carg >= 1) {
1842 /* Handle file parsing */
1843 int remote = complete_is_remote(argv[0]);
1844 char *filematch = NULL;
1845
1846 if (carg > 1 && line[cursor-1] != ' ')
1847 filematch = argv[carg - 1];
1848
1849 if (remote != 0 &&
1850 complete_match(el, complete_ctx->conn,
1851 *complete_ctx->remote_pathp, filematch,
1852 remote, carg == argc, quote, terminated) != 0)
1853 ret = CC_REDISPLAY;
1854 }
1855
1856 xfree(line);
1857 return ret;
1858}
Darren Tuckere67f7db2010-01-08 19:50:02 +11001859#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11001860
Damien Miller20e1fab2004-02-18 14:30:55 +11001861int
Darren Tucker21063192010-01-08 17:10:36 +11001862interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001863{
Darren Tucker909d8582010-01-08 19:02:40 +11001864 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11001865 char *dir = NULL;
1866 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10001867 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11001868 EditLine *el = NULL;
1869#ifdef USE_LIBEDIT
1870 History *hl = NULL;
1871 HistEvent hev;
1872 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11001873 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11001874
1875 if (!batchmode && isatty(STDIN_FILENO)) {
1876 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1877 fatal("Couldn't initialise editline");
1878 if ((hl = history_init()) == NULL)
1879 fatal("Couldn't initialise editline history");
1880 history(hl, &hev, H_SETSIZE, 100);
1881 el_set(el, EL_HIST, history, hl);
1882
1883 el_set(el, EL_PROMPT, prompt);
1884 el_set(el, EL_EDITOR, "emacs");
1885 el_set(el, EL_TERMINAL, NULL);
1886 el_set(el, EL_SIGNAL, 1);
1887 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11001888
1889 /* Tab Completion */
1890 el_set(el, EL_ADDFN, "ftp-complete",
Darren Tuckerd78739a2010-10-24 10:56:32 +11001891 "Context sensitive argument completion", complete);
Darren Tucker909d8582010-01-08 19:02:40 +11001892 complete_ctx.conn = conn;
1893 complete_ctx.remote_pathp = &remote_path;
1894 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
1895 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11001896 }
1897#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001898
Darren Tucker909d8582010-01-08 19:02:40 +11001899 remote_path = do_realpath(conn, ".");
1900 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11001901 fatal("Need cwd");
1902
1903 if (file1 != NULL) {
1904 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11001905 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11001906
1907 if (remote_is_dir(conn, dir) && file2 == NULL) {
1908 printf("Changing to: %s\n", dir);
1909 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001910 if (parse_dispatch_command(conn, cmd,
1911 &remote_path, 1) != 0) {
Darren Tuckercd516ef2004-12-06 22:43:43 +11001912 xfree(dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001913 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001914 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001915 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11001916 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001917 } else {
1918 if (file2 == NULL)
1919 snprintf(cmd, sizeof cmd, "get %s", dir);
1920 else
1921 snprintf(cmd, sizeof cmd, "get %s %s", dir,
1922 file2);
1923
Darren Tucker909d8582010-01-08 19:02:40 +11001924 err = parse_dispatch_command(conn, cmd,
1925 &remote_path, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001926 xfree(dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001927 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001928 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001929 return (err);
1930 }
1931 xfree(dir);
1932 }
1933
Darren Tucker93e7e8f2005-08-23 08:06:55 +10001934#if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF)
Damien Miller20e1fab2004-02-18 14:30:55 +11001935 setvbuf(stdout, NULL, _IOLBF, 0);
1936 setvbuf(infile, NULL, _IOLBF, 0);
1937#else
Damien Miller37294fb2005-07-17 17:18:49 +10001938 setlinebuf(stdout);
1939 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11001940#endif
1941
Damien Miller0e2c1022005-08-12 22:16:22 +10001942 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11001943 err = 0;
1944 for (;;) {
1945 char *cp;
1946
Darren Tuckercdf547a2004-05-24 10:12:19 +10001947 signal(SIGINT, SIG_IGN);
1948
Darren Tucker2d963d82004-11-07 20:04:10 +11001949 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001950 if (interactive)
1951 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11001952 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001953 if (interactive)
1954 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11001955 break;
1956 }
Damien Miller0e2c1022005-08-12 22:16:22 +10001957 if (!interactive) { /* Echo command */
1958 printf("sftp> %s", cmd);
1959 if (strlen(cmd) > 0 &&
1960 cmd[strlen(cmd) - 1] != '\n')
1961 printf("\n");
1962 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001963 } else {
1964#ifdef USE_LIBEDIT
1965 const char *line;
1966 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001967
Darren Tucker909d8582010-01-08 19:02:40 +11001968 if ((line = el_gets(el, &count)) == NULL ||
1969 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001970 printf("\n");
1971 break;
1972 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001973 history(hl, &hev, H_ENTER, line);
1974 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
1975 fprintf(stderr, "Error: input line too long\n");
1976 continue;
1977 }
1978#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001979 }
1980
Damien Miller20e1fab2004-02-18 14:30:55 +11001981 cp = strrchr(cmd, '\n');
1982 if (cp)
1983 *cp = '\0';
1984
Darren Tuckercdf547a2004-05-24 10:12:19 +10001985 /* Handle user interrupts gracefully during commands */
1986 interrupted = 0;
1987 signal(SIGINT, cmd_interrupt);
1988
Darren Tucker909d8582010-01-08 19:02:40 +11001989 err = parse_dispatch_command(conn, cmd, &remote_path,
1990 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11001991 if (err != 0)
1992 break;
1993 }
Darren Tucker909d8582010-01-08 19:02:40 +11001994 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001995 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001996
Tim Rice027e8b12005-08-15 14:52:50 -07001997#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10001998 if (el != NULL)
1999 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07002000#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10002001
Damien Miller20e1fab2004-02-18 14:30:55 +11002002 /* err == 1 signifies normal "quit" exit */
2003 return (err >= 0 ? 0 : -1);
2004}
Damien Miller62d57f62003-01-10 21:43:24 +11002005
Ben Lindstrombba81212001-06-25 05:01:22 +00002006static void
Damien Millercc685c12003-06-04 22:51:38 +10002007connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11002008{
2009 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002010
Damien Miller33804262001-02-04 23:20:18 +11002011#ifdef USE_PIPES
2012 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002013
Damien Miller33804262001-02-04 23:20:18 +11002014 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2015 fatal("pipe: %s", strerror(errno));
2016 *in = pin[0];
2017 *out = pout[1];
2018 c_in = pout[0];
2019 c_out = pin[1];
2020#else /* USE_PIPES */
2021 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002022
Damien Miller33804262001-02-04 23:20:18 +11002023 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2024 fatal("socketpair: %s", strerror(errno));
2025 *in = *out = inout[0];
2026 c_in = c_out = inout[1];
2027#endif /* USE_PIPES */
2028
Damien Millercc685c12003-06-04 22:51:38 +10002029 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002030 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002031 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002032 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2033 (dup2(c_out, STDOUT_FILENO) == -1)) {
2034 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002035 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002036 }
2037 close(*in);
2038 close(*out);
2039 close(c_in);
2040 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002041
2042 /*
2043 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002044 * ignore SIGINT if we want to gracefully abort commands,
2045 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002046 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2047 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002048 */
2049 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002050 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002051 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002052 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002053 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002054 }
2055
Damien Millercc685c12003-06-04 22:51:38 +10002056 signal(SIGTERM, killchild);
2057 signal(SIGINT, killchild);
2058 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11002059 close(c_in);
2060 close(c_out);
2061}
2062
Ben Lindstrombba81212001-06-25 05:01:22 +00002063static void
Damien Miller33804262001-02-04 23:20:18 +11002064usage(void)
2065{
Damien Miller025e01c2002-02-08 22:06:29 +11002066 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002067
Ben Lindstrom1e243242001-09-18 05:38:44 +00002068 fprintf(stderr,
Darren Tucker1b0dd172009-10-07 08:37:48 +11002069 "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002070 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002071 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002072 " [-o ssh_option] [-P port] [-R num_requests] "
2073 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002074 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002075 " %s [user@]host[:file ...]\n"
2076 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002077 " %s -b batchfile [user@]host\n",
2078 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002079 exit(1);
2080}
2081
Kevin Stevesef4eea92001-02-05 12:42:17 +00002082int
Damien Miller33804262001-02-04 23:20:18 +11002083main(int argc, char **argv)
2084{
Damien Miller956f3fb2003-01-10 21:40:00 +11002085 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002086 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002087 int debug_level = 0, sshver = 2;
2088 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002089 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002090 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002091 LogLevel ll = SYSLOG_LEVEL_INFO;
2092 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002093 extern int optind;
2094 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002095 struct sftp_conn *conn;
2096 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2097 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002098 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002099
Darren Tuckerce321d82005-10-03 18:11:24 +10002100 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2101 sanitise_stdfd();
2102
Damien Miller59d3d5b2003-08-22 09:34:41 +10002103 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002104 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002105 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002106 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002107 addargs(&args, "-oForwardX11 no");
2108 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002109 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002110 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002111
Ben Lindstrom387c4722001-05-08 20:27:25 +00002112 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002113 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002114
Darren Tucker282b4022009-10-07 08:23:06 +11002115 while ((ch = getopt(argc, argv,
Damien Miller65e42f82010-09-24 22:15:11 +10002116 "1246hpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002117 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002118 /* Passed through to ssh(1) */
2119 case '4':
2120 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002121 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002122 addargs(&args, "-%c", ch);
2123 break;
2124 /* Passed through to ssh(1) with argument */
2125 case 'F':
2126 case 'c':
2127 case 'i':
2128 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002129 addargs(&args, "-%c", ch);
2130 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002131 break;
2132 case 'q':
2133 showprogress = 0;
2134 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002135 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002136 case 'P':
2137 addargs(&args, "-oPort %s", optarg);
2138 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002139 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002140 if (debug_level < 3) {
2141 addargs(&args, "-v");
2142 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2143 }
2144 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002145 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002146 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002147 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002148 if (sftp_server == NULL)
2149 sftp_server = _PATH_SFTP_SERVER;
2150 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002151 case '2':
2152 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002153 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002154 case 'B':
2155 copy_buffer_len = strtol(optarg, &cp, 10);
2156 if (copy_buffer_len == 0 || *cp != '\0')
2157 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002158 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002159 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002160 if (batchmode)
2161 fatal("Batch file already specified.");
2162
2163 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002164 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002165 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002166 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002167 showprogress = 0;
Damien Miller44f75c12004-01-21 10:58:47 +11002168 batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002169 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002170 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002171 case 'p':
2172 global_pflag = 1;
2173 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002174 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002175 sftp_direct = optarg;
2176 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002177 case 'l':
2178 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2179 &errstr);
2180 if (errstr != NULL)
2181 usage();
2182 limit_kbps *= 1024; /* kbps */
2183 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002184 case 'r':
2185 global_rflag = 1;
2186 break;
Damien Miller16a13332002-02-13 14:03:56 +11002187 case 'R':
2188 num_requests = strtol(optarg, &cp, 10);
2189 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002190 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002191 optarg);
2192 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002193 case 's':
2194 sftp_server = optarg;
2195 break;
2196 case 'S':
2197 ssh_program = optarg;
2198 replacearg(&args, 0, "%s", ssh_program);
2199 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002200 case 'h':
2201 default:
Damien Miller33804262001-02-04 23:20:18 +11002202 usage();
2203 }
2204 }
2205
Damien Millerc0f27d82004-03-08 23:12:19 +11002206 if (!isatty(STDERR_FILENO))
2207 showprogress = 0;
2208
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002209 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2210
Damien Millerd14ee1e2002-02-05 12:27:31 +11002211 if (sftp_direct == NULL) {
2212 if (optind == argc || argc > (optind + 2))
2213 usage();
Damien Miller33804262001-02-04 23:20:18 +11002214
Damien Millerd14ee1e2002-02-05 12:27:31 +11002215 userhost = xstrdup(argv[optind]);
2216 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002217
Ben Lindstromc276c122002-12-23 02:14:51 +00002218 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002219 host = userhost;
2220 else {
2221 *host++ = '\0';
2222 if (!userhost[0]) {
2223 fprintf(stderr, "Missing username\n");
2224 usage();
2225 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002226 addargs(&args, "-l");
2227 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002228 }
2229
Damien Millerec692032004-01-27 21:22:00 +11002230 if ((cp = colon(host)) != NULL) {
2231 *cp++ = '\0';
2232 file1 = cp;
2233 }
2234
Damien Millerd14ee1e2002-02-05 12:27:31 +11002235 host = cleanhostname(host);
2236 if (!*host) {
2237 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002238 usage();
2239 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002240
Damien Millerd14ee1e2002-02-05 12:27:31 +11002241 addargs(&args, "-oProtocol %d", sshver);
2242
2243 /* no subsystem if the server-spec contains a '/' */
2244 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2245 addargs(&args, "-s");
2246
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002247 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002248 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002249 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002250 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002251
Damien Millercc685c12003-06-04 22:51:38 +10002252 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002253 } else {
2254 args.list = NULL;
2255 addargs(&args, "sftp-server");
2256
Damien Millercc685c12003-06-04 22:51:38 +10002257 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002258 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002259 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002260
Damien Miller65e42f82010-09-24 22:15:11 +10002261 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002262 if (conn == NULL)
2263 fatal("Couldn't initialise connection to server");
2264
2265 if (!batchmode) {
2266 if (sftp_direct == NULL)
2267 fprintf(stderr, "Connected to %s.\n", host);
2268 else
2269 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2270 }
2271
2272 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002273
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002274#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002275 shutdown(in, SHUT_RDWR);
2276 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002277#endif
2278
Damien Miller33804262001-02-04 23:20:18 +11002279 close(in);
2280 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002281 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002282 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002283
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002284 while (waitpid(sshpid, NULL, 0) == -1)
2285 if (errno != EINTR)
2286 fatal("Couldn't wait for ssh process: %s",
2287 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002288
Damien Miller956f3fb2003-01-10 21:40:00 +11002289 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002290}