blob: 46bee1982ca96f4a4c45e9907e8260b283910e0c [file] [log] [blame]
Damien Millera6e121a2010-10-07 21:39:17 +11001/* $OpenBSD: sftp.c,v 1.128 2010/09/25 09:30:16 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
135#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 },
179 { "ln", I_SYMLINK, REMOTE },
180 { "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
221 write(STDERR_FILENO, msg, sizeof(msg) - 1);
222 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"
243 "ln oldpath newpath Symlink remote file\n"
244 "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
272 if ((shell = getenv("SHELL")) == NULL)
273 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
Damien Miller1cbc2922007-10-26 14:27:45 +1000380parse_ls_flags(char **argv, int argc, int *lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100381{
Damien Millerf184bcf2008-06-29 22:45:13 +1000382 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000383 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100384
Damien Miller1cbc2922007-10-26 14:27:45 +1000385 optind = optreset = 1;
386 opterr = 0;
387
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000388 *lflag = LS_NAME_SORT;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100389 while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000390 switch (ch) {
391 case '1':
392 *lflag &= ~VIEW_FLAGS;
393 *lflag |= LS_SHORT_VIEW;
394 break;
395 case 'S':
396 *lflag &= ~SORT_FLAGS;
397 *lflag |= LS_SIZE_SORT;
398 break;
399 case 'a':
400 *lflag |= LS_SHOW_ALL;
401 break;
402 case 'f':
403 *lflag &= ~SORT_FLAGS;
404 break;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100405 case 'h':
406 *lflag |= LS_SI_UNITS;
407 break;
Damien Miller1cbc2922007-10-26 14:27:45 +1000408 case 'l':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100409 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000410 *lflag |= LS_LONG_VIEW;
411 break;
412 case 'n':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100413 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000414 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
415 break;
416 case 'r':
417 *lflag |= LS_REVERSE_SORT;
418 break;
419 case 't':
420 *lflag &= ~SORT_FLAGS;
421 *lflag |= LS_TIME_SORT;
422 break;
423 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000424 error("ls: Invalid flag -%c", optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000425 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100426 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100427 }
428
Damien Miller1cbc2922007-10-26 14:27:45 +1000429 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100430}
431
432static int
Damien Millerd671e5a2008-05-19 14:53:33 +1000433parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
434{
Damien Millerf184bcf2008-06-29 22:45:13 +1000435 extern int opterr, optind, optopt, optreset;
Damien Millerd671e5a2008-05-19 14:53:33 +1000436 int ch;
437
438 optind = optreset = 1;
439 opterr = 0;
440
441 *hflag = *iflag = 0;
442 while ((ch = getopt(argc, argv, "hi")) != -1) {
443 switch (ch) {
444 case 'h':
445 *hflag = 1;
446 break;
447 case 'i':
448 *iflag = 1;
449 break;
450 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000451 error("%s: Invalid flag -%c", cmd, optopt);
Damien Millerd671e5a2008-05-19 14:53:33 +1000452 return -1;
453 }
454 }
455
456 return optind;
457}
458
459static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100460is_dir(char *path)
461{
462 struct stat sb;
463
464 /* XXX: report errors? */
465 if (stat(path, &sb) == -1)
466 return(0);
467
Darren Tucker1e80e402006-09-21 12:59:33 +1000468 return(S_ISDIR(sb.st_mode));
Damien Miller20e1fab2004-02-18 14:30:55 +1100469}
470
471static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100472remote_is_dir(struct sftp_conn *conn, char *path)
473{
474 Attrib *a;
475
476 /* XXX: report errors? */
477 if ((a = do_stat(conn, path, 1)) == NULL)
478 return(0);
479 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
480 return(0);
Darren Tucker1e80e402006-09-21 12:59:33 +1000481 return(S_ISDIR(a->perm));
Damien Miller20e1fab2004-02-18 14:30:55 +1100482}
483
Darren Tucker1b0dd172009-10-07 08:37:48 +1100484/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
Damien Miller20e1fab2004-02-18 14:30:55 +1100485static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100486pathname_is_dir(char *pathname)
487{
488 size_t l = strlen(pathname);
489
490 return l > 0 && pathname[l - 1] == '/';
491}
492
493static int
494process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
495 int pflag, int rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100496{
497 char *abs_src = NULL;
498 char *abs_dst = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100499 glob_t g;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100500 char *filename, *tmp=NULL;
501 int i, err = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100502
503 abs_src = xstrdup(src);
504 abs_src = make_absolute(abs_src, pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +1100505 memset(&g, 0, sizeof(g));
Darren Tucker1b0dd172009-10-07 08:37:48 +1100506
Damien Miller20e1fab2004-02-18 14:30:55 +1100507 debug3("Looking up %s", abs_src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100508 if (remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100509 error("File \"%s\" not found.", abs_src);
510 err = -1;
511 goto out;
512 }
513
Darren Tucker1b0dd172009-10-07 08:37:48 +1100514 /*
515 * If multiple matches then dst must be a directory or
516 * unspecified.
517 */
518 if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
519 error("Multiple source paths, but destination "
520 "\"%s\" is not a directory", dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100521 err = -1;
522 goto out;
523 }
524
Darren Tuckercdf547a2004-05-24 10:12:19 +1000525 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100526 tmp = xstrdup(g.gl_pathv[i]);
527 if ((filename = basename(tmp)) == NULL) {
528 error("basename %s: %s", tmp, strerror(errno));
529 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100530 err = -1;
531 goto out;
532 }
533
534 if (g.gl_matchc == 1 && dst) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100535 if (is_dir(dst)) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100536 abs_dst = path_append(dst, filename);
537 } else {
Damien Miller20e1fab2004-02-18 14:30:55 +1100538 abs_dst = xstrdup(dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100539 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100540 } else if (dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100541 abs_dst = path_append(dst, filename);
542 } else {
543 abs_dst = xstrdup(filename);
544 }
545 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100546
547 printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100548 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
549 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
550 pflag || global_pflag, 1) == -1)
551 err = -1;
552 } else {
553 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
554 pflag || global_pflag) == -1)
555 err = -1;
556 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100557 xfree(abs_dst);
558 abs_dst = NULL;
559 }
560
561out:
562 xfree(abs_src);
Damien Miller20e1fab2004-02-18 14:30:55 +1100563 globfree(&g);
564 return(err);
565}
566
567static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100568process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
569 int pflag, int rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100570{
571 char *tmp_dst = NULL;
572 char *abs_dst = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100573 char *tmp = NULL, *filename = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100574 glob_t g;
575 int err = 0;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100576 int i, dst_is_dir = 1;
Damien Milleraec5cf82008-02-10 22:26:24 +1100577 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100578
579 if (dst) {
580 tmp_dst = xstrdup(dst);
581 tmp_dst = make_absolute(tmp_dst, pwd);
582 }
583
584 memset(&g, 0, sizeof(g));
585 debug3("Looking up %s", src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100586 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100587 error("File \"%s\" not found.", src);
588 err = -1;
589 goto out;
590 }
591
Darren Tucker1b0dd172009-10-07 08:37:48 +1100592 /* If we aren't fetching to pwd then stash this status for later */
593 if (tmp_dst != NULL)
594 dst_is_dir = remote_is_dir(conn, tmp_dst);
595
Damien Miller20e1fab2004-02-18 14:30:55 +1100596 /* If multiple matches, dst may be directory or unspecified */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100597 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
598 error("Multiple paths match, but destination "
599 "\"%s\" is not a directory", tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100600 err = -1;
601 goto out;
602 }
603
Darren Tuckercdf547a2004-05-24 10:12:19 +1000604 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Milleraec5cf82008-02-10 22:26:24 +1100605 if (stat(g.gl_pathv[i], &sb) == -1) {
606 err = -1;
607 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
608 continue;
609 }
Darren Tucker1b0dd172009-10-07 08:37:48 +1100610
611 tmp = xstrdup(g.gl_pathv[i]);
612 if ((filename = basename(tmp)) == NULL) {
613 error("basename %s: %s", tmp, strerror(errno));
614 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100615 err = -1;
616 goto out;
617 }
618
619 if (g.gl_matchc == 1 && tmp_dst) {
620 /* If directory specified, append filename */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100621 if (dst_is_dir)
622 abs_dst = path_append(tmp_dst, filename);
623 else
Damien Miller20e1fab2004-02-18 14:30:55 +1100624 abs_dst = xstrdup(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100625 } else if (tmp_dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100626 abs_dst = path_append(tmp_dst, filename);
627 } else {
628 abs_dst = make_absolute(xstrdup(filename), pwd);
629 }
630 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100631
632 printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100633 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
634 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
635 pflag || global_pflag, 1) == -1)
636 err = -1;
637 } else {
638 if (do_upload(conn, g.gl_pathv[i], abs_dst,
639 pflag || global_pflag) == -1)
640 err = -1;
641 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100642 }
643
644out:
645 if (abs_dst)
646 xfree(abs_dst);
647 if (tmp_dst)
648 xfree(tmp_dst);
649 globfree(&g);
650 return(err);
651}
652
653static int
654sdirent_comp(const void *aa, const void *bb)
655{
656 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
657 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000658 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100659
Darren Tuckerb9123452004-06-22 13:06:45 +1000660#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000661 if (sort_flag & LS_NAME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000662 return (rmul * strcmp(a->filename, b->filename));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000663 else if (sort_flag & LS_TIME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000664 return (rmul * NCMP(a->a.mtime, b->a.mtime));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000665 else if (sort_flag & LS_SIZE_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000666 return (rmul * NCMP(a->a.size, b->a.size));
667
668 fatal("Unknown ls sort type");
Damien Miller20e1fab2004-02-18 14:30:55 +1100669}
670
671/* sftp ls.1 replacement for directories */
672static int
673do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
674{
Damien Millereccb9de2005-06-17 12:59:34 +1000675 int n;
676 u_int c = 1, colspace = 0, columns = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100677 SFTP_DIRENT **d;
678
679 if ((n = do_readdir(conn, path, &d)) != 0)
680 return (n);
681
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000682 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000683 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100684 struct winsize ws;
685 char *tmp;
686
687 /* Count entries for sort and find longest filename */
Darren Tucker9a526452004-06-22 13:09:55 +1000688 for (n = 0; d[n] != NULL; n++) {
689 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
690 m = MAX(m, strlen(d[n]->filename));
691 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100692
693 /* Add any subpath that also needs to be counted */
694 tmp = path_strip(path, strip_path);
695 m += strlen(tmp);
696 xfree(tmp);
697
698 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
699 width = ws.ws_col;
700
701 columns = width / (m + 2);
702 columns = MAX(columns, 1);
703 colspace = width / columns;
704 colspace = MIN(colspace, width);
705 }
706
Darren Tuckerb9123452004-06-22 13:06:45 +1000707 if (lflag & SORT_FLAGS) {
Damien Miller653b93b2005-11-05 15:15:23 +1100708 for (n = 0; d[n] != NULL; n++)
709 ; /* count entries */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000710 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
Darren Tuckerb9123452004-06-22 13:06:45 +1000711 qsort(d, n, sizeof(*d), sdirent_comp);
712 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100713
Darren Tuckercdf547a2004-05-24 10:12:19 +1000714 for (n = 0; d[n] != NULL && !interrupted; n++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100715 char *tmp, *fname;
716
Darren Tucker9a526452004-06-22 13:09:55 +1000717 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
718 continue;
719
Damien Miller20e1fab2004-02-18 14:30:55 +1100720 tmp = path_append(path, d[n]->filename);
721 fname = path_strip(tmp, strip_path);
722 xfree(tmp);
723
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000724 if (lflag & LS_LONG_VIEW) {
Darren Tucker2901e2d2010-01-13 22:44:06 +1100725 if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000726 char *lname;
727 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100728
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000729 memset(&sb, 0, sizeof(sb));
730 attrib_to_stat(&d[n]->a, &sb);
Darren Tucker2901e2d2010-01-13 22:44:06 +1100731 lname = ls_file(fname, &sb, 1,
732 (lflag & LS_SI_UNITS));
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000733 printf("%s\n", lname);
734 xfree(lname);
735 } else
736 printf("%s\n", d[n]->longname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100737 } else {
738 printf("%-*s", colspace, fname);
739 if (c >= columns) {
740 printf("\n");
741 c = 1;
742 } else
743 c++;
744 }
745
746 xfree(fname);
747 }
748
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000749 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100750 printf("\n");
751
752 free_sftp_dirents(d);
753 return (0);
754}
755
756/* sftp ls.1 replacement which handles path globs */
757static int
758do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
759 int lflag)
760{
761 glob_t g;
Damien Millereccb9de2005-06-17 12:59:34 +1000762 u_int i, c = 1, colspace = 0, columns = 1;
Darren Tucker596dcfa2004-12-11 13:37:22 +1100763 Attrib *a = NULL;
Damien Millera6e121a2010-10-07 21:39:17 +1100764 int err;
765 char *fname, *lname;
Damien Miller20e1fab2004-02-18 14:30:55 +1100766
767 memset(&g, 0, sizeof(g));
768
Damien Millera6e121a2010-10-07 21:39:17 +1100769 if (remote_glob(conn, path,
770 GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT, NULL, &g) ||
771 (g.gl_pathc && !g.gl_matchc)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100772 if (g.gl_pathc)
773 globfree(&g);
Damien Miller20e1fab2004-02-18 14:30:55 +1100774 error("Can't ls: \"%s\" not found", path);
Damien Millera6e121a2010-10-07 21:39:17 +1100775 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100776 }
777
Darren Tuckercdf547a2004-05-24 10:12:19 +1000778 if (interrupted)
779 goto out;
780
Damien Miller20e1fab2004-02-18 14:30:55 +1100781 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100782 * If the glob returns a single match and it is a directory,
783 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100784 */
Damien Millera6e121a2010-10-07 21:39:17 +1100785 if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
786 S_ISDIR(g.gl_statv[0]->st_mode)) {
787 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
788 globfree(&g);
789 return err;
Damien Miller20e1fab2004-02-18 14:30:55 +1100790 }
791
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000792 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000793 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100794 struct winsize ws;
795
796 /* Count entries for sort and find longest filename */
797 for (i = 0; g.gl_pathv[i]; i++)
798 m = MAX(m, strlen(g.gl_pathv[i]));
799
800 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
801 width = ws.ws_col;
802
803 columns = width / (m + 2);
804 columns = MAX(columns, 1);
805 colspace = width / columns;
806 }
807
Darren Tucker596dcfa2004-12-11 13:37:22 +1100808 for (i = 0; g.gl_pathv[i] && !interrupted; i++, a = NULL) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100809 fname = path_strip(g.gl_pathv[i], strip_path);
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000810 if (lflag & LS_LONG_VIEW) {
Damien Millera6e121a2010-10-07 21:39:17 +1100811 if (g.gl_statv[i] == NULL) {
812 error("no stat information for %s", fname);
813 continue;
814 }
815 lname = ls_file(fname, g.gl_statv[i], 1,
816 (lflag & LS_SI_UNITS));
Damien Miller20e1fab2004-02-18 14:30:55 +1100817 printf("%s\n", lname);
818 xfree(lname);
819 } else {
820 printf("%-*s", colspace, fname);
821 if (c >= columns) {
822 printf("\n");
823 c = 1;
824 } else
825 c++;
826 }
827 xfree(fname);
828 }
829
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000830 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100831 printf("\n");
832
Darren Tuckercdf547a2004-05-24 10:12:19 +1000833 out:
Damien Miller20e1fab2004-02-18 14:30:55 +1100834 if (g.gl_pathc)
835 globfree(&g);
836
Damien Millera6e121a2010-10-07 21:39:17 +1100837 return 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100838}
839
Damien Millerd671e5a2008-05-19 14:53:33 +1000840static int
841do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
842{
Darren Tucker7b598892008-06-09 22:49:36 +1000843 struct sftp_statvfs st;
Damien Millerd671e5a2008-05-19 14:53:33 +1000844 char s_used[FMT_SCALED_STRSIZE];
845 char s_avail[FMT_SCALED_STRSIZE];
846 char s_root[FMT_SCALED_STRSIZE];
847 char s_total[FMT_SCALED_STRSIZE];
Darren Tuckerb5082e92010-01-08 18:51:47 +1100848 unsigned long long ffree;
Damien Millerd671e5a2008-05-19 14:53:33 +1000849
850 if (do_statvfs(conn, path, &st, 1) == -1)
851 return -1;
852 if (iflag) {
Darren Tuckerb5082e92010-01-08 18:51:47 +1100853 ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
Damien Millerd671e5a2008-05-19 14:53:33 +1000854 printf(" Inodes Used Avail "
855 "(root) %%Capacity\n");
856 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
857 (unsigned long long)st.f_files,
858 (unsigned long long)(st.f_files - st.f_ffree),
859 (unsigned long long)st.f_favail,
Darren Tuckerb5082e92010-01-08 18:51:47 +1100860 (unsigned long long)st.f_ffree, ffree);
Damien Millerd671e5a2008-05-19 14:53:33 +1000861 } else if (hflag) {
862 strlcpy(s_used, "error", sizeof(s_used));
863 strlcpy(s_avail, "error", sizeof(s_avail));
864 strlcpy(s_root, "error", sizeof(s_root));
865 strlcpy(s_total, "error", sizeof(s_total));
866 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
867 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
868 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
869 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
870 printf(" Size Used Avail (root) %%Capacity\n");
871 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
872 s_total, s_used, s_avail, s_root,
873 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
874 st.f_blocks));
875 } else {
876 printf(" Size Used Avail "
877 "(root) %%Capacity\n");
878 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
879 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
880 (unsigned long long)(st.f_frsize *
881 (st.f_blocks - st.f_bfree) / 1024),
882 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
883 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
884 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
885 st.f_blocks));
886 }
887 return 0;
888}
889
Damien Miller1cbc2922007-10-26 14:27:45 +1000890/*
891 * Undo escaping of glob sequences in place. Used to undo extra escaping
892 * applied in makeargv() when the string is destined for a function that
893 * does not glob it.
894 */
895static void
896undo_glob_escape(char *s)
897{
898 size_t i, j;
899
900 for (i = j = 0;;) {
901 if (s[i] == '\0') {
902 s[j] = '\0';
903 return;
904 }
905 if (s[i] != '\\') {
906 s[j++] = s[i++];
907 continue;
908 }
909 /* s[i] == '\\' */
910 ++i;
911 switch (s[i]) {
912 case '?':
913 case '[':
914 case '*':
915 case '\\':
916 s[j++] = s[i++];
917 break;
918 case '\0':
919 s[j++] = '\\';
920 s[j] = '\0';
921 return;
922 default:
923 s[j++] = '\\';
924 s[j++] = s[i++];
925 break;
926 }
927 }
928}
929
930/*
931 * Split a string into an argument vector using sh(1)-style quoting,
932 * comment and escaping rules, but with some tweaks to handle glob(3)
933 * wildcards.
Darren Tucker909d8582010-01-08 19:02:40 +1100934 * The "sloppy" flag allows for recovery from missing terminating quote, for
935 * use in parsing incomplete commandlines during tab autocompletion.
936 *
Damien Miller1cbc2922007-10-26 14:27:45 +1000937 * Returns NULL on error or a NULL-terminated array of arguments.
Darren Tucker909d8582010-01-08 19:02:40 +1100938 *
939 * If "lastquote" is not NULL, the quoting character used for the last
940 * argument is placed in *lastquote ("\0", "'" or "\"").
941 *
942 * If "terminated" is not NULL, *terminated will be set to 1 when the
943 * last argument's quote has been properly terminated or 0 otherwise.
944 * This parameter is only of use if "sloppy" is set.
Damien Miller1cbc2922007-10-26 14:27:45 +1000945 */
946#define MAXARGS 128
947#define MAXARGLEN 8192
948static char **
Darren Tucker909d8582010-01-08 19:02:40 +1100949makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
950 u_int *terminated)
Damien Miller1cbc2922007-10-26 14:27:45 +1000951{
952 int argc, quot;
953 size_t i, j;
954 static char argvs[MAXARGLEN];
955 static char *argv[MAXARGS + 1];
956 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
957
958 *argcp = argc = 0;
959 if (strlen(arg) > sizeof(argvs) - 1) {
960 args_too_longs:
961 error("string too long");
962 return NULL;
963 }
Darren Tucker909d8582010-01-08 19:02:40 +1100964 if (terminated != NULL)
965 *terminated = 1;
966 if (lastquote != NULL)
967 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +1000968 state = MA_START;
969 i = j = 0;
970 for (;;) {
971 if (isspace(arg[i])) {
972 if (state == MA_UNQUOTED) {
973 /* Terminate current argument */
974 argvs[j++] = '\0';
975 argc++;
976 state = MA_START;
977 } else if (state != MA_START)
978 argvs[j++] = arg[i];
979 } else if (arg[i] == '"' || arg[i] == '\'') {
980 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
981 if (state == MA_START) {
982 argv[argc] = argvs + j;
983 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +1100984 if (lastquote != NULL)
985 *lastquote = arg[i];
Damien Miller1cbc2922007-10-26 14:27:45 +1000986 } else if (state == MA_UNQUOTED)
987 state = q;
988 else if (state == q)
989 state = MA_UNQUOTED;
990 else
991 argvs[j++] = arg[i];
992 } else if (arg[i] == '\\') {
993 if (state == MA_SQUOTE || state == MA_DQUOTE) {
994 quot = state == MA_SQUOTE ? '\'' : '"';
995 /* Unescape quote we are in */
996 /* XXX support \n and friends? */
997 if (arg[i + 1] == quot) {
998 i++;
999 argvs[j++] = arg[i];
1000 } else if (arg[i + 1] == '?' ||
1001 arg[i + 1] == '[' || arg[i + 1] == '*') {
1002 /*
1003 * Special case for sftp: append
1004 * double-escaped glob sequence -
1005 * glob will undo one level of
1006 * escaping. NB. string can grow here.
1007 */
1008 if (j >= sizeof(argvs) - 5)
1009 goto args_too_longs;
1010 argvs[j++] = '\\';
1011 argvs[j++] = arg[i++];
1012 argvs[j++] = '\\';
1013 argvs[j++] = arg[i];
1014 } else {
1015 argvs[j++] = arg[i++];
1016 argvs[j++] = arg[i];
1017 }
1018 } else {
1019 if (state == MA_START) {
1020 argv[argc] = argvs + j;
1021 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001022 if (lastquote != NULL)
1023 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001024 }
1025 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1026 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1027 /*
1028 * Special case for sftp: append
1029 * escaped glob sequence -
1030 * glob will undo one level of
1031 * escaping.
1032 */
1033 argvs[j++] = arg[i++];
1034 argvs[j++] = arg[i];
1035 } else {
1036 /* Unescape everything */
1037 /* XXX support \n and friends? */
1038 i++;
1039 argvs[j++] = arg[i];
1040 }
1041 }
1042 } else if (arg[i] == '#') {
1043 if (state == MA_SQUOTE || state == MA_DQUOTE)
1044 argvs[j++] = arg[i];
1045 else
1046 goto string_done;
1047 } else if (arg[i] == '\0') {
1048 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001049 if (sloppy) {
1050 state = MA_UNQUOTED;
1051 if (terminated != NULL)
1052 *terminated = 0;
1053 goto string_done;
1054 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001055 error("Unterminated quoted argument");
1056 return NULL;
1057 }
1058 string_done:
1059 if (state == MA_UNQUOTED) {
1060 argvs[j++] = '\0';
1061 argc++;
1062 }
1063 break;
1064 } else {
1065 if (state == MA_START) {
1066 argv[argc] = argvs + j;
1067 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001068 if (lastquote != NULL)
1069 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001070 }
1071 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1072 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1073 /*
1074 * Special case for sftp: escape quoted
1075 * glob(3) wildcards. NB. string can grow
1076 * here.
1077 */
1078 if (j >= sizeof(argvs) - 3)
1079 goto args_too_longs;
1080 argvs[j++] = '\\';
1081 argvs[j++] = arg[i];
1082 } else
1083 argvs[j++] = arg[i];
1084 }
1085 i++;
1086 }
1087 *argcp = argc;
1088 return argv;
1089}
1090
Damien Miller20e1fab2004-02-18 14:30:55 +11001091static int
Darren Tucker909d8582010-01-08 19:02:40 +11001092parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag,
1093 int *hflag, unsigned long *n_arg, char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001094{
1095 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001096 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001097 int base = 0;
1098 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001099 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001100
1101 /* Skip leading whitespace */
1102 cp = cp + strspn(cp, WHITESPACE);
1103
Damien Miller20e1fab2004-02-18 14:30:55 +11001104 /* Check for leading '-' (disable error processing) */
1105 *iflag = 0;
1106 if (*cp == '-') {
1107 *iflag = 1;
1108 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001109 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001110 }
1111
Darren Tucker70cc0922010-01-09 22:28:03 +11001112 /* Ignore blank lines and lines which begin with comment '#' char */
1113 if (*cp == '\0' || *cp == '#')
1114 return (0);
1115
Darren Tucker909d8582010-01-08 19:02:40 +11001116 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001117 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001118
Damien Miller1cbc2922007-10-26 14:27:45 +10001119 /* Figure out which command we have */
1120 for (i = 0; cmds[i].c != NULL; i++) {
1121 if (strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001122 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001123 }
1124 cmdnum = cmds[i].n;
1125 cmd = cmds[i].c;
1126
1127 /* Special case */
1128 if (*cp == '!') {
1129 cp++;
1130 cmdnum = I_SHELL;
1131 } else if (cmdnum == -1) {
1132 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001133 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001134 }
1135
1136 /* Get arguments and parse flags */
Darren Tucker1b0dd172009-10-07 08:37:48 +11001137 *lflag = *pflag = *rflag = *hflag = *n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001138 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001139 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001140 switch (cmdnum) {
1141 case I_GET:
1142 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001143 if ((optidx = parse_getput_flags(cmd, argv, argc, pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001144 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001145 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001146 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001147 error("You must specify at least one path after a "
1148 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001149 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001150 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001151 *path1 = xstrdup(argv[optidx]);
1152 /* Get second pathname (optional) */
1153 if (argc - optidx > 1) {
1154 *path2 = xstrdup(argv[optidx + 1]);
1155 /* Destination is not globbed */
1156 undo_glob_escape(*path2);
1157 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001158 break;
1159 case I_RENAME:
1160 case I_SYMLINK:
Damien Miller1cbc2922007-10-26 14:27:45 +10001161 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001162 error("You must specify two paths after a %s "
1163 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001164 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001165 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001166 *path1 = xstrdup(argv[optidx]);
1167 *path2 = xstrdup(argv[optidx + 1]);
1168 /* Paths are not globbed */
1169 undo_glob_escape(*path1);
1170 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001171 break;
1172 case I_RM:
1173 case I_MKDIR:
1174 case I_RMDIR:
1175 case I_CHDIR:
1176 case I_LCHDIR:
1177 case I_LMKDIR:
1178 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001179 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001180 error("You must specify a path after a %s command.",
1181 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001182 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001183 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001184 *path1 = xstrdup(argv[optidx]);
1185 /* Only "rm" globs */
1186 if (cmdnum != I_RM)
1187 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001188 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001189 case I_DF:
1190 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1191 iflag)) == -1)
1192 return -1;
1193 /* Default to current directory if no path specified */
1194 if (argc - optidx < 1)
1195 *path1 = NULL;
1196 else {
1197 *path1 = xstrdup(argv[optidx]);
1198 undo_glob_escape(*path1);
1199 }
1200 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001201 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001202 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001203 return(-1);
1204 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001205 if (argc - optidx > 0)
1206 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001207 break;
1208 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001209 /* Skip ls command and following whitespace */
1210 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001211 case I_SHELL:
1212 /* Uses the rest of the line */
1213 break;
1214 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001215 case I_CHMOD:
1216 base = 8;
1217 case I_CHOWN:
1218 case I_CHGRP:
1219 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001220 if (argc - optidx < 1)
1221 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001222 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001223 l = strtol(argv[optidx], &cp2, base);
1224 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1225 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1226 l < 0) {
1227 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001228 error("You must supply a numeric argument "
1229 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001230 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001231 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001232 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001233 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001234 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001235 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001236 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001237 error("You must specify a path after a %s command.",
1238 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001239 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001240 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001241 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001242 break;
1243 case I_QUIT:
1244 case I_PWD:
1245 case I_LPWD:
1246 case I_HELP:
1247 case I_VERSION:
1248 case I_PROGRESS:
1249 break;
1250 default:
1251 fatal("Command not implemented");
1252 }
1253
1254 *cpp = cp;
1255 return(cmdnum);
1256}
1257
1258static int
1259parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1260 int err_abort)
1261{
1262 char *path1, *path2, *tmp;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001263 int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001264 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001265 Attrib a, *aa;
1266 char path_buf[MAXPATHLEN];
1267 int err = 0;
1268 glob_t g;
1269
1270 path1 = path2 = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001271 cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag, &n_arg,
Damien Miller20e1fab2004-02-18 14:30:55 +11001272 &path1, &path2);
1273
1274 if (iflag != 0)
1275 err_abort = 0;
1276
1277 memset(&g, 0, sizeof(g));
1278
1279 /* Perform command */
1280 switch (cmdnum) {
1281 case 0:
1282 /* Blank line */
1283 break;
1284 case -1:
1285 /* Unrecognized command */
1286 err = -1;
1287 break;
1288 case I_GET:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001289 err = process_get(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001290 break;
1291 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001292 err = process_put(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001293 break;
1294 case I_RENAME:
1295 path1 = make_absolute(path1, *pwd);
1296 path2 = make_absolute(path2, *pwd);
1297 err = do_rename(conn, path1, path2);
1298 break;
1299 case I_SYMLINK:
1300 path2 = make_absolute(path2, *pwd);
1301 err = do_symlink(conn, path1, path2);
1302 break;
1303 case I_RM:
1304 path1 = make_absolute(path1, *pwd);
1305 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001306 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001307 printf("Removing %s\n", g.gl_pathv[i]);
1308 err = do_rm(conn, g.gl_pathv[i]);
1309 if (err != 0 && err_abort)
1310 break;
1311 }
1312 break;
1313 case I_MKDIR:
1314 path1 = make_absolute(path1, *pwd);
1315 attrib_clear(&a);
1316 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1317 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001318 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001319 break;
1320 case I_RMDIR:
1321 path1 = make_absolute(path1, *pwd);
1322 err = do_rmdir(conn, path1);
1323 break;
1324 case I_CHDIR:
1325 path1 = make_absolute(path1, *pwd);
1326 if ((tmp = do_realpath(conn, path1)) == NULL) {
1327 err = 1;
1328 break;
1329 }
1330 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1331 xfree(tmp);
1332 err = 1;
1333 break;
1334 }
1335 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1336 error("Can't change directory: Can't check target");
1337 xfree(tmp);
1338 err = 1;
1339 break;
1340 }
1341 if (!S_ISDIR(aa->perm)) {
1342 error("Can't change directory: \"%s\" is not "
1343 "a directory", tmp);
1344 xfree(tmp);
1345 err = 1;
1346 break;
1347 }
1348 xfree(*pwd);
1349 *pwd = tmp;
1350 break;
1351 case I_LS:
1352 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001353 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001354 break;
1355 }
1356
1357 /* Strip pwd off beginning of non-absolute paths */
1358 tmp = NULL;
1359 if (*path1 != '/')
1360 tmp = *pwd;
1361
1362 path1 = make_absolute(path1, *pwd);
1363 err = do_globbed_ls(conn, path1, tmp, lflag);
1364 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001365 case I_DF:
1366 /* Default to current directory if no path specified */
1367 if (path1 == NULL)
1368 path1 = xstrdup(*pwd);
1369 path1 = make_absolute(path1, *pwd);
1370 err = do_df(conn, path1, hflag, iflag);
1371 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001372 case I_LCHDIR:
1373 if (chdir(path1) == -1) {
1374 error("Couldn't change local directory to "
1375 "\"%s\": %s", path1, strerror(errno));
1376 err = 1;
1377 }
1378 break;
1379 case I_LMKDIR:
1380 if (mkdir(path1, 0777) == -1) {
1381 error("Couldn't create local directory "
1382 "\"%s\": %s", path1, strerror(errno));
1383 err = 1;
1384 }
1385 break;
1386 case I_LLS:
1387 local_do_ls(cmd);
1388 break;
1389 case I_SHELL:
1390 local_do_shell(cmd);
1391 break;
1392 case I_LUMASK:
1393 umask(n_arg);
1394 printf("Local umask: %03lo\n", n_arg);
1395 break;
1396 case I_CHMOD:
1397 path1 = make_absolute(path1, *pwd);
1398 attrib_clear(&a);
1399 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1400 a.perm = n_arg;
1401 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001402 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001403 printf("Changing mode on %s\n", g.gl_pathv[i]);
1404 err = do_setstat(conn, g.gl_pathv[i], &a);
1405 if (err != 0 && err_abort)
1406 break;
1407 }
1408 break;
1409 case I_CHOWN:
1410 case I_CHGRP:
1411 path1 = make_absolute(path1, *pwd);
1412 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001413 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001414 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001415 if (err_abort) {
1416 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001417 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001418 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001419 continue;
1420 }
1421 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1422 error("Can't get current ownership of "
1423 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001424 if (err_abort) {
1425 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001426 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001427 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001428 continue;
1429 }
1430 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1431 if (cmdnum == I_CHOWN) {
1432 printf("Changing owner on %s\n", g.gl_pathv[i]);
1433 aa->uid = n_arg;
1434 } else {
1435 printf("Changing group on %s\n", g.gl_pathv[i]);
1436 aa->gid = n_arg;
1437 }
1438 err = do_setstat(conn, g.gl_pathv[i], aa);
1439 if (err != 0 && err_abort)
1440 break;
1441 }
1442 break;
1443 case I_PWD:
1444 printf("Remote working directory: %s\n", *pwd);
1445 break;
1446 case I_LPWD:
1447 if (!getcwd(path_buf, sizeof(path_buf))) {
1448 error("Couldn't get local cwd: %s", strerror(errno));
1449 err = -1;
1450 break;
1451 }
1452 printf("Local working directory: %s\n", path_buf);
1453 break;
1454 case I_QUIT:
1455 /* Processed below */
1456 break;
1457 case I_HELP:
1458 help();
1459 break;
1460 case I_VERSION:
1461 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1462 break;
1463 case I_PROGRESS:
1464 showprogress = !showprogress;
1465 if (showprogress)
1466 printf("Progress meter enabled\n");
1467 else
1468 printf("Progress meter disabled\n");
1469 break;
1470 default:
1471 fatal("%d is not implemented", cmdnum);
1472 }
1473
1474 if (g.gl_pathc)
1475 globfree(&g);
1476 if (path1)
1477 xfree(path1);
1478 if (path2)
1479 xfree(path2);
1480
1481 /* If an unignored error occurs in batch mode we should abort. */
1482 if (err_abort && err != 0)
1483 return (-1);
1484 else if (cmdnum == I_QUIT)
1485 return (1);
1486
1487 return (0);
1488}
1489
Darren Tucker2d963d82004-11-07 20:04:10 +11001490#ifdef USE_LIBEDIT
1491static char *
1492prompt(EditLine *el)
1493{
1494 return ("sftp> ");
1495}
Darren Tucker2d963d82004-11-07 20:04:10 +11001496
Darren Tucker909d8582010-01-08 19:02:40 +11001497/* Display entries in 'list' after skipping the first 'len' chars */
1498static void
1499complete_display(char **list, u_int len)
1500{
1501 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1502 struct winsize ws;
1503 char *tmp;
1504
1505 /* Count entries for sort and find longest */
1506 for (y = 0; list[y]; y++)
1507 m = MAX(m, strlen(list[y]));
1508
1509 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1510 width = ws.ws_col;
1511
1512 m = m > len ? m - len : 0;
1513 columns = width / (m + 2);
1514 columns = MAX(columns, 1);
1515 colspace = width / columns;
1516 colspace = MIN(colspace, width);
1517
1518 printf("\n");
1519 m = 1;
1520 for (y = 0; list[y]; y++) {
1521 llen = strlen(list[y]);
1522 tmp = llen > len ? list[y] + len : "";
1523 printf("%-*s", colspace, tmp);
1524 if (m >= columns) {
1525 printf("\n");
1526 m = 1;
1527 } else
1528 m++;
1529 }
1530 printf("\n");
1531}
1532
1533/*
1534 * Given a "list" of words that begin with a common prefix of "word",
1535 * attempt to find an autocompletion to extends "word" by the next
1536 * characters common to all entries in "list".
1537 */
1538static char *
1539complete_ambiguous(const char *word, char **list, size_t count)
1540{
1541 if (word == NULL)
1542 return NULL;
1543
1544 if (count > 0) {
1545 u_int y, matchlen = strlen(list[0]);
1546
1547 /* Find length of common stem */
1548 for (y = 1; list[y]; y++) {
1549 u_int x;
1550
1551 for (x = 0; x < matchlen; x++)
1552 if (list[0][x] != list[y][x])
1553 break;
1554
1555 matchlen = x;
1556 }
1557
1558 if (matchlen > strlen(word)) {
1559 char *tmp = xstrdup(list[0]);
1560
Darren Tucker340d1682010-01-09 08:54:31 +11001561 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001562 return tmp;
1563 }
1564 }
1565
1566 return xstrdup(word);
1567}
1568
1569/* Autocomplete a sftp command */
1570static int
1571complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1572 int terminated)
1573{
1574 u_int y, count = 0, cmdlen, tmplen;
1575 char *tmp, **list, argterm[3];
1576 const LineInfo *lf;
1577
1578 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1579
1580 /* No command specified: display all available commands */
1581 if (cmd == NULL) {
1582 for (y = 0; cmds[y].c; y++)
1583 list[count++] = xstrdup(cmds[y].c);
1584
1585 list[count] = NULL;
1586 complete_display(list, 0);
1587
1588 for (y = 0; list[y] != NULL; y++)
1589 xfree(list[y]);
1590 xfree(list);
1591 return count;
1592 }
1593
1594 /* Prepare subset of commands that start with "cmd" */
1595 cmdlen = strlen(cmd);
1596 for (y = 0; cmds[y].c; y++) {
1597 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
1598 list[count++] = xstrdup(cmds[y].c);
1599 }
1600 list[count] = NULL;
1601
1602 if (count == 0)
1603 return 0;
1604
1605 /* Complete ambigious command */
1606 tmp = complete_ambiguous(cmd, list, count);
1607 if (count > 1)
1608 complete_display(list, 0);
1609
1610 for (y = 0; list[y]; y++)
1611 xfree(list[y]);
1612 xfree(list);
1613
1614 if (tmp != NULL) {
1615 tmplen = strlen(tmp);
1616 cmdlen = strlen(cmd);
1617 /* If cmd may be extended then do so */
1618 if (tmplen > cmdlen)
1619 if (el_insertstr(el, tmp + cmdlen) == -1)
1620 fatal("el_insertstr failed.");
1621 lf = el_line(el);
1622 /* Terminate argument cleanly */
1623 if (count == 1) {
1624 y = 0;
1625 if (!terminated)
1626 argterm[y++] = quote;
1627 if (lastarg || *(lf->cursor) != ' ')
1628 argterm[y++] = ' ';
1629 argterm[y] = '\0';
1630 if (y > 0 && el_insertstr(el, argterm) == -1)
1631 fatal("el_insertstr failed.");
1632 }
1633 xfree(tmp);
1634 }
1635
1636 return count;
1637}
1638
1639/*
1640 * Determine whether a particular sftp command's arguments (if any)
1641 * represent local or remote files.
1642 */
1643static int
1644complete_is_remote(char *cmd) {
1645 int i;
1646
1647 if (cmd == NULL)
1648 return -1;
1649
1650 for (i = 0; cmds[i].c; i++) {
1651 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
1652 return cmds[i].t;
1653 }
1654
1655 return -1;
1656}
1657
1658/* Autocomplete a filename "file" */
1659static int
1660complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1661 char *file, int remote, int lastarg, char quote, int terminated)
1662{
1663 glob_t g;
1664 char *tmp, *tmp2, ins[3];
1665 u_int i, hadglob, pwdlen, len, tmplen, filelen;
1666 const LineInfo *lf;
1667
1668 /* Glob from "file" location */
1669 if (file == NULL)
1670 tmp = xstrdup("*");
1671 else
1672 xasprintf(&tmp, "%s*", file);
1673
1674 memset(&g, 0, sizeof(g));
1675 if (remote != LOCAL) {
1676 tmp = make_absolute(tmp, remote_path);
1677 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1678 } else
1679 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1680
1681 /* Determine length of pwd so we can trim completion display */
1682 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1683 /* Terminate counting on first unescaped glob metacharacter */
1684 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1685 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1686 hadglob = 1;
1687 break;
1688 }
1689 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1690 tmplen++;
1691 if (tmp[tmplen] == '/')
1692 pwdlen = tmplen + 1; /* track last seen '/' */
1693 }
1694 xfree(tmp);
1695
1696 if (g.gl_matchc == 0)
1697 goto out;
1698
1699 if (g.gl_matchc > 1)
1700 complete_display(g.gl_pathv, pwdlen);
1701
1702 tmp = NULL;
1703 /* Don't try to extend globs */
1704 if (file == NULL || hadglob)
1705 goto out;
1706
1707 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
1708 tmp = path_strip(tmp2, remote_path);
1709 xfree(tmp2);
1710
1711 if (tmp == NULL)
1712 goto out;
1713
1714 tmplen = strlen(tmp);
1715 filelen = strlen(file);
1716
1717 if (tmplen > filelen) {
1718 tmp2 = tmp + filelen;
1719 len = strlen(tmp2);
1720 /* quote argument on way out */
1721 for (i = 0; i < len; i++) {
1722 ins[0] = '\\';
1723 ins[1] = tmp2[i];
1724 ins[2] = '\0';
1725 switch (tmp2[i]) {
1726 case '\'':
1727 case '"':
1728 case '\\':
1729 case '\t':
1730 case ' ':
1731 if (quote == '\0' || tmp2[i] == quote) {
1732 if (el_insertstr(el, ins) == -1)
1733 fatal("el_insertstr "
1734 "failed.");
1735 break;
1736 }
1737 /* FALLTHROUGH */
1738 default:
1739 if (el_insertstr(el, ins + 1) == -1)
1740 fatal("el_insertstr failed.");
1741 break;
1742 }
1743 }
1744 }
1745
1746 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11001747 if (g.gl_matchc == 1) {
1748 i = 0;
1749 if (!terminated)
1750 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11001751 if (*(lf->cursor - 1) != '/' &&
1752 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11001753 ins[i++] = ' ';
1754 ins[i] = '\0';
1755 if (i > 0 && el_insertstr(el, ins) == -1)
1756 fatal("el_insertstr failed.");
1757 }
1758 xfree(tmp);
1759
1760 out:
1761 globfree(&g);
1762 return g.gl_matchc;
1763}
1764
1765/* tab-completion hook function, called via libedit */
1766static unsigned char
1767complete(EditLine *el, int ch)
1768{
1769 char **argv, *line, quote;
1770 u_int argc, carg, cursor, len, terminated, ret = CC_ERROR;
1771 const LineInfo *lf;
1772 struct complete_ctx *complete_ctx;
1773
1774 lf = el_line(el);
1775 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1776 fatal("%s: el_get failed", __func__);
1777
1778 /* Figure out which argument the cursor points to */
1779 cursor = lf->cursor - lf->buffer;
1780 line = (char *)xmalloc(cursor + 1);
1781 memcpy(line, lf->buffer, cursor);
1782 line[cursor] = '\0';
1783 argv = makeargv(line, &carg, 1, &quote, &terminated);
1784 xfree(line);
1785
1786 /* Get all the arguments on the line */
1787 len = lf->lastchar - lf->buffer;
1788 line = (char *)xmalloc(len + 1);
1789 memcpy(line, lf->buffer, len);
1790 line[len] = '\0';
1791 argv = makeargv(line, &argc, 1, NULL, NULL);
1792
1793 /* Ensure cursor is at EOL or a argument boundary */
1794 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1795 line[cursor] != '\n') {
1796 xfree(line);
1797 return ret;
1798 }
1799
1800 if (carg == 0) {
1801 /* Show all available commands */
1802 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1803 ret = CC_REDISPLAY;
1804 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1805 /* Handle the command parsing */
1806 if (complete_cmd_parse(el, argv[0], argc == carg,
1807 quote, terminated) != 0)
1808 ret = CC_REDISPLAY;
1809 } else if (carg >= 1) {
1810 /* Handle file parsing */
1811 int remote = complete_is_remote(argv[0]);
1812 char *filematch = NULL;
1813
1814 if (carg > 1 && line[cursor-1] != ' ')
1815 filematch = argv[carg - 1];
1816
1817 if (remote != 0 &&
1818 complete_match(el, complete_ctx->conn,
1819 *complete_ctx->remote_pathp, filematch,
1820 remote, carg == argc, quote, terminated) != 0)
1821 ret = CC_REDISPLAY;
1822 }
1823
1824 xfree(line);
1825 return ret;
1826}
Darren Tuckere67f7db2010-01-08 19:50:02 +11001827#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11001828
Damien Miller20e1fab2004-02-18 14:30:55 +11001829int
Darren Tucker21063192010-01-08 17:10:36 +11001830interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001831{
Darren Tucker909d8582010-01-08 19:02:40 +11001832 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11001833 char *dir = NULL;
1834 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10001835 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11001836 EditLine *el = NULL;
1837#ifdef USE_LIBEDIT
1838 History *hl = NULL;
1839 HistEvent hev;
1840 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11001841 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11001842
1843 if (!batchmode && isatty(STDIN_FILENO)) {
1844 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1845 fatal("Couldn't initialise editline");
1846 if ((hl = history_init()) == NULL)
1847 fatal("Couldn't initialise editline history");
1848 history(hl, &hev, H_SETSIZE, 100);
1849 el_set(el, EL_HIST, history, hl);
1850
1851 el_set(el, EL_PROMPT, prompt);
1852 el_set(el, EL_EDITOR, "emacs");
1853 el_set(el, EL_TERMINAL, NULL);
1854 el_set(el, EL_SIGNAL, 1);
1855 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11001856
1857 /* Tab Completion */
1858 el_set(el, EL_ADDFN, "ftp-complete",
1859 "Context senstive argument completion", complete);
1860 complete_ctx.conn = conn;
1861 complete_ctx.remote_pathp = &remote_path;
1862 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
1863 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11001864 }
1865#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001866
Darren Tucker909d8582010-01-08 19:02:40 +11001867 remote_path = do_realpath(conn, ".");
1868 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11001869 fatal("Need cwd");
1870
1871 if (file1 != NULL) {
1872 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11001873 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11001874
1875 if (remote_is_dir(conn, dir) && file2 == NULL) {
1876 printf("Changing to: %s\n", dir);
1877 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001878 if (parse_dispatch_command(conn, cmd,
1879 &remote_path, 1) != 0) {
Darren Tuckercd516ef2004-12-06 22:43:43 +11001880 xfree(dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001881 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001882 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001883 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11001884 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001885 } else {
1886 if (file2 == NULL)
1887 snprintf(cmd, sizeof cmd, "get %s", dir);
1888 else
1889 snprintf(cmd, sizeof cmd, "get %s %s", dir,
1890 file2);
1891
Darren Tucker909d8582010-01-08 19:02:40 +11001892 err = parse_dispatch_command(conn, cmd,
1893 &remote_path, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001894 xfree(dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001895 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001896 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001897 return (err);
1898 }
1899 xfree(dir);
1900 }
1901
Darren Tucker93e7e8f2005-08-23 08:06:55 +10001902#if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF)
Damien Miller20e1fab2004-02-18 14:30:55 +11001903 setvbuf(stdout, NULL, _IOLBF, 0);
1904 setvbuf(infile, NULL, _IOLBF, 0);
1905#else
Damien Miller37294fb2005-07-17 17:18:49 +10001906 setlinebuf(stdout);
1907 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11001908#endif
1909
Damien Miller0e2c1022005-08-12 22:16:22 +10001910 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11001911 err = 0;
1912 for (;;) {
1913 char *cp;
1914
Darren Tuckercdf547a2004-05-24 10:12:19 +10001915 signal(SIGINT, SIG_IGN);
1916
Darren Tucker2d963d82004-11-07 20:04:10 +11001917 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001918 if (interactive)
1919 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11001920 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001921 if (interactive)
1922 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11001923 break;
1924 }
Damien Miller0e2c1022005-08-12 22:16:22 +10001925 if (!interactive) { /* Echo command */
1926 printf("sftp> %s", cmd);
1927 if (strlen(cmd) > 0 &&
1928 cmd[strlen(cmd) - 1] != '\n')
1929 printf("\n");
1930 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001931 } else {
1932#ifdef USE_LIBEDIT
1933 const char *line;
1934 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001935
Darren Tucker909d8582010-01-08 19:02:40 +11001936 if ((line = el_gets(el, &count)) == NULL ||
1937 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001938 printf("\n");
1939 break;
1940 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001941 history(hl, &hev, H_ENTER, line);
1942 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
1943 fprintf(stderr, "Error: input line too long\n");
1944 continue;
1945 }
1946#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001947 }
1948
Damien Miller20e1fab2004-02-18 14:30:55 +11001949 cp = strrchr(cmd, '\n');
1950 if (cp)
1951 *cp = '\0';
1952
Darren Tuckercdf547a2004-05-24 10:12:19 +10001953 /* Handle user interrupts gracefully during commands */
1954 interrupted = 0;
1955 signal(SIGINT, cmd_interrupt);
1956
Darren Tucker909d8582010-01-08 19:02:40 +11001957 err = parse_dispatch_command(conn, cmd, &remote_path,
1958 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11001959 if (err != 0)
1960 break;
1961 }
Darren Tucker909d8582010-01-08 19:02:40 +11001962 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001963 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001964
Tim Rice027e8b12005-08-15 14:52:50 -07001965#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10001966 if (el != NULL)
1967 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07001968#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10001969
Damien Miller20e1fab2004-02-18 14:30:55 +11001970 /* err == 1 signifies normal "quit" exit */
1971 return (err >= 0 ? 0 : -1);
1972}
Damien Miller62d57f62003-01-10 21:43:24 +11001973
Ben Lindstrombba81212001-06-25 05:01:22 +00001974static void
Damien Millercc685c12003-06-04 22:51:38 +10001975connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11001976{
1977 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001978
Damien Miller33804262001-02-04 23:20:18 +11001979#ifdef USE_PIPES
1980 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001981
Damien Miller33804262001-02-04 23:20:18 +11001982 if ((pipe(pin) == -1) || (pipe(pout) == -1))
1983 fatal("pipe: %s", strerror(errno));
1984 *in = pin[0];
1985 *out = pout[1];
1986 c_in = pout[0];
1987 c_out = pin[1];
1988#else /* USE_PIPES */
1989 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001990
Damien Miller33804262001-02-04 23:20:18 +11001991 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
1992 fatal("socketpair: %s", strerror(errno));
1993 *in = *out = inout[0];
1994 c_in = c_out = inout[1];
1995#endif /* USE_PIPES */
1996
Damien Millercc685c12003-06-04 22:51:38 +10001997 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11001998 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10001999 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002000 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2001 (dup2(c_out, STDOUT_FILENO) == -1)) {
2002 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002003 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002004 }
2005 close(*in);
2006 close(*out);
2007 close(c_in);
2008 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002009
2010 /*
2011 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002012 * ignore SIGINT if we want to gracefully abort commands,
2013 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002014 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2015 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002016 */
2017 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002018 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002019 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002020 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002021 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002022 }
2023
Damien Millercc685c12003-06-04 22:51:38 +10002024 signal(SIGTERM, killchild);
2025 signal(SIGINT, killchild);
2026 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11002027 close(c_in);
2028 close(c_out);
2029}
2030
Ben Lindstrombba81212001-06-25 05:01:22 +00002031static void
Damien Miller33804262001-02-04 23:20:18 +11002032usage(void)
2033{
Damien Miller025e01c2002-02-08 22:06:29 +11002034 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002035
Ben Lindstrom1e243242001-09-18 05:38:44 +00002036 fprintf(stderr,
Darren Tucker1b0dd172009-10-07 08:37:48 +11002037 "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002038 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002039 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002040 " [-o ssh_option] [-P port] [-R num_requests] "
2041 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002042 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002043 " %s [user@]host[:file ...]\n"
2044 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002045 " %s -b batchfile [user@]host\n",
2046 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002047 exit(1);
2048}
2049
Kevin Stevesef4eea92001-02-05 12:42:17 +00002050int
Damien Miller33804262001-02-04 23:20:18 +11002051main(int argc, char **argv)
2052{
Damien Miller956f3fb2003-01-10 21:40:00 +11002053 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002054 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002055 int debug_level = 0, sshver = 2;
2056 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002057 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002058 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002059 LogLevel ll = SYSLOG_LEVEL_INFO;
2060 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002061 extern int optind;
2062 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002063 struct sftp_conn *conn;
2064 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2065 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002066 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002067
Darren Tuckerce321d82005-10-03 18:11:24 +10002068 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2069 sanitise_stdfd();
2070
Damien Miller59d3d5b2003-08-22 09:34:41 +10002071 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002072 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002073 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002074 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002075 addargs(&args, "-oForwardX11 no");
2076 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002077 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002078 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002079
Ben Lindstrom387c4722001-05-08 20:27:25 +00002080 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002081 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002082
Darren Tucker282b4022009-10-07 08:23:06 +11002083 while ((ch = getopt(argc, argv,
Damien Miller65e42f82010-09-24 22:15:11 +10002084 "1246hpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002085 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002086 /* Passed through to ssh(1) */
2087 case '4':
2088 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002089 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002090 addargs(&args, "-%c", ch);
2091 break;
2092 /* Passed through to ssh(1) with argument */
2093 case 'F':
2094 case 'c':
2095 case 'i':
2096 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002097 addargs(&args, "-%c", ch);
2098 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002099 break;
2100 case 'q':
2101 showprogress = 0;
2102 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002103 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002104 case 'P':
2105 addargs(&args, "-oPort %s", optarg);
2106 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002107 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002108 if (debug_level < 3) {
2109 addargs(&args, "-v");
2110 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2111 }
2112 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002113 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002114 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002115 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002116 if (sftp_server == NULL)
2117 sftp_server = _PATH_SFTP_SERVER;
2118 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002119 case '2':
2120 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002121 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002122 case 'B':
2123 copy_buffer_len = strtol(optarg, &cp, 10);
2124 if (copy_buffer_len == 0 || *cp != '\0')
2125 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002126 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002127 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002128 if (batchmode)
2129 fatal("Batch file already specified.");
2130
2131 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002132 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002133 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002134 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002135 showprogress = 0;
Damien Miller44f75c12004-01-21 10:58:47 +11002136 batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002137 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002138 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002139 case 'p':
2140 global_pflag = 1;
2141 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002142 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002143 sftp_direct = optarg;
2144 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002145 case 'l':
2146 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2147 &errstr);
2148 if (errstr != NULL)
2149 usage();
2150 limit_kbps *= 1024; /* kbps */
2151 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002152 case 'r':
2153 global_rflag = 1;
2154 break;
Damien Miller16a13332002-02-13 14:03:56 +11002155 case 'R':
2156 num_requests = strtol(optarg, &cp, 10);
2157 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002158 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002159 optarg);
2160 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002161 case 's':
2162 sftp_server = optarg;
2163 break;
2164 case 'S':
2165 ssh_program = optarg;
2166 replacearg(&args, 0, "%s", ssh_program);
2167 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002168 case 'h':
2169 default:
Damien Miller33804262001-02-04 23:20:18 +11002170 usage();
2171 }
2172 }
2173
Damien Millerc0f27d82004-03-08 23:12:19 +11002174 if (!isatty(STDERR_FILENO))
2175 showprogress = 0;
2176
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002177 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2178
Damien Millerd14ee1e2002-02-05 12:27:31 +11002179 if (sftp_direct == NULL) {
2180 if (optind == argc || argc > (optind + 2))
2181 usage();
Damien Miller33804262001-02-04 23:20:18 +11002182
Damien Millerd14ee1e2002-02-05 12:27:31 +11002183 userhost = xstrdup(argv[optind]);
2184 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002185
Ben Lindstromc276c122002-12-23 02:14:51 +00002186 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002187 host = userhost;
2188 else {
2189 *host++ = '\0';
2190 if (!userhost[0]) {
2191 fprintf(stderr, "Missing username\n");
2192 usage();
2193 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002194 addargs(&args, "-l");
2195 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002196 }
2197
Damien Millerec692032004-01-27 21:22:00 +11002198 if ((cp = colon(host)) != NULL) {
2199 *cp++ = '\0';
2200 file1 = cp;
2201 }
2202
Damien Millerd14ee1e2002-02-05 12:27:31 +11002203 host = cleanhostname(host);
2204 if (!*host) {
2205 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002206 usage();
2207 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002208
Damien Millerd14ee1e2002-02-05 12:27:31 +11002209 addargs(&args, "-oProtocol %d", sshver);
2210
2211 /* no subsystem if the server-spec contains a '/' */
2212 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2213 addargs(&args, "-s");
2214
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002215 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002216 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002217 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002218 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002219
Damien Millercc685c12003-06-04 22:51:38 +10002220 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002221 } else {
2222 args.list = NULL;
2223 addargs(&args, "sftp-server");
2224
Damien Millercc685c12003-06-04 22:51:38 +10002225 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002226 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002227 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002228
Damien Miller65e42f82010-09-24 22:15:11 +10002229 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002230 if (conn == NULL)
2231 fatal("Couldn't initialise connection to server");
2232
2233 if (!batchmode) {
2234 if (sftp_direct == NULL)
2235 fprintf(stderr, "Connected to %s.\n", host);
2236 else
2237 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2238 }
2239
2240 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002241
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002242#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002243 shutdown(in, SHUT_RDWR);
2244 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002245#endif
2246
Damien Miller33804262001-02-04 23:20:18 +11002247 close(in);
2248 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002249 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002250 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002251
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002252 while (waitpid(sshpid, NULL, 0) == -1)
2253 if (errno != EINTR)
2254 fatal("Couldn't wait for ssh process: %s",
2255 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002256
Damien Miller956f3fb2003-01-10 21:40:00 +11002257 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002258}