blob: f6cadd113aec0b16572565de6a64c5fdcb01070f [file] [log] [blame]
Damien Miller56883e12010-09-24 22:15:39 +10001/* $OpenBSD: sftp.c,v 1.127 2010/09/23 13:34:43 jmc 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 Miller20e1fab2004-02-18 14:30:55 +1100764
765 memset(&g, 0, sizeof(g));
766
767 if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE,
Darren Tucker596dcfa2004-12-11 13:37:22 +1100768 NULL, &g) || (g.gl_pathc && !g.gl_matchc)) {
769 if (g.gl_pathc)
770 globfree(&g);
Damien Miller20e1fab2004-02-18 14:30:55 +1100771 error("Can't ls: \"%s\" not found", path);
772 return (-1);
773 }
774
Darren Tuckercdf547a2004-05-24 10:12:19 +1000775 if (interrupted)
776 goto out;
777
Damien Miller20e1fab2004-02-18 14:30:55 +1100778 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100779 * If the glob returns a single match and it is a directory,
780 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100781 */
Darren Tucker596dcfa2004-12-11 13:37:22 +1100782 if (g.gl_matchc == 1) {
783 if ((a = do_lstat(conn, g.gl_pathv[0], 1)) == NULL) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100784 globfree(&g);
785 return (-1);
786 }
787 if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
788 S_ISDIR(a->perm)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100789 int err;
790
791 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +1100792 globfree(&g);
Darren Tucker596dcfa2004-12-11 13:37:22 +1100793 return (err);
Damien Miller20e1fab2004-02-18 14:30:55 +1100794 }
795 }
796
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000797 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000798 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100799 struct winsize ws;
800
801 /* Count entries for sort and find longest filename */
802 for (i = 0; g.gl_pathv[i]; i++)
803 m = MAX(m, strlen(g.gl_pathv[i]));
804
805 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
806 width = ws.ws_col;
807
808 columns = width / (m + 2);
809 columns = MAX(columns, 1);
810 colspace = width / columns;
811 }
812
Darren Tucker596dcfa2004-12-11 13:37:22 +1100813 for (i = 0; g.gl_pathv[i] && !interrupted; i++, a = NULL) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100814 char *fname;
815
816 fname = path_strip(g.gl_pathv[i], strip_path);
817
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000818 if (lflag & LS_LONG_VIEW) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100819 char *lname;
820 struct stat sb;
821
822 /*
823 * XXX: this is slow - 1 roundtrip per path
824 * A solution to this is to fork glob() and
825 * build a sftp specific version which keeps the
826 * attribs (which currently get thrown away)
827 * that the server returns as well as the filenames.
828 */
829 memset(&sb, 0, sizeof(sb));
Darren Tucker596dcfa2004-12-11 13:37:22 +1100830 if (a == NULL)
831 a = do_lstat(conn, g.gl_pathv[i], 1);
Damien Miller20e1fab2004-02-18 14:30:55 +1100832 if (a != NULL)
833 attrib_to_stat(a, &sb);
Darren Tucker2901e2d2010-01-13 22:44:06 +1100834 lname = ls_file(fname, &sb, 1, (lflag & LS_SI_UNITS));
Damien Miller20e1fab2004-02-18 14:30:55 +1100835 printf("%s\n", lname);
836 xfree(lname);
837 } else {
838 printf("%-*s", colspace, fname);
839 if (c >= columns) {
840 printf("\n");
841 c = 1;
842 } else
843 c++;
844 }
845 xfree(fname);
846 }
847
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000848 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100849 printf("\n");
850
Darren Tuckercdf547a2004-05-24 10:12:19 +1000851 out:
Damien Miller20e1fab2004-02-18 14:30:55 +1100852 if (g.gl_pathc)
853 globfree(&g);
854
855 return (0);
856}
857
Damien Millerd671e5a2008-05-19 14:53:33 +1000858static int
859do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
860{
Darren Tucker7b598892008-06-09 22:49:36 +1000861 struct sftp_statvfs st;
Damien Millerd671e5a2008-05-19 14:53:33 +1000862 char s_used[FMT_SCALED_STRSIZE];
863 char s_avail[FMT_SCALED_STRSIZE];
864 char s_root[FMT_SCALED_STRSIZE];
865 char s_total[FMT_SCALED_STRSIZE];
Darren Tuckerb5082e92010-01-08 18:51:47 +1100866 unsigned long long ffree;
Damien Millerd671e5a2008-05-19 14:53:33 +1000867
868 if (do_statvfs(conn, path, &st, 1) == -1)
869 return -1;
870 if (iflag) {
Darren Tuckerb5082e92010-01-08 18:51:47 +1100871 ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
Damien Millerd671e5a2008-05-19 14:53:33 +1000872 printf(" Inodes Used Avail "
873 "(root) %%Capacity\n");
874 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
875 (unsigned long long)st.f_files,
876 (unsigned long long)(st.f_files - st.f_ffree),
877 (unsigned long long)st.f_favail,
Darren Tuckerb5082e92010-01-08 18:51:47 +1100878 (unsigned long long)st.f_ffree, ffree);
Damien Millerd671e5a2008-05-19 14:53:33 +1000879 } else if (hflag) {
880 strlcpy(s_used, "error", sizeof(s_used));
881 strlcpy(s_avail, "error", sizeof(s_avail));
882 strlcpy(s_root, "error", sizeof(s_root));
883 strlcpy(s_total, "error", sizeof(s_total));
884 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
885 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
886 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
887 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
888 printf(" Size Used Avail (root) %%Capacity\n");
889 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
890 s_total, s_used, s_avail, s_root,
891 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
892 st.f_blocks));
893 } else {
894 printf(" Size Used Avail "
895 "(root) %%Capacity\n");
896 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
897 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
898 (unsigned long long)(st.f_frsize *
899 (st.f_blocks - st.f_bfree) / 1024),
900 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
901 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
902 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
903 st.f_blocks));
904 }
905 return 0;
906}
907
Damien Miller1cbc2922007-10-26 14:27:45 +1000908/*
909 * Undo escaping of glob sequences in place. Used to undo extra escaping
910 * applied in makeargv() when the string is destined for a function that
911 * does not glob it.
912 */
913static void
914undo_glob_escape(char *s)
915{
916 size_t i, j;
917
918 for (i = j = 0;;) {
919 if (s[i] == '\0') {
920 s[j] = '\0';
921 return;
922 }
923 if (s[i] != '\\') {
924 s[j++] = s[i++];
925 continue;
926 }
927 /* s[i] == '\\' */
928 ++i;
929 switch (s[i]) {
930 case '?':
931 case '[':
932 case '*':
933 case '\\':
934 s[j++] = s[i++];
935 break;
936 case '\0':
937 s[j++] = '\\';
938 s[j] = '\0';
939 return;
940 default:
941 s[j++] = '\\';
942 s[j++] = s[i++];
943 break;
944 }
945 }
946}
947
948/*
949 * Split a string into an argument vector using sh(1)-style quoting,
950 * comment and escaping rules, but with some tweaks to handle glob(3)
951 * wildcards.
Darren Tucker909d8582010-01-08 19:02:40 +1100952 * The "sloppy" flag allows for recovery from missing terminating quote, for
953 * use in parsing incomplete commandlines during tab autocompletion.
954 *
Damien Miller1cbc2922007-10-26 14:27:45 +1000955 * Returns NULL on error or a NULL-terminated array of arguments.
Darren Tucker909d8582010-01-08 19:02:40 +1100956 *
957 * If "lastquote" is not NULL, the quoting character used for the last
958 * argument is placed in *lastquote ("\0", "'" or "\"").
959 *
960 * If "terminated" is not NULL, *terminated will be set to 1 when the
961 * last argument's quote has been properly terminated or 0 otherwise.
962 * This parameter is only of use if "sloppy" is set.
Damien Miller1cbc2922007-10-26 14:27:45 +1000963 */
964#define MAXARGS 128
965#define MAXARGLEN 8192
966static char **
Darren Tucker909d8582010-01-08 19:02:40 +1100967makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
968 u_int *terminated)
Damien Miller1cbc2922007-10-26 14:27:45 +1000969{
970 int argc, quot;
971 size_t i, j;
972 static char argvs[MAXARGLEN];
973 static char *argv[MAXARGS + 1];
974 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
975
976 *argcp = argc = 0;
977 if (strlen(arg) > sizeof(argvs) - 1) {
978 args_too_longs:
979 error("string too long");
980 return NULL;
981 }
Darren Tucker909d8582010-01-08 19:02:40 +1100982 if (terminated != NULL)
983 *terminated = 1;
984 if (lastquote != NULL)
985 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +1000986 state = MA_START;
987 i = j = 0;
988 for (;;) {
989 if (isspace(arg[i])) {
990 if (state == MA_UNQUOTED) {
991 /* Terminate current argument */
992 argvs[j++] = '\0';
993 argc++;
994 state = MA_START;
995 } else if (state != MA_START)
996 argvs[j++] = arg[i];
997 } else if (arg[i] == '"' || arg[i] == '\'') {
998 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
999 if (state == MA_START) {
1000 argv[argc] = argvs + j;
1001 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +11001002 if (lastquote != NULL)
1003 *lastquote = arg[i];
Damien Miller1cbc2922007-10-26 14:27:45 +10001004 } else if (state == MA_UNQUOTED)
1005 state = q;
1006 else if (state == q)
1007 state = MA_UNQUOTED;
1008 else
1009 argvs[j++] = arg[i];
1010 } else if (arg[i] == '\\') {
1011 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1012 quot = state == MA_SQUOTE ? '\'' : '"';
1013 /* Unescape quote we are in */
1014 /* XXX support \n and friends? */
1015 if (arg[i + 1] == quot) {
1016 i++;
1017 argvs[j++] = arg[i];
1018 } else if (arg[i + 1] == '?' ||
1019 arg[i + 1] == '[' || arg[i + 1] == '*') {
1020 /*
1021 * Special case for sftp: append
1022 * double-escaped glob sequence -
1023 * glob will undo one level of
1024 * escaping. NB. string can grow here.
1025 */
1026 if (j >= sizeof(argvs) - 5)
1027 goto args_too_longs;
1028 argvs[j++] = '\\';
1029 argvs[j++] = arg[i++];
1030 argvs[j++] = '\\';
1031 argvs[j++] = arg[i];
1032 } else {
1033 argvs[j++] = arg[i++];
1034 argvs[j++] = arg[i];
1035 }
1036 } else {
1037 if (state == MA_START) {
1038 argv[argc] = argvs + j;
1039 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001040 if (lastquote != NULL)
1041 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001042 }
1043 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1044 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1045 /*
1046 * Special case for sftp: append
1047 * escaped glob sequence -
1048 * glob will undo one level of
1049 * escaping.
1050 */
1051 argvs[j++] = arg[i++];
1052 argvs[j++] = arg[i];
1053 } else {
1054 /* Unescape everything */
1055 /* XXX support \n and friends? */
1056 i++;
1057 argvs[j++] = arg[i];
1058 }
1059 }
1060 } else if (arg[i] == '#') {
1061 if (state == MA_SQUOTE || state == MA_DQUOTE)
1062 argvs[j++] = arg[i];
1063 else
1064 goto string_done;
1065 } else if (arg[i] == '\0') {
1066 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001067 if (sloppy) {
1068 state = MA_UNQUOTED;
1069 if (terminated != NULL)
1070 *terminated = 0;
1071 goto string_done;
1072 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001073 error("Unterminated quoted argument");
1074 return NULL;
1075 }
1076 string_done:
1077 if (state == MA_UNQUOTED) {
1078 argvs[j++] = '\0';
1079 argc++;
1080 }
1081 break;
1082 } else {
1083 if (state == MA_START) {
1084 argv[argc] = argvs + j;
1085 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001086 if (lastquote != NULL)
1087 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001088 }
1089 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1090 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1091 /*
1092 * Special case for sftp: escape quoted
1093 * glob(3) wildcards. NB. string can grow
1094 * here.
1095 */
1096 if (j >= sizeof(argvs) - 3)
1097 goto args_too_longs;
1098 argvs[j++] = '\\';
1099 argvs[j++] = arg[i];
1100 } else
1101 argvs[j++] = arg[i];
1102 }
1103 i++;
1104 }
1105 *argcp = argc;
1106 return argv;
1107}
1108
Damien Miller20e1fab2004-02-18 14:30:55 +11001109static int
Darren Tucker909d8582010-01-08 19:02:40 +11001110parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag,
1111 int *hflag, unsigned long *n_arg, char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001112{
1113 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001114 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001115 int base = 0;
1116 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001117 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001118
1119 /* Skip leading whitespace */
1120 cp = cp + strspn(cp, WHITESPACE);
1121
Damien Miller20e1fab2004-02-18 14:30:55 +11001122 /* Check for leading '-' (disable error processing) */
1123 *iflag = 0;
1124 if (*cp == '-') {
1125 *iflag = 1;
1126 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001127 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001128 }
1129
Darren Tucker70cc0922010-01-09 22:28:03 +11001130 /* Ignore blank lines and lines which begin with comment '#' char */
1131 if (*cp == '\0' || *cp == '#')
1132 return (0);
1133
Darren Tucker909d8582010-01-08 19:02:40 +11001134 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001135 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001136
Damien Miller1cbc2922007-10-26 14:27:45 +10001137 /* Figure out which command we have */
1138 for (i = 0; cmds[i].c != NULL; i++) {
1139 if (strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001140 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001141 }
1142 cmdnum = cmds[i].n;
1143 cmd = cmds[i].c;
1144
1145 /* Special case */
1146 if (*cp == '!') {
1147 cp++;
1148 cmdnum = I_SHELL;
1149 } else if (cmdnum == -1) {
1150 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001151 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001152 }
1153
1154 /* Get arguments and parse flags */
Darren Tucker1b0dd172009-10-07 08:37:48 +11001155 *lflag = *pflag = *rflag = *hflag = *n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001156 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001157 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001158 switch (cmdnum) {
1159 case I_GET:
1160 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001161 if ((optidx = parse_getput_flags(cmd, argv, argc, pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001162 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001163 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001164 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001165 error("You must specify at least one path after a "
1166 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001167 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001168 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001169 *path1 = xstrdup(argv[optidx]);
1170 /* Get second pathname (optional) */
1171 if (argc - optidx > 1) {
1172 *path2 = xstrdup(argv[optidx + 1]);
1173 /* Destination is not globbed */
1174 undo_glob_escape(*path2);
1175 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001176 break;
1177 case I_RENAME:
1178 case I_SYMLINK:
Damien Miller1cbc2922007-10-26 14:27:45 +10001179 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001180 error("You must specify two paths after a %s "
1181 "command.", 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 *path2 = xstrdup(argv[optidx + 1]);
1186 /* Paths are not globbed */
1187 undo_glob_escape(*path1);
1188 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001189 break;
1190 case I_RM:
1191 case I_MKDIR:
1192 case I_RMDIR:
1193 case I_CHDIR:
1194 case I_LCHDIR:
1195 case I_LMKDIR:
1196 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001197 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001198 error("You must specify a path after a %s command.",
1199 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001200 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001201 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001202 *path1 = xstrdup(argv[optidx]);
1203 /* Only "rm" globs */
1204 if (cmdnum != I_RM)
1205 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001206 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001207 case I_DF:
1208 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1209 iflag)) == -1)
1210 return -1;
1211 /* Default to current directory if no path specified */
1212 if (argc - optidx < 1)
1213 *path1 = NULL;
1214 else {
1215 *path1 = xstrdup(argv[optidx]);
1216 undo_glob_escape(*path1);
1217 }
1218 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001219 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001220 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001221 return(-1);
1222 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001223 if (argc - optidx > 0)
1224 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001225 break;
1226 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001227 /* Skip ls command and following whitespace */
1228 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001229 case I_SHELL:
1230 /* Uses the rest of the line */
1231 break;
1232 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001233 case I_CHMOD:
1234 base = 8;
1235 case I_CHOWN:
1236 case I_CHGRP:
1237 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001238 if (argc - optidx < 1)
1239 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001240 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001241 l = strtol(argv[optidx], &cp2, base);
1242 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1243 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1244 l < 0) {
1245 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001246 error("You must supply a numeric argument "
1247 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001248 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001249 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001250 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001251 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001252 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001253 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001254 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001255 error("You must specify a path after a %s command.",
1256 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001257 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001258 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001259 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001260 break;
1261 case I_QUIT:
1262 case I_PWD:
1263 case I_LPWD:
1264 case I_HELP:
1265 case I_VERSION:
1266 case I_PROGRESS:
1267 break;
1268 default:
1269 fatal("Command not implemented");
1270 }
1271
1272 *cpp = cp;
1273 return(cmdnum);
1274}
1275
1276static int
1277parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1278 int err_abort)
1279{
1280 char *path1, *path2, *tmp;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001281 int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001282 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001283 Attrib a, *aa;
1284 char path_buf[MAXPATHLEN];
1285 int err = 0;
1286 glob_t g;
1287
1288 path1 = path2 = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001289 cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag, &n_arg,
Damien Miller20e1fab2004-02-18 14:30:55 +11001290 &path1, &path2);
1291
1292 if (iflag != 0)
1293 err_abort = 0;
1294
1295 memset(&g, 0, sizeof(g));
1296
1297 /* Perform command */
1298 switch (cmdnum) {
1299 case 0:
1300 /* Blank line */
1301 break;
1302 case -1:
1303 /* Unrecognized command */
1304 err = -1;
1305 break;
1306 case I_GET:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001307 err = process_get(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001308 break;
1309 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001310 err = process_put(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001311 break;
1312 case I_RENAME:
1313 path1 = make_absolute(path1, *pwd);
1314 path2 = make_absolute(path2, *pwd);
1315 err = do_rename(conn, path1, path2);
1316 break;
1317 case I_SYMLINK:
1318 path2 = make_absolute(path2, *pwd);
1319 err = do_symlink(conn, path1, path2);
1320 break;
1321 case I_RM:
1322 path1 = make_absolute(path1, *pwd);
1323 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001324 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001325 printf("Removing %s\n", g.gl_pathv[i]);
1326 err = do_rm(conn, g.gl_pathv[i]);
1327 if (err != 0 && err_abort)
1328 break;
1329 }
1330 break;
1331 case I_MKDIR:
1332 path1 = make_absolute(path1, *pwd);
1333 attrib_clear(&a);
1334 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1335 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001336 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001337 break;
1338 case I_RMDIR:
1339 path1 = make_absolute(path1, *pwd);
1340 err = do_rmdir(conn, path1);
1341 break;
1342 case I_CHDIR:
1343 path1 = make_absolute(path1, *pwd);
1344 if ((tmp = do_realpath(conn, path1)) == NULL) {
1345 err = 1;
1346 break;
1347 }
1348 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1349 xfree(tmp);
1350 err = 1;
1351 break;
1352 }
1353 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1354 error("Can't change directory: Can't check target");
1355 xfree(tmp);
1356 err = 1;
1357 break;
1358 }
1359 if (!S_ISDIR(aa->perm)) {
1360 error("Can't change directory: \"%s\" is not "
1361 "a directory", tmp);
1362 xfree(tmp);
1363 err = 1;
1364 break;
1365 }
1366 xfree(*pwd);
1367 *pwd = tmp;
1368 break;
1369 case I_LS:
1370 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001371 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001372 break;
1373 }
1374
1375 /* Strip pwd off beginning of non-absolute paths */
1376 tmp = NULL;
1377 if (*path1 != '/')
1378 tmp = *pwd;
1379
1380 path1 = make_absolute(path1, *pwd);
1381 err = do_globbed_ls(conn, path1, tmp, lflag);
1382 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001383 case I_DF:
1384 /* Default to current directory if no path specified */
1385 if (path1 == NULL)
1386 path1 = xstrdup(*pwd);
1387 path1 = make_absolute(path1, *pwd);
1388 err = do_df(conn, path1, hflag, iflag);
1389 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001390 case I_LCHDIR:
1391 if (chdir(path1) == -1) {
1392 error("Couldn't change local directory to "
1393 "\"%s\": %s", path1, strerror(errno));
1394 err = 1;
1395 }
1396 break;
1397 case I_LMKDIR:
1398 if (mkdir(path1, 0777) == -1) {
1399 error("Couldn't create local directory "
1400 "\"%s\": %s", path1, strerror(errno));
1401 err = 1;
1402 }
1403 break;
1404 case I_LLS:
1405 local_do_ls(cmd);
1406 break;
1407 case I_SHELL:
1408 local_do_shell(cmd);
1409 break;
1410 case I_LUMASK:
1411 umask(n_arg);
1412 printf("Local umask: %03lo\n", n_arg);
1413 break;
1414 case I_CHMOD:
1415 path1 = make_absolute(path1, *pwd);
1416 attrib_clear(&a);
1417 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1418 a.perm = n_arg;
1419 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001420 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001421 printf("Changing mode on %s\n", g.gl_pathv[i]);
1422 err = do_setstat(conn, g.gl_pathv[i], &a);
1423 if (err != 0 && err_abort)
1424 break;
1425 }
1426 break;
1427 case I_CHOWN:
1428 case I_CHGRP:
1429 path1 = make_absolute(path1, *pwd);
1430 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001431 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001432 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001433 if (err_abort) {
1434 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001435 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001436 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001437 continue;
1438 }
1439 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1440 error("Can't get current ownership of "
1441 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001442 if (err_abort) {
1443 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001444 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001445 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001446 continue;
1447 }
1448 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1449 if (cmdnum == I_CHOWN) {
1450 printf("Changing owner on %s\n", g.gl_pathv[i]);
1451 aa->uid = n_arg;
1452 } else {
1453 printf("Changing group on %s\n", g.gl_pathv[i]);
1454 aa->gid = n_arg;
1455 }
1456 err = do_setstat(conn, g.gl_pathv[i], aa);
1457 if (err != 0 && err_abort)
1458 break;
1459 }
1460 break;
1461 case I_PWD:
1462 printf("Remote working directory: %s\n", *pwd);
1463 break;
1464 case I_LPWD:
1465 if (!getcwd(path_buf, sizeof(path_buf))) {
1466 error("Couldn't get local cwd: %s", strerror(errno));
1467 err = -1;
1468 break;
1469 }
1470 printf("Local working directory: %s\n", path_buf);
1471 break;
1472 case I_QUIT:
1473 /* Processed below */
1474 break;
1475 case I_HELP:
1476 help();
1477 break;
1478 case I_VERSION:
1479 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1480 break;
1481 case I_PROGRESS:
1482 showprogress = !showprogress;
1483 if (showprogress)
1484 printf("Progress meter enabled\n");
1485 else
1486 printf("Progress meter disabled\n");
1487 break;
1488 default:
1489 fatal("%d is not implemented", cmdnum);
1490 }
1491
1492 if (g.gl_pathc)
1493 globfree(&g);
1494 if (path1)
1495 xfree(path1);
1496 if (path2)
1497 xfree(path2);
1498
1499 /* If an unignored error occurs in batch mode we should abort. */
1500 if (err_abort && err != 0)
1501 return (-1);
1502 else if (cmdnum == I_QUIT)
1503 return (1);
1504
1505 return (0);
1506}
1507
Darren Tucker2d963d82004-11-07 20:04:10 +11001508#ifdef USE_LIBEDIT
1509static char *
1510prompt(EditLine *el)
1511{
1512 return ("sftp> ");
1513}
Darren Tucker2d963d82004-11-07 20:04:10 +11001514
Darren Tucker909d8582010-01-08 19:02:40 +11001515/* Display entries in 'list' after skipping the first 'len' chars */
1516static void
1517complete_display(char **list, u_int len)
1518{
1519 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1520 struct winsize ws;
1521 char *tmp;
1522
1523 /* Count entries for sort and find longest */
1524 for (y = 0; list[y]; y++)
1525 m = MAX(m, strlen(list[y]));
1526
1527 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1528 width = ws.ws_col;
1529
1530 m = m > len ? m - len : 0;
1531 columns = width / (m + 2);
1532 columns = MAX(columns, 1);
1533 colspace = width / columns;
1534 colspace = MIN(colspace, width);
1535
1536 printf("\n");
1537 m = 1;
1538 for (y = 0; list[y]; y++) {
1539 llen = strlen(list[y]);
1540 tmp = llen > len ? list[y] + len : "";
1541 printf("%-*s", colspace, tmp);
1542 if (m >= columns) {
1543 printf("\n");
1544 m = 1;
1545 } else
1546 m++;
1547 }
1548 printf("\n");
1549}
1550
1551/*
1552 * Given a "list" of words that begin with a common prefix of "word",
1553 * attempt to find an autocompletion to extends "word" by the next
1554 * characters common to all entries in "list".
1555 */
1556static char *
1557complete_ambiguous(const char *word, char **list, size_t count)
1558{
1559 if (word == NULL)
1560 return NULL;
1561
1562 if (count > 0) {
1563 u_int y, matchlen = strlen(list[0]);
1564
1565 /* Find length of common stem */
1566 for (y = 1; list[y]; y++) {
1567 u_int x;
1568
1569 for (x = 0; x < matchlen; x++)
1570 if (list[0][x] != list[y][x])
1571 break;
1572
1573 matchlen = x;
1574 }
1575
1576 if (matchlen > strlen(word)) {
1577 char *tmp = xstrdup(list[0]);
1578
Darren Tucker340d1682010-01-09 08:54:31 +11001579 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001580 return tmp;
1581 }
1582 }
1583
1584 return xstrdup(word);
1585}
1586
1587/* Autocomplete a sftp command */
1588static int
1589complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1590 int terminated)
1591{
1592 u_int y, count = 0, cmdlen, tmplen;
1593 char *tmp, **list, argterm[3];
1594 const LineInfo *lf;
1595
1596 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1597
1598 /* No command specified: display all available commands */
1599 if (cmd == NULL) {
1600 for (y = 0; cmds[y].c; y++)
1601 list[count++] = xstrdup(cmds[y].c);
1602
1603 list[count] = NULL;
1604 complete_display(list, 0);
1605
1606 for (y = 0; list[y] != NULL; y++)
1607 xfree(list[y]);
1608 xfree(list);
1609 return count;
1610 }
1611
1612 /* Prepare subset of commands that start with "cmd" */
1613 cmdlen = strlen(cmd);
1614 for (y = 0; cmds[y].c; y++) {
1615 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
1616 list[count++] = xstrdup(cmds[y].c);
1617 }
1618 list[count] = NULL;
1619
1620 if (count == 0)
1621 return 0;
1622
1623 /* Complete ambigious command */
1624 tmp = complete_ambiguous(cmd, list, count);
1625 if (count > 1)
1626 complete_display(list, 0);
1627
1628 for (y = 0; list[y]; y++)
1629 xfree(list[y]);
1630 xfree(list);
1631
1632 if (tmp != NULL) {
1633 tmplen = strlen(tmp);
1634 cmdlen = strlen(cmd);
1635 /* If cmd may be extended then do so */
1636 if (tmplen > cmdlen)
1637 if (el_insertstr(el, tmp + cmdlen) == -1)
1638 fatal("el_insertstr failed.");
1639 lf = el_line(el);
1640 /* Terminate argument cleanly */
1641 if (count == 1) {
1642 y = 0;
1643 if (!terminated)
1644 argterm[y++] = quote;
1645 if (lastarg || *(lf->cursor) != ' ')
1646 argterm[y++] = ' ';
1647 argterm[y] = '\0';
1648 if (y > 0 && el_insertstr(el, argterm) == -1)
1649 fatal("el_insertstr failed.");
1650 }
1651 xfree(tmp);
1652 }
1653
1654 return count;
1655}
1656
1657/*
1658 * Determine whether a particular sftp command's arguments (if any)
1659 * represent local or remote files.
1660 */
1661static int
1662complete_is_remote(char *cmd) {
1663 int i;
1664
1665 if (cmd == NULL)
1666 return -1;
1667
1668 for (i = 0; cmds[i].c; i++) {
1669 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
1670 return cmds[i].t;
1671 }
1672
1673 return -1;
1674}
1675
1676/* Autocomplete a filename "file" */
1677static int
1678complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1679 char *file, int remote, int lastarg, char quote, int terminated)
1680{
1681 glob_t g;
1682 char *tmp, *tmp2, ins[3];
1683 u_int i, hadglob, pwdlen, len, tmplen, filelen;
1684 const LineInfo *lf;
1685
1686 /* Glob from "file" location */
1687 if (file == NULL)
1688 tmp = xstrdup("*");
1689 else
1690 xasprintf(&tmp, "%s*", file);
1691
1692 memset(&g, 0, sizeof(g));
1693 if (remote != LOCAL) {
1694 tmp = make_absolute(tmp, remote_path);
1695 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1696 } else
1697 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1698
1699 /* Determine length of pwd so we can trim completion display */
1700 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1701 /* Terminate counting on first unescaped glob metacharacter */
1702 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1703 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1704 hadglob = 1;
1705 break;
1706 }
1707 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1708 tmplen++;
1709 if (tmp[tmplen] == '/')
1710 pwdlen = tmplen + 1; /* track last seen '/' */
1711 }
1712 xfree(tmp);
1713
1714 if (g.gl_matchc == 0)
1715 goto out;
1716
1717 if (g.gl_matchc > 1)
1718 complete_display(g.gl_pathv, pwdlen);
1719
1720 tmp = NULL;
1721 /* Don't try to extend globs */
1722 if (file == NULL || hadglob)
1723 goto out;
1724
1725 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
1726 tmp = path_strip(tmp2, remote_path);
1727 xfree(tmp2);
1728
1729 if (tmp == NULL)
1730 goto out;
1731
1732 tmplen = strlen(tmp);
1733 filelen = strlen(file);
1734
1735 if (tmplen > filelen) {
1736 tmp2 = tmp + filelen;
1737 len = strlen(tmp2);
1738 /* quote argument on way out */
1739 for (i = 0; i < len; i++) {
1740 ins[0] = '\\';
1741 ins[1] = tmp2[i];
1742 ins[2] = '\0';
1743 switch (tmp2[i]) {
1744 case '\'':
1745 case '"':
1746 case '\\':
1747 case '\t':
1748 case ' ':
1749 if (quote == '\0' || tmp2[i] == quote) {
1750 if (el_insertstr(el, ins) == -1)
1751 fatal("el_insertstr "
1752 "failed.");
1753 break;
1754 }
1755 /* FALLTHROUGH */
1756 default:
1757 if (el_insertstr(el, ins + 1) == -1)
1758 fatal("el_insertstr failed.");
1759 break;
1760 }
1761 }
1762 }
1763
1764 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11001765 if (g.gl_matchc == 1) {
1766 i = 0;
1767 if (!terminated)
1768 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11001769 if (*(lf->cursor - 1) != '/' &&
1770 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11001771 ins[i++] = ' ';
1772 ins[i] = '\0';
1773 if (i > 0 && el_insertstr(el, ins) == -1)
1774 fatal("el_insertstr failed.");
1775 }
1776 xfree(tmp);
1777
1778 out:
1779 globfree(&g);
1780 return g.gl_matchc;
1781}
1782
1783/* tab-completion hook function, called via libedit */
1784static unsigned char
1785complete(EditLine *el, int ch)
1786{
1787 char **argv, *line, quote;
1788 u_int argc, carg, cursor, len, terminated, ret = CC_ERROR;
1789 const LineInfo *lf;
1790 struct complete_ctx *complete_ctx;
1791
1792 lf = el_line(el);
1793 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1794 fatal("%s: el_get failed", __func__);
1795
1796 /* Figure out which argument the cursor points to */
1797 cursor = lf->cursor - lf->buffer;
1798 line = (char *)xmalloc(cursor + 1);
1799 memcpy(line, lf->buffer, cursor);
1800 line[cursor] = '\0';
1801 argv = makeargv(line, &carg, 1, &quote, &terminated);
1802 xfree(line);
1803
1804 /* Get all the arguments on the line */
1805 len = lf->lastchar - lf->buffer;
1806 line = (char *)xmalloc(len + 1);
1807 memcpy(line, lf->buffer, len);
1808 line[len] = '\0';
1809 argv = makeargv(line, &argc, 1, NULL, NULL);
1810
1811 /* Ensure cursor is at EOL or a argument boundary */
1812 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1813 line[cursor] != '\n') {
1814 xfree(line);
1815 return ret;
1816 }
1817
1818 if (carg == 0) {
1819 /* Show all available commands */
1820 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1821 ret = CC_REDISPLAY;
1822 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1823 /* Handle the command parsing */
1824 if (complete_cmd_parse(el, argv[0], argc == carg,
1825 quote, terminated) != 0)
1826 ret = CC_REDISPLAY;
1827 } else if (carg >= 1) {
1828 /* Handle file parsing */
1829 int remote = complete_is_remote(argv[0]);
1830 char *filematch = NULL;
1831
1832 if (carg > 1 && line[cursor-1] != ' ')
1833 filematch = argv[carg - 1];
1834
1835 if (remote != 0 &&
1836 complete_match(el, complete_ctx->conn,
1837 *complete_ctx->remote_pathp, filematch,
1838 remote, carg == argc, quote, terminated) != 0)
1839 ret = CC_REDISPLAY;
1840 }
1841
1842 xfree(line);
1843 return ret;
1844}
Darren Tuckere67f7db2010-01-08 19:50:02 +11001845#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11001846
Damien Miller20e1fab2004-02-18 14:30:55 +11001847int
Darren Tucker21063192010-01-08 17:10:36 +11001848interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001849{
Darren Tucker909d8582010-01-08 19:02:40 +11001850 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11001851 char *dir = NULL;
1852 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10001853 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11001854 EditLine *el = NULL;
1855#ifdef USE_LIBEDIT
1856 History *hl = NULL;
1857 HistEvent hev;
1858 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11001859 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11001860
1861 if (!batchmode && isatty(STDIN_FILENO)) {
1862 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1863 fatal("Couldn't initialise editline");
1864 if ((hl = history_init()) == NULL)
1865 fatal("Couldn't initialise editline history");
1866 history(hl, &hev, H_SETSIZE, 100);
1867 el_set(el, EL_HIST, history, hl);
1868
1869 el_set(el, EL_PROMPT, prompt);
1870 el_set(el, EL_EDITOR, "emacs");
1871 el_set(el, EL_TERMINAL, NULL);
1872 el_set(el, EL_SIGNAL, 1);
1873 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11001874
1875 /* Tab Completion */
1876 el_set(el, EL_ADDFN, "ftp-complete",
1877 "Context senstive argument completion", complete);
1878 complete_ctx.conn = conn;
1879 complete_ctx.remote_pathp = &remote_path;
1880 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
1881 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11001882 }
1883#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001884
Darren Tucker909d8582010-01-08 19:02:40 +11001885 remote_path = do_realpath(conn, ".");
1886 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11001887 fatal("Need cwd");
1888
1889 if (file1 != NULL) {
1890 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11001891 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11001892
1893 if (remote_is_dir(conn, dir) && file2 == NULL) {
1894 printf("Changing to: %s\n", dir);
1895 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001896 if (parse_dispatch_command(conn, cmd,
1897 &remote_path, 1) != 0) {
Darren Tuckercd516ef2004-12-06 22:43:43 +11001898 xfree(dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001899 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001900 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001901 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11001902 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001903 } else {
1904 if (file2 == NULL)
1905 snprintf(cmd, sizeof cmd, "get %s", dir);
1906 else
1907 snprintf(cmd, sizeof cmd, "get %s %s", dir,
1908 file2);
1909
Darren Tucker909d8582010-01-08 19:02:40 +11001910 err = parse_dispatch_command(conn, cmd,
1911 &remote_path, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +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 (err);
1916 }
1917 xfree(dir);
1918 }
1919
Darren Tucker93e7e8f2005-08-23 08:06:55 +10001920#if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF)
Damien Miller20e1fab2004-02-18 14:30:55 +11001921 setvbuf(stdout, NULL, _IOLBF, 0);
1922 setvbuf(infile, NULL, _IOLBF, 0);
1923#else
Damien Miller37294fb2005-07-17 17:18:49 +10001924 setlinebuf(stdout);
1925 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11001926#endif
1927
Damien Miller0e2c1022005-08-12 22:16:22 +10001928 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11001929 err = 0;
1930 for (;;) {
1931 char *cp;
1932
Darren Tuckercdf547a2004-05-24 10:12:19 +10001933 signal(SIGINT, SIG_IGN);
1934
Darren Tucker2d963d82004-11-07 20:04:10 +11001935 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001936 if (interactive)
1937 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11001938 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001939 if (interactive)
1940 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11001941 break;
1942 }
Damien Miller0e2c1022005-08-12 22:16:22 +10001943 if (!interactive) { /* Echo command */
1944 printf("sftp> %s", cmd);
1945 if (strlen(cmd) > 0 &&
1946 cmd[strlen(cmd) - 1] != '\n')
1947 printf("\n");
1948 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001949 } else {
1950#ifdef USE_LIBEDIT
1951 const char *line;
1952 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001953
Darren Tucker909d8582010-01-08 19:02:40 +11001954 if ((line = el_gets(el, &count)) == NULL ||
1955 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001956 printf("\n");
1957 break;
1958 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001959 history(hl, &hev, H_ENTER, line);
1960 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
1961 fprintf(stderr, "Error: input line too long\n");
1962 continue;
1963 }
1964#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001965 }
1966
Damien Miller20e1fab2004-02-18 14:30:55 +11001967 cp = strrchr(cmd, '\n');
1968 if (cp)
1969 *cp = '\0';
1970
Darren Tuckercdf547a2004-05-24 10:12:19 +10001971 /* Handle user interrupts gracefully during commands */
1972 interrupted = 0;
1973 signal(SIGINT, cmd_interrupt);
1974
Darren Tucker909d8582010-01-08 19:02:40 +11001975 err = parse_dispatch_command(conn, cmd, &remote_path,
1976 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11001977 if (err != 0)
1978 break;
1979 }
Darren Tucker909d8582010-01-08 19:02:40 +11001980 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001981 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001982
Tim Rice027e8b12005-08-15 14:52:50 -07001983#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10001984 if (el != NULL)
1985 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07001986#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10001987
Damien Miller20e1fab2004-02-18 14:30:55 +11001988 /* err == 1 signifies normal "quit" exit */
1989 return (err >= 0 ? 0 : -1);
1990}
Damien Miller62d57f62003-01-10 21:43:24 +11001991
Ben Lindstrombba81212001-06-25 05:01:22 +00001992static void
Damien Millercc685c12003-06-04 22:51:38 +10001993connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11001994{
1995 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001996
Damien Miller33804262001-02-04 23:20:18 +11001997#ifdef USE_PIPES
1998 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001999
Damien Miller33804262001-02-04 23:20:18 +11002000 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2001 fatal("pipe: %s", strerror(errno));
2002 *in = pin[0];
2003 *out = pout[1];
2004 c_in = pout[0];
2005 c_out = pin[1];
2006#else /* USE_PIPES */
2007 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002008
Damien Miller33804262001-02-04 23:20:18 +11002009 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2010 fatal("socketpair: %s", strerror(errno));
2011 *in = *out = inout[0];
2012 c_in = c_out = inout[1];
2013#endif /* USE_PIPES */
2014
Damien Millercc685c12003-06-04 22:51:38 +10002015 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002016 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002017 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002018 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2019 (dup2(c_out, STDOUT_FILENO) == -1)) {
2020 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002021 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002022 }
2023 close(*in);
2024 close(*out);
2025 close(c_in);
2026 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002027
2028 /*
2029 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002030 * ignore SIGINT if we want to gracefully abort commands,
2031 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002032 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2033 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002034 */
2035 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002036 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002037 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002038 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002039 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002040 }
2041
Damien Millercc685c12003-06-04 22:51:38 +10002042 signal(SIGTERM, killchild);
2043 signal(SIGINT, killchild);
2044 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11002045 close(c_in);
2046 close(c_out);
2047}
2048
Ben Lindstrombba81212001-06-25 05:01:22 +00002049static void
Damien Miller33804262001-02-04 23:20:18 +11002050usage(void)
2051{
Damien Miller025e01c2002-02-08 22:06:29 +11002052 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002053
Ben Lindstrom1e243242001-09-18 05:38:44 +00002054 fprintf(stderr,
Darren Tucker1b0dd172009-10-07 08:37:48 +11002055 "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002056 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002057 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002058 " [-o ssh_option] [-P port] [-R num_requests] "
2059 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002060 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002061 " %s [user@]host[:file ...]\n"
2062 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002063 " %s -b batchfile [user@]host\n",
2064 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002065 exit(1);
2066}
2067
Kevin Stevesef4eea92001-02-05 12:42:17 +00002068int
Damien Miller33804262001-02-04 23:20:18 +11002069main(int argc, char **argv)
2070{
Damien Miller956f3fb2003-01-10 21:40:00 +11002071 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002072 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002073 int debug_level = 0, sshver = 2;
2074 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002075 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002076 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002077 LogLevel ll = SYSLOG_LEVEL_INFO;
2078 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002079 extern int optind;
2080 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002081 struct sftp_conn *conn;
2082 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2083 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002084 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002085
Darren Tuckerce321d82005-10-03 18:11:24 +10002086 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2087 sanitise_stdfd();
2088
Damien Miller59d3d5b2003-08-22 09:34:41 +10002089 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002090 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002091 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002092 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002093 addargs(&args, "-oForwardX11 no");
2094 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002095 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002096 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002097
Ben Lindstrom387c4722001-05-08 20:27:25 +00002098 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002099 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002100
Darren Tucker282b4022009-10-07 08:23:06 +11002101 while ((ch = getopt(argc, argv,
Damien Miller65e42f82010-09-24 22:15:11 +10002102 "1246hpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002103 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002104 /* Passed through to ssh(1) */
2105 case '4':
2106 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002107 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002108 addargs(&args, "-%c", ch);
2109 break;
2110 /* Passed through to ssh(1) with argument */
2111 case 'F':
2112 case 'c':
2113 case 'i':
2114 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002115 addargs(&args, "-%c", ch);
2116 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002117 break;
2118 case 'q':
2119 showprogress = 0;
2120 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002121 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002122 case 'P':
2123 addargs(&args, "-oPort %s", optarg);
2124 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002125 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002126 if (debug_level < 3) {
2127 addargs(&args, "-v");
2128 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2129 }
2130 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002131 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002132 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002133 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002134 if (sftp_server == NULL)
2135 sftp_server = _PATH_SFTP_SERVER;
2136 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002137 case '2':
2138 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002139 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002140 case 'B':
2141 copy_buffer_len = strtol(optarg, &cp, 10);
2142 if (copy_buffer_len == 0 || *cp != '\0')
2143 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002144 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002145 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002146 if (batchmode)
2147 fatal("Batch file already specified.");
2148
2149 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002150 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002151 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002152 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002153 showprogress = 0;
Damien Miller44f75c12004-01-21 10:58:47 +11002154 batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002155 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002156 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002157 case 'p':
2158 global_pflag = 1;
2159 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002160 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002161 sftp_direct = optarg;
2162 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002163 case 'l':
2164 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2165 &errstr);
2166 if (errstr != NULL)
2167 usage();
2168 limit_kbps *= 1024; /* kbps */
2169 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002170 case 'r':
2171 global_rflag = 1;
2172 break;
Damien Miller16a13332002-02-13 14:03:56 +11002173 case 'R':
2174 num_requests = strtol(optarg, &cp, 10);
2175 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002176 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002177 optarg);
2178 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002179 case 's':
2180 sftp_server = optarg;
2181 break;
2182 case 'S':
2183 ssh_program = optarg;
2184 replacearg(&args, 0, "%s", ssh_program);
2185 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002186 case 'h':
2187 default:
Damien Miller33804262001-02-04 23:20:18 +11002188 usage();
2189 }
2190 }
2191
Damien Millerc0f27d82004-03-08 23:12:19 +11002192 if (!isatty(STDERR_FILENO))
2193 showprogress = 0;
2194
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002195 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2196
Damien Millerd14ee1e2002-02-05 12:27:31 +11002197 if (sftp_direct == NULL) {
2198 if (optind == argc || argc > (optind + 2))
2199 usage();
Damien Miller33804262001-02-04 23:20:18 +11002200
Damien Millerd14ee1e2002-02-05 12:27:31 +11002201 userhost = xstrdup(argv[optind]);
2202 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002203
Ben Lindstromc276c122002-12-23 02:14:51 +00002204 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002205 host = userhost;
2206 else {
2207 *host++ = '\0';
2208 if (!userhost[0]) {
2209 fprintf(stderr, "Missing username\n");
2210 usage();
2211 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002212 addargs(&args, "-l");
2213 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002214 }
2215
Damien Millerec692032004-01-27 21:22:00 +11002216 if ((cp = colon(host)) != NULL) {
2217 *cp++ = '\0';
2218 file1 = cp;
2219 }
2220
Damien Millerd14ee1e2002-02-05 12:27:31 +11002221 host = cleanhostname(host);
2222 if (!*host) {
2223 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002224 usage();
2225 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002226
Damien Millerd14ee1e2002-02-05 12:27:31 +11002227 addargs(&args, "-oProtocol %d", sshver);
2228
2229 /* no subsystem if the server-spec contains a '/' */
2230 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2231 addargs(&args, "-s");
2232
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002233 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002234 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002235 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002236 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002237
Damien Millercc685c12003-06-04 22:51:38 +10002238 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002239 } else {
2240 args.list = NULL;
2241 addargs(&args, "sftp-server");
2242
Damien Millercc685c12003-06-04 22:51:38 +10002243 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002244 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002245 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002246
Damien Miller65e42f82010-09-24 22:15:11 +10002247 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002248 if (conn == NULL)
2249 fatal("Couldn't initialise connection to server");
2250
2251 if (!batchmode) {
2252 if (sftp_direct == NULL)
2253 fprintf(stderr, "Connected to %s.\n", host);
2254 else
2255 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2256 }
2257
2258 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002259
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002260#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002261 shutdown(in, SHUT_RDWR);
2262 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002263#endif
2264
Damien Miller33804262001-02-04 23:20:18 +11002265 close(in);
2266 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002267 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002268 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002269
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002270 while (waitpid(sshpid, NULL, 0) == -1)
2271 if (errno != EINTR)
2272 fatal("Couldn't wait for ssh process: %s",
2273 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002274
Damien Miller956f3fb2003-01-10 21:40:00 +11002275 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002276}