blob: 9f5fa354dc43bd368f85de4de6bccf41264927ee [file] [log] [blame]
Darren Tucker340d1682010-01-09 08:54:31 +11001/* $OpenBSD: sftp.c,v 1.117 2010/01/08 21:50:49 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
1115 /* Ignore blank lines and lines which begin with comment '#' char */
1116 if (*cp == '\0' || *cp == '#')
1117 return (0);
1118
1119 /* Check for leading '-' (disable error processing) */
1120 *iflag = 0;
1121 if (*cp == '-') {
1122 *iflag = 1;
1123 cp++;
1124 }
1125
Darren Tucker909d8582010-01-08 19:02:40 +11001126 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001127 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001128
Damien Miller1cbc2922007-10-26 14:27:45 +10001129 /* Figure out which command we have */
1130 for (i = 0; cmds[i].c != NULL; i++) {
1131 if (strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001132 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001133 }
1134 cmdnum = cmds[i].n;
1135 cmd = cmds[i].c;
1136
1137 /* Special case */
1138 if (*cp == '!') {
1139 cp++;
1140 cmdnum = I_SHELL;
1141 } else if (cmdnum == -1) {
1142 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001143 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001144 }
1145
1146 /* Get arguments and parse flags */
Darren Tucker1b0dd172009-10-07 08:37:48 +11001147 *lflag = *pflag = *rflag = *hflag = *n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001148 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001149 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001150 switch (cmdnum) {
1151 case I_GET:
1152 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001153 if ((optidx = parse_getput_flags(cmd, argv, argc, pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001154 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001155 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001156 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001157 error("You must specify at least one path after a "
1158 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001159 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001160 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001161 *path1 = xstrdup(argv[optidx]);
1162 /* Get second pathname (optional) */
1163 if (argc - optidx > 1) {
1164 *path2 = xstrdup(argv[optidx + 1]);
1165 /* Destination is not globbed */
1166 undo_glob_escape(*path2);
1167 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001168 break;
1169 case I_RENAME:
1170 case I_SYMLINK:
Damien Miller1cbc2922007-10-26 14:27:45 +10001171 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001172 error("You must specify two paths after a %s "
1173 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001174 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001175 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001176 *path1 = xstrdup(argv[optidx]);
1177 *path2 = xstrdup(argv[optidx + 1]);
1178 /* Paths are not globbed */
1179 undo_glob_escape(*path1);
1180 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001181 break;
1182 case I_RM:
1183 case I_MKDIR:
1184 case I_RMDIR:
1185 case I_CHDIR:
1186 case I_LCHDIR:
1187 case I_LMKDIR:
1188 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001189 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001190 error("You must specify a path after a %s command.",
1191 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001192 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001193 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001194 *path1 = xstrdup(argv[optidx]);
1195 /* Only "rm" globs */
1196 if (cmdnum != I_RM)
1197 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001198 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001199 case I_DF:
1200 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1201 iflag)) == -1)
1202 return -1;
1203 /* Default to current directory if no path specified */
1204 if (argc - optidx < 1)
1205 *path1 = NULL;
1206 else {
1207 *path1 = xstrdup(argv[optidx]);
1208 undo_glob_escape(*path1);
1209 }
1210 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001211 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001212 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001213 return(-1);
1214 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001215 if (argc - optidx > 0)
1216 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001217 break;
1218 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001219 /* Skip ls command and following whitespace */
1220 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001221 case I_SHELL:
1222 /* Uses the rest of the line */
1223 break;
1224 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001225 case I_CHMOD:
1226 base = 8;
1227 case I_CHOWN:
1228 case I_CHGRP:
1229 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001230 if (argc - optidx < 1)
1231 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001232 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001233 l = strtol(argv[optidx], &cp2, base);
1234 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1235 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1236 l < 0) {
1237 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001238 error("You must supply a numeric argument "
1239 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001240 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001241 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001242 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001243 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001244 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001245 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001246 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001247 error("You must specify a path after a %s command.",
1248 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001249 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001250 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001251 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001252 break;
1253 case I_QUIT:
1254 case I_PWD:
1255 case I_LPWD:
1256 case I_HELP:
1257 case I_VERSION:
1258 case I_PROGRESS:
1259 break;
1260 default:
1261 fatal("Command not implemented");
1262 }
1263
1264 *cpp = cp;
1265 return(cmdnum);
1266}
1267
1268static int
1269parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1270 int err_abort)
1271{
1272 char *path1, *path2, *tmp;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001273 int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001274 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001275 Attrib a, *aa;
1276 char path_buf[MAXPATHLEN];
1277 int err = 0;
1278 glob_t g;
1279
1280 path1 = path2 = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001281 cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag, &n_arg,
Damien Miller20e1fab2004-02-18 14:30:55 +11001282 &path1, &path2);
1283
1284 if (iflag != 0)
1285 err_abort = 0;
1286
1287 memset(&g, 0, sizeof(g));
1288
1289 /* Perform command */
1290 switch (cmdnum) {
1291 case 0:
1292 /* Blank line */
1293 break;
1294 case -1:
1295 /* Unrecognized command */
1296 err = -1;
1297 break;
1298 case I_GET:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001299 err = process_get(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001300 break;
1301 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001302 err = process_put(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001303 break;
1304 case I_RENAME:
1305 path1 = make_absolute(path1, *pwd);
1306 path2 = make_absolute(path2, *pwd);
1307 err = do_rename(conn, path1, path2);
1308 break;
1309 case I_SYMLINK:
1310 path2 = make_absolute(path2, *pwd);
1311 err = do_symlink(conn, path1, path2);
1312 break;
1313 case I_RM:
1314 path1 = make_absolute(path1, *pwd);
1315 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001316 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001317 printf("Removing %s\n", g.gl_pathv[i]);
1318 err = do_rm(conn, g.gl_pathv[i]);
1319 if (err != 0 && err_abort)
1320 break;
1321 }
1322 break;
1323 case I_MKDIR:
1324 path1 = make_absolute(path1, *pwd);
1325 attrib_clear(&a);
1326 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1327 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001328 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001329 break;
1330 case I_RMDIR:
1331 path1 = make_absolute(path1, *pwd);
1332 err = do_rmdir(conn, path1);
1333 break;
1334 case I_CHDIR:
1335 path1 = make_absolute(path1, *pwd);
1336 if ((tmp = do_realpath(conn, path1)) == NULL) {
1337 err = 1;
1338 break;
1339 }
1340 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1341 xfree(tmp);
1342 err = 1;
1343 break;
1344 }
1345 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1346 error("Can't change directory: Can't check target");
1347 xfree(tmp);
1348 err = 1;
1349 break;
1350 }
1351 if (!S_ISDIR(aa->perm)) {
1352 error("Can't change directory: \"%s\" is not "
1353 "a directory", tmp);
1354 xfree(tmp);
1355 err = 1;
1356 break;
1357 }
1358 xfree(*pwd);
1359 *pwd = tmp;
1360 break;
1361 case I_LS:
1362 if (!path1) {
1363 do_globbed_ls(conn, *pwd, *pwd, lflag);
1364 break;
1365 }
1366
1367 /* Strip pwd off beginning of non-absolute paths */
1368 tmp = NULL;
1369 if (*path1 != '/')
1370 tmp = *pwd;
1371
1372 path1 = make_absolute(path1, *pwd);
1373 err = do_globbed_ls(conn, path1, tmp, lflag);
1374 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001375 case I_DF:
1376 /* Default to current directory if no path specified */
1377 if (path1 == NULL)
1378 path1 = xstrdup(*pwd);
1379 path1 = make_absolute(path1, *pwd);
1380 err = do_df(conn, path1, hflag, iflag);
1381 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001382 case I_LCHDIR:
1383 if (chdir(path1) == -1) {
1384 error("Couldn't change local directory to "
1385 "\"%s\": %s", path1, strerror(errno));
1386 err = 1;
1387 }
1388 break;
1389 case I_LMKDIR:
1390 if (mkdir(path1, 0777) == -1) {
1391 error("Couldn't create local directory "
1392 "\"%s\": %s", path1, strerror(errno));
1393 err = 1;
1394 }
1395 break;
1396 case I_LLS:
1397 local_do_ls(cmd);
1398 break;
1399 case I_SHELL:
1400 local_do_shell(cmd);
1401 break;
1402 case I_LUMASK:
1403 umask(n_arg);
1404 printf("Local umask: %03lo\n", n_arg);
1405 break;
1406 case I_CHMOD:
1407 path1 = make_absolute(path1, *pwd);
1408 attrib_clear(&a);
1409 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1410 a.perm = n_arg;
1411 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001412 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001413 printf("Changing mode on %s\n", g.gl_pathv[i]);
1414 err = do_setstat(conn, g.gl_pathv[i], &a);
1415 if (err != 0 && err_abort)
1416 break;
1417 }
1418 break;
1419 case I_CHOWN:
1420 case I_CHGRP:
1421 path1 = make_absolute(path1, *pwd);
1422 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001423 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001424 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001425 if (err_abort) {
1426 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001427 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001428 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001429 continue;
1430 }
1431 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1432 error("Can't get current ownership of "
1433 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001434 if (err_abort) {
1435 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001436 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001437 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001438 continue;
1439 }
1440 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1441 if (cmdnum == I_CHOWN) {
1442 printf("Changing owner on %s\n", g.gl_pathv[i]);
1443 aa->uid = n_arg;
1444 } else {
1445 printf("Changing group on %s\n", g.gl_pathv[i]);
1446 aa->gid = n_arg;
1447 }
1448 err = do_setstat(conn, g.gl_pathv[i], aa);
1449 if (err != 0 && err_abort)
1450 break;
1451 }
1452 break;
1453 case I_PWD:
1454 printf("Remote working directory: %s\n", *pwd);
1455 break;
1456 case I_LPWD:
1457 if (!getcwd(path_buf, sizeof(path_buf))) {
1458 error("Couldn't get local cwd: %s", strerror(errno));
1459 err = -1;
1460 break;
1461 }
1462 printf("Local working directory: %s\n", path_buf);
1463 break;
1464 case I_QUIT:
1465 /* Processed below */
1466 break;
1467 case I_HELP:
1468 help();
1469 break;
1470 case I_VERSION:
1471 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1472 break;
1473 case I_PROGRESS:
1474 showprogress = !showprogress;
1475 if (showprogress)
1476 printf("Progress meter enabled\n");
1477 else
1478 printf("Progress meter disabled\n");
1479 break;
1480 default:
1481 fatal("%d is not implemented", cmdnum);
1482 }
1483
1484 if (g.gl_pathc)
1485 globfree(&g);
1486 if (path1)
1487 xfree(path1);
1488 if (path2)
1489 xfree(path2);
1490
1491 /* If an unignored error occurs in batch mode we should abort. */
1492 if (err_abort && err != 0)
1493 return (-1);
1494 else if (cmdnum == I_QUIT)
1495 return (1);
1496
1497 return (0);
1498}
1499
Darren Tucker2d963d82004-11-07 20:04:10 +11001500#ifdef USE_LIBEDIT
1501static char *
1502prompt(EditLine *el)
1503{
1504 return ("sftp> ");
1505}
Darren Tucker2d963d82004-11-07 20:04:10 +11001506
Darren Tucker909d8582010-01-08 19:02:40 +11001507/* Display entries in 'list' after skipping the first 'len' chars */
1508static void
1509complete_display(char **list, u_int len)
1510{
1511 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1512 struct winsize ws;
1513 char *tmp;
1514
1515 /* Count entries for sort and find longest */
1516 for (y = 0; list[y]; y++)
1517 m = MAX(m, strlen(list[y]));
1518
1519 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1520 width = ws.ws_col;
1521
1522 m = m > len ? m - len : 0;
1523 columns = width / (m + 2);
1524 columns = MAX(columns, 1);
1525 colspace = width / columns;
1526 colspace = MIN(colspace, width);
1527
1528 printf("\n");
1529 m = 1;
1530 for (y = 0; list[y]; y++) {
1531 llen = strlen(list[y]);
1532 tmp = llen > len ? list[y] + len : "";
1533 printf("%-*s", colspace, tmp);
1534 if (m >= columns) {
1535 printf("\n");
1536 m = 1;
1537 } else
1538 m++;
1539 }
1540 printf("\n");
1541}
1542
1543/*
1544 * Given a "list" of words that begin with a common prefix of "word",
1545 * attempt to find an autocompletion to extends "word" by the next
1546 * characters common to all entries in "list".
1547 */
1548static char *
1549complete_ambiguous(const char *word, char **list, size_t count)
1550{
1551 if (word == NULL)
1552 return NULL;
1553
1554 if (count > 0) {
1555 u_int y, matchlen = strlen(list[0]);
1556
1557 /* Find length of common stem */
1558 for (y = 1; list[y]; y++) {
1559 u_int x;
1560
1561 for (x = 0; x < matchlen; x++)
1562 if (list[0][x] != list[y][x])
1563 break;
1564
1565 matchlen = x;
1566 }
1567
1568 if (matchlen > strlen(word)) {
1569 char *tmp = xstrdup(list[0]);
1570
Darren Tucker340d1682010-01-09 08:54:31 +11001571 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001572 return tmp;
1573 }
1574 }
1575
1576 return xstrdup(word);
1577}
1578
1579/* Autocomplete a sftp command */
1580static int
1581complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1582 int terminated)
1583{
1584 u_int y, count = 0, cmdlen, tmplen;
1585 char *tmp, **list, argterm[3];
1586 const LineInfo *lf;
1587
1588 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1589
1590 /* No command specified: display all available commands */
1591 if (cmd == NULL) {
1592 for (y = 0; cmds[y].c; y++)
1593 list[count++] = xstrdup(cmds[y].c);
1594
1595 list[count] = NULL;
1596 complete_display(list, 0);
1597
1598 for (y = 0; list[y] != NULL; y++)
1599 xfree(list[y]);
1600 xfree(list);
1601 return count;
1602 }
1603
1604 /* Prepare subset of commands that start with "cmd" */
1605 cmdlen = strlen(cmd);
1606 for (y = 0; cmds[y].c; y++) {
1607 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
1608 list[count++] = xstrdup(cmds[y].c);
1609 }
1610 list[count] = NULL;
1611
1612 if (count == 0)
1613 return 0;
1614
1615 /* Complete ambigious command */
1616 tmp = complete_ambiguous(cmd, list, count);
1617 if (count > 1)
1618 complete_display(list, 0);
1619
1620 for (y = 0; list[y]; y++)
1621 xfree(list[y]);
1622 xfree(list);
1623
1624 if (tmp != NULL) {
1625 tmplen = strlen(tmp);
1626 cmdlen = strlen(cmd);
1627 /* If cmd may be extended then do so */
1628 if (tmplen > cmdlen)
1629 if (el_insertstr(el, tmp + cmdlen) == -1)
1630 fatal("el_insertstr failed.");
1631 lf = el_line(el);
1632 /* Terminate argument cleanly */
1633 if (count == 1) {
1634 y = 0;
1635 if (!terminated)
1636 argterm[y++] = quote;
1637 if (lastarg || *(lf->cursor) != ' ')
1638 argterm[y++] = ' ';
1639 argterm[y] = '\0';
1640 if (y > 0 && el_insertstr(el, argterm) == -1)
1641 fatal("el_insertstr failed.");
1642 }
1643 xfree(tmp);
1644 }
1645
1646 return count;
1647}
1648
1649/*
1650 * Determine whether a particular sftp command's arguments (if any)
1651 * represent local or remote files.
1652 */
1653static int
1654complete_is_remote(char *cmd) {
1655 int i;
1656
1657 if (cmd == NULL)
1658 return -1;
1659
1660 for (i = 0; cmds[i].c; i++) {
1661 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
1662 return cmds[i].t;
1663 }
1664
1665 return -1;
1666}
1667
1668/* Autocomplete a filename "file" */
1669static int
1670complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1671 char *file, int remote, int lastarg, char quote, int terminated)
1672{
1673 glob_t g;
1674 char *tmp, *tmp2, ins[3];
1675 u_int i, hadglob, pwdlen, len, tmplen, filelen;
1676 const LineInfo *lf;
1677
1678 /* Glob from "file" location */
1679 if (file == NULL)
1680 tmp = xstrdup("*");
1681 else
1682 xasprintf(&tmp, "%s*", file);
1683
1684 memset(&g, 0, sizeof(g));
1685 if (remote != LOCAL) {
1686 tmp = make_absolute(tmp, remote_path);
1687 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1688 } else
1689 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1690
1691 /* Determine length of pwd so we can trim completion display */
1692 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1693 /* Terminate counting on first unescaped glob metacharacter */
1694 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1695 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1696 hadglob = 1;
1697 break;
1698 }
1699 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1700 tmplen++;
1701 if (tmp[tmplen] == '/')
1702 pwdlen = tmplen + 1; /* track last seen '/' */
1703 }
1704 xfree(tmp);
1705
1706 if (g.gl_matchc == 0)
1707 goto out;
1708
1709 if (g.gl_matchc > 1)
1710 complete_display(g.gl_pathv, pwdlen);
1711
1712 tmp = NULL;
1713 /* Don't try to extend globs */
1714 if (file == NULL || hadglob)
1715 goto out;
1716
1717 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
1718 tmp = path_strip(tmp2, remote_path);
1719 xfree(tmp2);
1720
1721 if (tmp == NULL)
1722 goto out;
1723
1724 tmplen = strlen(tmp);
1725 filelen = strlen(file);
1726
1727 if (tmplen > filelen) {
1728 tmp2 = tmp + filelen;
1729 len = strlen(tmp2);
1730 /* quote argument on way out */
1731 for (i = 0; i < len; i++) {
1732 ins[0] = '\\';
1733 ins[1] = tmp2[i];
1734 ins[2] = '\0';
1735 switch (tmp2[i]) {
1736 case '\'':
1737 case '"':
1738 case '\\':
1739 case '\t':
1740 case ' ':
1741 if (quote == '\0' || tmp2[i] == quote) {
1742 if (el_insertstr(el, ins) == -1)
1743 fatal("el_insertstr "
1744 "failed.");
1745 break;
1746 }
1747 /* FALLTHROUGH */
1748 default:
1749 if (el_insertstr(el, ins + 1) == -1)
1750 fatal("el_insertstr failed.");
1751 break;
1752 }
1753 }
1754 }
1755
1756 lf = el_line(el);
1757 /*
1758 * XXX should we really extend here? the user may not be done if
1759 * the filename is a directory.
1760 */
1761 if (g.gl_matchc == 1) {
1762 i = 0;
1763 if (!terminated)
1764 ins[i++] = quote;
1765 if (lastarg || *(lf->cursor) != ' ')
1766 ins[i++] = ' ';
1767 ins[i] = '\0';
1768 if (i > 0 && el_insertstr(el, ins) == -1)
1769 fatal("el_insertstr failed.");
1770 }
1771 xfree(tmp);
1772
1773 out:
1774 globfree(&g);
1775 return g.gl_matchc;
1776}
1777
1778/* tab-completion hook function, called via libedit */
1779static unsigned char
1780complete(EditLine *el, int ch)
1781{
1782 char **argv, *line, quote;
1783 u_int argc, carg, cursor, len, terminated, ret = CC_ERROR;
1784 const LineInfo *lf;
1785 struct complete_ctx *complete_ctx;
1786
1787 lf = el_line(el);
1788 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1789 fatal("%s: el_get failed", __func__);
1790
1791 /* Figure out which argument the cursor points to */
1792 cursor = lf->cursor - lf->buffer;
1793 line = (char *)xmalloc(cursor + 1);
1794 memcpy(line, lf->buffer, cursor);
1795 line[cursor] = '\0';
1796 argv = makeargv(line, &carg, 1, &quote, &terminated);
1797 xfree(line);
1798
1799 /* Get all the arguments on the line */
1800 len = lf->lastchar - lf->buffer;
1801 line = (char *)xmalloc(len + 1);
1802 memcpy(line, lf->buffer, len);
1803 line[len] = '\0';
1804 argv = makeargv(line, &argc, 1, NULL, NULL);
1805
1806 /* Ensure cursor is at EOL or a argument boundary */
1807 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1808 line[cursor] != '\n') {
1809 xfree(line);
1810 return ret;
1811 }
1812
1813 if (carg == 0) {
1814 /* Show all available commands */
1815 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1816 ret = CC_REDISPLAY;
1817 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1818 /* Handle the command parsing */
1819 if (complete_cmd_parse(el, argv[0], argc == carg,
1820 quote, terminated) != 0)
1821 ret = CC_REDISPLAY;
1822 } else if (carg >= 1) {
1823 /* Handle file parsing */
1824 int remote = complete_is_remote(argv[0]);
1825 char *filematch = NULL;
1826
1827 if (carg > 1 && line[cursor-1] != ' ')
1828 filematch = argv[carg - 1];
1829
1830 if (remote != 0 &&
1831 complete_match(el, complete_ctx->conn,
1832 *complete_ctx->remote_pathp, filematch,
1833 remote, carg == argc, quote, terminated) != 0)
1834 ret = CC_REDISPLAY;
1835 }
1836
1837 xfree(line);
1838 return ret;
1839}
Darren Tuckere67f7db2010-01-08 19:50:02 +11001840#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11001841
Damien Miller20e1fab2004-02-18 14:30:55 +11001842int
Darren Tucker21063192010-01-08 17:10:36 +11001843interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001844{
Darren Tucker909d8582010-01-08 19:02:40 +11001845 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11001846 char *dir = NULL;
1847 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10001848 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11001849 EditLine *el = NULL;
1850#ifdef USE_LIBEDIT
1851 History *hl = NULL;
1852 HistEvent hev;
1853 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11001854 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11001855
1856 if (!batchmode && isatty(STDIN_FILENO)) {
1857 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1858 fatal("Couldn't initialise editline");
1859 if ((hl = history_init()) == NULL)
1860 fatal("Couldn't initialise editline history");
1861 history(hl, &hev, H_SETSIZE, 100);
1862 el_set(el, EL_HIST, history, hl);
1863
1864 el_set(el, EL_PROMPT, prompt);
1865 el_set(el, EL_EDITOR, "emacs");
1866 el_set(el, EL_TERMINAL, NULL);
1867 el_set(el, EL_SIGNAL, 1);
1868 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11001869
1870 /* Tab Completion */
1871 el_set(el, EL_ADDFN, "ftp-complete",
1872 "Context senstive argument completion", complete);
1873 complete_ctx.conn = conn;
1874 complete_ctx.remote_pathp = &remote_path;
1875 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
1876 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11001877 }
1878#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001879
Darren Tucker909d8582010-01-08 19:02:40 +11001880 remote_path = do_realpath(conn, ".");
1881 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11001882 fatal("Need cwd");
1883
1884 if (file1 != NULL) {
1885 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11001886 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11001887
1888 if (remote_is_dir(conn, dir) && file2 == NULL) {
1889 printf("Changing to: %s\n", dir);
1890 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001891 if (parse_dispatch_command(conn, cmd,
1892 &remote_path, 1) != 0) {
Darren Tuckercd516ef2004-12-06 22:43:43 +11001893 xfree(dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001894 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001895 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001896 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11001897 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001898 } else {
1899 if (file2 == NULL)
1900 snprintf(cmd, sizeof cmd, "get %s", dir);
1901 else
1902 snprintf(cmd, sizeof cmd, "get %s %s", dir,
1903 file2);
1904
Darren Tucker909d8582010-01-08 19:02:40 +11001905 err = parse_dispatch_command(conn, cmd,
1906 &remote_path, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001907 xfree(dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001908 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001909 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001910 return (err);
1911 }
1912 xfree(dir);
1913 }
1914
Darren Tucker93e7e8f2005-08-23 08:06:55 +10001915#if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF)
Damien Miller20e1fab2004-02-18 14:30:55 +11001916 setvbuf(stdout, NULL, _IOLBF, 0);
1917 setvbuf(infile, NULL, _IOLBF, 0);
1918#else
Damien Miller37294fb2005-07-17 17:18:49 +10001919 setlinebuf(stdout);
1920 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11001921#endif
1922
Damien Miller0e2c1022005-08-12 22:16:22 +10001923 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11001924 err = 0;
1925 for (;;) {
1926 char *cp;
1927
Darren Tuckercdf547a2004-05-24 10:12:19 +10001928 signal(SIGINT, SIG_IGN);
1929
Darren Tucker2d963d82004-11-07 20:04:10 +11001930 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001931 if (interactive)
1932 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11001933 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001934 if (interactive)
1935 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11001936 break;
1937 }
Damien Miller0e2c1022005-08-12 22:16:22 +10001938 if (!interactive) { /* Echo command */
1939 printf("sftp> %s", cmd);
1940 if (strlen(cmd) > 0 &&
1941 cmd[strlen(cmd) - 1] != '\n')
1942 printf("\n");
1943 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001944 } else {
1945#ifdef USE_LIBEDIT
1946 const char *line;
1947 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001948
Darren Tucker909d8582010-01-08 19:02:40 +11001949 if ((line = el_gets(el, &count)) == NULL ||
1950 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001951 printf("\n");
1952 break;
1953 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001954 history(hl, &hev, H_ENTER, line);
1955 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
1956 fprintf(stderr, "Error: input line too long\n");
1957 continue;
1958 }
1959#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001960 }
1961
Damien Miller20e1fab2004-02-18 14:30:55 +11001962 cp = strrchr(cmd, '\n');
1963 if (cp)
1964 *cp = '\0';
1965
Darren Tuckercdf547a2004-05-24 10:12:19 +10001966 /* Handle user interrupts gracefully during commands */
1967 interrupted = 0;
1968 signal(SIGINT, cmd_interrupt);
1969
Darren Tucker909d8582010-01-08 19:02:40 +11001970 err = parse_dispatch_command(conn, cmd, &remote_path,
1971 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11001972 if (err != 0)
1973 break;
1974 }
Darren Tucker909d8582010-01-08 19:02:40 +11001975 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001976 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001977
Tim Rice027e8b12005-08-15 14:52:50 -07001978#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10001979 if (el != NULL)
1980 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07001981#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10001982
Damien Miller20e1fab2004-02-18 14:30:55 +11001983 /* err == 1 signifies normal "quit" exit */
1984 return (err >= 0 ? 0 : -1);
1985}
Damien Miller62d57f62003-01-10 21:43:24 +11001986
Ben Lindstrombba81212001-06-25 05:01:22 +00001987static void
Damien Millercc685c12003-06-04 22:51:38 +10001988connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11001989{
1990 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001991
Damien Miller33804262001-02-04 23:20:18 +11001992#ifdef USE_PIPES
1993 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001994
Damien Miller33804262001-02-04 23:20:18 +11001995 if ((pipe(pin) == -1) || (pipe(pout) == -1))
1996 fatal("pipe: %s", strerror(errno));
1997 *in = pin[0];
1998 *out = pout[1];
1999 c_in = pout[0];
2000 c_out = pin[1];
2001#else /* USE_PIPES */
2002 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002003
Damien Miller33804262001-02-04 23:20:18 +11002004 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2005 fatal("socketpair: %s", strerror(errno));
2006 *in = *out = inout[0];
2007 c_in = c_out = inout[1];
2008#endif /* USE_PIPES */
2009
Damien Millercc685c12003-06-04 22:51:38 +10002010 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002011 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002012 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002013 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2014 (dup2(c_out, STDOUT_FILENO) == -1)) {
2015 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002016 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002017 }
2018 close(*in);
2019 close(*out);
2020 close(c_in);
2021 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002022
2023 /*
2024 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002025 * ignore SIGINT if we want to gracefully abort commands,
2026 * otherwise the signal will make it to the ssh process and
Darren Tuckercdf547a2004-05-24 10:12:19 +10002027 * kill it too
2028 */
2029 signal(SIGINT, SIG_IGN);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002030 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002031 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002032 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002033 }
2034
Damien Millercc685c12003-06-04 22:51:38 +10002035 signal(SIGTERM, killchild);
2036 signal(SIGINT, killchild);
2037 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11002038 close(c_in);
2039 close(c_out);
2040}
2041
Ben Lindstrombba81212001-06-25 05:01:22 +00002042static void
Damien Miller33804262001-02-04 23:20:18 +11002043usage(void)
2044{
Damien Miller025e01c2002-02-08 22:06:29 +11002045 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002046
Ben Lindstrom1e243242001-09-18 05:38:44 +00002047 fprintf(stderr,
Darren Tucker1b0dd172009-10-07 08:37:48 +11002048 "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002049 " [-D sftp_server_path] [-F ssh_config] "
2050 "[-i identity_file]\n"
2051 " [-o ssh_option] [-P port] [-R num_requests] "
2052 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002053 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002054 " %s [user@]host[:file ...]\n"
2055 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002056 " %s -b batchfile [user@]host\n",
2057 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002058 exit(1);
2059}
2060
Kevin Stevesef4eea92001-02-05 12:42:17 +00002061int
Damien Miller33804262001-02-04 23:20:18 +11002062main(int argc, char **argv)
2063{
Damien Miller956f3fb2003-01-10 21:40:00 +11002064 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002065 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002066 int debug_level = 0, sshver = 2;
2067 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002068 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002069 LogLevel ll = SYSLOG_LEVEL_INFO;
2070 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002071 extern int optind;
2072 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002073 struct sftp_conn *conn;
2074 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2075 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller33804262001-02-04 23:20:18 +11002076
Darren Tuckerce321d82005-10-03 18:11:24 +10002077 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2078 sanitise_stdfd();
2079
Damien Miller59d3d5b2003-08-22 09:34:41 +10002080 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002081 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002082 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002083 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002084 addargs(&args, "-oForwardX11 no");
2085 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002086 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002087 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002088
Ben Lindstrom387c4722001-05-08 20:27:25 +00002089 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002090 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002091
Darren Tucker282b4022009-10-07 08:23:06 +11002092 while ((ch = getopt(argc, argv,
Darren Tucker1b0dd172009-10-07 08:37:48 +11002093 "1246hqrvCc:D:i:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002094 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002095 /* Passed through to ssh(1) */
2096 case '4':
2097 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002098 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002099 addargs(&args, "-%c", ch);
2100 break;
2101 /* Passed through to ssh(1) with argument */
2102 case 'F':
2103 case 'c':
2104 case 'i':
2105 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002106 addargs(&args, "-%c", ch);
2107 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002108 break;
2109 case 'q':
2110 showprogress = 0;
2111 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002112 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002113 case 'P':
2114 addargs(&args, "-oPort %s", optarg);
2115 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002116 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002117 if (debug_level < 3) {
2118 addargs(&args, "-v");
2119 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2120 }
2121 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002122 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002123 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002124 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002125 if (sftp_server == NULL)
2126 sftp_server = _PATH_SFTP_SERVER;
2127 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002128 case '2':
2129 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002130 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002131 case 'B':
2132 copy_buffer_len = strtol(optarg, &cp, 10);
2133 if (copy_buffer_len == 0 || *cp != '\0')
2134 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002135 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002136 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002137 if (batchmode)
2138 fatal("Batch file already specified.");
2139
2140 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002141 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002142 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002143 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002144 showprogress = 0;
Damien Miller44f75c12004-01-21 10:58:47 +11002145 batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002146 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002147 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002148 case 'p':
2149 global_pflag = 1;
2150 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002151 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002152 sftp_direct = optarg;
2153 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002154 case 'r':
2155 global_rflag = 1;
2156 break;
Damien Miller16a13332002-02-13 14:03:56 +11002157 case 'R':
2158 num_requests = strtol(optarg, &cp, 10);
2159 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002160 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002161 optarg);
2162 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002163 case 's':
2164 sftp_server = optarg;
2165 break;
2166 case 'S':
2167 ssh_program = optarg;
2168 replacearg(&args, 0, "%s", ssh_program);
2169 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002170 case 'h':
2171 default:
Damien Miller33804262001-02-04 23:20:18 +11002172 usage();
2173 }
2174 }
2175
Damien Millerc0f27d82004-03-08 23:12:19 +11002176 if (!isatty(STDERR_FILENO))
2177 showprogress = 0;
2178
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002179 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2180
Damien Millerd14ee1e2002-02-05 12:27:31 +11002181 if (sftp_direct == NULL) {
2182 if (optind == argc || argc > (optind + 2))
2183 usage();
Damien Miller33804262001-02-04 23:20:18 +11002184
Damien Millerd14ee1e2002-02-05 12:27:31 +11002185 userhost = xstrdup(argv[optind]);
2186 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002187
Ben Lindstromc276c122002-12-23 02:14:51 +00002188 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002189 host = userhost;
2190 else {
2191 *host++ = '\0';
2192 if (!userhost[0]) {
2193 fprintf(stderr, "Missing username\n");
2194 usage();
2195 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002196 addargs(&args, "-l");
2197 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002198 }
2199
Damien Millerec692032004-01-27 21:22:00 +11002200 if ((cp = colon(host)) != NULL) {
2201 *cp++ = '\0';
2202 file1 = cp;
2203 }
2204
Damien Millerd14ee1e2002-02-05 12:27:31 +11002205 host = cleanhostname(host);
2206 if (!*host) {
2207 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002208 usage();
2209 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002210
Damien Millerd14ee1e2002-02-05 12:27:31 +11002211 addargs(&args, "-oProtocol %d", sshver);
2212
2213 /* no subsystem if the server-spec contains a '/' */
2214 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2215 addargs(&args, "-s");
2216
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002217 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002218 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002219 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002220 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002221
Damien Millercc685c12003-06-04 22:51:38 +10002222 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002223 } else {
2224 args.list = NULL;
2225 addargs(&args, "sftp-server");
2226
Damien Millercc685c12003-06-04 22:51:38 +10002227 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002228 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002229 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002230
Darren Tucker21063192010-01-08 17:10:36 +11002231 conn = do_init(in, out, copy_buffer_len, num_requests);
2232 if (conn == NULL)
2233 fatal("Couldn't initialise connection to server");
2234
2235 if (!batchmode) {
2236 if (sftp_direct == NULL)
2237 fprintf(stderr, "Connected to %s.\n", host);
2238 else
2239 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2240 }
2241
2242 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002243
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002244#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002245 shutdown(in, SHUT_RDWR);
2246 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002247#endif
2248
Damien Miller33804262001-02-04 23:20:18 +11002249 close(in);
2250 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002251 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002252 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002253
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002254 while (waitpid(sshpid, NULL, 0) == -1)
2255 if (errno != EINTR)
2256 fatal("Couldn't wait for ssh process: %s",
2257 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002258
Damien Miller956f3fb2003-01-10 21:40:00 +11002259 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002260}