blob: 1421fcb02b5636e3919dce2044b3b5ae20b7d450 [file] [log] [blame]
Damien Miller38d9a962010-10-07 22:07:11 +11001/* $OpenBSD: sftp.c,v 1.130 2010/10/05 05:13:18 djm Exp $ */
Damien Miller33804262001-02-04 23:20:18 +11002/*
Damien Miller4e60ed72004-02-17 17:07:59 +11003 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
Damien Miller33804262001-02-04 23:20:18 +11004 *
Damien Miller4e60ed72004-02-17 17:07:59 +11005 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
Damien Miller33804262001-02-04 23:20:18 +11008 *
Damien Miller4e60ed72004-02-17 17:07:59 +11009 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Damien Miller33804262001-02-04 23:20:18 +110016 */
17
18#include "includes.h"
19
Damien Miller9cf6d072006-03-15 11:29:24 +110020#include <sys/types.h>
Damien Millerd7834352006-08-05 12:39:39 +100021#include <sys/ioctl.h>
Damien Millerf17883e2006-03-15 11:45:54 +110022#ifdef HAVE_SYS_STAT_H
23# include <sys/stat.h>
24#endif
Damien Miller8dbffe72006-08-05 11:02:17 +100025#include <sys/param.h>
Damien Millere3b60b52006-07-10 21:08:03 +100026#include <sys/socket.h>
Damien Miller9cf6d072006-03-15 11:29:24 +110027#include <sys/wait.h>
Darren Tucker5b2e2ba2008-06-08 09:25:28 +100028#ifdef HAVE_SYS_STATVFS_H
Damien Millerd671e5a2008-05-19 14:53:33 +100029#include <sys/statvfs.h>
Darren Tucker5b2e2ba2008-06-08 09:25:28 +100030#endif
Darren Tucker2d963d82004-11-07 20:04:10 +110031
Damien Miller1cbc2922007-10-26 14:27:45 +100032#include <ctype.h>
Darren Tucker39972492006-07-12 22:22:46 +100033#include <errno.h>
34
Damien Miller03e20032006-03-15 11:16:59 +110035#ifdef HAVE_PATHS_H
Damien Millera9263d02006-03-15 11:18:26 +110036# include <paths.h>
Damien Miller03e20032006-03-15 11:16:59 +110037#endif
Darren Tucker1b0dd172009-10-07 08:37:48 +110038#ifdef HAVE_LIBGEN_H
39#include <libgen.h>
40#endif
Darren Tucker2d963d82004-11-07 20:04:10 +110041#ifdef USE_LIBEDIT
42#include <histedit.h>
43#else
44typedef void EditLine;
45#endif
Damien Miller6ff3cad2006-03-15 11:52:09 +110046#include <signal.h>
Damien Millere7a1e5c2006-08-05 11:34:19 +100047#include <stdlib.h>
Damien Millera7a73ee2006-08-05 11:37:59 +100048#include <stdio.h>
Damien Millere3476ed2006-07-24 14:13:33 +100049#include <string.h>
Damien Millere6b3b612006-07-24 14:01:23 +100050#include <unistd.h>
Damien Millerd7834352006-08-05 12:39:39 +100051#include <stdarg.h>
Damien Miller33804262001-02-04 23:20:18 +110052
Damien Millera7058ec2008-05-20 08:57:06 +100053#ifdef HAVE_UTIL_H
54# include <util.h>
55#endif
56
57#ifdef HAVE_LIBUTIL_H
58# include <libutil.h>
59#endif
60
Damien Miller33804262001-02-04 23:20:18 +110061#include "xmalloc.h"
62#include "log.h"
63#include "pathnames.h"
Ben Lindstrom4529b702001-05-03 23:39:53 +000064#include "misc.h"
Damien Miller33804262001-02-04 23:20:18 +110065
66#include "sftp.h"
Damien Millerd7834352006-08-05 12:39:39 +100067#include "buffer.h"
Damien Miller33804262001-02-04 23:20:18 +110068#include "sftp-common.h"
69#include "sftp-client.h"
Damien Millerd7d46bb2004-02-18 14:11:13 +110070
Darren Tucker21063192010-01-08 17:10:36 +110071#define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */
72#define DEFAULT_NUM_REQUESTS 64 /* # concurrent outstanding requests */
73
Damien Miller20e1fab2004-02-18 14:30:55 +110074/* File to read commands from */
75FILE* infile;
76
77/* Are we in batchfile mode? */
78int batchmode = 0;
79
Damien Miller20e1fab2004-02-18 14:30:55 +110080/* PID of ssh transport process */
81static pid_t sshpid = -1;
82
83/* This is set to 0 if the progressmeter is not desired. */
Damien Millerc0f27d82004-03-08 23:12:19 +110084int showprogress = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +110085
Darren Tucker1b0dd172009-10-07 08:37:48 +110086/* When this option is set, we always recursively download/upload directories */
87int global_rflag = 0;
88
89/* When this option is set, the file transfers will always preserve times */
90int global_pflag = 0;
91
Darren Tuckercdf547a2004-05-24 10:12:19 +100092/* SIGINT received during command processing */
93volatile sig_atomic_t interrupted = 0;
94
Darren Tuckerb9123452004-06-22 13:06:45 +100095/* I wish qsort() took a separate ctx for the comparison function...*/
96int sort_flag;
97
Darren Tucker909d8582010-01-08 19:02:40 +110098/* Context used for commandline completion */
99struct complete_ctx {
100 struct sftp_conn *conn;
101 char **remote_pathp;
102};
103
Damien Miller20e1fab2004-02-18 14:30:55 +1100104int remote_glob(struct sftp_conn *, const char *, int,
105 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
Damien Miller33804262001-02-04 23:20:18 +1100106
Kevin Steves12888d12001-03-05 19:50:57 +0000107extern char *__progname;
Kevin Steves12888d12001-03-05 19:50:57 +0000108
Damien Miller20e1fab2004-02-18 14:30:55 +1100109/* Separators for interactive commands */
110#define WHITESPACE " \t\r\n"
Damien Millerd7686fd2001-02-10 00:40:03 +1100111
Darren Tuckerb9123452004-06-22 13:06:45 +1000112/* ls flags */
Darren Tucker2901e2d2010-01-13 22:44:06 +1100113#define LS_LONG_VIEW 0x0001 /* Full view ala ls -l */
114#define LS_SHORT_VIEW 0x0002 /* Single row view ala ls -1 */
115#define LS_NUMERIC_VIEW 0x0004 /* Long view with numeric uid/gid */
116#define LS_NAME_SORT 0x0008 /* Sort by name (default) */
117#define LS_TIME_SORT 0x0010 /* Sort by mtime */
118#define LS_SIZE_SORT 0x0020 /* Sort by file size */
119#define LS_REVERSE_SORT 0x0040 /* Reverse sort order */
120#define LS_SHOW_ALL 0x0080 /* Don't skip filenames starting with '.' */
121#define LS_SI_UNITS 0x0100 /* Display sizes as K, M, G, etc. */
Darren Tuckerb9123452004-06-22 13:06:45 +1000122
Darren Tucker2901e2d2010-01-13 22:44:06 +1100123#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS)
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000124#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
Damien Miller20e1fab2004-02-18 14:30:55 +1100125
126/* Commands for interactive mode */
127#define I_CHDIR 1
128#define I_CHGRP 2
129#define I_CHMOD 3
130#define I_CHOWN 4
Damien Millerd671e5a2008-05-19 14:53:33 +1000131#define I_DF 24
Damien Miller20e1fab2004-02-18 14:30:55 +1100132#define I_GET 5
133#define I_HELP 6
134#define I_LCHDIR 7
135#define I_LLS 8
136#define I_LMKDIR 9
137#define I_LPWD 10
138#define I_LS 11
139#define I_LUMASK 12
140#define I_MKDIR 13
141#define I_PUT 14
142#define I_PWD 15
143#define I_QUIT 16
144#define I_RENAME 17
145#define I_RM 18
146#define I_RMDIR 19
147#define I_SHELL 20
148#define I_SYMLINK 21
149#define I_VERSION 22
150#define I_PROGRESS 23
151
152struct CMD {
153 const char *c;
154 const int n;
Darren Tucker909d8582010-01-08 19:02:40 +1100155 const int t;
Damien Miller20e1fab2004-02-18 14:30:55 +1100156};
157
Darren Tucker909d8582010-01-08 19:02:40 +1100158/* Type of completion */
159#define NOARGS 0
160#define REMOTE 1
161#define LOCAL 2
162
Damien Miller20e1fab2004-02-18 14:30:55 +1100163static const struct CMD cmds[] = {
Darren Tucker909d8582010-01-08 19:02:40 +1100164 { "bye", I_QUIT, NOARGS },
165 { "cd", I_CHDIR, REMOTE },
166 { "chdir", I_CHDIR, REMOTE },
167 { "chgrp", I_CHGRP, REMOTE },
168 { "chmod", I_CHMOD, REMOTE },
169 { "chown", I_CHOWN, REMOTE },
170 { "df", I_DF, REMOTE },
171 { "dir", I_LS, REMOTE },
172 { "exit", I_QUIT, NOARGS },
173 { "get", I_GET, REMOTE },
174 { "help", I_HELP, NOARGS },
175 { "lcd", I_LCHDIR, LOCAL },
176 { "lchdir", I_LCHDIR, LOCAL },
177 { "lls", I_LLS, LOCAL },
178 { "lmkdir", I_LMKDIR, LOCAL },
179 { "ln", I_SYMLINK, REMOTE },
180 { "lpwd", I_LPWD, LOCAL },
181 { "ls", I_LS, REMOTE },
182 { "lumask", I_LUMASK, NOARGS },
183 { "mkdir", I_MKDIR, REMOTE },
Damien Miller099fc162010-05-10 11:56:50 +1000184 { "mget", I_GET, REMOTE },
185 { "mput", I_PUT, LOCAL },
Darren Tucker909d8582010-01-08 19:02:40 +1100186 { "progress", I_PROGRESS, NOARGS },
187 { "put", I_PUT, LOCAL },
188 { "pwd", I_PWD, REMOTE },
189 { "quit", I_QUIT, NOARGS },
190 { "rename", I_RENAME, REMOTE },
191 { "rm", I_RM, REMOTE },
192 { "rmdir", I_RMDIR, REMOTE },
193 { "symlink", I_SYMLINK, REMOTE },
194 { "version", I_VERSION, NOARGS },
195 { "!", I_SHELL, NOARGS },
196 { "?", I_HELP, NOARGS },
197 { NULL, -1, -1 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100198};
199
Darren Tucker21063192010-01-08 17:10:36 +1100200int interactive_loop(struct sftp_conn *, char *file1, char *file2);
Damien Miller20e1fab2004-02-18 14:30:55 +1100201
Damien Millerb6c85fc2007-01-05 16:30:41 +1100202/* ARGSUSED */
Damien Miller20e1fab2004-02-18 14:30:55 +1100203static void
Darren Tuckercdf547a2004-05-24 10:12:19 +1000204killchild(int signo)
205{
Darren Tuckerba66df82005-01-24 21:57:40 +1100206 if (sshpid > 1) {
Darren Tuckercdf547a2004-05-24 10:12:19 +1000207 kill(sshpid, SIGTERM);
Darren Tuckerba66df82005-01-24 21:57:40 +1100208 waitpid(sshpid, NULL, 0);
209 }
Darren Tuckercdf547a2004-05-24 10:12:19 +1000210
211 _exit(1);
212}
213
Damien Millerb6c85fc2007-01-05 16:30:41 +1100214/* ARGSUSED */
Darren Tuckercdf547a2004-05-24 10:12:19 +1000215static void
216cmd_interrupt(int signo)
217{
218 const char msg[] = "\rInterrupt \n";
Darren Tuckere2f189a2004-12-06 22:45:53 +1100219 int olderrno = errno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000220
221 write(STDERR_FILENO, msg, sizeof(msg) - 1);
222 interrupted = 1;
Darren Tuckere2f189a2004-12-06 22:45:53 +1100223 errno = olderrno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000224}
225
226static void
Damien Miller20e1fab2004-02-18 14:30:55 +1100227help(void)
228{
Damien Miller62fd18a2009-01-28 16:14:09 +1100229 printf("Available commands:\n"
230 "bye Quit sftp\n"
231 "cd path Change remote directory to 'path'\n"
232 "chgrp grp path Change group of file 'path' to 'grp'\n"
233 "chmod mode path Change permissions of file 'path' to 'mode'\n"
234 "chown own path Change owner of file 'path' to 'own'\n"
235 "df [-hi] [path] Display statistics for current directory or\n"
236 " filesystem containing 'path'\n"
237 "exit Quit sftp\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100238 "get [-Ppr] remote [local] Download file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100239 "help Display this help text\n"
240 "lcd path Change local directory to 'path'\n"
241 "lls [ls-options [path]] Display local directory listing\n"
242 "lmkdir path Create local directory\n"
243 "ln oldpath newpath Symlink remote file\n"
244 "lpwd Print local working directory\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100245 "ls [-1afhlnrSt] [path] Display remote directory listing\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100246 "lumask umask Set local umask to 'umask'\n"
247 "mkdir path Create remote directory\n"
248 "progress Toggle display of progress meter\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100249 "put [-Ppr] local [remote] Upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100250 "pwd Display remote working directory\n"
251 "quit Quit sftp\n"
252 "rename oldpath newpath Rename remote file\n"
253 "rm path Delete remote file\n"
254 "rmdir path Remove remote directory\n"
255 "symlink oldpath newpath Symlink remote file\n"
256 "version Show SFTP version\n"
257 "!command Execute 'command' in local shell\n"
258 "! Escape to local shell\n"
259 "? Synonym for help\n");
Damien Miller20e1fab2004-02-18 14:30:55 +1100260}
261
262static void
263local_do_shell(const char *args)
264{
265 int status;
266 char *shell;
267 pid_t pid;
268
269 if (!*args)
270 args = NULL;
271
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':
1728 case ' ':
1729 if (quote == '\0' || tmp2[i] == quote) {
1730 if (el_insertstr(el, ins) == -1)
1731 fatal("el_insertstr "
1732 "failed.");
1733 break;
1734 }
1735 /* FALLTHROUGH */
1736 default:
1737 if (el_insertstr(el, ins + 1) == -1)
1738 fatal("el_insertstr failed.");
1739 break;
1740 }
1741 }
1742 }
1743
1744 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11001745 if (g.gl_matchc == 1) {
1746 i = 0;
1747 if (!terminated)
1748 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11001749 if (*(lf->cursor - 1) != '/' &&
1750 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11001751 ins[i++] = ' ';
1752 ins[i] = '\0';
1753 if (i > 0 && el_insertstr(el, ins) == -1)
1754 fatal("el_insertstr failed.");
1755 }
1756 xfree(tmp);
1757
1758 out:
1759 globfree(&g);
1760 return g.gl_matchc;
1761}
1762
1763/* tab-completion hook function, called via libedit */
1764static unsigned char
1765complete(EditLine *el, int ch)
1766{
1767 char **argv, *line, quote;
1768 u_int argc, carg, cursor, len, terminated, ret = CC_ERROR;
1769 const LineInfo *lf;
1770 struct complete_ctx *complete_ctx;
1771
1772 lf = el_line(el);
1773 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1774 fatal("%s: el_get failed", __func__);
1775
1776 /* Figure out which argument the cursor points to */
1777 cursor = lf->cursor - lf->buffer;
1778 line = (char *)xmalloc(cursor + 1);
1779 memcpy(line, lf->buffer, cursor);
1780 line[cursor] = '\0';
1781 argv = makeargv(line, &carg, 1, &quote, &terminated);
1782 xfree(line);
1783
1784 /* Get all the arguments on the line */
1785 len = lf->lastchar - lf->buffer;
1786 line = (char *)xmalloc(len + 1);
1787 memcpy(line, lf->buffer, len);
1788 line[len] = '\0';
1789 argv = makeargv(line, &argc, 1, NULL, NULL);
1790
1791 /* Ensure cursor is at EOL or a argument boundary */
1792 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1793 line[cursor] != '\n') {
1794 xfree(line);
1795 return ret;
1796 }
1797
1798 if (carg == 0) {
1799 /* Show all available commands */
1800 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1801 ret = CC_REDISPLAY;
1802 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1803 /* Handle the command parsing */
1804 if (complete_cmd_parse(el, argv[0], argc == carg,
1805 quote, terminated) != 0)
1806 ret = CC_REDISPLAY;
1807 } else if (carg >= 1) {
1808 /* Handle file parsing */
1809 int remote = complete_is_remote(argv[0]);
1810 char *filematch = NULL;
1811
1812 if (carg > 1 && line[cursor-1] != ' ')
1813 filematch = argv[carg - 1];
1814
1815 if (remote != 0 &&
1816 complete_match(el, complete_ctx->conn,
1817 *complete_ctx->remote_pathp, filematch,
1818 remote, carg == argc, quote, terminated) != 0)
1819 ret = CC_REDISPLAY;
1820 }
1821
1822 xfree(line);
1823 return ret;
1824}
Darren Tuckere67f7db2010-01-08 19:50:02 +11001825#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11001826
Damien Miller20e1fab2004-02-18 14:30:55 +11001827int
Darren Tucker21063192010-01-08 17:10:36 +11001828interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001829{
Darren Tucker909d8582010-01-08 19:02:40 +11001830 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11001831 char *dir = NULL;
1832 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10001833 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11001834 EditLine *el = NULL;
1835#ifdef USE_LIBEDIT
1836 History *hl = NULL;
1837 HistEvent hev;
1838 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11001839 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11001840
1841 if (!batchmode && isatty(STDIN_FILENO)) {
1842 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1843 fatal("Couldn't initialise editline");
1844 if ((hl = history_init()) == NULL)
1845 fatal("Couldn't initialise editline history");
1846 history(hl, &hev, H_SETSIZE, 100);
1847 el_set(el, EL_HIST, history, hl);
1848
1849 el_set(el, EL_PROMPT, prompt);
1850 el_set(el, EL_EDITOR, "emacs");
1851 el_set(el, EL_TERMINAL, NULL);
1852 el_set(el, EL_SIGNAL, 1);
1853 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11001854
1855 /* Tab Completion */
1856 el_set(el, EL_ADDFN, "ftp-complete",
1857 "Context senstive argument completion", complete);
1858 complete_ctx.conn = conn;
1859 complete_ctx.remote_pathp = &remote_path;
1860 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
1861 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11001862 }
1863#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001864
Darren Tucker909d8582010-01-08 19:02:40 +11001865 remote_path = do_realpath(conn, ".");
1866 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11001867 fatal("Need cwd");
1868
1869 if (file1 != NULL) {
1870 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11001871 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11001872
1873 if (remote_is_dir(conn, dir) && file2 == NULL) {
1874 printf("Changing to: %s\n", dir);
1875 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001876 if (parse_dispatch_command(conn, cmd,
1877 &remote_path, 1) != 0) {
Darren Tuckercd516ef2004-12-06 22:43:43 +11001878 xfree(dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001879 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001880 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001881 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11001882 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001883 } else {
1884 if (file2 == NULL)
1885 snprintf(cmd, sizeof cmd, "get %s", dir);
1886 else
1887 snprintf(cmd, sizeof cmd, "get %s %s", dir,
1888 file2);
1889
Darren Tucker909d8582010-01-08 19:02:40 +11001890 err = parse_dispatch_command(conn, cmd,
1891 &remote_path, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001892 xfree(dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001893 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001894 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001895 return (err);
1896 }
1897 xfree(dir);
1898 }
1899
Darren Tucker93e7e8f2005-08-23 08:06:55 +10001900#if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF)
Damien Miller20e1fab2004-02-18 14:30:55 +11001901 setvbuf(stdout, NULL, _IOLBF, 0);
1902 setvbuf(infile, NULL, _IOLBF, 0);
1903#else
Damien Miller37294fb2005-07-17 17:18:49 +10001904 setlinebuf(stdout);
1905 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11001906#endif
1907
Damien Miller0e2c1022005-08-12 22:16:22 +10001908 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11001909 err = 0;
1910 for (;;) {
1911 char *cp;
1912
Darren Tuckercdf547a2004-05-24 10:12:19 +10001913 signal(SIGINT, SIG_IGN);
1914
Darren Tucker2d963d82004-11-07 20:04:10 +11001915 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001916 if (interactive)
1917 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11001918 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001919 if (interactive)
1920 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11001921 break;
1922 }
Damien Miller0e2c1022005-08-12 22:16:22 +10001923 if (!interactive) { /* Echo command */
1924 printf("sftp> %s", cmd);
1925 if (strlen(cmd) > 0 &&
1926 cmd[strlen(cmd) - 1] != '\n')
1927 printf("\n");
1928 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001929 } else {
1930#ifdef USE_LIBEDIT
1931 const char *line;
1932 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001933
Darren Tucker909d8582010-01-08 19:02:40 +11001934 if ((line = el_gets(el, &count)) == NULL ||
1935 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001936 printf("\n");
1937 break;
1938 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001939 history(hl, &hev, H_ENTER, line);
1940 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
1941 fprintf(stderr, "Error: input line too long\n");
1942 continue;
1943 }
1944#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001945 }
1946
Damien Miller20e1fab2004-02-18 14:30:55 +11001947 cp = strrchr(cmd, '\n');
1948 if (cp)
1949 *cp = '\0';
1950
Darren Tuckercdf547a2004-05-24 10:12:19 +10001951 /* Handle user interrupts gracefully during commands */
1952 interrupted = 0;
1953 signal(SIGINT, cmd_interrupt);
1954
Darren Tucker909d8582010-01-08 19:02:40 +11001955 err = parse_dispatch_command(conn, cmd, &remote_path,
1956 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11001957 if (err != 0)
1958 break;
1959 }
Darren Tucker909d8582010-01-08 19:02:40 +11001960 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001961 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001962
Tim Rice027e8b12005-08-15 14:52:50 -07001963#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10001964 if (el != NULL)
1965 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07001966#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10001967
Damien Miller20e1fab2004-02-18 14:30:55 +11001968 /* err == 1 signifies normal "quit" exit */
1969 return (err >= 0 ? 0 : -1);
1970}
Damien Miller62d57f62003-01-10 21:43:24 +11001971
Ben Lindstrombba81212001-06-25 05:01:22 +00001972static void
Damien Millercc685c12003-06-04 22:51:38 +10001973connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11001974{
1975 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001976
Damien Miller33804262001-02-04 23:20:18 +11001977#ifdef USE_PIPES
1978 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001979
Damien Miller33804262001-02-04 23:20:18 +11001980 if ((pipe(pin) == -1) || (pipe(pout) == -1))
1981 fatal("pipe: %s", strerror(errno));
1982 *in = pin[0];
1983 *out = pout[1];
1984 c_in = pout[0];
1985 c_out = pin[1];
1986#else /* USE_PIPES */
1987 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001988
Damien Miller33804262001-02-04 23:20:18 +11001989 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
1990 fatal("socketpair: %s", strerror(errno));
1991 *in = *out = inout[0];
1992 c_in = c_out = inout[1];
1993#endif /* USE_PIPES */
1994
Damien Millercc685c12003-06-04 22:51:38 +10001995 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11001996 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10001997 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11001998 if ((dup2(c_in, STDIN_FILENO) == -1) ||
1999 (dup2(c_out, STDOUT_FILENO) == -1)) {
2000 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002001 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002002 }
2003 close(*in);
2004 close(*out);
2005 close(c_in);
2006 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002007
2008 /*
2009 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002010 * ignore SIGINT if we want to gracefully abort commands,
2011 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002012 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2013 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002014 */
2015 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002016 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002017 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002018 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002019 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002020 }
2021
Damien Millercc685c12003-06-04 22:51:38 +10002022 signal(SIGTERM, killchild);
2023 signal(SIGINT, killchild);
2024 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11002025 close(c_in);
2026 close(c_out);
2027}
2028
Ben Lindstrombba81212001-06-25 05:01:22 +00002029static void
Damien Miller33804262001-02-04 23:20:18 +11002030usage(void)
2031{
Damien Miller025e01c2002-02-08 22:06:29 +11002032 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002033
Ben Lindstrom1e243242001-09-18 05:38:44 +00002034 fprintf(stderr,
Darren Tucker1b0dd172009-10-07 08:37:48 +11002035 "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002036 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002037 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002038 " [-o ssh_option] [-P port] [-R num_requests] "
2039 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002040 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002041 " %s [user@]host[:file ...]\n"
2042 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002043 " %s -b batchfile [user@]host\n",
2044 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002045 exit(1);
2046}
2047
Kevin Stevesef4eea92001-02-05 12:42:17 +00002048int
Damien Miller33804262001-02-04 23:20:18 +11002049main(int argc, char **argv)
2050{
Damien Miller956f3fb2003-01-10 21:40:00 +11002051 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002052 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002053 int debug_level = 0, sshver = 2;
2054 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002055 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002056 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002057 LogLevel ll = SYSLOG_LEVEL_INFO;
2058 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002059 extern int optind;
2060 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002061 struct sftp_conn *conn;
2062 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2063 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002064 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002065
Darren Tuckerce321d82005-10-03 18:11:24 +10002066 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2067 sanitise_stdfd();
2068
Damien Miller59d3d5b2003-08-22 09:34:41 +10002069 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002070 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002071 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002072 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002073 addargs(&args, "-oForwardX11 no");
2074 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002075 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002076 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002077
Ben Lindstrom387c4722001-05-08 20:27:25 +00002078 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002079 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002080
Darren Tucker282b4022009-10-07 08:23:06 +11002081 while ((ch = getopt(argc, argv,
Damien Miller65e42f82010-09-24 22:15:11 +10002082 "1246hpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002083 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002084 /* Passed through to ssh(1) */
2085 case '4':
2086 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002087 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002088 addargs(&args, "-%c", ch);
2089 break;
2090 /* Passed through to ssh(1) with argument */
2091 case 'F':
2092 case 'c':
2093 case 'i':
2094 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002095 addargs(&args, "-%c", ch);
2096 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002097 break;
2098 case 'q':
2099 showprogress = 0;
2100 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002101 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002102 case 'P':
2103 addargs(&args, "-oPort %s", optarg);
2104 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002105 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002106 if (debug_level < 3) {
2107 addargs(&args, "-v");
2108 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2109 }
2110 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002111 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002112 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002113 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002114 if (sftp_server == NULL)
2115 sftp_server = _PATH_SFTP_SERVER;
2116 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002117 case '2':
2118 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002119 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002120 case 'B':
2121 copy_buffer_len = strtol(optarg, &cp, 10);
2122 if (copy_buffer_len == 0 || *cp != '\0')
2123 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002124 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002125 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002126 if (batchmode)
2127 fatal("Batch file already specified.");
2128
2129 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002130 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002131 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002132 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002133 showprogress = 0;
Damien Miller44f75c12004-01-21 10:58:47 +11002134 batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002135 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002136 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002137 case 'p':
2138 global_pflag = 1;
2139 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002140 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002141 sftp_direct = optarg;
2142 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002143 case 'l':
2144 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2145 &errstr);
2146 if (errstr != NULL)
2147 usage();
2148 limit_kbps *= 1024; /* kbps */
2149 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002150 case 'r':
2151 global_rflag = 1;
2152 break;
Damien Miller16a13332002-02-13 14:03:56 +11002153 case 'R':
2154 num_requests = strtol(optarg, &cp, 10);
2155 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002156 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002157 optarg);
2158 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002159 case 's':
2160 sftp_server = optarg;
2161 break;
2162 case 'S':
2163 ssh_program = optarg;
2164 replacearg(&args, 0, "%s", ssh_program);
2165 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002166 case 'h':
2167 default:
Damien Miller33804262001-02-04 23:20:18 +11002168 usage();
2169 }
2170 }
2171
Damien Millerc0f27d82004-03-08 23:12:19 +11002172 if (!isatty(STDERR_FILENO))
2173 showprogress = 0;
2174
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002175 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2176
Damien Millerd14ee1e2002-02-05 12:27:31 +11002177 if (sftp_direct == NULL) {
2178 if (optind == argc || argc > (optind + 2))
2179 usage();
Damien Miller33804262001-02-04 23:20:18 +11002180
Damien Millerd14ee1e2002-02-05 12:27:31 +11002181 userhost = xstrdup(argv[optind]);
2182 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002183
Ben Lindstromc276c122002-12-23 02:14:51 +00002184 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002185 host = userhost;
2186 else {
2187 *host++ = '\0';
2188 if (!userhost[0]) {
2189 fprintf(stderr, "Missing username\n");
2190 usage();
2191 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002192 addargs(&args, "-l");
2193 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002194 }
2195
Damien Millerec692032004-01-27 21:22:00 +11002196 if ((cp = colon(host)) != NULL) {
2197 *cp++ = '\0';
2198 file1 = cp;
2199 }
2200
Damien Millerd14ee1e2002-02-05 12:27:31 +11002201 host = cleanhostname(host);
2202 if (!*host) {
2203 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002204 usage();
2205 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002206
Damien Millerd14ee1e2002-02-05 12:27:31 +11002207 addargs(&args, "-oProtocol %d", sshver);
2208
2209 /* no subsystem if the server-spec contains a '/' */
2210 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2211 addargs(&args, "-s");
2212
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002213 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002214 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002215 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002216 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002217
Damien Millercc685c12003-06-04 22:51:38 +10002218 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002219 } else {
2220 args.list = NULL;
2221 addargs(&args, "sftp-server");
2222
Damien Millercc685c12003-06-04 22:51:38 +10002223 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002224 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002225 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002226
Damien Miller65e42f82010-09-24 22:15:11 +10002227 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002228 if (conn == NULL)
2229 fatal("Couldn't initialise connection to server");
2230
2231 if (!batchmode) {
2232 if (sftp_direct == NULL)
2233 fprintf(stderr, "Connected to %s.\n", host);
2234 else
2235 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2236 }
2237
2238 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002239
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002240#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002241 shutdown(in, SHUT_RDWR);
2242 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002243#endif
2244
Damien Miller33804262001-02-04 23:20:18 +11002245 close(in);
2246 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002247 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002248 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002249
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002250 while (waitpid(sshpid, NULL, 0) == -1)
2251 if (errno != EINTR)
2252 fatal("Couldn't wait for ssh process: %s",
2253 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002254
Damien Miller956f3fb2003-01-10 21:40:00 +11002255 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002256}