blob: 342ae7efca0f908966f15a27ab245c71a8eda86b [file] [log] [blame]
Damien Millerd6d9fa02013-02-12 11:02:46 +11001/* $OpenBSD: sftp.c,v 1.142 2013/02/08 00:41:12 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
Damien Miller33804262001-02-04 23:20:18 +110057#include "xmalloc.h"
58#include "log.h"
59#include "pathnames.h"
Ben Lindstrom4529b702001-05-03 23:39:53 +000060#include "misc.h"
Damien Miller33804262001-02-04 23:20:18 +110061
62#include "sftp.h"
Damien Millerd7834352006-08-05 12:39:39 +100063#include "buffer.h"
Damien Miller33804262001-02-04 23:20:18 +110064#include "sftp-common.h"
65#include "sftp-client.h"
Damien Millerd7d46bb2004-02-18 14:11:13 +110066
Darren Tucker21063192010-01-08 17:10:36 +110067#define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */
68#define DEFAULT_NUM_REQUESTS 64 /* # concurrent outstanding requests */
69
Damien Miller20e1fab2004-02-18 14:30:55 +110070/* File to read commands from */
71FILE* infile;
72
73/* Are we in batchfile mode? */
74int batchmode = 0;
75
Damien Miller20e1fab2004-02-18 14:30:55 +110076/* PID of ssh transport process */
77static pid_t sshpid = -1;
78
79/* This is set to 0 if the progressmeter is not desired. */
Damien Millerc0f27d82004-03-08 23:12:19 +110080int showprogress = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +110081
Darren Tucker1b0dd172009-10-07 08:37:48 +110082/* When this option is set, we always recursively download/upload directories */
83int global_rflag = 0;
84
85/* When this option is set, the file transfers will always preserve times */
86int global_pflag = 0;
87
Darren Tuckercdf547a2004-05-24 10:12:19 +100088/* SIGINT received during command processing */
89volatile sig_atomic_t interrupted = 0;
90
Darren Tuckerb9123452004-06-22 13:06:45 +100091/* I wish qsort() took a separate ctx for the comparison function...*/
92int sort_flag;
93
Darren Tucker909d8582010-01-08 19:02:40 +110094/* Context used for commandline completion */
95struct complete_ctx {
96 struct sftp_conn *conn;
97 char **remote_pathp;
98};
99
Damien Miller20e1fab2004-02-18 14:30:55 +1100100int remote_glob(struct sftp_conn *, const char *, int,
101 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
Damien Miller33804262001-02-04 23:20:18 +1100102
Kevin Steves12888d12001-03-05 19:50:57 +0000103extern char *__progname;
Kevin Steves12888d12001-03-05 19:50:57 +0000104
Damien Miller20e1fab2004-02-18 14:30:55 +1100105/* Separators for interactive commands */
106#define WHITESPACE " \t\r\n"
Damien Millerd7686fd2001-02-10 00:40:03 +1100107
Darren Tuckerb9123452004-06-22 13:06:45 +1000108/* ls flags */
Darren Tucker2901e2d2010-01-13 22:44:06 +1100109#define LS_LONG_VIEW 0x0001 /* Full view ala ls -l */
110#define LS_SHORT_VIEW 0x0002 /* Single row view ala ls -1 */
111#define LS_NUMERIC_VIEW 0x0004 /* Long view with numeric uid/gid */
112#define LS_NAME_SORT 0x0008 /* Sort by name (default) */
113#define LS_TIME_SORT 0x0010 /* Sort by mtime */
114#define LS_SIZE_SORT 0x0020 /* Sort by file size */
115#define LS_REVERSE_SORT 0x0040 /* Reverse sort order */
116#define LS_SHOW_ALL 0x0080 /* Don't skip filenames starting with '.' */
117#define LS_SI_UNITS 0x0100 /* Display sizes as K, M, G, etc. */
Darren Tuckerb9123452004-06-22 13:06:45 +1000118
Darren Tucker2901e2d2010-01-13 22:44:06 +1100119#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS)
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000120#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
Damien Miller20e1fab2004-02-18 14:30:55 +1100121
122/* Commands for interactive mode */
123#define I_CHDIR 1
124#define I_CHGRP 2
125#define I_CHMOD 3
126#define I_CHOWN 4
Damien Millerd671e5a2008-05-19 14:53:33 +1000127#define I_DF 24
Damien Miller20e1fab2004-02-18 14:30:55 +1100128#define I_GET 5
129#define I_HELP 6
130#define I_LCHDIR 7
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100131#define I_LINK 25
Damien Miller20e1fab2004-02-18 14:30:55 +1100132#define I_LLS 8
133#define I_LMKDIR 9
134#define I_LPWD 10
135#define I_LS 11
136#define I_LUMASK 12
137#define I_MKDIR 13
138#define I_PUT 14
139#define I_PWD 15
140#define I_QUIT 16
141#define I_RENAME 17
142#define I_RM 18
143#define I_RMDIR 19
144#define I_SHELL 20
145#define I_SYMLINK 21
146#define I_VERSION 22
147#define I_PROGRESS 23
148
149struct CMD {
150 const char *c;
151 const int n;
Darren Tucker909d8582010-01-08 19:02:40 +1100152 const int t;
Damien Miller20e1fab2004-02-18 14:30:55 +1100153};
154
Darren Tucker909d8582010-01-08 19:02:40 +1100155/* Type of completion */
156#define NOARGS 0
157#define REMOTE 1
158#define LOCAL 2
159
Damien Miller20e1fab2004-02-18 14:30:55 +1100160static const struct CMD cmds[] = {
Darren Tucker909d8582010-01-08 19:02:40 +1100161 { "bye", I_QUIT, NOARGS },
162 { "cd", I_CHDIR, REMOTE },
163 { "chdir", I_CHDIR, REMOTE },
164 { "chgrp", I_CHGRP, REMOTE },
165 { "chmod", I_CHMOD, REMOTE },
166 { "chown", I_CHOWN, REMOTE },
167 { "df", I_DF, REMOTE },
168 { "dir", I_LS, REMOTE },
169 { "exit", I_QUIT, NOARGS },
170 { "get", I_GET, REMOTE },
171 { "help", I_HELP, NOARGS },
172 { "lcd", I_LCHDIR, LOCAL },
173 { "lchdir", I_LCHDIR, LOCAL },
174 { "lls", I_LLS, LOCAL },
175 { "lmkdir", I_LMKDIR, LOCAL },
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100176 { "ln", I_LINK, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100177 { "lpwd", I_LPWD, LOCAL },
178 { "ls", I_LS, REMOTE },
179 { "lumask", I_LUMASK, NOARGS },
180 { "mkdir", I_MKDIR, REMOTE },
Damien Miller099fc162010-05-10 11:56:50 +1000181 { "mget", I_GET, REMOTE },
182 { "mput", I_PUT, LOCAL },
Darren Tucker909d8582010-01-08 19:02:40 +1100183 { "progress", I_PROGRESS, NOARGS },
184 { "put", I_PUT, LOCAL },
185 { "pwd", I_PWD, REMOTE },
186 { "quit", I_QUIT, NOARGS },
187 { "rename", I_RENAME, REMOTE },
188 { "rm", I_RM, REMOTE },
189 { "rmdir", I_RMDIR, REMOTE },
190 { "symlink", I_SYMLINK, REMOTE },
191 { "version", I_VERSION, NOARGS },
192 { "!", I_SHELL, NOARGS },
193 { "?", I_HELP, NOARGS },
194 { NULL, -1, -1 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100195};
196
Darren Tucker21063192010-01-08 17:10:36 +1100197int interactive_loop(struct sftp_conn *, char *file1, char *file2);
Damien Miller20e1fab2004-02-18 14:30:55 +1100198
Damien Millerb6c85fc2007-01-05 16:30:41 +1100199/* ARGSUSED */
Damien Miller20e1fab2004-02-18 14:30:55 +1100200static void
Darren Tuckercdf547a2004-05-24 10:12:19 +1000201killchild(int signo)
202{
Darren Tuckerba66df82005-01-24 21:57:40 +1100203 if (sshpid > 1) {
Darren Tuckercdf547a2004-05-24 10:12:19 +1000204 kill(sshpid, SIGTERM);
Darren Tuckerba66df82005-01-24 21:57:40 +1100205 waitpid(sshpid, NULL, 0);
206 }
Darren Tuckercdf547a2004-05-24 10:12:19 +1000207
208 _exit(1);
209}
210
Damien Millerb6c85fc2007-01-05 16:30:41 +1100211/* ARGSUSED */
Darren Tuckercdf547a2004-05-24 10:12:19 +1000212static void
213cmd_interrupt(int signo)
214{
215 const char msg[] = "\rInterrupt \n";
Darren Tuckere2f189a2004-12-06 22:45:53 +1100216 int olderrno = errno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000217
218 write(STDERR_FILENO, msg, sizeof(msg) - 1);
219 interrupted = 1;
Darren Tuckere2f189a2004-12-06 22:45:53 +1100220 errno = olderrno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000221}
222
223static void
Damien Miller20e1fab2004-02-18 14:30:55 +1100224help(void)
225{
Damien Miller62fd18a2009-01-28 16:14:09 +1100226 printf("Available commands:\n"
227 "bye Quit sftp\n"
228 "cd path Change remote directory to 'path'\n"
229 "chgrp grp path Change group of file 'path' to 'grp'\n"
230 "chmod mode path Change permissions of file 'path' to 'mode'\n"
231 "chown own path Change owner of file 'path' to 'own'\n"
232 "df [-hi] [path] Display statistics for current directory or\n"
233 " filesystem containing 'path'\n"
234 "exit Quit sftp\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100235 "get [-Ppr] remote [local] Download file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100236 "help Display this help text\n"
237 "lcd path Change local directory to 'path'\n"
238 "lls [ls-options [path]] Display local directory listing\n"
239 "lmkdir path Create local directory\n"
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100240 "ln [-s] oldpath newpath Link remote file (-s for symlink)\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100241 "lpwd Print local working directory\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100242 "ls [-1afhlnrSt] [path] Display remote directory listing\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100243 "lumask umask Set local umask to 'umask'\n"
244 "mkdir path Create remote directory\n"
245 "progress Toggle display of progress meter\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100246 "put [-Ppr] local [remote] Upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100247 "pwd Display remote working directory\n"
248 "quit Quit sftp\n"
249 "rename oldpath newpath Rename remote file\n"
250 "rm path Delete remote file\n"
251 "rmdir path Remove remote directory\n"
252 "symlink oldpath newpath Symlink remote file\n"
253 "version Show SFTP version\n"
254 "!command Execute 'command' in local shell\n"
255 "! Escape to local shell\n"
256 "? Synonym for help\n");
Damien Miller20e1fab2004-02-18 14:30:55 +1100257}
258
259static void
260local_do_shell(const char *args)
261{
262 int status;
263 char *shell;
264 pid_t pid;
265
266 if (!*args)
267 args = NULL;
268
Damien Miller38d9a962010-10-07 22:07:11 +1100269 if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
Damien Miller20e1fab2004-02-18 14:30:55 +1100270 shell = _PATH_BSHELL;
271
272 if ((pid = fork()) == -1)
273 fatal("Couldn't fork: %s", strerror(errno));
274
275 if (pid == 0) {
276 /* XXX: child has pipe fds to ssh subproc open - issue? */
277 if (args) {
278 debug3("Executing %s -c \"%s\"", shell, args);
279 execl(shell, shell, "-c", args, (char *)NULL);
280 } else {
281 debug3("Executing %s", shell);
282 execl(shell, shell, (char *)NULL);
283 }
284 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
285 strerror(errno));
286 _exit(1);
287 }
288 while (waitpid(pid, &status, 0) == -1)
289 if (errno != EINTR)
290 fatal("Couldn't wait for child: %s", strerror(errno));
291 if (!WIFEXITED(status))
Damien Miller55b04f12006-03-26 14:23:17 +1100292 error("Shell exited abnormally");
Damien Miller20e1fab2004-02-18 14:30:55 +1100293 else if (WEXITSTATUS(status))
294 error("Shell exited with status %d", WEXITSTATUS(status));
295}
296
297static void
298local_do_ls(const char *args)
299{
300 if (!args || !*args)
301 local_do_shell(_PATH_LS);
302 else {
303 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
304 char *buf = xmalloc(len);
305
306 /* XXX: quoting - rip quoting code from ftp? */
307 snprintf(buf, len, _PATH_LS " %s", args);
308 local_do_shell(buf);
309 xfree(buf);
310 }
311}
312
313/* Strip one path (usually the pwd) from the start of another */
314static char *
315path_strip(char *path, char *strip)
316{
317 size_t len;
318
319 if (strip == NULL)
320 return (xstrdup(path));
321
322 len = strlen(strip);
Darren Tuckere2f189a2004-12-06 22:45:53 +1100323 if (strncmp(path, strip, len) == 0) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100324 if (strip[len - 1] != '/' && path[len] == '/')
325 len++;
326 return (xstrdup(path + len));
327 }
328
329 return (xstrdup(path));
330}
331
332static char *
Damien Miller20e1fab2004-02-18 14:30:55 +1100333make_absolute(char *p, char *pwd)
334{
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000335 char *abs_str;
Damien Miller20e1fab2004-02-18 14:30:55 +1100336
337 /* Derelativise */
338 if (p && p[0] != '/') {
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000339 abs_str = path_append(pwd, p);
Damien Miller20e1fab2004-02-18 14:30:55 +1100340 xfree(p);
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000341 return(abs_str);
Damien Miller20e1fab2004-02-18 14:30:55 +1100342 } else
343 return(p);
344}
345
346static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100347parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag,
348 int *rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100349{
Damien Millerf184bcf2008-06-29 22:45:13 +1000350 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000351 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100352
Damien Miller1cbc2922007-10-26 14:27:45 +1000353 optind = optreset = 1;
354 opterr = 0;
355
Darren Tucker1b0dd172009-10-07 08:37:48 +1100356 *rflag = *pflag = 0;
357 while ((ch = getopt(argc, argv, "PpRr")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000358 switch (ch) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100359 case 'p':
360 case 'P':
361 *pflag = 1;
362 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100363 case 'r':
364 case 'R':
365 *rflag = 1;
366 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100367 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000368 error("%s: Invalid flag -%c", cmd, optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000369 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100370 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100371 }
372
Damien Miller1cbc2922007-10-26 14:27:45 +1000373 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100374}
375
376static int
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100377parse_link_flags(const char *cmd, char **argv, int argc, int *sflag)
378{
379 extern int opterr, optind, optopt, optreset;
380 int ch;
381
382 optind = optreset = 1;
383 opterr = 0;
384
385 *sflag = 0;
386 while ((ch = getopt(argc, argv, "s")) != -1) {
387 switch (ch) {
388 case 's':
389 *sflag = 1;
390 break;
391 default:
392 error("%s: Invalid flag -%c", cmd, optopt);
393 return -1;
394 }
395 }
396
397 return optind;
398}
399
400static int
Damien Miller1cbc2922007-10-26 14:27:45 +1000401parse_ls_flags(char **argv, int argc, int *lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100402{
Damien Millerf184bcf2008-06-29 22:45:13 +1000403 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000404 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100405
Damien Miller1cbc2922007-10-26 14:27:45 +1000406 optind = optreset = 1;
407 opterr = 0;
408
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000409 *lflag = LS_NAME_SORT;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100410 while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000411 switch (ch) {
412 case '1':
413 *lflag &= ~VIEW_FLAGS;
414 *lflag |= LS_SHORT_VIEW;
415 break;
416 case 'S':
417 *lflag &= ~SORT_FLAGS;
418 *lflag |= LS_SIZE_SORT;
419 break;
420 case 'a':
421 *lflag |= LS_SHOW_ALL;
422 break;
423 case 'f':
424 *lflag &= ~SORT_FLAGS;
425 break;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100426 case 'h':
427 *lflag |= LS_SI_UNITS;
428 break;
Damien Miller1cbc2922007-10-26 14:27:45 +1000429 case 'l':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100430 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000431 *lflag |= LS_LONG_VIEW;
432 break;
433 case 'n':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100434 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000435 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
436 break;
437 case 'r':
438 *lflag |= LS_REVERSE_SORT;
439 break;
440 case 't':
441 *lflag &= ~SORT_FLAGS;
442 *lflag |= LS_TIME_SORT;
443 break;
444 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000445 error("ls: Invalid flag -%c", optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000446 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100447 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100448 }
449
Damien Miller1cbc2922007-10-26 14:27:45 +1000450 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100451}
452
453static int
Damien Millerd671e5a2008-05-19 14:53:33 +1000454parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
455{
Damien Millerf184bcf2008-06-29 22:45:13 +1000456 extern int opterr, optind, optopt, optreset;
Damien Millerd671e5a2008-05-19 14:53:33 +1000457 int ch;
458
459 optind = optreset = 1;
460 opterr = 0;
461
462 *hflag = *iflag = 0;
463 while ((ch = getopt(argc, argv, "hi")) != -1) {
464 switch (ch) {
465 case 'h':
466 *hflag = 1;
467 break;
468 case 'i':
469 *iflag = 1;
470 break;
471 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000472 error("%s: Invalid flag -%c", cmd, optopt);
Damien Millerd671e5a2008-05-19 14:53:33 +1000473 return -1;
474 }
475 }
476
477 return optind;
478}
479
480static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100481is_dir(char *path)
482{
483 struct stat sb;
484
485 /* XXX: report errors? */
486 if (stat(path, &sb) == -1)
487 return(0);
488
Darren Tucker1e80e402006-09-21 12:59:33 +1000489 return(S_ISDIR(sb.st_mode));
Damien Miller20e1fab2004-02-18 14:30:55 +1100490}
491
492static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100493remote_is_dir(struct sftp_conn *conn, char *path)
494{
495 Attrib *a;
496
497 /* XXX: report errors? */
498 if ((a = do_stat(conn, path, 1)) == NULL)
499 return(0);
500 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
501 return(0);
Darren Tucker1e80e402006-09-21 12:59:33 +1000502 return(S_ISDIR(a->perm));
Damien Miller20e1fab2004-02-18 14:30:55 +1100503}
504
Darren Tucker1b0dd172009-10-07 08:37:48 +1100505/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
Damien Miller20e1fab2004-02-18 14:30:55 +1100506static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100507pathname_is_dir(char *pathname)
508{
509 size_t l = strlen(pathname);
510
511 return l > 0 && pathname[l - 1] == '/';
512}
513
514static int
515process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
516 int pflag, int rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100517{
518 char *abs_src = NULL;
519 char *abs_dst = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100520 glob_t g;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100521 char *filename, *tmp=NULL;
522 int i, err = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100523
524 abs_src = xstrdup(src);
525 abs_src = make_absolute(abs_src, pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +1100526 memset(&g, 0, sizeof(g));
Darren Tucker1b0dd172009-10-07 08:37:48 +1100527
Damien Miller20e1fab2004-02-18 14:30:55 +1100528 debug3("Looking up %s", abs_src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100529 if (remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100530 error("File \"%s\" not found.", abs_src);
531 err = -1;
532 goto out;
533 }
534
Darren Tucker1b0dd172009-10-07 08:37:48 +1100535 /*
536 * If multiple matches then dst must be a directory or
537 * unspecified.
538 */
539 if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
540 error("Multiple source paths, but destination "
541 "\"%s\" is not a directory", dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100542 err = -1;
543 goto out;
544 }
545
Darren Tuckercdf547a2004-05-24 10:12:19 +1000546 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100547 tmp = xstrdup(g.gl_pathv[i]);
548 if ((filename = basename(tmp)) == NULL) {
549 error("basename %s: %s", tmp, strerror(errno));
550 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100551 err = -1;
552 goto out;
553 }
554
555 if (g.gl_matchc == 1 && dst) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100556 if (is_dir(dst)) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100557 abs_dst = path_append(dst, filename);
558 } else {
Damien Miller20e1fab2004-02-18 14:30:55 +1100559 abs_dst = xstrdup(dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100560 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100561 } else if (dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100562 abs_dst = path_append(dst, filename);
563 } else {
564 abs_dst = xstrdup(filename);
565 }
566 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100567
568 printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100569 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
570 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
571 pflag || global_pflag, 1) == -1)
572 err = -1;
573 } else {
574 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
575 pflag || global_pflag) == -1)
576 err = -1;
577 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100578 xfree(abs_dst);
579 abs_dst = NULL;
580 }
581
582out:
583 xfree(abs_src);
Damien Miller20e1fab2004-02-18 14:30:55 +1100584 globfree(&g);
585 return(err);
586}
587
588static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100589process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
590 int pflag, int rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100591{
592 char *tmp_dst = NULL;
593 char *abs_dst = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100594 char *tmp = NULL, *filename = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100595 glob_t g;
596 int err = 0;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100597 int i, dst_is_dir = 1;
Damien Milleraec5cf82008-02-10 22:26:24 +1100598 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100599
600 if (dst) {
601 tmp_dst = xstrdup(dst);
602 tmp_dst = make_absolute(tmp_dst, pwd);
603 }
604
605 memset(&g, 0, sizeof(g));
606 debug3("Looking up %s", src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100607 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100608 error("File \"%s\" not found.", src);
609 err = -1;
610 goto out;
611 }
612
Darren Tucker1b0dd172009-10-07 08:37:48 +1100613 /* If we aren't fetching to pwd then stash this status for later */
614 if (tmp_dst != NULL)
615 dst_is_dir = remote_is_dir(conn, tmp_dst);
616
Damien Miller20e1fab2004-02-18 14:30:55 +1100617 /* If multiple matches, dst may be directory or unspecified */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100618 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
619 error("Multiple paths match, but destination "
620 "\"%s\" is not a directory", tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100621 err = -1;
622 goto out;
623 }
624
Darren Tuckercdf547a2004-05-24 10:12:19 +1000625 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Milleraec5cf82008-02-10 22:26:24 +1100626 if (stat(g.gl_pathv[i], &sb) == -1) {
627 err = -1;
628 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
629 continue;
630 }
Darren Tucker1b0dd172009-10-07 08:37:48 +1100631
632 tmp = xstrdup(g.gl_pathv[i]);
633 if ((filename = basename(tmp)) == NULL) {
634 error("basename %s: %s", tmp, strerror(errno));
635 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100636 err = -1;
637 goto out;
638 }
639
640 if (g.gl_matchc == 1 && tmp_dst) {
641 /* If directory specified, append filename */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100642 if (dst_is_dir)
643 abs_dst = path_append(tmp_dst, filename);
644 else
Damien Miller20e1fab2004-02-18 14:30:55 +1100645 abs_dst = xstrdup(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100646 } else if (tmp_dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100647 abs_dst = path_append(tmp_dst, filename);
648 } else {
649 abs_dst = make_absolute(xstrdup(filename), pwd);
650 }
651 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100652
653 printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100654 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
655 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
656 pflag || global_pflag, 1) == -1)
657 err = -1;
658 } else {
659 if (do_upload(conn, g.gl_pathv[i], abs_dst,
660 pflag || global_pflag) == -1)
661 err = -1;
662 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100663 }
664
665out:
666 if (abs_dst)
667 xfree(abs_dst);
668 if (tmp_dst)
669 xfree(tmp_dst);
670 globfree(&g);
671 return(err);
672}
673
674static int
675sdirent_comp(const void *aa, const void *bb)
676{
677 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
678 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000679 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100680
Darren Tuckerb9123452004-06-22 13:06:45 +1000681#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000682 if (sort_flag & LS_NAME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000683 return (rmul * strcmp(a->filename, b->filename));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000684 else if (sort_flag & LS_TIME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000685 return (rmul * NCMP(a->a.mtime, b->a.mtime));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000686 else if (sort_flag & LS_SIZE_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000687 return (rmul * NCMP(a->a.size, b->a.size));
688
689 fatal("Unknown ls sort type");
Damien Miller20e1fab2004-02-18 14:30:55 +1100690}
691
692/* sftp ls.1 replacement for directories */
693static int
694do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
695{
Damien Millereccb9de2005-06-17 12:59:34 +1000696 int n;
697 u_int c = 1, colspace = 0, columns = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100698 SFTP_DIRENT **d;
699
700 if ((n = do_readdir(conn, path, &d)) != 0)
701 return (n);
702
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000703 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000704 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100705 struct winsize ws;
706 char *tmp;
707
708 /* Count entries for sort and find longest filename */
Darren Tucker9a526452004-06-22 13:09:55 +1000709 for (n = 0; d[n] != NULL; n++) {
710 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
711 m = MAX(m, strlen(d[n]->filename));
712 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100713
714 /* Add any subpath that also needs to be counted */
715 tmp = path_strip(path, strip_path);
716 m += strlen(tmp);
717 xfree(tmp);
718
719 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
720 width = ws.ws_col;
721
722 columns = width / (m + 2);
723 columns = MAX(columns, 1);
724 colspace = width / columns;
725 colspace = MIN(colspace, width);
726 }
727
Darren Tuckerb9123452004-06-22 13:06:45 +1000728 if (lflag & SORT_FLAGS) {
Damien Miller653b93b2005-11-05 15:15:23 +1100729 for (n = 0; d[n] != NULL; n++)
730 ; /* count entries */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000731 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
Darren Tuckerb9123452004-06-22 13:06:45 +1000732 qsort(d, n, sizeof(*d), sdirent_comp);
733 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100734
Darren Tuckercdf547a2004-05-24 10:12:19 +1000735 for (n = 0; d[n] != NULL && !interrupted; n++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100736 char *tmp, *fname;
737
Darren Tucker9a526452004-06-22 13:09:55 +1000738 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
739 continue;
740
Damien Miller20e1fab2004-02-18 14:30:55 +1100741 tmp = path_append(path, d[n]->filename);
742 fname = path_strip(tmp, strip_path);
743 xfree(tmp);
744
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000745 if (lflag & LS_LONG_VIEW) {
Darren Tucker2901e2d2010-01-13 22:44:06 +1100746 if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000747 char *lname;
748 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100749
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000750 memset(&sb, 0, sizeof(sb));
751 attrib_to_stat(&d[n]->a, &sb);
Darren Tucker2901e2d2010-01-13 22:44:06 +1100752 lname = ls_file(fname, &sb, 1,
753 (lflag & LS_SI_UNITS));
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000754 printf("%s\n", lname);
755 xfree(lname);
756 } else
757 printf("%s\n", d[n]->longname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100758 } else {
759 printf("%-*s", colspace, fname);
760 if (c >= columns) {
761 printf("\n");
762 c = 1;
763 } else
764 c++;
765 }
766
767 xfree(fname);
768 }
769
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000770 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100771 printf("\n");
772
773 free_sftp_dirents(d);
774 return (0);
775}
776
777/* sftp ls.1 replacement which handles path globs */
778static int
779do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
780 int lflag)
781{
Damien Millera6e121a2010-10-07 21:39:17 +1100782 char *fname, *lname;
Damien Miller68e2e562010-10-07 21:39:55 +1100783 glob_t g;
784 int err;
785 struct winsize ws;
786 u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100787
788 memset(&g, 0, sizeof(g));
789
Damien Millera6e121a2010-10-07 21:39:17 +1100790 if (remote_glob(conn, path,
Damien Millerd7be70d2011-09-22 21:43:06 +1000791 GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
792 NULL, &g) ||
Damien Millera6e121a2010-10-07 21:39:17 +1100793 (g.gl_pathc && !g.gl_matchc)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100794 if (g.gl_pathc)
795 globfree(&g);
Damien Miller20e1fab2004-02-18 14:30:55 +1100796 error("Can't ls: \"%s\" not found", path);
Damien Millera6e121a2010-10-07 21:39:17 +1100797 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100798 }
799
Darren Tuckercdf547a2004-05-24 10:12:19 +1000800 if (interrupted)
801 goto out;
802
Damien Miller20e1fab2004-02-18 14:30:55 +1100803 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100804 * If the glob returns a single match and it is a directory,
805 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100806 */
Damien Millera6e121a2010-10-07 21:39:17 +1100807 if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
808 S_ISDIR(g.gl_statv[0]->st_mode)) {
809 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
810 globfree(&g);
811 return err;
Damien Miller20e1fab2004-02-18 14:30:55 +1100812 }
813
Damien Miller68e2e562010-10-07 21:39:55 +1100814 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
815 width = ws.ws_col;
Damien Miller20e1fab2004-02-18 14:30:55 +1100816
Damien Miller68e2e562010-10-07 21:39:55 +1100817 if (!(lflag & LS_SHORT_VIEW)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100818 /* Count entries for sort and find longest filename */
819 for (i = 0; g.gl_pathv[i]; i++)
820 m = MAX(m, strlen(g.gl_pathv[i]));
821
Damien Miller20e1fab2004-02-18 14:30:55 +1100822 columns = width / (m + 2);
823 columns = MAX(columns, 1);
824 colspace = width / columns;
825 }
826
Damien Millerea858292012-06-30 08:33:32 +1000827 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100828 fname = path_strip(g.gl_pathv[i], strip_path);
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000829 if (lflag & LS_LONG_VIEW) {
Damien Millera6e121a2010-10-07 21:39:17 +1100830 if (g.gl_statv[i] == NULL) {
831 error("no stat information for %s", fname);
832 continue;
833 }
834 lname = ls_file(fname, g.gl_statv[i], 1,
835 (lflag & LS_SI_UNITS));
Damien Miller20e1fab2004-02-18 14:30:55 +1100836 printf("%s\n", lname);
837 xfree(lname);
838 } else {
839 printf("%-*s", colspace, fname);
840 if (c >= columns) {
841 printf("\n");
842 c = 1;
843 } else
844 c++;
845 }
846 xfree(fname);
847 }
848
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000849 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100850 printf("\n");
851
Darren Tuckercdf547a2004-05-24 10:12:19 +1000852 out:
Damien Miller20e1fab2004-02-18 14:30:55 +1100853 if (g.gl_pathc)
854 globfree(&g);
855
Damien Millera6e121a2010-10-07 21:39:17 +1100856 return 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100857}
858
Damien Millerd671e5a2008-05-19 14:53:33 +1000859static int
860do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
861{
Darren Tucker7b598892008-06-09 22:49:36 +1000862 struct sftp_statvfs st;
Damien Millerd671e5a2008-05-19 14:53:33 +1000863 char s_used[FMT_SCALED_STRSIZE];
864 char s_avail[FMT_SCALED_STRSIZE];
865 char s_root[FMT_SCALED_STRSIZE];
866 char s_total[FMT_SCALED_STRSIZE];
Darren Tuckerb5082e92010-01-08 18:51:47 +1100867 unsigned long long ffree;
Damien Millerd671e5a2008-05-19 14:53:33 +1000868
869 if (do_statvfs(conn, path, &st, 1) == -1)
870 return -1;
871 if (iflag) {
Darren Tuckerb5082e92010-01-08 18:51:47 +1100872 ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
Damien Millerd671e5a2008-05-19 14:53:33 +1000873 printf(" Inodes Used Avail "
874 "(root) %%Capacity\n");
875 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
876 (unsigned long long)st.f_files,
877 (unsigned long long)(st.f_files - st.f_ffree),
878 (unsigned long long)st.f_favail,
Darren Tuckerb5082e92010-01-08 18:51:47 +1100879 (unsigned long long)st.f_ffree, ffree);
Damien Millerd671e5a2008-05-19 14:53:33 +1000880 } else if (hflag) {
881 strlcpy(s_used, "error", sizeof(s_used));
882 strlcpy(s_avail, "error", sizeof(s_avail));
883 strlcpy(s_root, "error", sizeof(s_root));
884 strlcpy(s_total, "error", sizeof(s_total));
885 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
886 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
887 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
888 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
889 printf(" Size Used Avail (root) %%Capacity\n");
890 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
891 s_total, s_used, s_avail, s_root,
892 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
893 st.f_blocks));
894 } else {
895 printf(" Size Used Avail "
896 "(root) %%Capacity\n");
897 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
898 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
899 (unsigned long long)(st.f_frsize *
900 (st.f_blocks - st.f_bfree) / 1024),
901 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
902 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
903 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
904 st.f_blocks));
905 }
906 return 0;
907}
908
Damien Miller1cbc2922007-10-26 14:27:45 +1000909/*
910 * Undo escaping of glob sequences in place. Used to undo extra escaping
911 * applied in makeargv() when the string is destined for a function that
912 * does not glob it.
913 */
914static void
915undo_glob_escape(char *s)
916{
917 size_t i, j;
918
919 for (i = j = 0;;) {
920 if (s[i] == '\0') {
921 s[j] = '\0';
922 return;
923 }
924 if (s[i] != '\\') {
925 s[j++] = s[i++];
926 continue;
927 }
928 /* s[i] == '\\' */
929 ++i;
930 switch (s[i]) {
931 case '?':
932 case '[':
933 case '*':
934 case '\\':
935 s[j++] = s[i++];
936 break;
937 case '\0':
938 s[j++] = '\\';
939 s[j] = '\0';
940 return;
941 default:
942 s[j++] = '\\';
943 s[j++] = s[i++];
944 break;
945 }
946 }
947}
948
949/*
950 * Split a string into an argument vector using sh(1)-style quoting,
951 * comment and escaping rules, but with some tweaks to handle glob(3)
952 * wildcards.
Darren Tucker909d8582010-01-08 19:02:40 +1100953 * The "sloppy" flag allows for recovery from missing terminating quote, for
954 * use in parsing incomplete commandlines during tab autocompletion.
955 *
Damien Miller1cbc2922007-10-26 14:27:45 +1000956 * Returns NULL on error or a NULL-terminated array of arguments.
Darren Tucker909d8582010-01-08 19:02:40 +1100957 *
958 * If "lastquote" is not NULL, the quoting character used for the last
959 * argument is placed in *lastquote ("\0", "'" or "\"").
960 *
961 * If "terminated" is not NULL, *terminated will be set to 1 when the
962 * last argument's quote has been properly terminated or 0 otherwise.
963 * This parameter is only of use if "sloppy" is set.
Damien Miller1cbc2922007-10-26 14:27:45 +1000964 */
965#define MAXARGS 128
966#define MAXARGLEN 8192
967static char **
Darren Tucker909d8582010-01-08 19:02:40 +1100968makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
969 u_int *terminated)
Damien Miller1cbc2922007-10-26 14:27:45 +1000970{
971 int argc, quot;
972 size_t i, j;
973 static char argvs[MAXARGLEN];
974 static char *argv[MAXARGS + 1];
975 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
976
977 *argcp = argc = 0;
978 if (strlen(arg) > sizeof(argvs) - 1) {
979 args_too_longs:
980 error("string too long");
981 return NULL;
982 }
Darren Tucker909d8582010-01-08 19:02:40 +1100983 if (terminated != NULL)
984 *terminated = 1;
985 if (lastquote != NULL)
986 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +1000987 state = MA_START;
988 i = j = 0;
989 for (;;) {
Damien Miller07daed52012-10-31 08:57:55 +1100990 if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){
Darren Tucker063018d2012-10-05 10:43:58 +1000991 error("Too many arguments.");
992 return NULL;
993 }
Damien Miller1cbc2922007-10-26 14:27:45 +1000994 if (isspace(arg[i])) {
995 if (state == MA_UNQUOTED) {
996 /* Terminate current argument */
997 argvs[j++] = '\0';
998 argc++;
999 state = MA_START;
1000 } else if (state != MA_START)
1001 argvs[j++] = arg[i];
1002 } else if (arg[i] == '"' || arg[i] == '\'') {
1003 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1004 if (state == MA_START) {
1005 argv[argc] = argvs + j;
1006 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +11001007 if (lastquote != NULL)
1008 *lastquote = arg[i];
Damien Miller1cbc2922007-10-26 14:27:45 +10001009 } else if (state == MA_UNQUOTED)
1010 state = q;
1011 else if (state == q)
1012 state = MA_UNQUOTED;
1013 else
1014 argvs[j++] = arg[i];
1015 } else if (arg[i] == '\\') {
1016 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1017 quot = state == MA_SQUOTE ? '\'' : '"';
1018 /* Unescape quote we are in */
1019 /* XXX support \n and friends? */
1020 if (arg[i + 1] == quot) {
1021 i++;
1022 argvs[j++] = arg[i];
1023 } else if (arg[i + 1] == '?' ||
1024 arg[i + 1] == '[' || arg[i + 1] == '*') {
1025 /*
1026 * Special case for sftp: append
1027 * double-escaped glob sequence -
1028 * glob will undo one level of
1029 * escaping. NB. string can grow here.
1030 */
1031 if (j >= sizeof(argvs) - 5)
1032 goto args_too_longs;
1033 argvs[j++] = '\\';
1034 argvs[j++] = arg[i++];
1035 argvs[j++] = '\\';
1036 argvs[j++] = arg[i];
1037 } else {
1038 argvs[j++] = arg[i++];
1039 argvs[j++] = arg[i];
1040 }
1041 } else {
1042 if (state == MA_START) {
1043 argv[argc] = argvs + j;
1044 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001045 if (lastquote != NULL)
1046 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001047 }
1048 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1049 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1050 /*
1051 * Special case for sftp: append
1052 * escaped glob sequence -
1053 * glob will undo one level of
1054 * escaping.
1055 */
1056 argvs[j++] = arg[i++];
1057 argvs[j++] = arg[i];
1058 } else {
1059 /* Unescape everything */
1060 /* XXX support \n and friends? */
1061 i++;
1062 argvs[j++] = arg[i];
1063 }
1064 }
1065 } else if (arg[i] == '#') {
1066 if (state == MA_SQUOTE || state == MA_DQUOTE)
1067 argvs[j++] = arg[i];
1068 else
1069 goto string_done;
1070 } else if (arg[i] == '\0') {
1071 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001072 if (sloppy) {
1073 state = MA_UNQUOTED;
1074 if (terminated != NULL)
1075 *terminated = 0;
1076 goto string_done;
1077 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001078 error("Unterminated quoted argument");
1079 return NULL;
1080 }
1081 string_done:
1082 if (state == MA_UNQUOTED) {
1083 argvs[j++] = '\0';
1084 argc++;
1085 }
1086 break;
1087 } else {
1088 if (state == MA_START) {
1089 argv[argc] = argvs + j;
1090 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001091 if (lastquote != NULL)
1092 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001093 }
1094 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1095 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1096 /*
1097 * Special case for sftp: escape quoted
1098 * glob(3) wildcards. NB. string can grow
1099 * here.
1100 */
1101 if (j >= sizeof(argvs) - 3)
1102 goto args_too_longs;
1103 argvs[j++] = '\\';
1104 argvs[j++] = arg[i];
1105 } else
1106 argvs[j++] = arg[i];
1107 }
1108 i++;
1109 }
1110 *argcp = argc;
1111 return argv;
1112}
1113
Damien Miller20e1fab2004-02-18 14:30:55 +11001114static int
Darren Tucker909d8582010-01-08 19:02:40 +11001115parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag,
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001116 int *hflag, int *sflag, unsigned long *n_arg, char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001117{
1118 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001119 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001120 int base = 0;
1121 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001122 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001123
1124 /* Skip leading whitespace */
1125 cp = cp + strspn(cp, WHITESPACE);
1126
Damien Miller20e1fab2004-02-18 14:30:55 +11001127 /* Check for leading '-' (disable error processing) */
1128 *iflag = 0;
1129 if (*cp == '-') {
1130 *iflag = 1;
1131 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001132 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001133 }
1134
Darren Tucker70cc0922010-01-09 22:28:03 +11001135 /* Ignore blank lines and lines which begin with comment '#' char */
1136 if (*cp == '\0' || *cp == '#')
1137 return (0);
1138
Darren Tucker909d8582010-01-08 19:02:40 +11001139 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001140 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001141
Damien Miller1cbc2922007-10-26 14:27:45 +10001142 /* Figure out which command we have */
1143 for (i = 0; cmds[i].c != NULL; i++) {
Damien Millerd6d9fa02013-02-12 11:02:46 +11001144 if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001145 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001146 }
1147 cmdnum = cmds[i].n;
1148 cmd = cmds[i].c;
1149
1150 /* Special case */
1151 if (*cp == '!') {
1152 cp++;
1153 cmdnum = I_SHELL;
1154 } else if (cmdnum == -1) {
1155 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001156 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001157 }
1158
1159 /* Get arguments and parse flags */
Darren Tucker1b0dd172009-10-07 08:37:48 +11001160 *lflag = *pflag = *rflag = *hflag = *n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001161 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001162 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001163 switch (cmdnum) {
1164 case I_GET:
1165 case I_PUT:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001166 if ((optidx = parse_getput_flags(cmd, argv, argc,
1167 pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001168 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001169 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001170 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001171 error("You must specify at least one path after a "
1172 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001173 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001174 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001175 *path1 = xstrdup(argv[optidx]);
1176 /* Get second pathname (optional) */
1177 if (argc - optidx > 1) {
1178 *path2 = xstrdup(argv[optidx + 1]);
1179 /* Destination is not globbed */
1180 undo_glob_escape(*path2);
1181 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001182 break;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001183 case I_LINK:
1184 if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1185 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001186 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001187 case I_RENAME:
Damien Miller1cbc2922007-10-26 14:27:45 +10001188 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001189 error("You must specify two paths after a %s "
1190 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001191 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001192 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001193 *path1 = xstrdup(argv[optidx]);
1194 *path2 = xstrdup(argv[optidx + 1]);
1195 /* Paths are not globbed */
1196 undo_glob_escape(*path1);
1197 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001198 break;
1199 case I_RM:
1200 case I_MKDIR:
1201 case I_RMDIR:
1202 case I_CHDIR:
1203 case I_LCHDIR:
1204 case I_LMKDIR:
1205 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001206 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001207 error("You must specify a path after a %s command.",
1208 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001209 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001210 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001211 *path1 = xstrdup(argv[optidx]);
1212 /* Only "rm" globs */
1213 if (cmdnum != I_RM)
1214 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001215 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001216 case I_DF:
1217 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1218 iflag)) == -1)
1219 return -1;
1220 /* Default to current directory if no path specified */
1221 if (argc - optidx < 1)
1222 *path1 = NULL;
1223 else {
1224 *path1 = xstrdup(argv[optidx]);
1225 undo_glob_escape(*path1);
1226 }
1227 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001228 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001229 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001230 return(-1);
1231 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001232 if (argc - optidx > 0)
1233 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001234 break;
1235 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001236 /* Skip ls command and following whitespace */
1237 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001238 case I_SHELL:
1239 /* Uses the rest of the line */
1240 break;
1241 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001242 case I_CHMOD:
1243 base = 8;
1244 case I_CHOWN:
1245 case I_CHGRP:
1246 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001247 if (argc - optidx < 1)
1248 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001249 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001250 l = strtol(argv[optidx], &cp2, base);
1251 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1252 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1253 l < 0) {
1254 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001255 error("You must supply a numeric argument "
1256 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001257 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001258 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001259 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001260 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001261 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001262 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001263 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001264 error("You must specify a path after a %s command.",
1265 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001266 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001267 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001268 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001269 break;
1270 case I_QUIT:
1271 case I_PWD:
1272 case I_LPWD:
1273 case I_HELP:
1274 case I_VERSION:
1275 case I_PROGRESS:
1276 break;
1277 default:
1278 fatal("Command not implemented");
1279 }
1280
1281 *cpp = cp;
1282 return(cmdnum);
1283}
1284
1285static int
1286parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1287 int err_abort)
1288{
1289 char *path1, *path2, *tmp;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001290 int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, sflag = 0;
1291 int cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001292 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001293 Attrib a, *aa;
1294 char path_buf[MAXPATHLEN];
1295 int err = 0;
1296 glob_t g;
1297
1298 path1 = path2 = NULL;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001299 cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag,
1300 &sflag, &n_arg, &path1, &path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001301
1302 if (iflag != 0)
1303 err_abort = 0;
1304
1305 memset(&g, 0, sizeof(g));
1306
1307 /* Perform command */
1308 switch (cmdnum) {
1309 case 0:
1310 /* Blank line */
1311 break;
1312 case -1:
1313 /* Unrecognized command */
1314 err = -1;
1315 break;
1316 case I_GET:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001317 err = process_get(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001318 break;
1319 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001320 err = process_put(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001321 break;
1322 case I_RENAME:
1323 path1 = make_absolute(path1, *pwd);
1324 path2 = make_absolute(path2, *pwd);
1325 err = do_rename(conn, path1, path2);
1326 break;
1327 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001328 sflag = 1;
1329 case I_LINK:
1330 path1 = make_absolute(path1, *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001331 path2 = make_absolute(path2, *pwd);
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001332 err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001333 break;
1334 case I_RM:
1335 path1 = make_absolute(path1, *pwd);
1336 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001337 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001338 printf("Removing %s\n", g.gl_pathv[i]);
1339 err = do_rm(conn, g.gl_pathv[i]);
1340 if (err != 0 && err_abort)
1341 break;
1342 }
1343 break;
1344 case I_MKDIR:
1345 path1 = make_absolute(path1, *pwd);
1346 attrib_clear(&a);
1347 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1348 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001349 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001350 break;
1351 case I_RMDIR:
1352 path1 = make_absolute(path1, *pwd);
1353 err = do_rmdir(conn, path1);
1354 break;
1355 case I_CHDIR:
1356 path1 = make_absolute(path1, *pwd);
1357 if ((tmp = do_realpath(conn, path1)) == NULL) {
1358 err = 1;
1359 break;
1360 }
1361 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1362 xfree(tmp);
1363 err = 1;
1364 break;
1365 }
1366 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1367 error("Can't change directory: Can't check target");
1368 xfree(tmp);
1369 err = 1;
1370 break;
1371 }
1372 if (!S_ISDIR(aa->perm)) {
1373 error("Can't change directory: \"%s\" is not "
1374 "a directory", tmp);
1375 xfree(tmp);
1376 err = 1;
1377 break;
1378 }
1379 xfree(*pwd);
1380 *pwd = tmp;
1381 break;
1382 case I_LS:
1383 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001384 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001385 break;
1386 }
1387
1388 /* Strip pwd off beginning of non-absolute paths */
1389 tmp = NULL;
1390 if (*path1 != '/')
1391 tmp = *pwd;
1392
1393 path1 = make_absolute(path1, *pwd);
1394 err = do_globbed_ls(conn, path1, tmp, lflag);
1395 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001396 case I_DF:
1397 /* Default to current directory if no path specified */
1398 if (path1 == NULL)
1399 path1 = xstrdup(*pwd);
1400 path1 = make_absolute(path1, *pwd);
1401 err = do_df(conn, path1, hflag, iflag);
1402 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001403 case I_LCHDIR:
1404 if (chdir(path1) == -1) {
1405 error("Couldn't change local directory to "
1406 "\"%s\": %s", path1, strerror(errno));
1407 err = 1;
1408 }
1409 break;
1410 case I_LMKDIR:
1411 if (mkdir(path1, 0777) == -1) {
1412 error("Couldn't create local directory "
1413 "\"%s\": %s", path1, strerror(errno));
1414 err = 1;
1415 }
1416 break;
1417 case I_LLS:
1418 local_do_ls(cmd);
1419 break;
1420 case I_SHELL:
1421 local_do_shell(cmd);
1422 break;
1423 case I_LUMASK:
1424 umask(n_arg);
1425 printf("Local umask: %03lo\n", n_arg);
1426 break;
1427 case I_CHMOD:
1428 path1 = make_absolute(path1, *pwd);
1429 attrib_clear(&a);
1430 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1431 a.perm = n_arg;
1432 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001433 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001434 printf("Changing mode on %s\n", g.gl_pathv[i]);
1435 err = do_setstat(conn, g.gl_pathv[i], &a);
1436 if (err != 0 && err_abort)
1437 break;
1438 }
1439 break;
1440 case I_CHOWN:
1441 case I_CHGRP:
1442 path1 = make_absolute(path1, *pwd);
1443 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001444 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001445 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001446 if (err_abort) {
1447 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001448 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001449 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001450 continue;
1451 }
1452 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1453 error("Can't get current ownership of "
1454 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001455 if (err_abort) {
1456 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001457 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001458 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001459 continue;
1460 }
1461 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1462 if (cmdnum == I_CHOWN) {
1463 printf("Changing owner on %s\n", g.gl_pathv[i]);
1464 aa->uid = n_arg;
1465 } else {
1466 printf("Changing group on %s\n", g.gl_pathv[i]);
1467 aa->gid = n_arg;
1468 }
1469 err = do_setstat(conn, g.gl_pathv[i], aa);
1470 if (err != 0 && err_abort)
1471 break;
1472 }
1473 break;
1474 case I_PWD:
1475 printf("Remote working directory: %s\n", *pwd);
1476 break;
1477 case I_LPWD:
1478 if (!getcwd(path_buf, sizeof(path_buf))) {
1479 error("Couldn't get local cwd: %s", strerror(errno));
1480 err = -1;
1481 break;
1482 }
1483 printf("Local working directory: %s\n", path_buf);
1484 break;
1485 case I_QUIT:
1486 /* Processed below */
1487 break;
1488 case I_HELP:
1489 help();
1490 break;
1491 case I_VERSION:
1492 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1493 break;
1494 case I_PROGRESS:
1495 showprogress = !showprogress;
1496 if (showprogress)
1497 printf("Progress meter enabled\n");
1498 else
1499 printf("Progress meter disabled\n");
1500 break;
1501 default:
1502 fatal("%d is not implemented", cmdnum);
1503 }
1504
1505 if (g.gl_pathc)
1506 globfree(&g);
1507 if (path1)
1508 xfree(path1);
1509 if (path2)
1510 xfree(path2);
1511
1512 /* If an unignored error occurs in batch mode we should abort. */
1513 if (err_abort && err != 0)
1514 return (-1);
1515 else if (cmdnum == I_QUIT)
1516 return (1);
1517
1518 return (0);
1519}
1520
Darren Tucker2d963d82004-11-07 20:04:10 +11001521#ifdef USE_LIBEDIT
1522static char *
1523prompt(EditLine *el)
1524{
1525 return ("sftp> ");
1526}
Darren Tucker2d963d82004-11-07 20:04:10 +11001527
Darren Tucker909d8582010-01-08 19:02:40 +11001528/* Display entries in 'list' after skipping the first 'len' chars */
1529static void
1530complete_display(char **list, u_int len)
1531{
1532 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1533 struct winsize ws;
1534 char *tmp;
1535
1536 /* Count entries for sort and find longest */
1537 for (y = 0; list[y]; y++)
1538 m = MAX(m, strlen(list[y]));
1539
1540 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1541 width = ws.ws_col;
1542
1543 m = m > len ? m - len : 0;
1544 columns = width / (m + 2);
1545 columns = MAX(columns, 1);
1546 colspace = width / columns;
1547 colspace = MIN(colspace, width);
1548
1549 printf("\n");
1550 m = 1;
1551 for (y = 0; list[y]; y++) {
1552 llen = strlen(list[y]);
1553 tmp = llen > len ? list[y] + len : "";
1554 printf("%-*s", colspace, tmp);
1555 if (m >= columns) {
1556 printf("\n");
1557 m = 1;
1558 } else
1559 m++;
1560 }
1561 printf("\n");
1562}
1563
1564/*
1565 * Given a "list" of words that begin with a common prefix of "word",
1566 * attempt to find an autocompletion to extends "word" by the next
1567 * characters common to all entries in "list".
1568 */
1569static char *
1570complete_ambiguous(const char *word, char **list, size_t count)
1571{
1572 if (word == NULL)
1573 return NULL;
1574
1575 if (count > 0) {
1576 u_int y, matchlen = strlen(list[0]);
1577
1578 /* Find length of common stem */
1579 for (y = 1; list[y]; y++) {
1580 u_int x;
1581
1582 for (x = 0; x < matchlen; x++)
1583 if (list[0][x] != list[y][x])
1584 break;
1585
1586 matchlen = x;
1587 }
1588
1589 if (matchlen > strlen(word)) {
1590 char *tmp = xstrdup(list[0]);
1591
Darren Tucker340d1682010-01-09 08:54:31 +11001592 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001593 return tmp;
1594 }
1595 }
1596
1597 return xstrdup(word);
1598}
1599
1600/* Autocomplete a sftp command */
1601static int
1602complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1603 int terminated)
1604{
1605 u_int y, count = 0, cmdlen, tmplen;
1606 char *tmp, **list, argterm[3];
1607 const LineInfo *lf;
1608
1609 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1610
1611 /* No command specified: display all available commands */
1612 if (cmd == NULL) {
1613 for (y = 0; cmds[y].c; y++)
1614 list[count++] = xstrdup(cmds[y].c);
1615
1616 list[count] = NULL;
1617 complete_display(list, 0);
1618
1619 for (y = 0; list[y] != NULL; y++)
1620 xfree(list[y]);
1621 xfree(list);
1622 return count;
1623 }
1624
1625 /* Prepare subset of commands that start with "cmd" */
1626 cmdlen = strlen(cmd);
1627 for (y = 0; cmds[y].c; y++) {
1628 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
1629 list[count++] = xstrdup(cmds[y].c);
1630 }
1631 list[count] = NULL;
1632
Damien Miller47d81152011-11-25 13:53:48 +11001633 if (count == 0) {
1634 xfree(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001635 return 0;
Damien Miller47d81152011-11-25 13:53:48 +11001636 }
Darren Tucker909d8582010-01-08 19:02:40 +11001637
1638 /* Complete ambigious command */
1639 tmp = complete_ambiguous(cmd, list, count);
1640 if (count > 1)
1641 complete_display(list, 0);
1642
1643 for (y = 0; list[y]; y++)
1644 xfree(list[y]);
1645 xfree(list);
1646
1647 if (tmp != NULL) {
1648 tmplen = strlen(tmp);
1649 cmdlen = strlen(cmd);
1650 /* If cmd may be extended then do so */
1651 if (tmplen > cmdlen)
1652 if (el_insertstr(el, tmp + cmdlen) == -1)
1653 fatal("el_insertstr failed.");
1654 lf = el_line(el);
1655 /* Terminate argument cleanly */
1656 if (count == 1) {
1657 y = 0;
1658 if (!terminated)
1659 argterm[y++] = quote;
1660 if (lastarg || *(lf->cursor) != ' ')
1661 argterm[y++] = ' ';
1662 argterm[y] = '\0';
1663 if (y > 0 && el_insertstr(el, argterm) == -1)
1664 fatal("el_insertstr failed.");
1665 }
1666 xfree(tmp);
1667 }
1668
1669 return count;
1670}
1671
1672/*
1673 * Determine whether a particular sftp command's arguments (if any)
1674 * represent local or remote files.
1675 */
1676static int
1677complete_is_remote(char *cmd) {
1678 int i;
1679
1680 if (cmd == NULL)
1681 return -1;
1682
1683 for (i = 0; cmds[i].c; i++) {
1684 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
1685 return cmds[i].t;
1686 }
1687
1688 return -1;
1689}
1690
1691/* Autocomplete a filename "file" */
1692static int
1693complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1694 char *file, int remote, int lastarg, char quote, int terminated)
1695{
1696 glob_t g;
1697 char *tmp, *tmp2, ins[3];
Darren Tucker17146d32012-10-05 10:46:16 +10001698 u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
Darren Tucker909d8582010-01-08 19:02:40 +11001699 const LineInfo *lf;
1700
1701 /* Glob from "file" location */
1702 if (file == NULL)
1703 tmp = xstrdup("*");
1704 else
1705 xasprintf(&tmp, "%s*", file);
1706
Darren Tucker191fcc62012-10-05 10:45:01 +10001707 /* Check if the path is absolute. */
1708 isabs = tmp[0] == '/';
1709
Darren Tucker909d8582010-01-08 19:02:40 +11001710 memset(&g, 0, sizeof(g));
1711 if (remote != LOCAL) {
1712 tmp = make_absolute(tmp, remote_path);
1713 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1714 } else
1715 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1716
1717 /* Determine length of pwd so we can trim completion display */
1718 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1719 /* Terminate counting on first unescaped glob metacharacter */
1720 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1721 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1722 hadglob = 1;
1723 break;
1724 }
1725 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1726 tmplen++;
1727 if (tmp[tmplen] == '/')
1728 pwdlen = tmplen + 1; /* track last seen '/' */
1729 }
1730 xfree(tmp);
1731
1732 if (g.gl_matchc == 0)
1733 goto out;
1734
1735 if (g.gl_matchc > 1)
1736 complete_display(g.gl_pathv, pwdlen);
1737
1738 tmp = NULL;
1739 /* Don't try to extend globs */
1740 if (file == NULL || hadglob)
1741 goto out;
1742
1743 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
Darren Tucker191fcc62012-10-05 10:45:01 +10001744 tmp = path_strip(tmp2, isabs ? NULL : remote_path);
Darren Tucker909d8582010-01-08 19:02:40 +11001745 xfree(tmp2);
1746
1747 if (tmp == NULL)
1748 goto out;
1749
1750 tmplen = strlen(tmp);
1751 filelen = strlen(file);
1752
Darren Tucker17146d32012-10-05 10:46:16 +10001753 /* Count the number of escaped characters in the input string. */
1754 cesc = isesc = 0;
1755 for (i = 0; i < filelen; i++) {
1756 if (!isesc && file[i] == '\\' && i + 1 < filelen){
1757 isesc = 1;
1758 cesc++;
1759 } else
1760 isesc = 0;
1761 }
1762
1763 if (tmplen > (filelen - cesc)) {
1764 tmp2 = tmp + filelen - cesc;
Darren Tucker909d8582010-01-08 19:02:40 +11001765 len = strlen(tmp2);
1766 /* quote argument on way out */
1767 for (i = 0; i < len; i++) {
1768 ins[0] = '\\';
1769 ins[1] = tmp2[i];
1770 ins[2] = '\0';
1771 switch (tmp2[i]) {
1772 case '\'':
1773 case '"':
1774 case '\\':
1775 case '\t':
Darren Tuckerd78739a2010-10-24 10:56:32 +11001776 case '[':
Darren Tucker909d8582010-01-08 19:02:40 +11001777 case ' ':
Darren Tucker17146d32012-10-05 10:46:16 +10001778 case '#':
1779 case '*':
Darren Tucker909d8582010-01-08 19:02:40 +11001780 if (quote == '\0' || tmp2[i] == quote) {
1781 if (el_insertstr(el, ins) == -1)
1782 fatal("el_insertstr "
1783 "failed.");
1784 break;
1785 }
1786 /* FALLTHROUGH */
1787 default:
1788 if (el_insertstr(el, ins + 1) == -1)
1789 fatal("el_insertstr failed.");
1790 break;
1791 }
1792 }
1793 }
1794
1795 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11001796 if (g.gl_matchc == 1) {
1797 i = 0;
1798 if (!terminated)
1799 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11001800 if (*(lf->cursor - 1) != '/' &&
1801 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11001802 ins[i++] = ' ';
1803 ins[i] = '\0';
1804 if (i > 0 && el_insertstr(el, ins) == -1)
1805 fatal("el_insertstr failed.");
1806 }
1807 xfree(tmp);
1808
1809 out:
1810 globfree(&g);
1811 return g.gl_matchc;
1812}
1813
1814/* tab-completion hook function, called via libedit */
1815static unsigned char
1816complete(EditLine *el, int ch)
1817{
1818 char **argv, *line, quote;
1819 u_int argc, carg, cursor, len, terminated, ret = CC_ERROR;
1820 const LineInfo *lf;
1821 struct complete_ctx *complete_ctx;
1822
1823 lf = el_line(el);
1824 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1825 fatal("%s: el_get failed", __func__);
1826
1827 /* Figure out which argument the cursor points to */
1828 cursor = lf->cursor - lf->buffer;
1829 line = (char *)xmalloc(cursor + 1);
1830 memcpy(line, lf->buffer, cursor);
1831 line[cursor] = '\0';
1832 argv = makeargv(line, &carg, 1, &quote, &terminated);
1833 xfree(line);
1834
1835 /* Get all the arguments on the line */
1836 len = lf->lastchar - lf->buffer;
1837 line = (char *)xmalloc(len + 1);
1838 memcpy(line, lf->buffer, len);
1839 line[len] = '\0';
1840 argv = makeargv(line, &argc, 1, NULL, NULL);
1841
1842 /* Ensure cursor is at EOL or a argument boundary */
1843 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1844 line[cursor] != '\n') {
1845 xfree(line);
1846 return ret;
1847 }
1848
1849 if (carg == 0) {
1850 /* Show all available commands */
1851 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1852 ret = CC_REDISPLAY;
1853 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1854 /* Handle the command parsing */
1855 if (complete_cmd_parse(el, argv[0], argc == carg,
1856 quote, terminated) != 0)
1857 ret = CC_REDISPLAY;
1858 } else if (carg >= 1) {
1859 /* Handle file parsing */
1860 int remote = complete_is_remote(argv[0]);
1861 char *filematch = NULL;
1862
1863 if (carg > 1 && line[cursor-1] != ' ')
1864 filematch = argv[carg - 1];
1865
1866 if (remote != 0 &&
1867 complete_match(el, complete_ctx->conn,
1868 *complete_ctx->remote_pathp, filematch,
1869 remote, carg == argc, quote, terminated) != 0)
1870 ret = CC_REDISPLAY;
1871 }
1872
1873 xfree(line);
1874 return ret;
1875}
Darren Tuckere67f7db2010-01-08 19:50:02 +11001876#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11001877
Damien Miller20e1fab2004-02-18 14:30:55 +11001878int
Darren Tucker21063192010-01-08 17:10:36 +11001879interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001880{
Darren Tucker909d8582010-01-08 19:02:40 +11001881 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11001882 char *dir = NULL;
1883 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10001884 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11001885 EditLine *el = NULL;
1886#ifdef USE_LIBEDIT
1887 History *hl = NULL;
1888 HistEvent hev;
1889 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11001890 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11001891
1892 if (!batchmode && isatty(STDIN_FILENO)) {
1893 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1894 fatal("Couldn't initialise editline");
1895 if ((hl = history_init()) == NULL)
1896 fatal("Couldn't initialise editline history");
1897 history(hl, &hev, H_SETSIZE, 100);
1898 el_set(el, EL_HIST, history, hl);
1899
1900 el_set(el, EL_PROMPT, prompt);
1901 el_set(el, EL_EDITOR, "emacs");
1902 el_set(el, EL_TERMINAL, NULL);
1903 el_set(el, EL_SIGNAL, 1);
1904 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11001905
1906 /* Tab Completion */
1907 el_set(el, EL_ADDFN, "ftp-complete",
Darren Tuckerd78739a2010-10-24 10:56:32 +11001908 "Context sensitive argument completion", complete);
Darren Tucker909d8582010-01-08 19:02:40 +11001909 complete_ctx.conn = conn;
1910 complete_ctx.remote_pathp = &remote_path;
1911 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
1912 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11001913 }
1914#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001915
Darren Tucker909d8582010-01-08 19:02:40 +11001916 remote_path = do_realpath(conn, ".");
1917 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11001918 fatal("Need cwd");
1919
1920 if (file1 != NULL) {
1921 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11001922 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11001923
1924 if (remote_is_dir(conn, dir) && file2 == NULL) {
1925 printf("Changing to: %s\n", dir);
1926 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001927 if (parse_dispatch_command(conn, cmd,
1928 &remote_path, 1) != 0) {
Darren Tuckercd516ef2004-12-06 22:43:43 +11001929 xfree(dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001930 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001931 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001932 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11001933 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001934 } else {
Darren Tucker0af24052012-10-05 10:41:25 +10001935 /* XXX this is wrong wrt quoting */
Damien Miller20e1fab2004-02-18 14:30:55 +11001936 if (file2 == NULL)
1937 snprintf(cmd, sizeof cmd, "get %s", dir);
1938 else
1939 snprintf(cmd, sizeof cmd, "get %s %s", dir,
1940 file2);
1941
Darren Tucker909d8582010-01-08 19:02:40 +11001942 err = parse_dispatch_command(conn, cmd,
1943 &remote_path, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001944 xfree(dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001945 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001946 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001947 return (err);
1948 }
1949 xfree(dir);
1950 }
1951
Damien Miller37294fb2005-07-17 17:18:49 +10001952 setlinebuf(stdout);
1953 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11001954
Damien Miller0e2c1022005-08-12 22:16:22 +10001955 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11001956 err = 0;
1957 for (;;) {
1958 char *cp;
1959
Darren Tuckercdf547a2004-05-24 10:12:19 +10001960 signal(SIGINT, SIG_IGN);
1961
Darren Tucker2d963d82004-11-07 20:04:10 +11001962 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001963 if (interactive)
1964 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11001965 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001966 if (interactive)
1967 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11001968 break;
1969 }
Damien Miller0e2c1022005-08-12 22:16:22 +10001970 if (!interactive) { /* Echo command */
1971 printf("sftp> %s", cmd);
1972 if (strlen(cmd) > 0 &&
1973 cmd[strlen(cmd) - 1] != '\n')
1974 printf("\n");
1975 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001976 } else {
1977#ifdef USE_LIBEDIT
1978 const char *line;
1979 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001980
Darren Tucker909d8582010-01-08 19:02:40 +11001981 if ((line = el_gets(el, &count)) == NULL ||
1982 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001983 printf("\n");
1984 break;
1985 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001986 history(hl, &hev, H_ENTER, line);
1987 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
1988 fprintf(stderr, "Error: input line too long\n");
1989 continue;
1990 }
1991#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001992 }
1993
Damien Miller20e1fab2004-02-18 14:30:55 +11001994 cp = strrchr(cmd, '\n');
1995 if (cp)
1996 *cp = '\0';
1997
Darren Tuckercdf547a2004-05-24 10:12:19 +10001998 /* Handle user interrupts gracefully during commands */
1999 interrupted = 0;
2000 signal(SIGINT, cmd_interrupt);
2001
Darren Tucker909d8582010-01-08 19:02:40 +11002002 err = parse_dispatch_command(conn, cmd, &remote_path,
2003 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11002004 if (err != 0)
2005 break;
2006 }
Darren Tucker909d8582010-01-08 19:02:40 +11002007 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11002008 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002009
Tim Rice027e8b12005-08-15 14:52:50 -07002010#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10002011 if (el != NULL)
2012 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07002013#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10002014
Damien Miller20e1fab2004-02-18 14:30:55 +11002015 /* err == 1 signifies normal "quit" exit */
2016 return (err >= 0 ? 0 : -1);
2017}
Damien Miller62d57f62003-01-10 21:43:24 +11002018
Ben Lindstrombba81212001-06-25 05:01:22 +00002019static void
Damien Millercc685c12003-06-04 22:51:38 +10002020connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11002021{
2022 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002023
Damien Miller33804262001-02-04 23:20:18 +11002024#ifdef USE_PIPES
2025 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002026
Damien Miller33804262001-02-04 23:20:18 +11002027 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2028 fatal("pipe: %s", strerror(errno));
2029 *in = pin[0];
2030 *out = pout[1];
2031 c_in = pout[0];
2032 c_out = pin[1];
2033#else /* USE_PIPES */
2034 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002035
Damien Miller33804262001-02-04 23:20:18 +11002036 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2037 fatal("socketpair: %s", strerror(errno));
2038 *in = *out = inout[0];
2039 c_in = c_out = inout[1];
2040#endif /* USE_PIPES */
2041
Damien Millercc685c12003-06-04 22:51:38 +10002042 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002043 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002044 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002045 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2046 (dup2(c_out, STDOUT_FILENO) == -1)) {
2047 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002048 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002049 }
2050 close(*in);
2051 close(*out);
2052 close(c_in);
2053 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002054
2055 /*
2056 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002057 * ignore SIGINT if we want to gracefully abort commands,
2058 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002059 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2060 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002061 */
2062 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002063 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002064 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002065 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002066 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002067 }
2068
Damien Millercc685c12003-06-04 22:51:38 +10002069 signal(SIGTERM, killchild);
2070 signal(SIGINT, killchild);
2071 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11002072 close(c_in);
2073 close(c_out);
2074}
2075
Ben Lindstrombba81212001-06-25 05:01:22 +00002076static void
Damien Miller33804262001-02-04 23:20:18 +11002077usage(void)
2078{
Damien Miller025e01c2002-02-08 22:06:29 +11002079 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002080
Ben Lindstrom1e243242001-09-18 05:38:44 +00002081 fprintf(stderr,
Darren Tucker1b0dd172009-10-07 08:37:48 +11002082 "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002083 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002084 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002085 " [-o ssh_option] [-P port] [-R num_requests] "
2086 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002087 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002088 " %s [user@]host[:file ...]\n"
2089 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002090 " %s -b batchfile [user@]host\n",
2091 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002092 exit(1);
2093}
2094
Kevin Stevesef4eea92001-02-05 12:42:17 +00002095int
Damien Miller33804262001-02-04 23:20:18 +11002096main(int argc, char **argv)
2097{
Damien Miller956f3fb2003-01-10 21:40:00 +11002098 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002099 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002100 int debug_level = 0, sshver = 2;
2101 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002102 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002103 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002104 LogLevel ll = SYSLOG_LEVEL_INFO;
2105 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002106 extern int optind;
2107 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002108 struct sftp_conn *conn;
2109 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2110 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002111 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002112
Darren Tuckerce321d82005-10-03 18:11:24 +10002113 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2114 sanitise_stdfd();
2115
Damien Miller59d3d5b2003-08-22 09:34:41 +10002116 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002117 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002118 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002119 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002120 addargs(&args, "-oForwardX11 no");
2121 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002122 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002123 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002124
Ben Lindstrom387c4722001-05-08 20:27:25 +00002125 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002126 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002127
Darren Tucker282b4022009-10-07 08:23:06 +11002128 while ((ch = getopt(argc, argv,
Damien Miller65e42f82010-09-24 22:15:11 +10002129 "1246hpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002130 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002131 /* Passed through to ssh(1) */
2132 case '4':
2133 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002134 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002135 addargs(&args, "-%c", ch);
2136 break;
2137 /* Passed through to ssh(1) with argument */
2138 case 'F':
2139 case 'c':
2140 case 'i':
2141 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002142 addargs(&args, "-%c", ch);
2143 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002144 break;
2145 case 'q':
2146 showprogress = 0;
2147 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002148 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002149 case 'P':
2150 addargs(&args, "-oPort %s", optarg);
2151 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002152 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002153 if (debug_level < 3) {
2154 addargs(&args, "-v");
2155 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2156 }
2157 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002158 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002159 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002160 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002161 if (sftp_server == NULL)
2162 sftp_server = _PATH_SFTP_SERVER;
2163 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002164 case '2':
2165 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002166 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002167 case 'B':
2168 copy_buffer_len = strtol(optarg, &cp, 10);
2169 if (copy_buffer_len == 0 || *cp != '\0')
2170 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002171 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002172 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002173 if (batchmode)
2174 fatal("Batch file already specified.");
2175
2176 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002177 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002178 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002179 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002180 showprogress = 0;
Damien Miller44f75c12004-01-21 10:58:47 +11002181 batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002182 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002183 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002184 case 'p':
2185 global_pflag = 1;
2186 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002187 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002188 sftp_direct = optarg;
2189 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002190 case 'l':
2191 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2192 &errstr);
2193 if (errstr != NULL)
2194 usage();
2195 limit_kbps *= 1024; /* kbps */
2196 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002197 case 'r':
2198 global_rflag = 1;
2199 break;
Damien Miller16a13332002-02-13 14:03:56 +11002200 case 'R':
2201 num_requests = strtol(optarg, &cp, 10);
2202 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002203 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002204 optarg);
2205 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002206 case 's':
2207 sftp_server = optarg;
2208 break;
2209 case 'S':
2210 ssh_program = optarg;
2211 replacearg(&args, 0, "%s", ssh_program);
2212 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002213 case 'h':
2214 default:
Damien Miller33804262001-02-04 23:20:18 +11002215 usage();
2216 }
2217 }
2218
Damien Millerc0f27d82004-03-08 23:12:19 +11002219 if (!isatty(STDERR_FILENO))
2220 showprogress = 0;
2221
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002222 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2223
Damien Millerd14ee1e2002-02-05 12:27:31 +11002224 if (sftp_direct == NULL) {
2225 if (optind == argc || argc > (optind + 2))
2226 usage();
Damien Miller33804262001-02-04 23:20:18 +11002227
Damien Millerd14ee1e2002-02-05 12:27:31 +11002228 userhost = xstrdup(argv[optind]);
2229 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002230
Ben Lindstromc276c122002-12-23 02:14:51 +00002231 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002232 host = userhost;
2233 else {
2234 *host++ = '\0';
2235 if (!userhost[0]) {
2236 fprintf(stderr, "Missing username\n");
2237 usage();
2238 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002239 addargs(&args, "-l");
2240 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002241 }
2242
Damien Millerec692032004-01-27 21:22:00 +11002243 if ((cp = colon(host)) != NULL) {
2244 *cp++ = '\0';
2245 file1 = cp;
2246 }
2247
Damien Millerd14ee1e2002-02-05 12:27:31 +11002248 host = cleanhostname(host);
2249 if (!*host) {
2250 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002251 usage();
2252 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002253
Damien Millerd14ee1e2002-02-05 12:27:31 +11002254 addargs(&args, "-oProtocol %d", sshver);
2255
2256 /* no subsystem if the server-spec contains a '/' */
2257 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2258 addargs(&args, "-s");
2259
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002260 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002261 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002262 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002263 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002264
Damien Millercc685c12003-06-04 22:51:38 +10002265 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002266 } else {
2267 args.list = NULL;
2268 addargs(&args, "sftp-server");
2269
Damien Millercc685c12003-06-04 22:51:38 +10002270 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002271 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002272 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002273
Damien Miller65e42f82010-09-24 22:15:11 +10002274 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002275 if (conn == NULL)
2276 fatal("Couldn't initialise connection to server");
2277
2278 if (!batchmode) {
2279 if (sftp_direct == NULL)
2280 fprintf(stderr, "Connected to %s.\n", host);
2281 else
2282 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2283 }
2284
2285 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002286
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002287#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002288 shutdown(in, SHUT_RDWR);
2289 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002290#endif
2291
Damien Miller33804262001-02-04 23:20:18 +11002292 close(in);
2293 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002294 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002295 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002296
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002297 while (waitpid(sshpid, NULL, 0) == -1)
2298 if (errno != EINTR)
2299 fatal("Couldn't wait for ssh process: %s",
2300 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002301
Damien Miller956f3fb2003-01-10 21:40:00 +11002302 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002303}