blob: d605505eab59b97ae15cfe2eccb2f41aa68a06a6 [file] [log] [blame]
Darren Tuckerd78739a2010-10-24 10:56:32 +11001/* $OpenBSD: sftp.c,v 1.131 2010/10/23 22:06:12 sthen 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
Damien Miller38d9a962010-10-07 22:07:11 +1100272 if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
Damien Miller20e1fab2004-02-18 14:30:55 +1100273 shell = _PATH_BSHELL;
274
275 if ((pid = fork()) == -1)
276 fatal("Couldn't fork: %s", strerror(errno));
277
278 if (pid == 0) {
279 /* XXX: child has pipe fds to ssh subproc open - issue? */
280 if (args) {
281 debug3("Executing %s -c \"%s\"", shell, args);
282 execl(shell, shell, "-c", args, (char *)NULL);
283 } else {
284 debug3("Executing %s", shell);
285 execl(shell, shell, (char *)NULL);
286 }
287 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
288 strerror(errno));
289 _exit(1);
290 }
291 while (waitpid(pid, &status, 0) == -1)
292 if (errno != EINTR)
293 fatal("Couldn't wait for child: %s", strerror(errno));
294 if (!WIFEXITED(status))
Damien Miller55b04f12006-03-26 14:23:17 +1100295 error("Shell exited abnormally");
Damien Miller20e1fab2004-02-18 14:30:55 +1100296 else if (WEXITSTATUS(status))
297 error("Shell exited with status %d", WEXITSTATUS(status));
298}
299
300static void
301local_do_ls(const char *args)
302{
303 if (!args || !*args)
304 local_do_shell(_PATH_LS);
305 else {
306 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
307 char *buf = xmalloc(len);
308
309 /* XXX: quoting - rip quoting code from ftp? */
310 snprintf(buf, len, _PATH_LS " %s", args);
311 local_do_shell(buf);
312 xfree(buf);
313 }
314}
315
316/* Strip one path (usually the pwd) from the start of another */
317static char *
318path_strip(char *path, char *strip)
319{
320 size_t len;
321
322 if (strip == NULL)
323 return (xstrdup(path));
324
325 len = strlen(strip);
Darren Tuckere2f189a2004-12-06 22:45:53 +1100326 if (strncmp(path, strip, len) == 0) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100327 if (strip[len - 1] != '/' && path[len] == '/')
328 len++;
329 return (xstrdup(path + len));
330 }
331
332 return (xstrdup(path));
333}
334
335static char *
Damien Miller20e1fab2004-02-18 14:30:55 +1100336make_absolute(char *p, char *pwd)
337{
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000338 char *abs_str;
Damien Miller20e1fab2004-02-18 14:30:55 +1100339
340 /* Derelativise */
341 if (p && p[0] != '/') {
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000342 abs_str = path_append(pwd, p);
Damien Miller20e1fab2004-02-18 14:30:55 +1100343 xfree(p);
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000344 return(abs_str);
Damien Miller20e1fab2004-02-18 14:30:55 +1100345 } else
346 return(p);
347}
348
349static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100350parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag,
351 int *rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100352{
Damien Millerf184bcf2008-06-29 22:45:13 +1000353 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000354 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100355
Damien Miller1cbc2922007-10-26 14:27:45 +1000356 optind = optreset = 1;
357 opterr = 0;
358
Darren Tucker1b0dd172009-10-07 08:37:48 +1100359 *rflag = *pflag = 0;
360 while ((ch = getopt(argc, argv, "PpRr")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000361 switch (ch) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100362 case 'p':
363 case 'P':
364 *pflag = 1;
365 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100366 case 'r':
367 case 'R':
368 *rflag = 1;
369 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100370 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000371 error("%s: Invalid flag -%c", cmd, optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000372 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100373 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100374 }
375
Damien Miller1cbc2922007-10-26 14:27:45 +1000376 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100377}
378
379static int
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{
Darren Tucker596dcfa2004-12-11 13:37:22 +1100761 Attrib *a = NULL;
Damien Millera6e121a2010-10-07 21:39:17 +1100762 char *fname, *lname;
Damien Miller68e2e562010-10-07 21:39:55 +1100763 glob_t g;
764 int err;
765 struct winsize ws;
766 u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100767
768 memset(&g, 0, sizeof(g));
769
Damien Millera6e121a2010-10-07 21:39:17 +1100770 if (remote_glob(conn, path,
771 GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT, NULL, &g) ||
772 (g.gl_pathc && !g.gl_matchc)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100773 if (g.gl_pathc)
774 globfree(&g);
Damien Miller20e1fab2004-02-18 14:30:55 +1100775 error("Can't ls: \"%s\" not found", path);
Damien Millera6e121a2010-10-07 21:39:17 +1100776 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100777 }
778
Darren Tuckercdf547a2004-05-24 10:12:19 +1000779 if (interrupted)
780 goto out;
781
Damien Miller20e1fab2004-02-18 14:30:55 +1100782 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100783 * If the glob returns a single match and it is a directory,
784 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100785 */
Damien Millera6e121a2010-10-07 21:39:17 +1100786 if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
787 S_ISDIR(g.gl_statv[0]->st_mode)) {
788 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
789 globfree(&g);
790 return err;
Damien Miller20e1fab2004-02-18 14:30:55 +1100791 }
792
Damien Miller68e2e562010-10-07 21:39:55 +1100793 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
794 width = ws.ws_col;
Damien Miller20e1fab2004-02-18 14:30:55 +1100795
Damien Miller68e2e562010-10-07 21:39:55 +1100796 if (!(lflag & LS_SHORT_VIEW)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100797 /* Count entries for sort and find longest filename */
798 for (i = 0; g.gl_pathv[i]; i++)
799 m = MAX(m, strlen(g.gl_pathv[i]));
800
Damien Miller20e1fab2004-02-18 14:30:55 +1100801 columns = width / (m + 2);
802 columns = MAX(columns, 1);
803 colspace = width / columns;
804 }
805
Darren Tucker596dcfa2004-12-11 13:37:22 +1100806 for (i = 0; g.gl_pathv[i] && !interrupted; i++, a = NULL) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100807 fname = path_strip(g.gl_pathv[i], strip_path);
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000808 if (lflag & LS_LONG_VIEW) {
Damien Millera6e121a2010-10-07 21:39:17 +1100809 if (g.gl_statv[i] == NULL) {
810 error("no stat information for %s", fname);
811 continue;
812 }
813 lname = ls_file(fname, g.gl_statv[i], 1,
814 (lflag & LS_SI_UNITS));
Damien Miller20e1fab2004-02-18 14:30:55 +1100815 printf("%s\n", lname);
816 xfree(lname);
817 } else {
818 printf("%-*s", colspace, fname);
819 if (c >= columns) {
820 printf("\n");
821 c = 1;
822 } else
823 c++;
824 }
825 xfree(fname);
826 }
827
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000828 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100829 printf("\n");
830
Darren Tuckercdf547a2004-05-24 10:12:19 +1000831 out:
Damien Miller20e1fab2004-02-18 14:30:55 +1100832 if (g.gl_pathc)
833 globfree(&g);
834
Damien Millera6e121a2010-10-07 21:39:17 +1100835 return 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100836}
837
Damien Millerd671e5a2008-05-19 14:53:33 +1000838static int
839do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
840{
Darren Tucker7b598892008-06-09 22:49:36 +1000841 struct sftp_statvfs st;
Damien Millerd671e5a2008-05-19 14:53:33 +1000842 char s_used[FMT_SCALED_STRSIZE];
843 char s_avail[FMT_SCALED_STRSIZE];
844 char s_root[FMT_SCALED_STRSIZE];
845 char s_total[FMT_SCALED_STRSIZE];
Darren Tuckerb5082e92010-01-08 18:51:47 +1100846 unsigned long long ffree;
Damien Millerd671e5a2008-05-19 14:53:33 +1000847
848 if (do_statvfs(conn, path, &st, 1) == -1)
849 return -1;
850 if (iflag) {
Darren Tuckerb5082e92010-01-08 18:51:47 +1100851 ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
Damien Millerd671e5a2008-05-19 14:53:33 +1000852 printf(" Inodes Used Avail "
853 "(root) %%Capacity\n");
854 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
855 (unsigned long long)st.f_files,
856 (unsigned long long)(st.f_files - st.f_ffree),
857 (unsigned long long)st.f_favail,
Darren Tuckerb5082e92010-01-08 18:51:47 +1100858 (unsigned long long)st.f_ffree, ffree);
Damien Millerd671e5a2008-05-19 14:53:33 +1000859 } else if (hflag) {
860 strlcpy(s_used, "error", sizeof(s_used));
861 strlcpy(s_avail, "error", sizeof(s_avail));
862 strlcpy(s_root, "error", sizeof(s_root));
863 strlcpy(s_total, "error", sizeof(s_total));
864 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
865 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
866 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
867 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
868 printf(" Size Used Avail (root) %%Capacity\n");
869 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
870 s_total, s_used, s_avail, s_root,
871 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
872 st.f_blocks));
873 } else {
874 printf(" Size Used Avail "
875 "(root) %%Capacity\n");
876 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
877 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
878 (unsigned long long)(st.f_frsize *
879 (st.f_blocks - st.f_bfree) / 1024),
880 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
881 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
882 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
883 st.f_blocks));
884 }
885 return 0;
886}
887
Damien Miller1cbc2922007-10-26 14:27:45 +1000888/*
889 * Undo escaping of glob sequences in place. Used to undo extra escaping
890 * applied in makeargv() when the string is destined for a function that
891 * does not glob it.
892 */
893static void
894undo_glob_escape(char *s)
895{
896 size_t i, j;
897
898 for (i = j = 0;;) {
899 if (s[i] == '\0') {
900 s[j] = '\0';
901 return;
902 }
903 if (s[i] != '\\') {
904 s[j++] = s[i++];
905 continue;
906 }
907 /* s[i] == '\\' */
908 ++i;
909 switch (s[i]) {
910 case '?':
911 case '[':
912 case '*':
913 case '\\':
914 s[j++] = s[i++];
915 break;
916 case '\0':
917 s[j++] = '\\';
918 s[j] = '\0';
919 return;
920 default:
921 s[j++] = '\\';
922 s[j++] = s[i++];
923 break;
924 }
925 }
926}
927
928/*
929 * Split a string into an argument vector using sh(1)-style quoting,
930 * comment and escaping rules, but with some tweaks to handle glob(3)
931 * wildcards.
Darren Tucker909d8582010-01-08 19:02:40 +1100932 * The "sloppy" flag allows for recovery from missing terminating quote, for
933 * use in parsing incomplete commandlines during tab autocompletion.
934 *
Damien Miller1cbc2922007-10-26 14:27:45 +1000935 * Returns NULL on error or a NULL-terminated array of arguments.
Darren Tucker909d8582010-01-08 19:02:40 +1100936 *
937 * If "lastquote" is not NULL, the quoting character used for the last
938 * argument is placed in *lastquote ("\0", "'" or "\"").
939 *
940 * If "terminated" is not NULL, *terminated will be set to 1 when the
941 * last argument's quote has been properly terminated or 0 otherwise.
942 * This parameter is only of use if "sloppy" is set.
Damien Miller1cbc2922007-10-26 14:27:45 +1000943 */
944#define MAXARGS 128
945#define MAXARGLEN 8192
946static char **
Darren Tucker909d8582010-01-08 19:02:40 +1100947makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
948 u_int *terminated)
Damien Miller1cbc2922007-10-26 14:27:45 +1000949{
950 int argc, quot;
951 size_t i, j;
952 static char argvs[MAXARGLEN];
953 static char *argv[MAXARGS + 1];
954 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
955
956 *argcp = argc = 0;
957 if (strlen(arg) > sizeof(argvs) - 1) {
958 args_too_longs:
959 error("string too long");
960 return NULL;
961 }
Darren Tucker909d8582010-01-08 19:02:40 +1100962 if (terminated != NULL)
963 *terminated = 1;
964 if (lastquote != NULL)
965 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +1000966 state = MA_START;
967 i = j = 0;
968 for (;;) {
969 if (isspace(arg[i])) {
970 if (state == MA_UNQUOTED) {
971 /* Terminate current argument */
972 argvs[j++] = '\0';
973 argc++;
974 state = MA_START;
975 } else if (state != MA_START)
976 argvs[j++] = arg[i];
977 } else if (arg[i] == '"' || arg[i] == '\'') {
978 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
979 if (state == MA_START) {
980 argv[argc] = argvs + j;
981 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +1100982 if (lastquote != NULL)
983 *lastquote = arg[i];
Damien Miller1cbc2922007-10-26 14:27:45 +1000984 } else if (state == MA_UNQUOTED)
985 state = q;
986 else if (state == q)
987 state = MA_UNQUOTED;
988 else
989 argvs[j++] = arg[i];
990 } else if (arg[i] == '\\') {
991 if (state == MA_SQUOTE || state == MA_DQUOTE) {
992 quot = state == MA_SQUOTE ? '\'' : '"';
993 /* Unescape quote we are in */
994 /* XXX support \n and friends? */
995 if (arg[i + 1] == quot) {
996 i++;
997 argvs[j++] = arg[i];
998 } else if (arg[i + 1] == '?' ||
999 arg[i + 1] == '[' || arg[i + 1] == '*') {
1000 /*
1001 * Special case for sftp: append
1002 * double-escaped glob sequence -
1003 * glob will undo one level of
1004 * escaping. NB. string can grow here.
1005 */
1006 if (j >= sizeof(argvs) - 5)
1007 goto args_too_longs;
1008 argvs[j++] = '\\';
1009 argvs[j++] = arg[i++];
1010 argvs[j++] = '\\';
1011 argvs[j++] = arg[i];
1012 } else {
1013 argvs[j++] = arg[i++];
1014 argvs[j++] = arg[i];
1015 }
1016 } else {
1017 if (state == MA_START) {
1018 argv[argc] = argvs + j;
1019 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001020 if (lastquote != NULL)
1021 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001022 }
1023 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1024 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1025 /*
1026 * Special case for sftp: append
1027 * escaped glob sequence -
1028 * glob will undo one level of
1029 * escaping.
1030 */
1031 argvs[j++] = arg[i++];
1032 argvs[j++] = arg[i];
1033 } else {
1034 /* Unescape everything */
1035 /* XXX support \n and friends? */
1036 i++;
1037 argvs[j++] = arg[i];
1038 }
1039 }
1040 } else if (arg[i] == '#') {
1041 if (state == MA_SQUOTE || state == MA_DQUOTE)
1042 argvs[j++] = arg[i];
1043 else
1044 goto string_done;
1045 } else if (arg[i] == '\0') {
1046 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001047 if (sloppy) {
1048 state = MA_UNQUOTED;
1049 if (terminated != NULL)
1050 *terminated = 0;
1051 goto string_done;
1052 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001053 error("Unterminated quoted argument");
1054 return NULL;
1055 }
1056 string_done:
1057 if (state == MA_UNQUOTED) {
1058 argvs[j++] = '\0';
1059 argc++;
1060 }
1061 break;
1062 } else {
1063 if (state == MA_START) {
1064 argv[argc] = argvs + j;
1065 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001066 if (lastquote != NULL)
1067 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001068 }
1069 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1070 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1071 /*
1072 * Special case for sftp: escape quoted
1073 * glob(3) wildcards. NB. string can grow
1074 * here.
1075 */
1076 if (j >= sizeof(argvs) - 3)
1077 goto args_too_longs;
1078 argvs[j++] = '\\';
1079 argvs[j++] = arg[i];
1080 } else
1081 argvs[j++] = arg[i];
1082 }
1083 i++;
1084 }
1085 *argcp = argc;
1086 return argv;
1087}
1088
Damien Miller20e1fab2004-02-18 14:30:55 +11001089static int
Darren Tucker909d8582010-01-08 19:02:40 +11001090parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag,
1091 int *hflag, unsigned long *n_arg, char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001092{
1093 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001094 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001095 int base = 0;
1096 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001097 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001098
1099 /* Skip leading whitespace */
1100 cp = cp + strspn(cp, WHITESPACE);
1101
Damien Miller20e1fab2004-02-18 14:30:55 +11001102 /* Check for leading '-' (disable error processing) */
1103 *iflag = 0;
1104 if (*cp == '-') {
1105 *iflag = 1;
1106 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001107 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001108 }
1109
Darren Tucker70cc0922010-01-09 22:28:03 +11001110 /* Ignore blank lines and lines which begin with comment '#' char */
1111 if (*cp == '\0' || *cp == '#')
1112 return (0);
1113
Darren Tucker909d8582010-01-08 19:02:40 +11001114 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001115 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001116
Damien Miller1cbc2922007-10-26 14:27:45 +10001117 /* Figure out which command we have */
1118 for (i = 0; cmds[i].c != NULL; i++) {
1119 if (strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001120 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001121 }
1122 cmdnum = cmds[i].n;
1123 cmd = cmds[i].c;
1124
1125 /* Special case */
1126 if (*cp == '!') {
1127 cp++;
1128 cmdnum = I_SHELL;
1129 } else if (cmdnum == -1) {
1130 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001131 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001132 }
1133
1134 /* Get arguments and parse flags */
Darren Tucker1b0dd172009-10-07 08:37:48 +11001135 *lflag = *pflag = *rflag = *hflag = *n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001136 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001137 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001138 switch (cmdnum) {
1139 case I_GET:
1140 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001141 if ((optidx = parse_getput_flags(cmd, argv, argc, pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001142 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001143 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001144 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001145 error("You must specify at least one path after a "
1146 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001147 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001148 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001149 *path1 = xstrdup(argv[optidx]);
1150 /* Get second pathname (optional) */
1151 if (argc - optidx > 1) {
1152 *path2 = xstrdup(argv[optidx + 1]);
1153 /* Destination is not globbed */
1154 undo_glob_escape(*path2);
1155 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001156 break;
1157 case I_RENAME:
1158 case I_SYMLINK:
Damien Miller1cbc2922007-10-26 14:27:45 +10001159 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001160 error("You must specify two paths after a %s "
1161 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001162 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001163 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001164 *path1 = xstrdup(argv[optidx]);
1165 *path2 = xstrdup(argv[optidx + 1]);
1166 /* Paths are not globbed */
1167 undo_glob_escape(*path1);
1168 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001169 break;
1170 case I_RM:
1171 case I_MKDIR:
1172 case I_RMDIR:
1173 case I_CHDIR:
1174 case I_LCHDIR:
1175 case I_LMKDIR:
1176 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001177 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001178 error("You must specify a path after a %s command.",
1179 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001180 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001181 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001182 *path1 = xstrdup(argv[optidx]);
1183 /* Only "rm" globs */
1184 if (cmdnum != I_RM)
1185 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001186 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001187 case I_DF:
1188 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1189 iflag)) == -1)
1190 return -1;
1191 /* Default to current directory if no path specified */
1192 if (argc - optidx < 1)
1193 *path1 = NULL;
1194 else {
1195 *path1 = xstrdup(argv[optidx]);
1196 undo_glob_escape(*path1);
1197 }
1198 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001199 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001200 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001201 return(-1);
1202 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001203 if (argc - optidx > 0)
1204 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001205 break;
1206 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001207 /* Skip ls command and following whitespace */
1208 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001209 case I_SHELL:
1210 /* Uses the rest of the line */
1211 break;
1212 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001213 case I_CHMOD:
1214 base = 8;
1215 case I_CHOWN:
1216 case I_CHGRP:
1217 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001218 if (argc - optidx < 1)
1219 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001220 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001221 l = strtol(argv[optidx], &cp2, base);
1222 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1223 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1224 l < 0) {
1225 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001226 error("You must supply a numeric argument "
1227 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001228 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001229 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001230 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001231 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001232 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001233 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001234 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001235 error("You must specify a path after a %s command.",
1236 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001237 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001238 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001239 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001240 break;
1241 case I_QUIT:
1242 case I_PWD:
1243 case I_LPWD:
1244 case I_HELP:
1245 case I_VERSION:
1246 case I_PROGRESS:
1247 break;
1248 default:
1249 fatal("Command not implemented");
1250 }
1251
1252 *cpp = cp;
1253 return(cmdnum);
1254}
1255
1256static int
1257parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1258 int err_abort)
1259{
1260 char *path1, *path2, *tmp;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001261 int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001262 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001263 Attrib a, *aa;
1264 char path_buf[MAXPATHLEN];
1265 int err = 0;
1266 glob_t g;
1267
1268 path1 = path2 = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001269 cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag, &n_arg,
Damien Miller20e1fab2004-02-18 14:30:55 +11001270 &path1, &path2);
1271
1272 if (iflag != 0)
1273 err_abort = 0;
1274
1275 memset(&g, 0, sizeof(g));
1276
1277 /* Perform command */
1278 switch (cmdnum) {
1279 case 0:
1280 /* Blank line */
1281 break;
1282 case -1:
1283 /* Unrecognized command */
1284 err = -1;
1285 break;
1286 case I_GET:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001287 err = process_get(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001288 break;
1289 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001290 err = process_put(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001291 break;
1292 case I_RENAME:
1293 path1 = make_absolute(path1, *pwd);
1294 path2 = make_absolute(path2, *pwd);
1295 err = do_rename(conn, path1, path2);
1296 break;
1297 case I_SYMLINK:
1298 path2 = make_absolute(path2, *pwd);
1299 err = do_symlink(conn, path1, path2);
1300 break;
1301 case I_RM:
1302 path1 = make_absolute(path1, *pwd);
1303 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001304 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001305 printf("Removing %s\n", g.gl_pathv[i]);
1306 err = do_rm(conn, g.gl_pathv[i]);
1307 if (err != 0 && err_abort)
1308 break;
1309 }
1310 break;
1311 case I_MKDIR:
1312 path1 = make_absolute(path1, *pwd);
1313 attrib_clear(&a);
1314 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1315 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001316 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001317 break;
1318 case I_RMDIR:
1319 path1 = make_absolute(path1, *pwd);
1320 err = do_rmdir(conn, path1);
1321 break;
1322 case I_CHDIR:
1323 path1 = make_absolute(path1, *pwd);
1324 if ((tmp = do_realpath(conn, path1)) == NULL) {
1325 err = 1;
1326 break;
1327 }
1328 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1329 xfree(tmp);
1330 err = 1;
1331 break;
1332 }
1333 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1334 error("Can't change directory: Can't check target");
1335 xfree(tmp);
1336 err = 1;
1337 break;
1338 }
1339 if (!S_ISDIR(aa->perm)) {
1340 error("Can't change directory: \"%s\" is not "
1341 "a directory", tmp);
1342 xfree(tmp);
1343 err = 1;
1344 break;
1345 }
1346 xfree(*pwd);
1347 *pwd = tmp;
1348 break;
1349 case I_LS:
1350 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001351 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001352 break;
1353 }
1354
1355 /* Strip pwd off beginning of non-absolute paths */
1356 tmp = NULL;
1357 if (*path1 != '/')
1358 tmp = *pwd;
1359
1360 path1 = make_absolute(path1, *pwd);
1361 err = do_globbed_ls(conn, path1, tmp, lflag);
1362 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001363 case I_DF:
1364 /* Default to current directory if no path specified */
1365 if (path1 == NULL)
1366 path1 = xstrdup(*pwd);
1367 path1 = make_absolute(path1, *pwd);
1368 err = do_df(conn, path1, hflag, iflag);
1369 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001370 case I_LCHDIR:
1371 if (chdir(path1) == -1) {
1372 error("Couldn't change local directory to "
1373 "\"%s\": %s", path1, strerror(errno));
1374 err = 1;
1375 }
1376 break;
1377 case I_LMKDIR:
1378 if (mkdir(path1, 0777) == -1) {
1379 error("Couldn't create local directory "
1380 "\"%s\": %s", path1, strerror(errno));
1381 err = 1;
1382 }
1383 break;
1384 case I_LLS:
1385 local_do_ls(cmd);
1386 break;
1387 case I_SHELL:
1388 local_do_shell(cmd);
1389 break;
1390 case I_LUMASK:
1391 umask(n_arg);
1392 printf("Local umask: %03lo\n", n_arg);
1393 break;
1394 case I_CHMOD:
1395 path1 = make_absolute(path1, *pwd);
1396 attrib_clear(&a);
1397 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1398 a.perm = n_arg;
1399 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001400 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001401 printf("Changing mode on %s\n", g.gl_pathv[i]);
1402 err = do_setstat(conn, g.gl_pathv[i], &a);
1403 if (err != 0 && err_abort)
1404 break;
1405 }
1406 break;
1407 case I_CHOWN:
1408 case I_CHGRP:
1409 path1 = make_absolute(path1, *pwd);
1410 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001411 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001412 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001413 if (err_abort) {
1414 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001415 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001416 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001417 continue;
1418 }
1419 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1420 error("Can't get current ownership of "
1421 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001422 if (err_abort) {
1423 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001424 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001425 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001426 continue;
1427 }
1428 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1429 if (cmdnum == I_CHOWN) {
1430 printf("Changing owner on %s\n", g.gl_pathv[i]);
1431 aa->uid = n_arg;
1432 } else {
1433 printf("Changing group on %s\n", g.gl_pathv[i]);
1434 aa->gid = n_arg;
1435 }
1436 err = do_setstat(conn, g.gl_pathv[i], aa);
1437 if (err != 0 && err_abort)
1438 break;
1439 }
1440 break;
1441 case I_PWD:
1442 printf("Remote working directory: %s\n", *pwd);
1443 break;
1444 case I_LPWD:
1445 if (!getcwd(path_buf, sizeof(path_buf))) {
1446 error("Couldn't get local cwd: %s", strerror(errno));
1447 err = -1;
1448 break;
1449 }
1450 printf("Local working directory: %s\n", path_buf);
1451 break;
1452 case I_QUIT:
1453 /* Processed below */
1454 break;
1455 case I_HELP:
1456 help();
1457 break;
1458 case I_VERSION:
1459 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1460 break;
1461 case I_PROGRESS:
1462 showprogress = !showprogress;
1463 if (showprogress)
1464 printf("Progress meter enabled\n");
1465 else
1466 printf("Progress meter disabled\n");
1467 break;
1468 default:
1469 fatal("%d is not implemented", cmdnum);
1470 }
1471
1472 if (g.gl_pathc)
1473 globfree(&g);
1474 if (path1)
1475 xfree(path1);
1476 if (path2)
1477 xfree(path2);
1478
1479 /* If an unignored error occurs in batch mode we should abort. */
1480 if (err_abort && err != 0)
1481 return (-1);
1482 else if (cmdnum == I_QUIT)
1483 return (1);
1484
1485 return (0);
1486}
1487
Darren Tucker2d963d82004-11-07 20:04:10 +11001488#ifdef USE_LIBEDIT
1489static char *
1490prompt(EditLine *el)
1491{
1492 return ("sftp> ");
1493}
Darren Tucker2d963d82004-11-07 20:04:10 +11001494
Darren Tucker909d8582010-01-08 19:02:40 +11001495/* Display entries in 'list' after skipping the first 'len' chars */
1496static void
1497complete_display(char **list, u_int len)
1498{
1499 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1500 struct winsize ws;
1501 char *tmp;
1502
1503 /* Count entries for sort and find longest */
1504 for (y = 0; list[y]; y++)
1505 m = MAX(m, strlen(list[y]));
1506
1507 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1508 width = ws.ws_col;
1509
1510 m = m > len ? m - len : 0;
1511 columns = width / (m + 2);
1512 columns = MAX(columns, 1);
1513 colspace = width / columns;
1514 colspace = MIN(colspace, width);
1515
1516 printf("\n");
1517 m = 1;
1518 for (y = 0; list[y]; y++) {
1519 llen = strlen(list[y]);
1520 tmp = llen > len ? list[y] + len : "";
1521 printf("%-*s", colspace, tmp);
1522 if (m >= columns) {
1523 printf("\n");
1524 m = 1;
1525 } else
1526 m++;
1527 }
1528 printf("\n");
1529}
1530
1531/*
1532 * Given a "list" of words that begin with a common prefix of "word",
1533 * attempt to find an autocompletion to extends "word" by the next
1534 * characters common to all entries in "list".
1535 */
1536static char *
1537complete_ambiguous(const char *word, char **list, size_t count)
1538{
1539 if (word == NULL)
1540 return NULL;
1541
1542 if (count > 0) {
1543 u_int y, matchlen = strlen(list[0]);
1544
1545 /* Find length of common stem */
1546 for (y = 1; list[y]; y++) {
1547 u_int x;
1548
1549 for (x = 0; x < matchlen; x++)
1550 if (list[0][x] != list[y][x])
1551 break;
1552
1553 matchlen = x;
1554 }
1555
1556 if (matchlen > strlen(word)) {
1557 char *tmp = xstrdup(list[0]);
1558
Darren Tucker340d1682010-01-09 08:54:31 +11001559 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001560 return tmp;
1561 }
1562 }
1563
1564 return xstrdup(word);
1565}
1566
1567/* Autocomplete a sftp command */
1568static int
1569complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1570 int terminated)
1571{
1572 u_int y, count = 0, cmdlen, tmplen;
1573 char *tmp, **list, argterm[3];
1574 const LineInfo *lf;
1575
1576 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1577
1578 /* No command specified: display all available commands */
1579 if (cmd == NULL) {
1580 for (y = 0; cmds[y].c; y++)
1581 list[count++] = xstrdup(cmds[y].c);
1582
1583 list[count] = NULL;
1584 complete_display(list, 0);
1585
1586 for (y = 0; list[y] != NULL; y++)
1587 xfree(list[y]);
1588 xfree(list);
1589 return count;
1590 }
1591
1592 /* Prepare subset of commands that start with "cmd" */
1593 cmdlen = strlen(cmd);
1594 for (y = 0; cmds[y].c; y++) {
1595 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
1596 list[count++] = xstrdup(cmds[y].c);
1597 }
1598 list[count] = NULL;
1599
1600 if (count == 0)
1601 return 0;
1602
1603 /* Complete ambigious command */
1604 tmp = complete_ambiguous(cmd, list, count);
1605 if (count > 1)
1606 complete_display(list, 0);
1607
1608 for (y = 0; list[y]; y++)
1609 xfree(list[y]);
1610 xfree(list);
1611
1612 if (tmp != NULL) {
1613 tmplen = strlen(tmp);
1614 cmdlen = strlen(cmd);
1615 /* If cmd may be extended then do so */
1616 if (tmplen > cmdlen)
1617 if (el_insertstr(el, tmp + cmdlen) == -1)
1618 fatal("el_insertstr failed.");
1619 lf = el_line(el);
1620 /* Terminate argument cleanly */
1621 if (count == 1) {
1622 y = 0;
1623 if (!terminated)
1624 argterm[y++] = quote;
1625 if (lastarg || *(lf->cursor) != ' ')
1626 argterm[y++] = ' ';
1627 argterm[y] = '\0';
1628 if (y > 0 && el_insertstr(el, argterm) == -1)
1629 fatal("el_insertstr failed.");
1630 }
1631 xfree(tmp);
1632 }
1633
1634 return count;
1635}
1636
1637/*
1638 * Determine whether a particular sftp command's arguments (if any)
1639 * represent local or remote files.
1640 */
1641static int
1642complete_is_remote(char *cmd) {
1643 int i;
1644
1645 if (cmd == NULL)
1646 return -1;
1647
1648 for (i = 0; cmds[i].c; i++) {
1649 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
1650 return cmds[i].t;
1651 }
1652
1653 return -1;
1654}
1655
1656/* Autocomplete a filename "file" */
1657static int
1658complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1659 char *file, int remote, int lastarg, char quote, int terminated)
1660{
1661 glob_t g;
1662 char *tmp, *tmp2, ins[3];
1663 u_int i, hadglob, pwdlen, len, tmplen, filelen;
1664 const LineInfo *lf;
1665
1666 /* Glob from "file" location */
1667 if (file == NULL)
1668 tmp = xstrdup("*");
1669 else
1670 xasprintf(&tmp, "%s*", file);
1671
1672 memset(&g, 0, sizeof(g));
1673 if (remote != LOCAL) {
1674 tmp = make_absolute(tmp, remote_path);
1675 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1676 } else
1677 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1678
1679 /* Determine length of pwd so we can trim completion display */
1680 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1681 /* Terminate counting on first unescaped glob metacharacter */
1682 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1683 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1684 hadglob = 1;
1685 break;
1686 }
1687 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1688 tmplen++;
1689 if (tmp[tmplen] == '/')
1690 pwdlen = tmplen + 1; /* track last seen '/' */
1691 }
1692 xfree(tmp);
1693
1694 if (g.gl_matchc == 0)
1695 goto out;
1696
1697 if (g.gl_matchc > 1)
1698 complete_display(g.gl_pathv, pwdlen);
1699
1700 tmp = NULL;
1701 /* Don't try to extend globs */
1702 if (file == NULL || hadglob)
1703 goto out;
1704
1705 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
1706 tmp = path_strip(tmp2, remote_path);
1707 xfree(tmp2);
1708
1709 if (tmp == NULL)
1710 goto out;
1711
1712 tmplen = strlen(tmp);
1713 filelen = strlen(file);
1714
1715 if (tmplen > filelen) {
1716 tmp2 = tmp + filelen;
1717 len = strlen(tmp2);
1718 /* quote argument on way out */
1719 for (i = 0; i < len; i++) {
1720 ins[0] = '\\';
1721 ins[1] = tmp2[i];
1722 ins[2] = '\0';
1723 switch (tmp2[i]) {
1724 case '\'':
1725 case '"':
1726 case '\\':
1727 case '\t':
Darren Tuckerd78739a2010-10-24 10:56:32 +11001728 case '[':
Darren Tucker909d8582010-01-08 19:02:40 +11001729 case ' ':
1730 if (quote == '\0' || tmp2[i] == quote) {
1731 if (el_insertstr(el, ins) == -1)
1732 fatal("el_insertstr "
1733 "failed.");
1734 break;
1735 }
1736 /* FALLTHROUGH */
1737 default:
1738 if (el_insertstr(el, ins + 1) == -1)
1739 fatal("el_insertstr failed.");
1740 break;
1741 }
1742 }
1743 }
1744
1745 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11001746 if (g.gl_matchc == 1) {
1747 i = 0;
1748 if (!terminated)
1749 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11001750 if (*(lf->cursor - 1) != '/' &&
1751 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11001752 ins[i++] = ' ';
1753 ins[i] = '\0';
1754 if (i > 0 && el_insertstr(el, ins) == -1)
1755 fatal("el_insertstr failed.");
1756 }
1757 xfree(tmp);
1758
1759 out:
1760 globfree(&g);
1761 return g.gl_matchc;
1762}
1763
1764/* tab-completion hook function, called via libedit */
1765static unsigned char
1766complete(EditLine *el, int ch)
1767{
1768 char **argv, *line, quote;
1769 u_int argc, carg, cursor, len, terminated, ret = CC_ERROR;
1770 const LineInfo *lf;
1771 struct complete_ctx *complete_ctx;
1772
1773 lf = el_line(el);
1774 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1775 fatal("%s: el_get failed", __func__);
1776
1777 /* Figure out which argument the cursor points to */
1778 cursor = lf->cursor - lf->buffer;
1779 line = (char *)xmalloc(cursor + 1);
1780 memcpy(line, lf->buffer, cursor);
1781 line[cursor] = '\0';
1782 argv = makeargv(line, &carg, 1, &quote, &terminated);
1783 xfree(line);
1784
1785 /* Get all the arguments on the line */
1786 len = lf->lastchar - lf->buffer;
1787 line = (char *)xmalloc(len + 1);
1788 memcpy(line, lf->buffer, len);
1789 line[len] = '\0';
1790 argv = makeargv(line, &argc, 1, NULL, NULL);
1791
1792 /* Ensure cursor is at EOL or a argument boundary */
1793 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1794 line[cursor] != '\n') {
1795 xfree(line);
1796 return ret;
1797 }
1798
1799 if (carg == 0) {
1800 /* Show all available commands */
1801 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1802 ret = CC_REDISPLAY;
1803 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1804 /* Handle the command parsing */
1805 if (complete_cmd_parse(el, argv[0], argc == carg,
1806 quote, terminated) != 0)
1807 ret = CC_REDISPLAY;
1808 } else if (carg >= 1) {
1809 /* Handle file parsing */
1810 int remote = complete_is_remote(argv[0]);
1811 char *filematch = NULL;
1812
1813 if (carg > 1 && line[cursor-1] != ' ')
1814 filematch = argv[carg - 1];
1815
1816 if (remote != 0 &&
1817 complete_match(el, complete_ctx->conn,
1818 *complete_ctx->remote_pathp, filematch,
1819 remote, carg == argc, quote, terminated) != 0)
1820 ret = CC_REDISPLAY;
1821 }
1822
1823 xfree(line);
1824 return ret;
1825}
Darren Tuckere67f7db2010-01-08 19:50:02 +11001826#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11001827
Damien Miller20e1fab2004-02-18 14:30:55 +11001828int
Darren Tucker21063192010-01-08 17:10:36 +11001829interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001830{
Darren Tucker909d8582010-01-08 19:02:40 +11001831 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11001832 char *dir = NULL;
1833 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10001834 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11001835 EditLine *el = NULL;
1836#ifdef USE_LIBEDIT
1837 History *hl = NULL;
1838 HistEvent hev;
1839 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11001840 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11001841
1842 if (!batchmode && isatty(STDIN_FILENO)) {
1843 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1844 fatal("Couldn't initialise editline");
1845 if ((hl = history_init()) == NULL)
1846 fatal("Couldn't initialise editline history");
1847 history(hl, &hev, H_SETSIZE, 100);
1848 el_set(el, EL_HIST, history, hl);
1849
1850 el_set(el, EL_PROMPT, prompt);
1851 el_set(el, EL_EDITOR, "emacs");
1852 el_set(el, EL_TERMINAL, NULL);
1853 el_set(el, EL_SIGNAL, 1);
1854 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11001855
1856 /* Tab Completion */
1857 el_set(el, EL_ADDFN, "ftp-complete",
Darren Tuckerd78739a2010-10-24 10:56:32 +11001858 "Context sensitive argument completion", complete);
Darren Tucker909d8582010-01-08 19:02:40 +11001859 complete_ctx.conn = conn;
1860 complete_ctx.remote_pathp = &remote_path;
1861 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
1862 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11001863 }
1864#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001865
Darren Tucker909d8582010-01-08 19:02:40 +11001866 remote_path = do_realpath(conn, ".");
1867 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11001868 fatal("Need cwd");
1869
1870 if (file1 != NULL) {
1871 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11001872 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11001873
1874 if (remote_is_dir(conn, dir) && file2 == NULL) {
1875 printf("Changing to: %s\n", dir);
1876 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001877 if (parse_dispatch_command(conn, cmd,
1878 &remote_path, 1) != 0) {
Darren Tuckercd516ef2004-12-06 22:43:43 +11001879 xfree(dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001880 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001881 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001882 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11001883 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001884 } else {
1885 if (file2 == NULL)
1886 snprintf(cmd, sizeof cmd, "get %s", dir);
1887 else
1888 snprintf(cmd, sizeof cmd, "get %s %s", dir,
1889 file2);
1890
Darren Tucker909d8582010-01-08 19:02:40 +11001891 err = parse_dispatch_command(conn, cmd,
1892 &remote_path, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001893 xfree(dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001894 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001895 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001896 return (err);
1897 }
1898 xfree(dir);
1899 }
1900
Darren Tucker93e7e8f2005-08-23 08:06:55 +10001901#if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF)
Damien Miller20e1fab2004-02-18 14:30:55 +11001902 setvbuf(stdout, NULL, _IOLBF, 0);
1903 setvbuf(infile, NULL, _IOLBF, 0);
1904#else
Damien Miller37294fb2005-07-17 17:18:49 +10001905 setlinebuf(stdout);
1906 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11001907#endif
1908
Damien Miller0e2c1022005-08-12 22:16:22 +10001909 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11001910 err = 0;
1911 for (;;) {
1912 char *cp;
1913
Darren Tuckercdf547a2004-05-24 10:12:19 +10001914 signal(SIGINT, SIG_IGN);
1915
Darren Tucker2d963d82004-11-07 20:04:10 +11001916 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001917 if (interactive)
1918 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11001919 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001920 if (interactive)
1921 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11001922 break;
1923 }
Damien Miller0e2c1022005-08-12 22:16:22 +10001924 if (!interactive) { /* Echo command */
1925 printf("sftp> %s", cmd);
1926 if (strlen(cmd) > 0 &&
1927 cmd[strlen(cmd) - 1] != '\n')
1928 printf("\n");
1929 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001930 } else {
1931#ifdef USE_LIBEDIT
1932 const char *line;
1933 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001934
Darren Tucker909d8582010-01-08 19:02:40 +11001935 if ((line = el_gets(el, &count)) == NULL ||
1936 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001937 printf("\n");
1938 break;
1939 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001940 history(hl, &hev, H_ENTER, line);
1941 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
1942 fprintf(stderr, "Error: input line too long\n");
1943 continue;
1944 }
1945#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001946 }
1947
Damien Miller20e1fab2004-02-18 14:30:55 +11001948 cp = strrchr(cmd, '\n');
1949 if (cp)
1950 *cp = '\0';
1951
Darren Tuckercdf547a2004-05-24 10:12:19 +10001952 /* Handle user interrupts gracefully during commands */
1953 interrupted = 0;
1954 signal(SIGINT, cmd_interrupt);
1955
Darren Tucker909d8582010-01-08 19:02:40 +11001956 err = parse_dispatch_command(conn, cmd, &remote_path,
1957 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11001958 if (err != 0)
1959 break;
1960 }
Darren Tucker909d8582010-01-08 19:02:40 +11001961 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001962 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001963
Tim Rice027e8b12005-08-15 14:52:50 -07001964#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10001965 if (el != NULL)
1966 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07001967#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10001968
Damien Miller20e1fab2004-02-18 14:30:55 +11001969 /* err == 1 signifies normal "quit" exit */
1970 return (err >= 0 ? 0 : -1);
1971}
Damien Miller62d57f62003-01-10 21:43:24 +11001972
Ben Lindstrombba81212001-06-25 05:01:22 +00001973static void
Damien Millercc685c12003-06-04 22:51:38 +10001974connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11001975{
1976 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001977
Damien Miller33804262001-02-04 23:20:18 +11001978#ifdef USE_PIPES
1979 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001980
Damien Miller33804262001-02-04 23:20:18 +11001981 if ((pipe(pin) == -1) || (pipe(pout) == -1))
1982 fatal("pipe: %s", strerror(errno));
1983 *in = pin[0];
1984 *out = pout[1];
1985 c_in = pout[0];
1986 c_out = pin[1];
1987#else /* USE_PIPES */
1988 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001989
Damien Miller33804262001-02-04 23:20:18 +11001990 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
1991 fatal("socketpair: %s", strerror(errno));
1992 *in = *out = inout[0];
1993 c_in = c_out = inout[1];
1994#endif /* USE_PIPES */
1995
Damien Millercc685c12003-06-04 22:51:38 +10001996 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11001997 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10001998 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11001999 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2000 (dup2(c_out, STDOUT_FILENO) == -1)) {
2001 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002002 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002003 }
2004 close(*in);
2005 close(*out);
2006 close(c_in);
2007 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002008
2009 /*
2010 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002011 * ignore SIGINT if we want to gracefully abort commands,
2012 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002013 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2014 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002015 */
2016 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002017 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002018 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002019 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002020 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002021 }
2022
Damien Millercc685c12003-06-04 22:51:38 +10002023 signal(SIGTERM, killchild);
2024 signal(SIGINT, killchild);
2025 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11002026 close(c_in);
2027 close(c_out);
2028}
2029
Ben Lindstrombba81212001-06-25 05:01:22 +00002030static void
Damien Miller33804262001-02-04 23:20:18 +11002031usage(void)
2032{
Damien Miller025e01c2002-02-08 22:06:29 +11002033 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002034
Ben Lindstrom1e243242001-09-18 05:38:44 +00002035 fprintf(stderr,
Darren Tucker1b0dd172009-10-07 08:37:48 +11002036 "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002037 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002038 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002039 " [-o ssh_option] [-P port] [-R num_requests] "
2040 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002041 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002042 " %s [user@]host[:file ...]\n"
2043 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002044 " %s -b batchfile [user@]host\n",
2045 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002046 exit(1);
2047}
2048
Kevin Stevesef4eea92001-02-05 12:42:17 +00002049int
Damien Miller33804262001-02-04 23:20:18 +11002050main(int argc, char **argv)
2051{
Damien Miller956f3fb2003-01-10 21:40:00 +11002052 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002053 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002054 int debug_level = 0, sshver = 2;
2055 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002056 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002057 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002058 LogLevel ll = SYSLOG_LEVEL_INFO;
2059 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002060 extern int optind;
2061 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002062 struct sftp_conn *conn;
2063 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2064 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002065 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002066
Darren Tuckerce321d82005-10-03 18:11:24 +10002067 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2068 sanitise_stdfd();
2069
Damien Miller59d3d5b2003-08-22 09:34:41 +10002070 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002071 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002072 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002073 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002074 addargs(&args, "-oForwardX11 no");
2075 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002076 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002077 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002078
Ben Lindstrom387c4722001-05-08 20:27:25 +00002079 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002080 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002081
Darren Tucker282b4022009-10-07 08:23:06 +11002082 while ((ch = getopt(argc, argv,
Damien Miller65e42f82010-09-24 22:15:11 +10002083 "1246hpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002084 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002085 /* Passed through to ssh(1) */
2086 case '4':
2087 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002088 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002089 addargs(&args, "-%c", ch);
2090 break;
2091 /* Passed through to ssh(1) with argument */
2092 case 'F':
2093 case 'c':
2094 case 'i':
2095 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002096 addargs(&args, "-%c", ch);
2097 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002098 break;
2099 case 'q':
2100 showprogress = 0;
2101 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002102 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002103 case 'P':
2104 addargs(&args, "-oPort %s", optarg);
2105 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002106 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002107 if (debug_level < 3) {
2108 addargs(&args, "-v");
2109 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2110 }
2111 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002112 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002113 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002114 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002115 if (sftp_server == NULL)
2116 sftp_server = _PATH_SFTP_SERVER;
2117 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002118 case '2':
2119 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002120 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002121 case 'B':
2122 copy_buffer_len = strtol(optarg, &cp, 10);
2123 if (copy_buffer_len == 0 || *cp != '\0')
2124 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002125 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002126 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002127 if (batchmode)
2128 fatal("Batch file already specified.");
2129
2130 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002131 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002132 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002133 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002134 showprogress = 0;
Damien Miller44f75c12004-01-21 10:58:47 +11002135 batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002136 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002137 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002138 case 'p':
2139 global_pflag = 1;
2140 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002141 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002142 sftp_direct = optarg;
2143 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002144 case 'l':
2145 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2146 &errstr);
2147 if (errstr != NULL)
2148 usage();
2149 limit_kbps *= 1024; /* kbps */
2150 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002151 case 'r':
2152 global_rflag = 1;
2153 break;
Damien Miller16a13332002-02-13 14:03:56 +11002154 case 'R':
2155 num_requests = strtol(optarg, &cp, 10);
2156 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002157 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002158 optarg);
2159 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002160 case 's':
2161 sftp_server = optarg;
2162 break;
2163 case 'S':
2164 ssh_program = optarg;
2165 replacearg(&args, 0, "%s", ssh_program);
2166 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002167 case 'h':
2168 default:
Damien Miller33804262001-02-04 23:20:18 +11002169 usage();
2170 }
2171 }
2172
Damien Millerc0f27d82004-03-08 23:12:19 +11002173 if (!isatty(STDERR_FILENO))
2174 showprogress = 0;
2175
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002176 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2177
Damien Millerd14ee1e2002-02-05 12:27:31 +11002178 if (sftp_direct == NULL) {
2179 if (optind == argc || argc > (optind + 2))
2180 usage();
Damien Miller33804262001-02-04 23:20:18 +11002181
Damien Millerd14ee1e2002-02-05 12:27:31 +11002182 userhost = xstrdup(argv[optind]);
2183 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002184
Ben Lindstromc276c122002-12-23 02:14:51 +00002185 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002186 host = userhost;
2187 else {
2188 *host++ = '\0';
2189 if (!userhost[0]) {
2190 fprintf(stderr, "Missing username\n");
2191 usage();
2192 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002193 addargs(&args, "-l");
2194 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002195 }
2196
Damien Millerec692032004-01-27 21:22:00 +11002197 if ((cp = colon(host)) != NULL) {
2198 *cp++ = '\0';
2199 file1 = cp;
2200 }
2201
Damien Millerd14ee1e2002-02-05 12:27:31 +11002202 host = cleanhostname(host);
2203 if (!*host) {
2204 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002205 usage();
2206 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002207
Damien Millerd14ee1e2002-02-05 12:27:31 +11002208 addargs(&args, "-oProtocol %d", sshver);
2209
2210 /* no subsystem if the server-spec contains a '/' */
2211 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2212 addargs(&args, "-s");
2213
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002214 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002215 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002216 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002217 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002218
Damien Millercc685c12003-06-04 22:51:38 +10002219 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002220 } else {
2221 args.list = NULL;
2222 addargs(&args, "sftp-server");
2223
Damien Millercc685c12003-06-04 22:51:38 +10002224 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002225 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002226 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002227
Damien Miller65e42f82010-09-24 22:15:11 +10002228 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002229 if (conn == NULL)
2230 fatal("Couldn't initialise connection to server");
2231
2232 if (!batchmode) {
2233 if (sftp_direct == NULL)
2234 fprintf(stderr, "Connected to %s.\n", host);
2235 else
2236 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2237 }
2238
2239 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002240
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002241#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002242 shutdown(in, SHUT_RDWR);
2243 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002244#endif
2245
Damien Miller33804262001-02-04 23:20:18 +11002246 close(in);
2247 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002248 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002249 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002250
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002251 while (waitpid(sshpid, NULL, 0) == -1)
2252 if (errno != EINTR)
2253 fatal("Couldn't wait for ssh process: %s",
2254 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002255
Damien Miller956f3fb2003-01-10 21:40:00 +11002256 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002257}