blob: 78f8ca178401cb01a02ec8f5548abc4961b73dc4 [file] [log] [blame]
Darren Tucker70cc0922010-01-09 22:28:03 +11001/* $OpenBSD: sftp.c,v 1.118 2010/01/09 11:13:02 dtucker 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 Tuckera4e9ffa2004-06-22 13:07:58 +1000113#define LS_LONG_VIEW 0x01 /* Full view ala ls -l */
114#define LS_SHORT_VIEW 0x02 /* Single row view ala ls -1 */
115#define LS_NUMERIC_VIEW 0x04 /* Long view with numeric uid/gid */
116#define LS_NAME_SORT 0x08 /* Sort by name (default) */
117#define LS_TIME_SORT 0x10 /* Sort by mtime */
118#define LS_SIZE_SORT 0x20 /* Sort by file size */
119#define LS_REVERSE_SORT 0x40 /* Reverse sort order */
Darren Tucker9a526452004-06-22 13:09:55 +1000120#define LS_SHOW_ALL 0x80 /* Don't skip filenames starting with '.' */
Darren Tuckerb9123452004-06-22 13:06:45 +1000121
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000122#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW)
123#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
Damien Miller20e1fab2004-02-18 14:30:55 +1100124
125/* Commands for interactive mode */
126#define I_CHDIR 1
127#define I_CHGRP 2
128#define I_CHMOD 3
129#define I_CHOWN 4
Damien Millerd671e5a2008-05-19 14:53:33 +1000130#define I_DF 24
Damien Miller20e1fab2004-02-18 14:30:55 +1100131#define I_GET 5
132#define I_HELP 6
133#define I_LCHDIR 7
134#define I_LLS 8
135#define I_LMKDIR 9
136#define I_LPWD 10
137#define I_LS 11
138#define I_LUMASK 12
139#define I_MKDIR 13
140#define I_PUT 14
141#define I_PWD 15
142#define I_QUIT 16
143#define I_RENAME 17
144#define I_RM 18
145#define I_RMDIR 19
146#define I_SHELL 20
147#define I_SYMLINK 21
148#define I_VERSION 22
149#define I_PROGRESS 23
150
151struct CMD {
152 const char *c;
153 const int n;
Darren Tucker909d8582010-01-08 19:02:40 +1100154 const int t;
Damien Miller20e1fab2004-02-18 14:30:55 +1100155};
156
Darren Tucker909d8582010-01-08 19:02:40 +1100157/* Type of completion */
158#define NOARGS 0
159#define REMOTE 1
160#define LOCAL 2
161
Damien Miller20e1fab2004-02-18 14:30:55 +1100162static const struct CMD cmds[] = {
Darren Tucker909d8582010-01-08 19:02:40 +1100163 { "bye", I_QUIT, NOARGS },
164 { "cd", I_CHDIR, REMOTE },
165 { "chdir", I_CHDIR, REMOTE },
166 { "chgrp", I_CHGRP, REMOTE },
167 { "chmod", I_CHMOD, REMOTE },
168 { "chown", I_CHOWN, REMOTE },
169 { "df", I_DF, REMOTE },
170 { "dir", I_LS, REMOTE },
171 { "exit", I_QUIT, NOARGS },
172 { "get", I_GET, REMOTE },
173 { "help", I_HELP, NOARGS },
174 { "lcd", I_LCHDIR, LOCAL },
175 { "lchdir", I_LCHDIR, LOCAL },
176 { "lls", I_LLS, LOCAL },
177 { "lmkdir", I_LMKDIR, LOCAL },
178 { "ln", I_SYMLINK, REMOTE },
179 { "lpwd", I_LPWD, LOCAL },
180 { "ls", I_LS, REMOTE },
181 { "lumask", I_LUMASK, NOARGS },
182 { "mkdir", I_MKDIR, REMOTE },
183 { "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 Tucker1b0dd172009-10-07 08:37:48 +1100235 "get [-Pr] remote-path [local-path] 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"
240 "ln oldpath newpath Symlink remote file\n"
241 "lpwd Print local working directory\n"
242 "ls [-1aflnrSt] [path] Display remote directory listing\n"
243 "lumask umask Set local umask to 'umask'\n"
244 "mkdir path Create remote directory\n"
245 "progress Toggle display of progress meter\n"
Darren Tucker1b0dd172009-10-07 08:37:48 +1100246 "put [-Pr] local-path [remote-path] 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
269 if ((shell = getenv("SHELL")) == NULL)
270 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
Damien Miller1cbc2922007-10-26 14:27:45 +1000377parse_ls_flags(char **argv, int argc, int *lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100378{
Damien Millerf184bcf2008-06-29 22:45:13 +1000379 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000380 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100381
Damien Miller1cbc2922007-10-26 14:27:45 +1000382 optind = optreset = 1;
383 opterr = 0;
384
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000385 *lflag = LS_NAME_SORT;
Damien Miller1cbc2922007-10-26 14:27:45 +1000386 while ((ch = getopt(argc, argv, "1Saflnrt")) != -1) {
387 switch (ch) {
388 case '1':
389 *lflag &= ~VIEW_FLAGS;
390 *lflag |= LS_SHORT_VIEW;
391 break;
392 case 'S':
393 *lflag &= ~SORT_FLAGS;
394 *lflag |= LS_SIZE_SORT;
395 break;
396 case 'a':
397 *lflag |= LS_SHOW_ALL;
398 break;
399 case 'f':
400 *lflag &= ~SORT_FLAGS;
401 break;
402 case 'l':
403 *lflag &= ~VIEW_FLAGS;
404 *lflag |= LS_LONG_VIEW;
405 break;
406 case 'n':
407 *lflag &= ~VIEW_FLAGS;
408 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
409 break;
410 case 'r':
411 *lflag |= LS_REVERSE_SORT;
412 break;
413 case 't':
414 *lflag &= ~SORT_FLAGS;
415 *lflag |= LS_TIME_SORT;
416 break;
417 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000418 error("ls: Invalid flag -%c", optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000419 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100420 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100421 }
422
Damien Miller1cbc2922007-10-26 14:27:45 +1000423 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100424}
425
426static int
Damien Millerd671e5a2008-05-19 14:53:33 +1000427parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
428{
Damien Millerf184bcf2008-06-29 22:45:13 +1000429 extern int opterr, optind, optopt, optreset;
Damien Millerd671e5a2008-05-19 14:53:33 +1000430 int ch;
431
432 optind = optreset = 1;
433 opterr = 0;
434
435 *hflag = *iflag = 0;
436 while ((ch = getopt(argc, argv, "hi")) != -1) {
437 switch (ch) {
438 case 'h':
439 *hflag = 1;
440 break;
441 case 'i':
442 *iflag = 1;
443 break;
444 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000445 error("%s: Invalid flag -%c", cmd, optopt);
Damien Millerd671e5a2008-05-19 14:53:33 +1000446 return -1;
447 }
448 }
449
450 return optind;
451}
452
453static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100454is_dir(char *path)
455{
456 struct stat sb;
457
458 /* XXX: report errors? */
459 if (stat(path, &sb) == -1)
460 return(0);
461
Darren Tucker1e80e402006-09-21 12:59:33 +1000462 return(S_ISDIR(sb.st_mode));
Damien Miller20e1fab2004-02-18 14:30:55 +1100463}
464
465static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100466remote_is_dir(struct sftp_conn *conn, char *path)
467{
468 Attrib *a;
469
470 /* XXX: report errors? */
471 if ((a = do_stat(conn, path, 1)) == NULL)
472 return(0);
473 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
474 return(0);
Darren Tucker1e80e402006-09-21 12:59:33 +1000475 return(S_ISDIR(a->perm));
Damien Miller20e1fab2004-02-18 14:30:55 +1100476}
477
Darren Tucker1b0dd172009-10-07 08:37:48 +1100478/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
Damien Miller20e1fab2004-02-18 14:30:55 +1100479static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100480pathname_is_dir(char *pathname)
481{
482 size_t l = strlen(pathname);
483
484 return l > 0 && pathname[l - 1] == '/';
485}
486
487static int
488process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
489 int pflag, int rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100490{
491 char *abs_src = NULL;
492 char *abs_dst = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100493 glob_t g;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100494 char *filename, *tmp=NULL;
495 int i, err = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100496
497 abs_src = xstrdup(src);
498 abs_src = make_absolute(abs_src, pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +1100499 memset(&g, 0, sizeof(g));
Darren Tucker1b0dd172009-10-07 08:37:48 +1100500
Damien Miller20e1fab2004-02-18 14:30:55 +1100501 debug3("Looking up %s", abs_src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100502 if (remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100503 error("File \"%s\" not found.", abs_src);
504 err = -1;
505 goto out;
506 }
507
Darren Tucker1b0dd172009-10-07 08:37:48 +1100508 /*
509 * If multiple matches then dst must be a directory or
510 * unspecified.
511 */
512 if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
513 error("Multiple source paths, but destination "
514 "\"%s\" is not a directory", dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100515 err = -1;
516 goto out;
517 }
518
Darren Tuckercdf547a2004-05-24 10:12:19 +1000519 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100520 tmp = xstrdup(g.gl_pathv[i]);
521 if ((filename = basename(tmp)) == NULL) {
522 error("basename %s: %s", tmp, strerror(errno));
523 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100524 err = -1;
525 goto out;
526 }
527
528 if (g.gl_matchc == 1 && dst) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100529 if (is_dir(dst)) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100530 abs_dst = path_append(dst, filename);
531 } else {
Damien Miller20e1fab2004-02-18 14:30:55 +1100532 abs_dst = xstrdup(dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100533 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100534 } else if (dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100535 abs_dst = path_append(dst, filename);
536 } else {
537 abs_dst = xstrdup(filename);
538 }
539 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100540
541 printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100542 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
543 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
544 pflag || global_pflag, 1) == -1)
545 err = -1;
546 } else {
547 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
548 pflag || global_pflag) == -1)
549 err = -1;
550 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100551 xfree(abs_dst);
552 abs_dst = NULL;
553 }
554
555out:
556 xfree(abs_src);
Damien Miller20e1fab2004-02-18 14:30:55 +1100557 globfree(&g);
558 return(err);
559}
560
561static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100562process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
563 int pflag, int rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100564{
565 char *tmp_dst = NULL;
566 char *abs_dst = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100567 char *tmp = NULL, *filename = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100568 glob_t g;
569 int err = 0;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100570 int i, dst_is_dir = 1;
Damien Milleraec5cf82008-02-10 22:26:24 +1100571 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100572
573 if (dst) {
574 tmp_dst = xstrdup(dst);
575 tmp_dst = make_absolute(tmp_dst, pwd);
576 }
577
578 memset(&g, 0, sizeof(g));
579 debug3("Looking up %s", src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100580 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100581 error("File \"%s\" not found.", src);
582 err = -1;
583 goto out;
584 }
585
Darren Tucker1b0dd172009-10-07 08:37:48 +1100586 /* If we aren't fetching to pwd then stash this status for later */
587 if (tmp_dst != NULL)
588 dst_is_dir = remote_is_dir(conn, tmp_dst);
589
Damien Miller20e1fab2004-02-18 14:30:55 +1100590 /* If multiple matches, dst may be directory or unspecified */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100591 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
592 error("Multiple paths match, but destination "
593 "\"%s\" is not a directory", tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100594 err = -1;
595 goto out;
596 }
597
Darren Tuckercdf547a2004-05-24 10:12:19 +1000598 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Milleraec5cf82008-02-10 22:26:24 +1100599 if (stat(g.gl_pathv[i], &sb) == -1) {
600 err = -1;
601 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
602 continue;
603 }
Darren Tucker1b0dd172009-10-07 08:37:48 +1100604
605 tmp = xstrdup(g.gl_pathv[i]);
606 if ((filename = basename(tmp)) == NULL) {
607 error("basename %s: %s", tmp, strerror(errno));
608 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100609 err = -1;
610 goto out;
611 }
612
613 if (g.gl_matchc == 1 && tmp_dst) {
614 /* If directory specified, append filename */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100615 if (dst_is_dir)
616 abs_dst = path_append(tmp_dst, filename);
617 else
Damien Miller20e1fab2004-02-18 14:30:55 +1100618 abs_dst = xstrdup(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100619 } else if (tmp_dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100620 abs_dst = path_append(tmp_dst, filename);
621 } else {
622 abs_dst = make_absolute(xstrdup(filename), pwd);
623 }
624 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100625
626 printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100627 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
628 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
629 pflag || global_pflag, 1) == -1)
630 err = -1;
631 } else {
632 if (do_upload(conn, g.gl_pathv[i], abs_dst,
633 pflag || global_pflag) == -1)
634 err = -1;
635 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100636 }
637
638out:
639 if (abs_dst)
640 xfree(abs_dst);
641 if (tmp_dst)
642 xfree(tmp_dst);
643 globfree(&g);
644 return(err);
645}
646
647static int
648sdirent_comp(const void *aa, const void *bb)
649{
650 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
651 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000652 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100653
Darren Tuckerb9123452004-06-22 13:06:45 +1000654#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000655 if (sort_flag & LS_NAME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000656 return (rmul * strcmp(a->filename, b->filename));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000657 else if (sort_flag & LS_TIME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000658 return (rmul * NCMP(a->a.mtime, b->a.mtime));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000659 else if (sort_flag & LS_SIZE_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000660 return (rmul * NCMP(a->a.size, b->a.size));
661
662 fatal("Unknown ls sort type");
Damien Miller20e1fab2004-02-18 14:30:55 +1100663}
664
665/* sftp ls.1 replacement for directories */
666static int
667do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
668{
Damien Millereccb9de2005-06-17 12:59:34 +1000669 int n;
670 u_int c = 1, colspace = 0, columns = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100671 SFTP_DIRENT **d;
672
673 if ((n = do_readdir(conn, path, &d)) != 0)
674 return (n);
675
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000676 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000677 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100678 struct winsize ws;
679 char *tmp;
680
681 /* Count entries for sort and find longest filename */
Darren Tucker9a526452004-06-22 13:09:55 +1000682 for (n = 0; d[n] != NULL; n++) {
683 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
684 m = MAX(m, strlen(d[n]->filename));
685 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100686
687 /* Add any subpath that also needs to be counted */
688 tmp = path_strip(path, strip_path);
689 m += strlen(tmp);
690 xfree(tmp);
691
692 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
693 width = ws.ws_col;
694
695 columns = width / (m + 2);
696 columns = MAX(columns, 1);
697 colspace = width / columns;
698 colspace = MIN(colspace, width);
699 }
700
Darren Tuckerb9123452004-06-22 13:06:45 +1000701 if (lflag & SORT_FLAGS) {
Damien Miller653b93b2005-11-05 15:15:23 +1100702 for (n = 0; d[n] != NULL; n++)
703 ; /* count entries */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000704 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
Darren Tuckerb9123452004-06-22 13:06:45 +1000705 qsort(d, n, sizeof(*d), sdirent_comp);
706 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100707
Darren Tuckercdf547a2004-05-24 10:12:19 +1000708 for (n = 0; d[n] != NULL && !interrupted; n++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100709 char *tmp, *fname;
710
Darren Tucker9a526452004-06-22 13:09:55 +1000711 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
712 continue;
713
Damien Miller20e1fab2004-02-18 14:30:55 +1100714 tmp = path_append(path, d[n]->filename);
715 fname = path_strip(tmp, strip_path);
716 xfree(tmp);
717
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000718 if (lflag & LS_LONG_VIEW) {
719 if (lflag & LS_NUMERIC_VIEW) {
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000720 char *lname;
721 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100722
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000723 memset(&sb, 0, sizeof(sb));
724 attrib_to_stat(&d[n]->a, &sb);
725 lname = ls_file(fname, &sb, 1);
726 printf("%s\n", lname);
727 xfree(lname);
728 } else
729 printf("%s\n", d[n]->longname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100730 } else {
731 printf("%-*s", colspace, fname);
732 if (c >= columns) {
733 printf("\n");
734 c = 1;
735 } else
736 c++;
737 }
738
739 xfree(fname);
740 }
741
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000742 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100743 printf("\n");
744
745 free_sftp_dirents(d);
746 return (0);
747}
748
749/* sftp ls.1 replacement which handles path globs */
750static int
751do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
752 int lflag)
753{
754 glob_t g;
Damien Millereccb9de2005-06-17 12:59:34 +1000755 u_int i, c = 1, colspace = 0, columns = 1;
Darren Tucker596dcfa2004-12-11 13:37:22 +1100756 Attrib *a = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100757
758 memset(&g, 0, sizeof(g));
759
760 if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE,
Darren Tucker596dcfa2004-12-11 13:37:22 +1100761 NULL, &g) || (g.gl_pathc && !g.gl_matchc)) {
762 if (g.gl_pathc)
763 globfree(&g);
Damien Miller20e1fab2004-02-18 14:30:55 +1100764 error("Can't ls: \"%s\" not found", path);
765 return (-1);
766 }
767
Darren Tuckercdf547a2004-05-24 10:12:19 +1000768 if (interrupted)
769 goto out;
770
Damien Miller20e1fab2004-02-18 14:30:55 +1100771 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100772 * If the glob returns a single match and it is a directory,
773 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100774 */
Darren Tucker596dcfa2004-12-11 13:37:22 +1100775 if (g.gl_matchc == 1) {
776 if ((a = do_lstat(conn, g.gl_pathv[0], 1)) == NULL) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100777 globfree(&g);
778 return (-1);
779 }
780 if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
781 S_ISDIR(a->perm)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100782 int err;
783
784 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +1100785 globfree(&g);
Darren Tucker596dcfa2004-12-11 13:37:22 +1100786 return (err);
Damien Miller20e1fab2004-02-18 14:30:55 +1100787 }
788 }
789
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000790 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000791 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100792 struct winsize ws;
793
794 /* Count entries for sort and find longest filename */
795 for (i = 0; g.gl_pathv[i]; i++)
796 m = MAX(m, strlen(g.gl_pathv[i]));
797
798 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
799 width = ws.ws_col;
800
801 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 char *fname;
808
809 fname = path_strip(g.gl_pathv[i], strip_path);
810
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000811 if (lflag & LS_LONG_VIEW) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100812 char *lname;
813 struct stat sb;
814
815 /*
816 * XXX: this is slow - 1 roundtrip per path
817 * A solution to this is to fork glob() and
818 * build a sftp specific version which keeps the
819 * attribs (which currently get thrown away)
820 * that the server returns as well as the filenames.
821 */
822 memset(&sb, 0, sizeof(sb));
Darren Tucker596dcfa2004-12-11 13:37:22 +1100823 if (a == NULL)
824 a = do_lstat(conn, g.gl_pathv[i], 1);
Damien Miller20e1fab2004-02-18 14:30:55 +1100825 if (a != NULL)
826 attrib_to_stat(a, &sb);
827 lname = ls_file(fname, &sb, 1);
828 printf("%s\n", lname);
829 xfree(lname);
830 } else {
831 printf("%-*s", colspace, fname);
832 if (c >= columns) {
833 printf("\n");
834 c = 1;
835 } else
836 c++;
837 }
838 xfree(fname);
839 }
840
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000841 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100842 printf("\n");
843
Darren Tuckercdf547a2004-05-24 10:12:19 +1000844 out:
Damien Miller20e1fab2004-02-18 14:30:55 +1100845 if (g.gl_pathc)
846 globfree(&g);
847
848 return (0);
849}
850
Damien Millerd671e5a2008-05-19 14:53:33 +1000851static int
852do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
853{
Darren Tucker7b598892008-06-09 22:49:36 +1000854 struct sftp_statvfs st;
Damien Millerd671e5a2008-05-19 14:53:33 +1000855 char s_used[FMT_SCALED_STRSIZE];
856 char s_avail[FMT_SCALED_STRSIZE];
857 char s_root[FMT_SCALED_STRSIZE];
858 char s_total[FMT_SCALED_STRSIZE];
Darren Tuckerb5082e92010-01-08 18:51:47 +1100859 unsigned long long ffree;
Damien Millerd671e5a2008-05-19 14:53:33 +1000860
861 if (do_statvfs(conn, path, &st, 1) == -1)
862 return -1;
863 if (iflag) {
Darren Tuckerb5082e92010-01-08 18:51:47 +1100864 ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
Damien Millerd671e5a2008-05-19 14:53:33 +1000865 printf(" Inodes Used Avail "
866 "(root) %%Capacity\n");
867 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
868 (unsigned long long)st.f_files,
869 (unsigned long long)(st.f_files - st.f_ffree),
870 (unsigned long long)st.f_favail,
Darren Tuckerb5082e92010-01-08 18:51:47 +1100871 (unsigned long long)st.f_ffree, ffree);
Damien Millerd671e5a2008-05-19 14:53:33 +1000872 } else if (hflag) {
873 strlcpy(s_used, "error", sizeof(s_used));
874 strlcpy(s_avail, "error", sizeof(s_avail));
875 strlcpy(s_root, "error", sizeof(s_root));
876 strlcpy(s_total, "error", sizeof(s_total));
877 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
878 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
879 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
880 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
881 printf(" Size Used Avail (root) %%Capacity\n");
882 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
883 s_total, s_used, s_avail, s_root,
884 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
885 st.f_blocks));
886 } else {
887 printf(" Size Used Avail "
888 "(root) %%Capacity\n");
889 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
890 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
891 (unsigned long long)(st.f_frsize *
892 (st.f_blocks - st.f_bfree) / 1024),
893 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
894 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
895 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
896 st.f_blocks));
897 }
898 return 0;
899}
900
Damien Miller1cbc2922007-10-26 14:27:45 +1000901/*
902 * Undo escaping of glob sequences in place. Used to undo extra escaping
903 * applied in makeargv() when the string is destined for a function that
904 * does not glob it.
905 */
906static void
907undo_glob_escape(char *s)
908{
909 size_t i, j;
910
911 for (i = j = 0;;) {
912 if (s[i] == '\0') {
913 s[j] = '\0';
914 return;
915 }
916 if (s[i] != '\\') {
917 s[j++] = s[i++];
918 continue;
919 }
920 /* s[i] == '\\' */
921 ++i;
922 switch (s[i]) {
923 case '?':
924 case '[':
925 case '*':
926 case '\\':
927 s[j++] = s[i++];
928 break;
929 case '\0':
930 s[j++] = '\\';
931 s[j] = '\0';
932 return;
933 default:
934 s[j++] = '\\';
935 s[j++] = s[i++];
936 break;
937 }
938 }
939}
940
941/*
942 * Split a string into an argument vector using sh(1)-style quoting,
943 * comment and escaping rules, but with some tweaks to handle glob(3)
944 * wildcards.
Darren Tucker909d8582010-01-08 19:02:40 +1100945 * The "sloppy" flag allows for recovery from missing terminating quote, for
946 * use in parsing incomplete commandlines during tab autocompletion.
947 *
Damien Miller1cbc2922007-10-26 14:27:45 +1000948 * Returns NULL on error or a NULL-terminated array of arguments.
Darren Tucker909d8582010-01-08 19:02:40 +1100949 *
950 * If "lastquote" is not NULL, the quoting character used for the last
951 * argument is placed in *lastquote ("\0", "'" or "\"").
952 *
953 * If "terminated" is not NULL, *terminated will be set to 1 when the
954 * last argument's quote has been properly terminated or 0 otherwise.
955 * This parameter is only of use if "sloppy" is set.
Damien Miller1cbc2922007-10-26 14:27:45 +1000956 */
957#define MAXARGS 128
958#define MAXARGLEN 8192
959static char **
Darren Tucker909d8582010-01-08 19:02:40 +1100960makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
961 u_int *terminated)
Damien Miller1cbc2922007-10-26 14:27:45 +1000962{
963 int argc, quot;
964 size_t i, j;
965 static char argvs[MAXARGLEN];
966 static char *argv[MAXARGS + 1];
967 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
968
969 *argcp = argc = 0;
970 if (strlen(arg) > sizeof(argvs) - 1) {
971 args_too_longs:
972 error("string too long");
973 return NULL;
974 }
Darren Tucker909d8582010-01-08 19:02:40 +1100975 if (terminated != NULL)
976 *terminated = 1;
977 if (lastquote != NULL)
978 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +1000979 state = MA_START;
980 i = j = 0;
981 for (;;) {
982 if (isspace(arg[i])) {
983 if (state == MA_UNQUOTED) {
984 /* Terminate current argument */
985 argvs[j++] = '\0';
986 argc++;
987 state = MA_START;
988 } else if (state != MA_START)
989 argvs[j++] = arg[i];
990 } else if (arg[i] == '"' || arg[i] == '\'') {
991 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
992 if (state == MA_START) {
993 argv[argc] = argvs + j;
994 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +1100995 if (lastquote != NULL)
996 *lastquote = arg[i];
Damien Miller1cbc2922007-10-26 14:27:45 +1000997 } else if (state == MA_UNQUOTED)
998 state = q;
999 else if (state == q)
1000 state = MA_UNQUOTED;
1001 else
1002 argvs[j++] = arg[i];
1003 } else if (arg[i] == '\\') {
1004 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1005 quot = state == MA_SQUOTE ? '\'' : '"';
1006 /* Unescape quote we are in */
1007 /* XXX support \n and friends? */
1008 if (arg[i + 1] == quot) {
1009 i++;
1010 argvs[j++] = arg[i];
1011 } else if (arg[i + 1] == '?' ||
1012 arg[i + 1] == '[' || arg[i + 1] == '*') {
1013 /*
1014 * Special case for sftp: append
1015 * double-escaped glob sequence -
1016 * glob will undo one level of
1017 * escaping. NB. string can grow here.
1018 */
1019 if (j >= sizeof(argvs) - 5)
1020 goto args_too_longs;
1021 argvs[j++] = '\\';
1022 argvs[j++] = arg[i++];
1023 argvs[j++] = '\\';
1024 argvs[j++] = arg[i];
1025 } else {
1026 argvs[j++] = arg[i++];
1027 argvs[j++] = arg[i];
1028 }
1029 } else {
1030 if (state == MA_START) {
1031 argv[argc] = argvs + j;
1032 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001033 if (lastquote != NULL)
1034 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001035 }
1036 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1037 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1038 /*
1039 * Special case for sftp: append
1040 * escaped glob sequence -
1041 * glob will undo one level of
1042 * escaping.
1043 */
1044 argvs[j++] = arg[i++];
1045 argvs[j++] = arg[i];
1046 } else {
1047 /* Unescape everything */
1048 /* XXX support \n and friends? */
1049 i++;
1050 argvs[j++] = arg[i];
1051 }
1052 }
1053 } else if (arg[i] == '#') {
1054 if (state == MA_SQUOTE || state == MA_DQUOTE)
1055 argvs[j++] = arg[i];
1056 else
1057 goto string_done;
1058 } else if (arg[i] == '\0') {
1059 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001060 if (sloppy) {
1061 state = MA_UNQUOTED;
1062 if (terminated != NULL)
1063 *terminated = 0;
1064 goto string_done;
1065 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001066 error("Unterminated quoted argument");
1067 return NULL;
1068 }
1069 string_done:
1070 if (state == MA_UNQUOTED) {
1071 argvs[j++] = '\0';
1072 argc++;
1073 }
1074 break;
1075 } else {
1076 if (state == MA_START) {
1077 argv[argc] = argvs + j;
1078 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001079 if (lastquote != NULL)
1080 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001081 }
1082 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1083 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1084 /*
1085 * Special case for sftp: escape quoted
1086 * glob(3) wildcards. NB. string can grow
1087 * here.
1088 */
1089 if (j >= sizeof(argvs) - 3)
1090 goto args_too_longs;
1091 argvs[j++] = '\\';
1092 argvs[j++] = arg[i];
1093 } else
1094 argvs[j++] = arg[i];
1095 }
1096 i++;
1097 }
1098 *argcp = argc;
1099 return argv;
1100}
1101
Damien Miller20e1fab2004-02-18 14:30:55 +11001102static int
Darren Tucker909d8582010-01-08 19:02:40 +11001103parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag,
1104 int *hflag, unsigned long *n_arg, char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001105{
1106 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001107 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001108 int base = 0;
1109 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001110 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001111
1112 /* Skip leading whitespace */
1113 cp = cp + strspn(cp, WHITESPACE);
1114
Damien Miller20e1fab2004-02-18 14:30:55 +11001115 /* Check for leading '-' (disable error processing) */
1116 *iflag = 0;
1117 if (*cp == '-') {
1118 *iflag = 1;
1119 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001120 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001121 }
1122
Darren Tucker70cc0922010-01-09 22:28:03 +11001123 /* Ignore blank lines and lines which begin with comment '#' char */
1124 if (*cp == '\0' || *cp == '#')
1125 return (0);
1126
Darren Tucker909d8582010-01-08 19:02:40 +11001127 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001128 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001129
Damien Miller1cbc2922007-10-26 14:27:45 +10001130 /* Figure out which command we have */
1131 for (i = 0; cmds[i].c != NULL; i++) {
1132 if (strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001133 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001134 }
1135 cmdnum = cmds[i].n;
1136 cmd = cmds[i].c;
1137
1138 /* Special case */
1139 if (*cp == '!') {
1140 cp++;
1141 cmdnum = I_SHELL;
1142 } else if (cmdnum == -1) {
1143 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001144 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001145 }
1146
1147 /* Get arguments and parse flags */
Darren Tucker1b0dd172009-10-07 08:37:48 +11001148 *lflag = *pflag = *rflag = *hflag = *n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001149 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001150 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001151 switch (cmdnum) {
1152 case I_GET:
1153 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001154 if ((optidx = parse_getput_flags(cmd, argv, argc, pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001155 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001156 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001157 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001158 error("You must specify at least one path after a "
1159 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001160 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001161 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001162 *path1 = xstrdup(argv[optidx]);
1163 /* Get second pathname (optional) */
1164 if (argc - optidx > 1) {
1165 *path2 = xstrdup(argv[optidx + 1]);
1166 /* Destination is not globbed */
1167 undo_glob_escape(*path2);
1168 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001169 break;
1170 case I_RENAME:
1171 case I_SYMLINK:
Damien Miller1cbc2922007-10-26 14:27:45 +10001172 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001173 error("You must specify two paths after a %s "
1174 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001175 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001176 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001177 *path1 = xstrdup(argv[optidx]);
1178 *path2 = xstrdup(argv[optidx + 1]);
1179 /* Paths are not globbed */
1180 undo_glob_escape(*path1);
1181 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001182 break;
1183 case I_RM:
1184 case I_MKDIR:
1185 case I_RMDIR:
1186 case I_CHDIR:
1187 case I_LCHDIR:
1188 case I_LMKDIR:
1189 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001190 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001191 error("You must specify a path after a %s command.",
1192 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001193 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001194 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001195 *path1 = xstrdup(argv[optidx]);
1196 /* Only "rm" globs */
1197 if (cmdnum != I_RM)
1198 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001199 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001200 case I_DF:
1201 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1202 iflag)) == -1)
1203 return -1;
1204 /* Default to current directory if no path specified */
1205 if (argc - optidx < 1)
1206 *path1 = NULL;
1207 else {
1208 *path1 = xstrdup(argv[optidx]);
1209 undo_glob_escape(*path1);
1210 }
1211 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001212 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001213 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001214 return(-1);
1215 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001216 if (argc - optidx > 0)
1217 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001218 break;
1219 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001220 /* Skip ls command and following whitespace */
1221 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001222 case I_SHELL:
1223 /* Uses the rest of the line */
1224 break;
1225 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001226 case I_CHMOD:
1227 base = 8;
1228 case I_CHOWN:
1229 case I_CHGRP:
1230 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001231 if (argc - optidx < 1)
1232 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001233 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001234 l = strtol(argv[optidx], &cp2, base);
1235 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1236 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1237 l < 0) {
1238 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001239 error("You must supply a numeric argument "
1240 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001241 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001242 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001243 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001244 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001245 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001246 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001247 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001248 error("You must specify a path after a %s command.",
1249 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001250 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001251 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001252 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001253 break;
1254 case I_QUIT:
1255 case I_PWD:
1256 case I_LPWD:
1257 case I_HELP:
1258 case I_VERSION:
1259 case I_PROGRESS:
1260 break;
1261 default:
1262 fatal("Command not implemented");
1263 }
1264
1265 *cpp = cp;
1266 return(cmdnum);
1267}
1268
1269static int
1270parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1271 int err_abort)
1272{
1273 char *path1, *path2, *tmp;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001274 int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001275 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001276 Attrib a, *aa;
1277 char path_buf[MAXPATHLEN];
1278 int err = 0;
1279 glob_t g;
1280
1281 path1 = path2 = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001282 cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag, &n_arg,
Damien Miller20e1fab2004-02-18 14:30:55 +11001283 &path1, &path2);
1284
1285 if (iflag != 0)
1286 err_abort = 0;
1287
1288 memset(&g, 0, sizeof(g));
1289
1290 /* Perform command */
1291 switch (cmdnum) {
1292 case 0:
1293 /* Blank line */
1294 break;
1295 case -1:
1296 /* Unrecognized command */
1297 err = -1;
1298 break;
1299 case I_GET:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001300 err = process_get(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001301 break;
1302 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001303 err = process_put(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001304 break;
1305 case I_RENAME:
1306 path1 = make_absolute(path1, *pwd);
1307 path2 = make_absolute(path2, *pwd);
1308 err = do_rename(conn, path1, path2);
1309 break;
1310 case I_SYMLINK:
1311 path2 = make_absolute(path2, *pwd);
1312 err = do_symlink(conn, path1, path2);
1313 break;
1314 case I_RM:
1315 path1 = make_absolute(path1, *pwd);
1316 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001317 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001318 printf("Removing %s\n", g.gl_pathv[i]);
1319 err = do_rm(conn, g.gl_pathv[i]);
1320 if (err != 0 && err_abort)
1321 break;
1322 }
1323 break;
1324 case I_MKDIR:
1325 path1 = make_absolute(path1, *pwd);
1326 attrib_clear(&a);
1327 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1328 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001329 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001330 break;
1331 case I_RMDIR:
1332 path1 = make_absolute(path1, *pwd);
1333 err = do_rmdir(conn, path1);
1334 break;
1335 case I_CHDIR:
1336 path1 = make_absolute(path1, *pwd);
1337 if ((tmp = do_realpath(conn, path1)) == NULL) {
1338 err = 1;
1339 break;
1340 }
1341 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1342 xfree(tmp);
1343 err = 1;
1344 break;
1345 }
1346 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1347 error("Can't change directory: Can't check target");
1348 xfree(tmp);
1349 err = 1;
1350 break;
1351 }
1352 if (!S_ISDIR(aa->perm)) {
1353 error("Can't change directory: \"%s\" is not "
1354 "a directory", tmp);
1355 xfree(tmp);
1356 err = 1;
1357 break;
1358 }
1359 xfree(*pwd);
1360 *pwd = tmp;
1361 break;
1362 case I_LS:
1363 if (!path1) {
1364 do_globbed_ls(conn, *pwd, *pwd, lflag);
1365 break;
1366 }
1367
1368 /* Strip pwd off beginning of non-absolute paths */
1369 tmp = NULL;
1370 if (*path1 != '/')
1371 tmp = *pwd;
1372
1373 path1 = make_absolute(path1, *pwd);
1374 err = do_globbed_ls(conn, path1, tmp, lflag);
1375 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001376 case I_DF:
1377 /* Default to current directory if no path specified */
1378 if (path1 == NULL)
1379 path1 = xstrdup(*pwd);
1380 path1 = make_absolute(path1, *pwd);
1381 err = do_df(conn, path1, hflag, iflag);
1382 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001383 case I_LCHDIR:
1384 if (chdir(path1) == -1) {
1385 error("Couldn't change local directory to "
1386 "\"%s\": %s", path1, strerror(errno));
1387 err = 1;
1388 }
1389 break;
1390 case I_LMKDIR:
1391 if (mkdir(path1, 0777) == -1) {
1392 error("Couldn't create local directory "
1393 "\"%s\": %s", path1, strerror(errno));
1394 err = 1;
1395 }
1396 break;
1397 case I_LLS:
1398 local_do_ls(cmd);
1399 break;
1400 case I_SHELL:
1401 local_do_shell(cmd);
1402 break;
1403 case I_LUMASK:
1404 umask(n_arg);
1405 printf("Local umask: %03lo\n", n_arg);
1406 break;
1407 case I_CHMOD:
1408 path1 = make_absolute(path1, *pwd);
1409 attrib_clear(&a);
1410 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1411 a.perm = n_arg;
1412 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001413 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001414 printf("Changing mode on %s\n", g.gl_pathv[i]);
1415 err = do_setstat(conn, g.gl_pathv[i], &a);
1416 if (err != 0 && err_abort)
1417 break;
1418 }
1419 break;
1420 case I_CHOWN:
1421 case I_CHGRP:
1422 path1 = make_absolute(path1, *pwd);
1423 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001424 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001425 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001426 if (err_abort) {
1427 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001428 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001429 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001430 continue;
1431 }
1432 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1433 error("Can't get current ownership of "
1434 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001435 if (err_abort) {
1436 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001437 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001438 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001439 continue;
1440 }
1441 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1442 if (cmdnum == I_CHOWN) {
1443 printf("Changing owner on %s\n", g.gl_pathv[i]);
1444 aa->uid = n_arg;
1445 } else {
1446 printf("Changing group on %s\n", g.gl_pathv[i]);
1447 aa->gid = n_arg;
1448 }
1449 err = do_setstat(conn, g.gl_pathv[i], aa);
1450 if (err != 0 && err_abort)
1451 break;
1452 }
1453 break;
1454 case I_PWD:
1455 printf("Remote working directory: %s\n", *pwd);
1456 break;
1457 case I_LPWD:
1458 if (!getcwd(path_buf, sizeof(path_buf))) {
1459 error("Couldn't get local cwd: %s", strerror(errno));
1460 err = -1;
1461 break;
1462 }
1463 printf("Local working directory: %s\n", path_buf);
1464 break;
1465 case I_QUIT:
1466 /* Processed below */
1467 break;
1468 case I_HELP:
1469 help();
1470 break;
1471 case I_VERSION:
1472 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1473 break;
1474 case I_PROGRESS:
1475 showprogress = !showprogress;
1476 if (showprogress)
1477 printf("Progress meter enabled\n");
1478 else
1479 printf("Progress meter disabled\n");
1480 break;
1481 default:
1482 fatal("%d is not implemented", cmdnum);
1483 }
1484
1485 if (g.gl_pathc)
1486 globfree(&g);
1487 if (path1)
1488 xfree(path1);
1489 if (path2)
1490 xfree(path2);
1491
1492 /* If an unignored error occurs in batch mode we should abort. */
1493 if (err_abort && err != 0)
1494 return (-1);
1495 else if (cmdnum == I_QUIT)
1496 return (1);
1497
1498 return (0);
1499}
1500
Darren Tucker2d963d82004-11-07 20:04:10 +11001501#ifdef USE_LIBEDIT
1502static char *
1503prompt(EditLine *el)
1504{
1505 return ("sftp> ");
1506}
Darren Tucker2d963d82004-11-07 20:04:10 +11001507
Darren Tucker909d8582010-01-08 19:02:40 +11001508/* Display entries in 'list' after skipping the first 'len' chars */
1509static void
1510complete_display(char **list, u_int len)
1511{
1512 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1513 struct winsize ws;
1514 char *tmp;
1515
1516 /* Count entries for sort and find longest */
1517 for (y = 0; list[y]; y++)
1518 m = MAX(m, strlen(list[y]));
1519
1520 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1521 width = ws.ws_col;
1522
1523 m = m > len ? m - len : 0;
1524 columns = width / (m + 2);
1525 columns = MAX(columns, 1);
1526 colspace = width / columns;
1527 colspace = MIN(colspace, width);
1528
1529 printf("\n");
1530 m = 1;
1531 for (y = 0; list[y]; y++) {
1532 llen = strlen(list[y]);
1533 tmp = llen > len ? list[y] + len : "";
1534 printf("%-*s", colspace, tmp);
1535 if (m >= columns) {
1536 printf("\n");
1537 m = 1;
1538 } else
1539 m++;
1540 }
1541 printf("\n");
1542}
1543
1544/*
1545 * Given a "list" of words that begin with a common prefix of "word",
1546 * attempt to find an autocompletion to extends "word" by the next
1547 * characters common to all entries in "list".
1548 */
1549static char *
1550complete_ambiguous(const char *word, char **list, size_t count)
1551{
1552 if (word == NULL)
1553 return NULL;
1554
1555 if (count > 0) {
1556 u_int y, matchlen = strlen(list[0]);
1557
1558 /* Find length of common stem */
1559 for (y = 1; list[y]; y++) {
1560 u_int x;
1561
1562 for (x = 0; x < matchlen; x++)
1563 if (list[0][x] != list[y][x])
1564 break;
1565
1566 matchlen = x;
1567 }
1568
1569 if (matchlen > strlen(word)) {
1570 char *tmp = xstrdup(list[0]);
1571
Darren Tucker340d1682010-01-09 08:54:31 +11001572 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001573 return tmp;
1574 }
1575 }
1576
1577 return xstrdup(word);
1578}
1579
1580/* Autocomplete a sftp command */
1581static int
1582complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1583 int terminated)
1584{
1585 u_int y, count = 0, cmdlen, tmplen;
1586 char *tmp, **list, argterm[3];
1587 const LineInfo *lf;
1588
1589 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1590
1591 /* No command specified: display all available commands */
1592 if (cmd == NULL) {
1593 for (y = 0; cmds[y].c; y++)
1594 list[count++] = xstrdup(cmds[y].c);
1595
1596 list[count] = NULL;
1597 complete_display(list, 0);
1598
1599 for (y = 0; list[y] != NULL; y++)
1600 xfree(list[y]);
1601 xfree(list);
1602 return count;
1603 }
1604
1605 /* Prepare subset of commands that start with "cmd" */
1606 cmdlen = strlen(cmd);
1607 for (y = 0; cmds[y].c; y++) {
1608 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
1609 list[count++] = xstrdup(cmds[y].c);
1610 }
1611 list[count] = NULL;
1612
1613 if (count == 0)
1614 return 0;
1615
1616 /* Complete ambigious command */
1617 tmp = complete_ambiguous(cmd, list, count);
1618 if (count > 1)
1619 complete_display(list, 0);
1620
1621 for (y = 0; list[y]; y++)
1622 xfree(list[y]);
1623 xfree(list);
1624
1625 if (tmp != NULL) {
1626 tmplen = strlen(tmp);
1627 cmdlen = strlen(cmd);
1628 /* If cmd may be extended then do so */
1629 if (tmplen > cmdlen)
1630 if (el_insertstr(el, tmp + cmdlen) == -1)
1631 fatal("el_insertstr failed.");
1632 lf = el_line(el);
1633 /* Terminate argument cleanly */
1634 if (count == 1) {
1635 y = 0;
1636 if (!terminated)
1637 argterm[y++] = quote;
1638 if (lastarg || *(lf->cursor) != ' ')
1639 argterm[y++] = ' ';
1640 argterm[y] = '\0';
1641 if (y > 0 && el_insertstr(el, argterm) == -1)
1642 fatal("el_insertstr failed.");
1643 }
1644 xfree(tmp);
1645 }
1646
1647 return count;
1648}
1649
1650/*
1651 * Determine whether a particular sftp command's arguments (if any)
1652 * represent local or remote files.
1653 */
1654static int
1655complete_is_remote(char *cmd) {
1656 int i;
1657
1658 if (cmd == NULL)
1659 return -1;
1660
1661 for (i = 0; cmds[i].c; i++) {
1662 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
1663 return cmds[i].t;
1664 }
1665
1666 return -1;
1667}
1668
1669/* Autocomplete a filename "file" */
1670static int
1671complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1672 char *file, int remote, int lastarg, char quote, int terminated)
1673{
1674 glob_t g;
1675 char *tmp, *tmp2, ins[3];
1676 u_int i, hadglob, pwdlen, len, tmplen, filelen;
1677 const LineInfo *lf;
1678
1679 /* Glob from "file" location */
1680 if (file == NULL)
1681 tmp = xstrdup("*");
1682 else
1683 xasprintf(&tmp, "%s*", file);
1684
1685 memset(&g, 0, sizeof(g));
1686 if (remote != LOCAL) {
1687 tmp = make_absolute(tmp, remote_path);
1688 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1689 } else
1690 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1691
1692 /* Determine length of pwd so we can trim completion display */
1693 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1694 /* Terminate counting on first unescaped glob metacharacter */
1695 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1696 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1697 hadglob = 1;
1698 break;
1699 }
1700 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1701 tmplen++;
1702 if (tmp[tmplen] == '/')
1703 pwdlen = tmplen + 1; /* track last seen '/' */
1704 }
1705 xfree(tmp);
1706
1707 if (g.gl_matchc == 0)
1708 goto out;
1709
1710 if (g.gl_matchc > 1)
1711 complete_display(g.gl_pathv, pwdlen);
1712
1713 tmp = NULL;
1714 /* Don't try to extend globs */
1715 if (file == NULL || hadglob)
1716 goto out;
1717
1718 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
1719 tmp = path_strip(tmp2, remote_path);
1720 xfree(tmp2);
1721
1722 if (tmp == NULL)
1723 goto out;
1724
1725 tmplen = strlen(tmp);
1726 filelen = strlen(file);
1727
1728 if (tmplen > filelen) {
1729 tmp2 = tmp + filelen;
1730 len = strlen(tmp2);
1731 /* quote argument on way out */
1732 for (i = 0; i < len; i++) {
1733 ins[0] = '\\';
1734 ins[1] = tmp2[i];
1735 ins[2] = '\0';
1736 switch (tmp2[i]) {
1737 case '\'':
1738 case '"':
1739 case '\\':
1740 case '\t':
1741 case ' ':
1742 if (quote == '\0' || tmp2[i] == quote) {
1743 if (el_insertstr(el, ins) == -1)
1744 fatal("el_insertstr "
1745 "failed.");
1746 break;
1747 }
1748 /* FALLTHROUGH */
1749 default:
1750 if (el_insertstr(el, ins + 1) == -1)
1751 fatal("el_insertstr failed.");
1752 break;
1753 }
1754 }
1755 }
1756
1757 lf = el_line(el);
1758 /*
1759 * XXX should we really extend here? the user may not be done if
1760 * the filename is a directory.
1761 */
1762 if (g.gl_matchc == 1) {
1763 i = 0;
1764 if (!terminated)
1765 ins[i++] = quote;
1766 if (lastarg || *(lf->cursor) != ' ')
1767 ins[i++] = ' ';
1768 ins[i] = '\0';
1769 if (i > 0 && el_insertstr(el, ins) == -1)
1770 fatal("el_insertstr failed.");
1771 }
1772 xfree(tmp);
1773
1774 out:
1775 globfree(&g);
1776 return g.gl_matchc;
1777}
1778
1779/* tab-completion hook function, called via libedit */
1780static unsigned char
1781complete(EditLine *el, int ch)
1782{
1783 char **argv, *line, quote;
1784 u_int argc, carg, cursor, len, terminated, ret = CC_ERROR;
1785 const LineInfo *lf;
1786 struct complete_ctx *complete_ctx;
1787
1788 lf = el_line(el);
1789 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1790 fatal("%s: el_get failed", __func__);
1791
1792 /* Figure out which argument the cursor points to */
1793 cursor = lf->cursor - lf->buffer;
1794 line = (char *)xmalloc(cursor + 1);
1795 memcpy(line, lf->buffer, cursor);
1796 line[cursor] = '\0';
1797 argv = makeargv(line, &carg, 1, &quote, &terminated);
1798 xfree(line);
1799
1800 /* Get all the arguments on the line */
1801 len = lf->lastchar - lf->buffer;
1802 line = (char *)xmalloc(len + 1);
1803 memcpy(line, lf->buffer, len);
1804 line[len] = '\0';
1805 argv = makeargv(line, &argc, 1, NULL, NULL);
1806
1807 /* Ensure cursor is at EOL or a argument boundary */
1808 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1809 line[cursor] != '\n') {
1810 xfree(line);
1811 return ret;
1812 }
1813
1814 if (carg == 0) {
1815 /* Show all available commands */
1816 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1817 ret = CC_REDISPLAY;
1818 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1819 /* Handle the command parsing */
1820 if (complete_cmd_parse(el, argv[0], argc == carg,
1821 quote, terminated) != 0)
1822 ret = CC_REDISPLAY;
1823 } else if (carg >= 1) {
1824 /* Handle file parsing */
1825 int remote = complete_is_remote(argv[0]);
1826 char *filematch = NULL;
1827
1828 if (carg > 1 && line[cursor-1] != ' ')
1829 filematch = argv[carg - 1];
1830
1831 if (remote != 0 &&
1832 complete_match(el, complete_ctx->conn,
1833 *complete_ctx->remote_pathp, filematch,
1834 remote, carg == argc, quote, terminated) != 0)
1835 ret = CC_REDISPLAY;
1836 }
1837
1838 xfree(line);
1839 return ret;
1840}
Darren Tuckere67f7db2010-01-08 19:50:02 +11001841#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11001842
Damien Miller20e1fab2004-02-18 14:30:55 +11001843int
Darren Tucker21063192010-01-08 17:10:36 +11001844interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001845{
Darren Tucker909d8582010-01-08 19:02:40 +11001846 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11001847 char *dir = NULL;
1848 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10001849 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11001850 EditLine *el = NULL;
1851#ifdef USE_LIBEDIT
1852 History *hl = NULL;
1853 HistEvent hev;
1854 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11001855 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11001856
1857 if (!batchmode && isatty(STDIN_FILENO)) {
1858 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1859 fatal("Couldn't initialise editline");
1860 if ((hl = history_init()) == NULL)
1861 fatal("Couldn't initialise editline history");
1862 history(hl, &hev, H_SETSIZE, 100);
1863 el_set(el, EL_HIST, history, hl);
1864
1865 el_set(el, EL_PROMPT, prompt);
1866 el_set(el, EL_EDITOR, "emacs");
1867 el_set(el, EL_TERMINAL, NULL);
1868 el_set(el, EL_SIGNAL, 1);
1869 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11001870
1871 /* Tab Completion */
1872 el_set(el, EL_ADDFN, "ftp-complete",
1873 "Context senstive argument completion", complete);
1874 complete_ctx.conn = conn;
1875 complete_ctx.remote_pathp = &remote_path;
1876 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
1877 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11001878 }
1879#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001880
Darren Tucker909d8582010-01-08 19:02:40 +11001881 remote_path = do_realpath(conn, ".");
1882 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11001883 fatal("Need cwd");
1884
1885 if (file1 != NULL) {
1886 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11001887 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11001888
1889 if (remote_is_dir(conn, dir) && file2 == NULL) {
1890 printf("Changing to: %s\n", dir);
1891 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001892 if (parse_dispatch_command(conn, cmd,
1893 &remote_path, 1) != 0) {
Darren Tuckercd516ef2004-12-06 22:43:43 +11001894 xfree(dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001895 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001896 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001897 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11001898 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001899 } else {
1900 if (file2 == NULL)
1901 snprintf(cmd, sizeof cmd, "get %s", dir);
1902 else
1903 snprintf(cmd, sizeof cmd, "get %s %s", dir,
1904 file2);
1905
Darren Tucker909d8582010-01-08 19:02:40 +11001906 err = parse_dispatch_command(conn, cmd,
1907 &remote_path, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001908 xfree(dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001909 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001910 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001911 return (err);
1912 }
1913 xfree(dir);
1914 }
1915
Darren Tucker93e7e8f2005-08-23 08:06:55 +10001916#if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF)
Damien Miller20e1fab2004-02-18 14:30:55 +11001917 setvbuf(stdout, NULL, _IOLBF, 0);
1918 setvbuf(infile, NULL, _IOLBF, 0);
1919#else
Damien Miller37294fb2005-07-17 17:18:49 +10001920 setlinebuf(stdout);
1921 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11001922#endif
1923
Damien Miller0e2c1022005-08-12 22:16:22 +10001924 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11001925 err = 0;
1926 for (;;) {
1927 char *cp;
1928
Darren Tuckercdf547a2004-05-24 10:12:19 +10001929 signal(SIGINT, SIG_IGN);
1930
Darren Tucker2d963d82004-11-07 20:04:10 +11001931 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001932 if (interactive)
1933 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11001934 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001935 if (interactive)
1936 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11001937 break;
1938 }
Damien Miller0e2c1022005-08-12 22:16:22 +10001939 if (!interactive) { /* Echo command */
1940 printf("sftp> %s", cmd);
1941 if (strlen(cmd) > 0 &&
1942 cmd[strlen(cmd) - 1] != '\n')
1943 printf("\n");
1944 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001945 } else {
1946#ifdef USE_LIBEDIT
1947 const char *line;
1948 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001949
Darren Tucker909d8582010-01-08 19:02:40 +11001950 if ((line = el_gets(el, &count)) == NULL ||
1951 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001952 printf("\n");
1953 break;
1954 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001955 history(hl, &hev, H_ENTER, line);
1956 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
1957 fprintf(stderr, "Error: input line too long\n");
1958 continue;
1959 }
1960#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001961 }
1962
Damien Miller20e1fab2004-02-18 14:30:55 +11001963 cp = strrchr(cmd, '\n');
1964 if (cp)
1965 *cp = '\0';
1966
Darren Tuckercdf547a2004-05-24 10:12:19 +10001967 /* Handle user interrupts gracefully during commands */
1968 interrupted = 0;
1969 signal(SIGINT, cmd_interrupt);
1970
Darren Tucker909d8582010-01-08 19:02:40 +11001971 err = parse_dispatch_command(conn, cmd, &remote_path,
1972 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11001973 if (err != 0)
1974 break;
1975 }
Darren Tucker909d8582010-01-08 19:02:40 +11001976 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001977 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001978
Tim Rice027e8b12005-08-15 14:52:50 -07001979#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10001980 if (el != NULL)
1981 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07001982#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10001983
Damien Miller20e1fab2004-02-18 14:30:55 +11001984 /* err == 1 signifies normal "quit" exit */
1985 return (err >= 0 ? 0 : -1);
1986}
Damien Miller62d57f62003-01-10 21:43:24 +11001987
Ben Lindstrombba81212001-06-25 05:01:22 +00001988static void
Damien Millercc685c12003-06-04 22:51:38 +10001989connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11001990{
1991 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001992
Damien Miller33804262001-02-04 23:20:18 +11001993#ifdef USE_PIPES
1994 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001995
Damien Miller33804262001-02-04 23:20:18 +11001996 if ((pipe(pin) == -1) || (pipe(pout) == -1))
1997 fatal("pipe: %s", strerror(errno));
1998 *in = pin[0];
1999 *out = pout[1];
2000 c_in = pout[0];
2001 c_out = pin[1];
2002#else /* USE_PIPES */
2003 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002004
Damien Miller33804262001-02-04 23:20:18 +11002005 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2006 fatal("socketpair: %s", strerror(errno));
2007 *in = *out = inout[0];
2008 c_in = c_out = inout[1];
2009#endif /* USE_PIPES */
2010
Damien Millercc685c12003-06-04 22:51:38 +10002011 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002012 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002013 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002014 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2015 (dup2(c_out, STDOUT_FILENO) == -1)) {
2016 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002017 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002018 }
2019 close(*in);
2020 close(*out);
2021 close(c_in);
2022 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002023
2024 /*
2025 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002026 * ignore SIGINT if we want to gracefully abort commands,
2027 * otherwise the signal will make it to the ssh process and
Darren Tuckercdf547a2004-05-24 10:12:19 +10002028 * kill it too
2029 */
2030 signal(SIGINT, SIG_IGN);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002031 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002032 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002033 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002034 }
2035
Damien Millercc685c12003-06-04 22:51:38 +10002036 signal(SIGTERM, killchild);
2037 signal(SIGINT, killchild);
2038 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11002039 close(c_in);
2040 close(c_out);
2041}
2042
Ben Lindstrombba81212001-06-25 05:01:22 +00002043static void
Damien Miller33804262001-02-04 23:20:18 +11002044usage(void)
2045{
Damien Miller025e01c2002-02-08 22:06:29 +11002046 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002047
Ben Lindstrom1e243242001-09-18 05:38:44 +00002048 fprintf(stderr,
Darren Tucker1b0dd172009-10-07 08:37:48 +11002049 "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002050 " [-D sftp_server_path] [-F ssh_config] "
2051 "[-i identity_file]\n"
2052 " [-o ssh_option] [-P port] [-R num_requests] "
2053 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002054 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002055 " %s [user@]host[:file ...]\n"
2056 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002057 " %s -b batchfile [user@]host\n",
2058 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002059 exit(1);
2060}
2061
Kevin Stevesef4eea92001-02-05 12:42:17 +00002062int
Damien Miller33804262001-02-04 23:20:18 +11002063main(int argc, char **argv)
2064{
Damien Miller956f3fb2003-01-10 21:40:00 +11002065 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002066 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002067 int debug_level = 0, sshver = 2;
2068 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002069 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002070 LogLevel ll = SYSLOG_LEVEL_INFO;
2071 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002072 extern int optind;
2073 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002074 struct sftp_conn *conn;
2075 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2076 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller33804262001-02-04 23:20:18 +11002077
Darren Tuckerce321d82005-10-03 18:11:24 +10002078 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2079 sanitise_stdfd();
2080
Damien Miller59d3d5b2003-08-22 09:34:41 +10002081 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002082 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002083 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002084 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002085 addargs(&args, "-oForwardX11 no");
2086 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002087 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002088 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002089
Ben Lindstrom387c4722001-05-08 20:27:25 +00002090 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002091 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002092
Darren Tucker282b4022009-10-07 08:23:06 +11002093 while ((ch = getopt(argc, argv,
Darren Tucker1b0dd172009-10-07 08:37:48 +11002094 "1246hqrvCc:D:i:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002095 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002096 /* Passed through to ssh(1) */
2097 case '4':
2098 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002099 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002100 addargs(&args, "-%c", ch);
2101 break;
2102 /* Passed through to ssh(1) with argument */
2103 case 'F':
2104 case 'c':
2105 case 'i':
2106 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002107 addargs(&args, "-%c", ch);
2108 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002109 break;
2110 case 'q':
2111 showprogress = 0;
2112 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002113 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002114 case 'P':
2115 addargs(&args, "-oPort %s", optarg);
2116 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002117 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002118 if (debug_level < 3) {
2119 addargs(&args, "-v");
2120 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2121 }
2122 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002123 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002124 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002125 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002126 if (sftp_server == NULL)
2127 sftp_server = _PATH_SFTP_SERVER;
2128 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002129 case '2':
2130 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002131 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002132 case 'B':
2133 copy_buffer_len = strtol(optarg, &cp, 10);
2134 if (copy_buffer_len == 0 || *cp != '\0')
2135 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002136 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002137 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002138 if (batchmode)
2139 fatal("Batch file already specified.");
2140
2141 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002142 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002143 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002144 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002145 showprogress = 0;
Damien Miller44f75c12004-01-21 10:58:47 +11002146 batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002147 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002148 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002149 case 'p':
2150 global_pflag = 1;
2151 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002152 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002153 sftp_direct = optarg;
2154 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002155 case 'r':
2156 global_rflag = 1;
2157 break;
Damien Miller16a13332002-02-13 14:03:56 +11002158 case 'R':
2159 num_requests = strtol(optarg, &cp, 10);
2160 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002161 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002162 optarg);
2163 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002164 case 's':
2165 sftp_server = optarg;
2166 break;
2167 case 'S':
2168 ssh_program = optarg;
2169 replacearg(&args, 0, "%s", ssh_program);
2170 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002171 case 'h':
2172 default:
Damien Miller33804262001-02-04 23:20:18 +11002173 usage();
2174 }
2175 }
2176
Damien Millerc0f27d82004-03-08 23:12:19 +11002177 if (!isatty(STDERR_FILENO))
2178 showprogress = 0;
2179
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002180 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2181
Damien Millerd14ee1e2002-02-05 12:27:31 +11002182 if (sftp_direct == NULL) {
2183 if (optind == argc || argc > (optind + 2))
2184 usage();
Damien Miller33804262001-02-04 23:20:18 +11002185
Damien Millerd14ee1e2002-02-05 12:27:31 +11002186 userhost = xstrdup(argv[optind]);
2187 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002188
Ben Lindstromc276c122002-12-23 02:14:51 +00002189 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002190 host = userhost;
2191 else {
2192 *host++ = '\0';
2193 if (!userhost[0]) {
2194 fprintf(stderr, "Missing username\n");
2195 usage();
2196 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002197 addargs(&args, "-l");
2198 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002199 }
2200
Damien Millerec692032004-01-27 21:22:00 +11002201 if ((cp = colon(host)) != NULL) {
2202 *cp++ = '\0';
2203 file1 = cp;
2204 }
2205
Damien Millerd14ee1e2002-02-05 12:27:31 +11002206 host = cleanhostname(host);
2207 if (!*host) {
2208 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002209 usage();
2210 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002211
Damien Millerd14ee1e2002-02-05 12:27:31 +11002212 addargs(&args, "-oProtocol %d", sshver);
2213
2214 /* no subsystem if the server-spec contains a '/' */
2215 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2216 addargs(&args, "-s");
2217
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002218 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002219 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002220 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002221 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002222
Damien Millercc685c12003-06-04 22:51:38 +10002223 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002224 } else {
2225 args.list = NULL;
2226 addargs(&args, "sftp-server");
2227
Damien Millercc685c12003-06-04 22:51:38 +10002228 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002229 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002230 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002231
Darren Tucker21063192010-01-08 17:10:36 +11002232 conn = do_init(in, out, copy_buffer_len, num_requests);
2233 if (conn == NULL)
2234 fatal("Couldn't initialise connection to server");
2235
2236 if (!batchmode) {
2237 if (sftp_direct == NULL)
2238 fprintf(stderr, "Connected to %s.\n", host);
2239 else
2240 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2241 }
2242
2243 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002244
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002245#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002246 shutdown(in, SHUT_RDWR);
2247 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002248#endif
2249
Damien Miller33804262001-02-04 23:20:18 +11002250 close(in);
2251 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002252 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002253 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002254
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002255 while (waitpid(sshpid, NULL, 0) == -1)
2256 if (errno != EINTR)
2257 fatal("Couldn't wait for ssh process: %s",
2258 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002259
Damien Miller956f3fb2003-01-10 21:40:00 +11002260 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002261}