blob: c994887e3d4502b0f8565b14e1284c70fd274a7a [file] [log] [blame]
Darren Tucker909d8582010-01-08 19:02:40 +11001/* $OpenBSD: sftp.c,v 1.116 2010/01/04 02:03:57 djm Exp $ */
Damien Miller33804262001-02-04 23:20:18 +11002/*
Damien Miller4e60ed72004-02-17 17:07:59 +11003 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
Damien Miller33804262001-02-04 23:20:18 +11004 *
Damien Miller4e60ed72004-02-17 17:07:59 +11005 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
Damien Miller33804262001-02-04 23:20:18 +11008 *
Damien Miller4e60ed72004-02-17 17:07:59 +11009 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Damien Miller33804262001-02-04 23:20:18 +110016 */
17
18#include "includes.h"
19
Damien Miller9cf6d072006-03-15 11:29:24 +110020#include <sys/types.h>
Damien Millerd7834352006-08-05 12:39:39 +100021#include <sys/ioctl.h>
Damien Millerf17883e2006-03-15 11:45:54 +110022#ifdef HAVE_SYS_STAT_H
23# include <sys/stat.h>
24#endif
Damien Miller8dbffe72006-08-05 11:02:17 +100025#include <sys/param.h>
Damien Millere3b60b52006-07-10 21:08:03 +100026#include <sys/socket.h>
Damien Miller9cf6d072006-03-15 11:29:24 +110027#include <sys/wait.h>
Darren Tucker5b2e2ba2008-06-08 09:25:28 +100028#ifdef HAVE_SYS_STATVFS_H
Damien Millerd671e5a2008-05-19 14:53:33 +100029#include <sys/statvfs.h>
Darren Tucker5b2e2ba2008-06-08 09:25:28 +100030#endif
Darren Tucker2d963d82004-11-07 20:04:10 +110031
Damien Miller1cbc2922007-10-26 14:27:45 +100032#include <ctype.h>
Darren Tucker39972492006-07-12 22:22:46 +100033#include <errno.h>
34
Damien Miller03e20032006-03-15 11:16:59 +110035#ifdef HAVE_PATHS_H
Damien Millera9263d02006-03-15 11:18:26 +110036# include <paths.h>
Damien Miller03e20032006-03-15 11:16:59 +110037#endif
Darren Tucker1b0dd172009-10-07 08:37:48 +110038#ifdef HAVE_LIBGEN_H
39#include <libgen.h>
40#endif
Darren Tucker2d963d82004-11-07 20:04:10 +110041#ifdef USE_LIBEDIT
42#include <histedit.h>
43#else
44typedef void EditLine;
45#endif
Damien Miller6ff3cad2006-03-15 11:52:09 +110046#include <signal.h>
Damien Millere7a1e5c2006-08-05 11:34:19 +100047#include <stdlib.h>
Damien Millera7a73ee2006-08-05 11:37:59 +100048#include <stdio.h>
Damien Millere3476ed2006-07-24 14:13:33 +100049#include <string.h>
Damien Millere6b3b612006-07-24 14:01:23 +100050#include <unistd.h>
Damien Millerd7834352006-08-05 12:39:39 +100051#include <stdarg.h>
Damien Miller33804262001-02-04 23:20:18 +110052
Damien Millera7058ec2008-05-20 08:57:06 +100053#ifdef HAVE_UTIL_H
54# include <util.h>
55#endif
56
57#ifdef HAVE_LIBUTIL_H
58# include <libutil.h>
59#endif
60
Damien Miller33804262001-02-04 23:20:18 +110061#include "xmalloc.h"
62#include "log.h"
63#include "pathnames.h"
Ben Lindstrom4529b702001-05-03 23:39:53 +000064#include "misc.h"
Damien Miller33804262001-02-04 23:20:18 +110065
66#include "sftp.h"
Damien Millerd7834352006-08-05 12:39:39 +100067#include "buffer.h"
Damien Miller33804262001-02-04 23:20:18 +110068#include "sftp-common.h"
69#include "sftp-client.h"
Damien Millerd7d46bb2004-02-18 14:11:13 +110070
Darren Tucker21063192010-01-08 17:10:36 +110071#define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */
72#define DEFAULT_NUM_REQUESTS 64 /* # concurrent outstanding requests */
73
Damien Miller20e1fab2004-02-18 14:30:55 +110074/* File to read commands from */
75FILE* infile;
76
77/* Are we in batchfile mode? */
78int batchmode = 0;
79
Damien Miller20e1fab2004-02-18 14:30:55 +110080/* PID of ssh transport process */
81static pid_t sshpid = -1;
82
83/* This is set to 0 if the progressmeter is not desired. */
Damien Millerc0f27d82004-03-08 23:12:19 +110084int showprogress = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +110085
Darren Tucker1b0dd172009-10-07 08:37:48 +110086/* When this option is set, we always recursively download/upload directories */
87int global_rflag = 0;
88
89/* When this option is set, the file transfers will always preserve times */
90int global_pflag = 0;
91
Darren Tuckercdf547a2004-05-24 10:12:19 +100092/* SIGINT received during command processing */
93volatile sig_atomic_t interrupted = 0;
94
Darren Tuckerb9123452004-06-22 13:06:45 +100095/* I wish qsort() took a separate ctx for the comparison function...*/
96int sort_flag;
97
Darren Tucker909d8582010-01-08 19:02:40 +110098/* Context used for commandline completion */
99struct complete_ctx {
100 struct sftp_conn *conn;
101 char **remote_pathp;
102};
103
Damien Miller20e1fab2004-02-18 14:30:55 +1100104int remote_glob(struct sftp_conn *, const char *, int,
105 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
Damien Miller33804262001-02-04 23:20:18 +1100106
Kevin Steves12888d12001-03-05 19:50:57 +0000107extern char *__progname;
Kevin Steves12888d12001-03-05 19:50:57 +0000108
Damien Miller20e1fab2004-02-18 14:30:55 +1100109/* Separators for interactive commands */
110#define WHITESPACE " \t\r\n"
Damien Millerd7686fd2001-02-10 00:40:03 +1100111
Darren Tuckerb9123452004-06-22 13:06:45 +1000112/* ls flags */
Darren 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
1571 tmp[matchlen] = NULL;
1572 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}
Darren Tuckere67f7db2010-01-08 19:50:02 +11001648#endif
Darren Tucker909d8582010-01-08 19:02:40 +11001649
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
Darren Tuckere67f7db2010-01-08 19:50:02 +11001669#ifdef USE_LIBEDIT
Darren Tucker909d8582010-01-08 19:02:40 +11001670/* Autocomplete a filename "file" */
1671static int
1672complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1673 char *file, int remote, int lastarg, char quote, int terminated)
1674{
1675 glob_t g;
1676 char *tmp, *tmp2, ins[3];
1677 u_int i, hadglob, pwdlen, len, tmplen, filelen;
1678 const LineInfo *lf;
1679
1680 /* Glob from "file" location */
1681 if (file == NULL)
1682 tmp = xstrdup("*");
1683 else
1684 xasprintf(&tmp, "%s*", file);
1685
1686 memset(&g, 0, sizeof(g));
1687 if (remote != LOCAL) {
1688 tmp = make_absolute(tmp, remote_path);
1689 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1690 } else
1691 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1692
1693 /* Determine length of pwd so we can trim completion display */
1694 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1695 /* Terminate counting on first unescaped glob metacharacter */
1696 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1697 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1698 hadglob = 1;
1699 break;
1700 }
1701 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1702 tmplen++;
1703 if (tmp[tmplen] == '/')
1704 pwdlen = tmplen + 1; /* track last seen '/' */
1705 }
1706 xfree(tmp);
1707
1708 if (g.gl_matchc == 0)
1709 goto out;
1710
1711 if (g.gl_matchc > 1)
1712 complete_display(g.gl_pathv, pwdlen);
1713
1714 tmp = NULL;
1715 /* Don't try to extend globs */
1716 if (file == NULL || hadglob)
1717 goto out;
1718
1719 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
1720 tmp = path_strip(tmp2, remote_path);
1721 xfree(tmp2);
1722
1723 if (tmp == NULL)
1724 goto out;
1725
1726 tmplen = strlen(tmp);
1727 filelen = strlen(file);
1728
1729 if (tmplen > filelen) {
1730 tmp2 = tmp + filelen;
1731 len = strlen(tmp2);
1732 /* quote argument on way out */
1733 for (i = 0; i < len; i++) {
1734 ins[0] = '\\';
1735 ins[1] = tmp2[i];
1736 ins[2] = '\0';
1737 switch (tmp2[i]) {
1738 case '\'':
1739 case '"':
1740 case '\\':
1741 case '\t':
1742 case ' ':
1743 if (quote == '\0' || tmp2[i] == quote) {
1744 if (el_insertstr(el, ins) == -1)
1745 fatal("el_insertstr "
1746 "failed.");
1747 break;
1748 }
1749 /* FALLTHROUGH */
1750 default:
1751 if (el_insertstr(el, ins + 1) == -1)
1752 fatal("el_insertstr failed.");
1753 break;
1754 }
1755 }
1756 }
1757
1758 lf = el_line(el);
1759 /*
1760 * XXX should we really extend here? the user may not be done if
1761 * the filename is a directory.
1762 */
1763 if (g.gl_matchc == 1) {
1764 i = 0;
1765 if (!terminated)
1766 ins[i++] = quote;
1767 if (lastarg || *(lf->cursor) != ' ')
1768 ins[i++] = ' ';
1769 ins[i] = '\0';
1770 if (i > 0 && el_insertstr(el, ins) == -1)
1771 fatal("el_insertstr failed.");
1772 }
1773 xfree(tmp);
1774
1775 out:
1776 globfree(&g);
1777 return g.gl_matchc;
1778}
1779
1780/* tab-completion hook function, called via libedit */
1781static unsigned char
1782complete(EditLine *el, int ch)
1783{
1784 char **argv, *line, quote;
1785 u_int argc, carg, cursor, len, terminated, ret = CC_ERROR;
1786 const LineInfo *lf;
1787 struct complete_ctx *complete_ctx;
1788
1789 lf = el_line(el);
1790 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1791 fatal("%s: el_get failed", __func__);
1792
1793 /* Figure out which argument the cursor points to */
1794 cursor = lf->cursor - lf->buffer;
1795 line = (char *)xmalloc(cursor + 1);
1796 memcpy(line, lf->buffer, cursor);
1797 line[cursor] = '\0';
1798 argv = makeargv(line, &carg, 1, &quote, &terminated);
1799 xfree(line);
1800
1801 /* Get all the arguments on the line */
1802 len = lf->lastchar - lf->buffer;
1803 line = (char *)xmalloc(len + 1);
1804 memcpy(line, lf->buffer, len);
1805 line[len] = '\0';
1806 argv = makeargv(line, &argc, 1, NULL, NULL);
1807
1808 /* Ensure cursor is at EOL or a argument boundary */
1809 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1810 line[cursor] != '\n') {
1811 xfree(line);
1812 return ret;
1813 }
1814
1815 if (carg == 0) {
1816 /* Show all available commands */
1817 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1818 ret = CC_REDISPLAY;
1819 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1820 /* Handle the command parsing */
1821 if (complete_cmd_parse(el, argv[0], argc == carg,
1822 quote, terminated) != 0)
1823 ret = CC_REDISPLAY;
1824 } else if (carg >= 1) {
1825 /* Handle file parsing */
1826 int remote = complete_is_remote(argv[0]);
1827 char *filematch = NULL;
1828
1829 if (carg > 1 && line[cursor-1] != ' ')
1830 filematch = argv[carg - 1];
1831
1832 if (remote != 0 &&
1833 complete_match(el, complete_ctx->conn,
1834 *complete_ctx->remote_pathp, filematch,
1835 remote, carg == argc, quote, terminated) != 0)
1836 ret = CC_REDISPLAY;
1837 }
1838
1839 xfree(line);
1840 return ret;
1841}
Darren Tuckere67f7db2010-01-08 19:50:02 +11001842#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11001843
Damien Miller20e1fab2004-02-18 14:30:55 +11001844int
Darren Tucker21063192010-01-08 17:10:36 +11001845interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001846{
Darren Tucker909d8582010-01-08 19:02:40 +11001847 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11001848 char *dir = NULL;
1849 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10001850 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11001851 EditLine *el = NULL;
1852#ifdef USE_LIBEDIT
1853 History *hl = NULL;
1854 HistEvent hev;
1855 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11001856 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11001857
1858 if (!batchmode && isatty(STDIN_FILENO)) {
1859 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1860 fatal("Couldn't initialise editline");
1861 if ((hl = history_init()) == NULL)
1862 fatal("Couldn't initialise editline history");
1863 history(hl, &hev, H_SETSIZE, 100);
1864 el_set(el, EL_HIST, history, hl);
1865
1866 el_set(el, EL_PROMPT, prompt);
1867 el_set(el, EL_EDITOR, "emacs");
1868 el_set(el, EL_TERMINAL, NULL);
1869 el_set(el, EL_SIGNAL, 1);
1870 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11001871
1872 /* Tab Completion */
1873 el_set(el, EL_ADDFN, "ftp-complete",
1874 "Context senstive argument completion", complete);
1875 complete_ctx.conn = conn;
1876 complete_ctx.remote_pathp = &remote_path;
1877 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
1878 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11001879 }
1880#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001881
Darren Tucker909d8582010-01-08 19:02:40 +11001882 remote_path = do_realpath(conn, ".");
1883 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11001884 fatal("Need cwd");
1885
1886 if (file1 != NULL) {
1887 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11001888 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11001889
1890 if (remote_is_dir(conn, dir) && file2 == NULL) {
1891 printf("Changing to: %s\n", dir);
1892 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001893 if (parse_dispatch_command(conn, cmd,
1894 &remote_path, 1) != 0) {
Darren Tuckercd516ef2004-12-06 22:43:43 +11001895 xfree(dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001896 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001897 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001898 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11001899 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001900 } else {
1901 if (file2 == NULL)
1902 snprintf(cmd, sizeof cmd, "get %s", dir);
1903 else
1904 snprintf(cmd, sizeof cmd, "get %s %s", dir,
1905 file2);
1906
Darren Tucker909d8582010-01-08 19:02:40 +11001907 err = parse_dispatch_command(conn, cmd,
1908 &remote_path, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001909 xfree(dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001910 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001911 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001912 return (err);
1913 }
1914 xfree(dir);
1915 }
1916
Darren Tucker93e7e8f2005-08-23 08:06:55 +10001917#if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF)
Damien Miller20e1fab2004-02-18 14:30:55 +11001918 setvbuf(stdout, NULL, _IOLBF, 0);
1919 setvbuf(infile, NULL, _IOLBF, 0);
1920#else
Damien Miller37294fb2005-07-17 17:18:49 +10001921 setlinebuf(stdout);
1922 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11001923#endif
1924
Damien Miller0e2c1022005-08-12 22:16:22 +10001925 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11001926 err = 0;
1927 for (;;) {
1928 char *cp;
1929
Darren Tuckercdf547a2004-05-24 10:12:19 +10001930 signal(SIGINT, SIG_IGN);
1931
Darren Tucker2d963d82004-11-07 20:04:10 +11001932 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001933 if (interactive)
1934 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11001935 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001936 if (interactive)
1937 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11001938 break;
1939 }
Damien Miller0e2c1022005-08-12 22:16:22 +10001940 if (!interactive) { /* Echo command */
1941 printf("sftp> %s", cmd);
1942 if (strlen(cmd) > 0 &&
1943 cmd[strlen(cmd) - 1] != '\n')
1944 printf("\n");
1945 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001946 } else {
1947#ifdef USE_LIBEDIT
1948 const char *line;
1949 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001950
Darren Tucker909d8582010-01-08 19:02:40 +11001951 if ((line = el_gets(el, &count)) == NULL ||
1952 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001953 printf("\n");
1954 break;
1955 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001956 history(hl, &hev, H_ENTER, line);
1957 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
1958 fprintf(stderr, "Error: input line too long\n");
1959 continue;
1960 }
1961#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001962 }
1963
Damien Miller20e1fab2004-02-18 14:30:55 +11001964 cp = strrchr(cmd, '\n');
1965 if (cp)
1966 *cp = '\0';
1967
Darren Tuckercdf547a2004-05-24 10:12:19 +10001968 /* Handle user interrupts gracefully during commands */
1969 interrupted = 0;
1970 signal(SIGINT, cmd_interrupt);
1971
Darren Tucker909d8582010-01-08 19:02:40 +11001972 err = parse_dispatch_command(conn, cmd, &remote_path,
1973 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11001974 if (err != 0)
1975 break;
1976 }
Darren Tucker909d8582010-01-08 19:02:40 +11001977 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001978 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001979
Tim Rice027e8b12005-08-15 14:52:50 -07001980#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10001981 if (el != NULL)
1982 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07001983#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10001984
Damien Miller20e1fab2004-02-18 14:30:55 +11001985 /* err == 1 signifies normal "quit" exit */
1986 return (err >= 0 ? 0 : -1);
1987}
Damien Miller62d57f62003-01-10 21:43:24 +11001988
Ben Lindstrombba81212001-06-25 05:01:22 +00001989static void
Damien Millercc685c12003-06-04 22:51:38 +10001990connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11001991{
1992 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001993
Damien Miller33804262001-02-04 23:20:18 +11001994#ifdef USE_PIPES
1995 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001996
Damien Miller33804262001-02-04 23:20:18 +11001997 if ((pipe(pin) == -1) || (pipe(pout) == -1))
1998 fatal("pipe: %s", strerror(errno));
1999 *in = pin[0];
2000 *out = pout[1];
2001 c_in = pout[0];
2002 c_out = pin[1];
2003#else /* USE_PIPES */
2004 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002005
Damien Miller33804262001-02-04 23:20:18 +11002006 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2007 fatal("socketpair: %s", strerror(errno));
2008 *in = *out = inout[0];
2009 c_in = c_out = inout[1];
2010#endif /* USE_PIPES */
2011
Damien Millercc685c12003-06-04 22:51:38 +10002012 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002013 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002014 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002015 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2016 (dup2(c_out, STDOUT_FILENO) == -1)) {
2017 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002018 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002019 }
2020 close(*in);
2021 close(*out);
2022 close(c_in);
2023 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002024
2025 /*
2026 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002027 * ignore SIGINT if we want to gracefully abort commands,
2028 * otherwise the signal will make it to the ssh process and
Darren Tuckercdf547a2004-05-24 10:12:19 +10002029 * kill it too
2030 */
2031 signal(SIGINT, SIG_IGN);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002032 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002033 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002034 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002035 }
2036
Damien Millercc685c12003-06-04 22:51:38 +10002037 signal(SIGTERM, killchild);
2038 signal(SIGINT, killchild);
2039 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11002040 close(c_in);
2041 close(c_out);
2042}
2043
Ben Lindstrombba81212001-06-25 05:01:22 +00002044static void
Damien Miller33804262001-02-04 23:20:18 +11002045usage(void)
2046{
Damien Miller025e01c2002-02-08 22:06:29 +11002047 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002048
Ben Lindstrom1e243242001-09-18 05:38:44 +00002049 fprintf(stderr,
Darren Tucker1b0dd172009-10-07 08:37:48 +11002050 "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002051 " [-D sftp_server_path] [-F ssh_config] "
2052 "[-i identity_file]\n"
2053 " [-o ssh_option] [-P port] [-R num_requests] "
2054 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002055 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002056 " %s [user@]host[:file ...]\n"
2057 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002058 " %s -b batchfile [user@]host\n",
2059 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002060 exit(1);
2061}
2062
Kevin Stevesef4eea92001-02-05 12:42:17 +00002063int
Damien Miller33804262001-02-04 23:20:18 +11002064main(int argc, char **argv)
2065{
Damien Miller956f3fb2003-01-10 21:40:00 +11002066 int in, out, ch, err;
Damien Miller7cf17eb2004-06-15 10:28:56 +10002067 char *host, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002068 int debug_level = 0, sshver = 2;
2069 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002070 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002071 LogLevel ll = SYSLOG_LEVEL_INFO;
2072 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002073 extern int optind;
2074 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002075 struct sftp_conn *conn;
2076 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2077 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller33804262001-02-04 23:20:18 +11002078
Darren Tuckerce321d82005-10-03 18:11:24 +10002079 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2080 sanitise_stdfd();
2081
Damien Miller59d3d5b2003-08-22 09:34:41 +10002082 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002083 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002084 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002085 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002086 addargs(&args, "-oForwardX11 no");
2087 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002088 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002089 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002090
Ben Lindstrom387c4722001-05-08 20:27:25 +00002091 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002092 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002093
Darren Tucker282b4022009-10-07 08:23:06 +11002094 while ((ch = getopt(argc, argv,
Darren Tucker1b0dd172009-10-07 08:37:48 +11002095 "1246hqrvCc:D:i:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002096 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002097 /* Passed through to ssh(1) */
2098 case '4':
2099 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002100 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002101 addargs(&args, "-%c", ch);
2102 break;
2103 /* Passed through to ssh(1) with argument */
2104 case 'F':
2105 case 'c':
2106 case 'i':
2107 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002108 addargs(&args, "-%c", ch);
2109 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002110 break;
2111 case 'q':
2112 showprogress = 0;
2113 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002114 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002115 case 'P':
2116 addargs(&args, "-oPort %s", optarg);
2117 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002118 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002119 if (debug_level < 3) {
2120 addargs(&args, "-v");
2121 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2122 }
2123 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002124 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002125 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002126 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002127 if (sftp_server == NULL)
2128 sftp_server = _PATH_SFTP_SERVER;
2129 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002130 case '2':
2131 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002132 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002133 case 'B':
2134 copy_buffer_len = strtol(optarg, &cp, 10);
2135 if (copy_buffer_len == 0 || *cp != '\0')
2136 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002137 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002138 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002139 if (batchmode)
2140 fatal("Batch file already specified.");
2141
2142 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002143 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002144 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002145 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002146 showprogress = 0;
Damien Miller44f75c12004-01-21 10:58:47 +11002147 batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002148 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002149 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002150 case 'p':
2151 global_pflag = 1;
2152 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002153 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002154 sftp_direct = optarg;
2155 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002156 case 'r':
2157 global_rflag = 1;
2158 break;
Damien Miller16a13332002-02-13 14:03:56 +11002159 case 'R':
2160 num_requests = strtol(optarg, &cp, 10);
2161 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002162 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002163 optarg);
2164 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002165 case 's':
2166 sftp_server = optarg;
2167 break;
2168 case 'S':
2169 ssh_program = optarg;
2170 replacearg(&args, 0, "%s", ssh_program);
2171 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002172 case 'h':
2173 default:
Damien Miller33804262001-02-04 23:20:18 +11002174 usage();
2175 }
2176 }
2177
Damien Millerc0f27d82004-03-08 23:12:19 +11002178 if (!isatty(STDERR_FILENO))
2179 showprogress = 0;
2180
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002181 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2182
Damien Millerd14ee1e2002-02-05 12:27:31 +11002183 if (sftp_direct == NULL) {
2184 if (optind == argc || argc > (optind + 2))
2185 usage();
Damien Miller33804262001-02-04 23:20:18 +11002186
Damien Millerd14ee1e2002-02-05 12:27:31 +11002187 userhost = xstrdup(argv[optind]);
2188 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002189
Ben Lindstromc276c122002-12-23 02:14:51 +00002190 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002191 host = userhost;
2192 else {
2193 *host++ = '\0';
2194 if (!userhost[0]) {
2195 fprintf(stderr, "Missing username\n");
2196 usage();
2197 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002198 addargs(&args, "-l");
2199 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002200 }
2201
Damien Millerec692032004-01-27 21:22:00 +11002202 if ((cp = colon(host)) != NULL) {
2203 *cp++ = '\0';
2204 file1 = cp;
2205 }
2206
Damien Millerd14ee1e2002-02-05 12:27:31 +11002207 host = cleanhostname(host);
2208 if (!*host) {
2209 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002210 usage();
2211 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002212
Damien Millerd14ee1e2002-02-05 12:27:31 +11002213 addargs(&args, "-oProtocol %d", sshver);
2214
2215 /* no subsystem if the server-spec contains a '/' */
2216 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2217 addargs(&args, "-s");
2218
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002219 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002220 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002221 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002222 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002223
Damien Millercc685c12003-06-04 22:51:38 +10002224 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002225 } else {
2226 args.list = NULL;
2227 addargs(&args, "sftp-server");
2228
Damien Millercc685c12003-06-04 22:51:38 +10002229 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002230 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002231 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002232
Darren Tucker21063192010-01-08 17:10:36 +11002233 conn = do_init(in, out, copy_buffer_len, num_requests);
2234 if (conn == NULL)
2235 fatal("Couldn't initialise connection to server");
2236
2237 if (!batchmode) {
2238 if (sftp_direct == NULL)
2239 fprintf(stderr, "Connected to %s.\n", host);
2240 else
2241 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2242 }
2243
2244 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002245
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002246#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002247 shutdown(in, SHUT_RDWR);
2248 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002249#endif
2250
Damien Miller33804262001-02-04 23:20:18 +11002251 close(in);
2252 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002253 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002254 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002255
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002256 while (waitpid(sshpid, NULL, 0) == -1)
2257 if (errno != EINTR)
2258 fatal("Couldn't wait for ssh process: %s",
2259 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002260
Damien Miller956f3fb2003-01-10 21:40:00 +11002261 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002262}