blob: abc689e8cfc6b9d3d7f9cbd3dc9b1aeaa9277934 [file] [log] [blame]
Damien Miller1edcbf62013-10-18 10:17:17 +11001/* $OpenBSD: sftp.c,v 1.157 2013/10/17 07:35:48 jmc 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 Tuckerae133d42013-06-06 08:30:20 +100041#ifdef HAVE_LOCALE_H
42# include <locale.h>
43#endif
Darren Tucker2d963d82004-11-07 20:04:10 +110044#ifdef USE_LIBEDIT
45#include <histedit.h>
46#else
47typedef void EditLine;
48#endif
Damien Miller6ff3cad2006-03-15 11:52:09 +110049#include <signal.h>
Damien Millere7a1e5c2006-08-05 11:34:19 +100050#include <stdlib.h>
Damien Millera7a73ee2006-08-05 11:37:59 +100051#include <stdio.h>
Damien Millere3476ed2006-07-24 14:13:33 +100052#include <string.h>
Damien Millere6b3b612006-07-24 14:01:23 +100053#include <unistd.h>
Damien Millerd7834352006-08-05 12:39:39 +100054#include <stdarg.h>
Damien Miller33804262001-02-04 23:20:18 +110055
Damien Millera7058ec2008-05-20 08:57:06 +100056#ifdef HAVE_UTIL_H
57# include <util.h>
58#endif
59
Damien Miller33804262001-02-04 23:20:18 +110060#include "xmalloc.h"
61#include "log.h"
62#include "pathnames.h"
Ben Lindstrom4529b702001-05-03 23:39:53 +000063#include "misc.h"
Damien Miller33804262001-02-04 23:20:18 +110064
65#include "sftp.h"
Damien Millerd7834352006-08-05 12:39:39 +100066#include "buffer.h"
Damien Miller33804262001-02-04 23:20:18 +110067#include "sftp-common.h"
68#include "sftp-client.h"
Damien Millerd7d46bb2004-02-18 14:11:13 +110069
Darren Tucker21063192010-01-08 17:10:36 +110070#define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */
71#define DEFAULT_NUM_REQUESTS 64 /* # concurrent outstanding requests */
72
Damien Miller20e1fab2004-02-18 14:30:55 +110073/* File to read commands from */
74FILE* infile;
75
76/* Are we in batchfile mode? */
77int batchmode = 0;
78
Damien Miller20e1fab2004-02-18 14:30:55 +110079/* PID of ssh transport process */
80static pid_t sshpid = -1;
81
Damien Miller9303e652013-04-23 15:22:40 +100082/* Suppress diagnositic messages */
83int quiet = 0;
84
Damien Miller20e1fab2004-02-18 14:30:55 +110085/* This is set to 0 if the progressmeter is not desired. */
Damien Millerc0f27d82004-03-08 23:12:19 +110086int showprogress = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +110087
Darren Tucker1b0dd172009-10-07 08:37:48 +110088/* When this option is set, we always recursively download/upload directories */
89int global_rflag = 0;
90
Damien Miller0d032412013-07-25 11:56:52 +100091/* When this option is set, we resume download if possible */
92int global_aflag = 0;
93
Darren Tucker1b0dd172009-10-07 08:37:48 +110094/* When this option is set, the file transfers will always preserve times */
95int global_pflag = 0;
96
Damien Millerf29238e2013-10-17 11:48:52 +110097/* When this option is set, transfers will have fsync() called on each file */
98int global_fflag = 0;
99
Darren Tuckercdf547a2004-05-24 10:12:19 +1000100/* SIGINT received during command processing */
101volatile sig_atomic_t interrupted = 0;
102
Darren Tuckerb9123452004-06-22 13:06:45 +1000103/* I wish qsort() took a separate ctx for the comparison function...*/
104int sort_flag;
105
Darren Tucker909d8582010-01-08 19:02:40 +1100106/* Context used for commandline completion */
107struct complete_ctx {
108 struct sftp_conn *conn;
109 char **remote_pathp;
110};
111
Damien Miller20e1fab2004-02-18 14:30:55 +1100112int remote_glob(struct sftp_conn *, const char *, int,
113 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
Damien Miller33804262001-02-04 23:20:18 +1100114
Kevin Steves12888d12001-03-05 19:50:57 +0000115extern char *__progname;
Kevin Steves12888d12001-03-05 19:50:57 +0000116
Damien Miller20e1fab2004-02-18 14:30:55 +1100117/* Separators for interactive commands */
118#define WHITESPACE " \t\r\n"
Damien Millerd7686fd2001-02-10 00:40:03 +1100119
Darren Tuckerb9123452004-06-22 13:06:45 +1000120/* ls flags */
Darren Tucker2901e2d2010-01-13 22:44:06 +1100121#define LS_LONG_VIEW 0x0001 /* Full view ala ls -l */
122#define LS_SHORT_VIEW 0x0002 /* Single row view ala ls -1 */
123#define LS_NUMERIC_VIEW 0x0004 /* Long view with numeric uid/gid */
124#define LS_NAME_SORT 0x0008 /* Sort by name (default) */
125#define LS_TIME_SORT 0x0010 /* Sort by mtime */
126#define LS_SIZE_SORT 0x0020 /* Sort by file size */
127#define LS_REVERSE_SORT 0x0040 /* Reverse sort order */
128#define LS_SHOW_ALL 0x0080 /* Don't skip filenames starting with '.' */
129#define LS_SI_UNITS 0x0100 /* Display sizes as K, M, G, etc. */
Darren Tuckerb9123452004-06-22 13:06:45 +1000130
Darren Tucker2901e2d2010-01-13 22:44:06 +1100131#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS)
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000132#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
Damien Miller20e1fab2004-02-18 14:30:55 +1100133
134/* Commands for interactive mode */
Damien Miller02e87802013-08-21 02:38:51 +1000135enum sftp_command {
136 I_CHDIR = 1,
137 I_CHGRP,
138 I_CHMOD,
139 I_CHOWN,
140 I_DF,
141 I_GET,
142 I_HELP,
143 I_LCHDIR,
144 I_LINK,
145 I_LLS,
146 I_LMKDIR,
147 I_LPWD,
148 I_LS,
149 I_LUMASK,
150 I_MKDIR,
151 I_PUT,
152 I_PWD,
153 I_QUIT,
154 I_RENAME,
155 I_RM,
156 I_RMDIR,
157 I_SHELL,
158 I_SYMLINK,
159 I_VERSION,
160 I_PROGRESS,
161 I_REGET,
162};
Damien Miller20e1fab2004-02-18 14:30:55 +1100163
164struct CMD {
165 const char *c;
166 const int n;
Darren Tucker909d8582010-01-08 19:02:40 +1100167 const int t;
Damien Miller20e1fab2004-02-18 14:30:55 +1100168};
169
Darren Tucker909d8582010-01-08 19:02:40 +1100170/* Type of completion */
171#define NOARGS 0
172#define REMOTE 1
173#define LOCAL 2
174
Damien Miller20e1fab2004-02-18 14:30:55 +1100175static const struct CMD cmds[] = {
Darren Tucker909d8582010-01-08 19:02:40 +1100176 { "bye", I_QUIT, NOARGS },
177 { "cd", I_CHDIR, REMOTE },
178 { "chdir", I_CHDIR, REMOTE },
179 { "chgrp", I_CHGRP, REMOTE },
180 { "chmod", I_CHMOD, REMOTE },
181 { "chown", I_CHOWN, REMOTE },
182 { "df", I_DF, REMOTE },
183 { "dir", I_LS, REMOTE },
184 { "exit", I_QUIT, NOARGS },
185 { "get", I_GET, REMOTE },
186 { "help", I_HELP, NOARGS },
187 { "lcd", I_LCHDIR, LOCAL },
188 { "lchdir", I_LCHDIR, LOCAL },
189 { "lls", I_LLS, LOCAL },
190 { "lmkdir", I_LMKDIR, LOCAL },
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100191 { "ln", I_LINK, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100192 { "lpwd", I_LPWD, LOCAL },
193 { "ls", I_LS, REMOTE },
194 { "lumask", I_LUMASK, NOARGS },
195 { "mkdir", I_MKDIR, REMOTE },
Damien Miller099fc162010-05-10 11:56:50 +1000196 { "mget", I_GET, REMOTE },
197 { "mput", I_PUT, LOCAL },
Darren Tucker909d8582010-01-08 19:02:40 +1100198 { "progress", I_PROGRESS, NOARGS },
199 { "put", I_PUT, LOCAL },
200 { "pwd", I_PWD, REMOTE },
201 { "quit", I_QUIT, NOARGS },
Damien Miller0d032412013-07-25 11:56:52 +1000202 { "reget", I_REGET, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100203 { "rename", I_RENAME, REMOTE },
204 { "rm", I_RM, REMOTE },
205 { "rmdir", I_RMDIR, REMOTE },
206 { "symlink", I_SYMLINK, REMOTE },
207 { "version", I_VERSION, NOARGS },
208 { "!", I_SHELL, NOARGS },
209 { "?", I_HELP, NOARGS },
210 { NULL, -1, -1 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100211};
212
Darren Tucker21063192010-01-08 17:10:36 +1100213int interactive_loop(struct sftp_conn *, char *file1, char *file2);
Damien Miller20e1fab2004-02-18 14:30:55 +1100214
Damien Millerb6c85fc2007-01-05 16:30:41 +1100215/* ARGSUSED */
Damien Miller20e1fab2004-02-18 14:30:55 +1100216static void
Darren Tuckercdf547a2004-05-24 10:12:19 +1000217killchild(int signo)
218{
Darren Tuckerba66df82005-01-24 21:57:40 +1100219 if (sshpid > 1) {
Darren Tuckercdf547a2004-05-24 10:12:19 +1000220 kill(sshpid, SIGTERM);
Darren Tuckerba66df82005-01-24 21:57:40 +1100221 waitpid(sshpid, NULL, 0);
222 }
Darren Tuckercdf547a2004-05-24 10:12:19 +1000223
224 _exit(1);
225}
226
Damien Millerb6c85fc2007-01-05 16:30:41 +1100227/* ARGSUSED */
Darren Tuckercdf547a2004-05-24 10:12:19 +1000228static void
229cmd_interrupt(int signo)
230{
231 const char msg[] = "\rInterrupt \n";
Darren Tuckere2f189a2004-12-06 22:45:53 +1100232 int olderrno = errno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000233
Darren Tuckerdbee3082013-05-16 20:32:29 +1000234 (void)write(STDERR_FILENO, msg, sizeof(msg) - 1);
Darren Tuckercdf547a2004-05-24 10:12:19 +1000235 interrupted = 1;
Darren Tuckere2f189a2004-12-06 22:45:53 +1100236 errno = olderrno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000237}
238
239static void
Damien Miller20e1fab2004-02-18 14:30:55 +1100240help(void)
241{
Damien Miller62fd18a2009-01-28 16:14:09 +1100242 printf("Available commands:\n"
243 "bye Quit sftp\n"
244 "cd path Change remote directory to 'path'\n"
245 "chgrp grp path Change group of file 'path' to 'grp'\n"
246 "chmod mode path Change permissions of file 'path' to 'mode'\n"
247 "chown own path Change owner of file 'path' to 'own'\n"
248 "df [-hi] [path] Display statistics for current directory or\n"
249 " filesystem containing 'path'\n"
250 "exit Quit sftp\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100251 "get [-Ppr] remote [local] Download file\n"
Damien Miller0d032412013-07-25 11:56:52 +1000252 "reget remote [local] Resume download file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100253 "help Display this help text\n"
254 "lcd path Change local directory to 'path'\n"
255 "lls [ls-options [path]] Display local directory listing\n"
256 "lmkdir path Create local directory\n"
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100257 "ln [-s] oldpath newpath Link remote file (-s for symlink)\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100258 "lpwd Print local working directory\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100259 "ls [-1afhlnrSt] [path] Display remote directory listing\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100260 "lumask umask Set local umask to 'umask'\n"
261 "mkdir path Create remote directory\n"
262 "progress Toggle display of progress meter\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100263 "put [-Ppr] local [remote] Upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100264 "pwd Display remote working directory\n"
265 "quit Quit sftp\n"
266 "rename oldpath newpath Rename remote file\n"
267 "rm path Delete remote file\n"
268 "rmdir path Remove remote directory\n"
269 "symlink oldpath newpath Symlink remote file\n"
270 "version Show SFTP version\n"
271 "!command Execute 'command' in local shell\n"
272 "! Escape to local shell\n"
273 "? Synonym for help\n");
Damien Miller20e1fab2004-02-18 14:30:55 +1100274}
275
276static void
277local_do_shell(const char *args)
278{
279 int status;
280 char *shell;
281 pid_t pid;
282
283 if (!*args)
284 args = NULL;
285
Damien Miller38d9a962010-10-07 22:07:11 +1100286 if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
Damien Miller20e1fab2004-02-18 14:30:55 +1100287 shell = _PATH_BSHELL;
288
289 if ((pid = fork()) == -1)
290 fatal("Couldn't fork: %s", strerror(errno));
291
292 if (pid == 0) {
293 /* XXX: child has pipe fds to ssh subproc open - issue? */
294 if (args) {
295 debug3("Executing %s -c \"%s\"", shell, args);
296 execl(shell, shell, "-c", args, (char *)NULL);
297 } else {
298 debug3("Executing %s", shell);
299 execl(shell, shell, (char *)NULL);
300 }
301 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
302 strerror(errno));
303 _exit(1);
304 }
305 while (waitpid(pid, &status, 0) == -1)
306 if (errno != EINTR)
307 fatal("Couldn't wait for child: %s", strerror(errno));
308 if (!WIFEXITED(status))
Damien Miller55b04f12006-03-26 14:23:17 +1100309 error("Shell exited abnormally");
Damien Miller20e1fab2004-02-18 14:30:55 +1100310 else if (WEXITSTATUS(status))
311 error("Shell exited with status %d", WEXITSTATUS(status));
312}
313
314static void
315local_do_ls(const char *args)
316{
317 if (!args || !*args)
318 local_do_shell(_PATH_LS);
319 else {
320 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
321 char *buf = xmalloc(len);
322
323 /* XXX: quoting - rip quoting code from ftp? */
324 snprintf(buf, len, _PATH_LS " %s", args);
325 local_do_shell(buf);
Darren Tuckera627d422013-06-02 07:31:17 +1000326 free(buf);
Damien Miller20e1fab2004-02-18 14:30:55 +1100327 }
328}
329
330/* Strip one path (usually the pwd) from the start of another */
331static char *
332path_strip(char *path, char *strip)
333{
334 size_t len;
335
336 if (strip == NULL)
337 return (xstrdup(path));
338
339 len = strlen(strip);
Darren Tuckere2f189a2004-12-06 22:45:53 +1100340 if (strncmp(path, strip, len) == 0) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100341 if (strip[len - 1] != '/' && path[len] == '/')
342 len++;
343 return (xstrdup(path + len));
344 }
345
346 return (xstrdup(path));
347}
348
349static char *
Damien Miller20e1fab2004-02-18 14:30:55 +1100350make_absolute(char *p, char *pwd)
351{
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000352 char *abs_str;
Damien Miller20e1fab2004-02-18 14:30:55 +1100353
354 /* Derelativise */
355 if (p && p[0] != '/') {
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000356 abs_str = path_append(pwd, p);
Darren Tuckera627d422013-06-02 07:31:17 +1000357 free(p);
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000358 return(abs_str);
Damien Miller20e1fab2004-02-18 14:30:55 +1100359 } else
360 return(p);
361}
362
363static int
Damien Miller0d032412013-07-25 11:56:52 +1000364parse_getput_flags(const char *cmd, char **argv, int argc,
Damien Millerf29238e2013-10-17 11:48:52 +1100365 int *aflag, int *fflag, int *pflag, int *rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100366{
Damien Millerf184bcf2008-06-29 22:45:13 +1000367 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000368 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100369
Damien Miller1cbc2922007-10-26 14:27:45 +1000370 optind = optreset = 1;
371 opterr = 0;
372
Damien Millerf29238e2013-10-17 11:48:52 +1100373 *aflag = *fflag = *rflag = *pflag = 0;
374 while ((ch = getopt(argc, argv, "afPpRr")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000375 switch (ch) {
Damien Miller0d032412013-07-25 11:56:52 +1000376 case 'a':
377 *aflag = 1;
378 break;
Damien Millerf29238e2013-10-17 11:48:52 +1100379 case 'f':
380 *fflag = 1;
381 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100382 case 'p':
383 case 'P':
384 *pflag = 1;
385 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100386 case 'r':
387 case 'R':
388 *rflag = 1;
389 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100390 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000391 error("%s: Invalid flag -%c", cmd, optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000392 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100393 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100394 }
395
Damien Miller1cbc2922007-10-26 14:27:45 +1000396 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100397}
398
399static int
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100400parse_link_flags(const char *cmd, char **argv, int argc, int *sflag)
401{
402 extern int opterr, optind, optopt, optreset;
403 int ch;
404
405 optind = optreset = 1;
406 opterr = 0;
407
408 *sflag = 0;
409 while ((ch = getopt(argc, argv, "s")) != -1) {
410 switch (ch) {
411 case 's':
412 *sflag = 1;
413 break;
414 default:
415 error("%s: Invalid flag -%c", cmd, optopt);
416 return -1;
417 }
418 }
419
420 return optind;
421}
422
423static int
Damien Millerc7dba122013-08-21 02:41:15 +1000424parse_rename_flags(const char *cmd, char **argv, int argc, int *lflag)
425{
426 extern int opterr, optind, optopt, optreset;
427 int ch;
428
429 optind = optreset = 1;
430 opterr = 0;
431
432 *lflag = 0;
433 while ((ch = getopt(argc, argv, "l")) != -1) {
434 switch (ch) {
435 case 'l':
436 *lflag = 1;
437 break;
438 default:
439 error("%s: Invalid flag -%c", cmd, optopt);
440 return -1;
441 }
442 }
443
444 return optind;
445}
446
447static int
Damien Miller1cbc2922007-10-26 14:27:45 +1000448parse_ls_flags(char **argv, int argc, int *lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100449{
Damien Millerf184bcf2008-06-29 22:45:13 +1000450 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000451 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100452
Damien Miller1cbc2922007-10-26 14:27:45 +1000453 optind = optreset = 1;
454 opterr = 0;
455
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000456 *lflag = LS_NAME_SORT;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100457 while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000458 switch (ch) {
459 case '1':
460 *lflag &= ~VIEW_FLAGS;
461 *lflag |= LS_SHORT_VIEW;
462 break;
463 case 'S':
464 *lflag &= ~SORT_FLAGS;
465 *lflag |= LS_SIZE_SORT;
466 break;
467 case 'a':
468 *lflag |= LS_SHOW_ALL;
469 break;
470 case 'f':
471 *lflag &= ~SORT_FLAGS;
472 break;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100473 case 'h':
474 *lflag |= LS_SI_UNITS;
475 break;
Damien Miller1cbc2922007-10-26 14:27:45 +1000476 case 'l':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100477 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000478 *lflag |= LS_LONG_VIEW;
479 break;
480 case 'n':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100481 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000482 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
483 break;
484 case 'r':
485 *lflag |= LS_REVERSE_SORT;
486 break;
487 case 't':
488 *lflag &= ~SORT_FLAGS;
489 *lflag |= LS_TIME_SORT;
490 break;
491 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000492 error("ls: Invalid flag -%c", optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000493 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100494 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100495 }
496
Damien Miller1cbc2922007-10-26 14:27:45 +1000497 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100498}
499
500static int
Damien Millerd671e5a2008-05-19 14:53:33 +1000501parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
502{
Damien Millerf184bcf2008-06-29 22:45:13 +1000503 extern int opterr, optind, optopt, optreset;
Damien Millerd671e5a2008-05-19 14:53:33 +1000504 int ch;
505
506 optind = optreset = 1;
507 opterr = 0;
508
509 *hflag = *iflag = 0;
510 while ((ch = getopt(argc, argv, "hi")) != -1) {
511 switch (ch) {
512 case 'h':
513 *hflag = 1;
514 break;
515 case 'i':
516 *iflag = 1;
517 break;
518 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000519 error("%s: Invalid flag -%c", cmd, optopt);
Damien Millerd671e5a2008-05-19 14:53:33 +1000520 return -1;
521 }
522 }
523
524 return optind;
525}
526
527static int
Damien Miller036d3072013-08-21 02:41:46 +1000528parse_no_flags(const char *cmd, char **argv, int argc)
529{
530 extern int opterr, optind, optopt, optreset;
531 int ch;
532
533 optind = optreset = 1;
534 opterr = 0;
535
536 while ((ch = getopt(argc, argv, "")) != -1) {
537 switch (ch) {
538 default:
539 error("%s: Invalid flag -%c", cmd, optopt);
540 return -1;
541 }
542 }
543
544 return optind;
545}
546
547static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100548is_dir(char *path)
549{
550 struct stat sb;
551
552 /* XXX: report errors? */
553 if (stat(path, &sb) == -1)
554 return(0);
555
Darren Tucker1e80e402006-09-21 12:59:33 +1000556 return(S_ISDIR(sb.st_mode));
Damien Miller20e1fab2004-02-18 14:30:55 +1100557}
558
559static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100560remote_is_dir(struct sftp_conn *conn, char *path)
561{
562 Attrib *a;
563
564 /* XXX: report errors? */
565 if ((a = do_stat(conn, path, 1)) == NULL)
566 return(0);
567 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
568 return(0);
Darren Tucker1e80e402006-09-21 12:59:33 +1000569 return(S_ISDIR(a->perm));
Damien Miller20e1fab2004-02-18 14:30:55 +1100570}
571
Darren Tucker1b0dd172009-10-07 08:37:48 +1100572/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
Damien Miller20e1fab2004-02-18 14:30:55 +1100573static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100574pathname_is_dir(char *pathname)
575{
576 size_t l = strlen(pathname);
577
578 return l > 0 && pathname[l - 1] == '/';
579}
580
581static int
582process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
Damien Millerf29238e2013-10-17 11:48:52 +1100583 int pflag, int rflag, int resume, int fflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100584{
585 char *abs_src = NULL;
586 char *abs_dst = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100587 glob_t g;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100588 char *filename, *tmp=NULL;
589 int i, err = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100590
591 abs_src = xstrdup(src);
592 abs_src = make_absolute(abs_src, pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +1100593 memset(&g, 0, sizeof(g));
Darren Tucker1b0dd172009-10-07 08:37:48 +1100594
Damien Miller20e1fab2004-02-18 14:30:55 +1100595 debug3("Looking up %s", abs_src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100596 if (remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100597 error("File \"%s\" not found.", abs_src);
598 err = -1;
599 goto out;
600 }
601
Darren Tucker1b0dd172009-10-07 08:37:48 +1100602 /*
603 * If multiple matches then dst must be a directory or
604 * unspecified.
605 */
606 if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
607 error("Multiple source paths, but destination "
608 "\"%s\" is not a directory", dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100609 err = -1;
610 goto out;
611 }
612
Darren Tuckercdf547a2004-05-24 10:12:19 +1000613 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100614 tmp = xstrdup(g.gl_pathv[i]);
615 if ((filename = basename(tmp)) == NULL) {
616 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000617 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100618 err = -1;
619 goto out;
620 }
621
622 if (g.gl_matchc == 1 && dst) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100623 if (is_dir(dst)) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100624 abs_dst = path_append(dst, filename);
625 } else {
Damien Miller20e1fab2004-02-18 14:30:55 +1100626 abs_dst = xstrdup(dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100627 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100628 } else if (dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100629 abs_dst = path_append(dst, filename);
630 } else {
631 abs_dst = xstrdup(filename);
632 }
Darren Tuckera627d422013-06-02 07:31:17 +1000633 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100634
Damien Miller0d032412013-07-25 11:56:52 +1000635 resume |= global_aflag;
636 if (!quiet && resume)
637 printf("Resuming %s to %s\n", g.gl_pathv[i], abs_dst);
638 else if (!quiet && !resume)
Damien Miller9303e652013-04-23 15:22:40 +1000639 printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100640 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
Damien Miller0d032412013-07-25 11:56:52 +1000641 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
Damien Millerf29238e2013-10-17 11:48:52 +1100642 pflag || global_pflag, 1, resume,
643 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100644 err = -1;
645 } else {
646 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
Damien Millerf29238e2013-10-17 11:48:52 +1100647 pflag || global_pflag, resume,
648 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100649 err = -1;
650 }
Darren Tuckera627d422013-06-02 07:31:17 +1000651 free(abs_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100652 abs_dst = NULL;
653 }
654
655out:
Darren Tuckera627d422013-06-02 07:31:17 +1000656 free(abs_src);
Damien Miller20e1fab2004-02-18 14:30:55 +1100657 globfree(&g);
658 return(err);
659}
660
661static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100662process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
Damien Millerf29238e2013-10-17 11:48:52 +1100663 int pflag, int rflag, int fflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100664{
665 char *tmp_dst = NULL;
666 char *abs_dst = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100667 char *tmp = NULL, *filename = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100668 glob_t g;
669 int err = 0;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100670 int i, dst_is_dir = 1;
Damien Milleraec5cf82008-02-10 22:26:24 +1100671 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100672
673 if (dst) {
674 tmp_dst = xstrdup(dst);
675 tmp_dst = make_absolute(tmp_dst, pwd);
676 }
677
678 memset(&g, 0, sizeof(g));
679 debug3("Looking up %s", src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100680 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100681 error("File \"%s\" not found.", src);
682 err = -1;
683 goto out;
684 }
685
Darren Tucker1b0dd172009-10-07 08:37:48 +1100686 /* If we aren't fetching to pwd then stash this status for later */
687 if (tmp_dst != NULL)
688 dst_is_dir = remote_is_dir(conn, tmp_dst);
689
Damien Miller20e1fab2004-02-18 14:30:55 +1100690 /* If multiple matches, dst may be directory or unspecified */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100691 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
692 error("Multiple paths match, but destination "
693 "\"%s\" is not a directory", tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100694 err = -1;
695 goto out;
696 }
697
Darren Tuckercdf547a2004-05-24 10:12:19 +1000698 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Milleraec5cf82008-02-10 22:26:24 +1100699 if (stat(g.gl_pathv[i], &sb) == -1) {
700 err = -1;
701 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
702 continue;
703 }
Damien Miller02e87802013-08-21 02:38:51 +1000704
Darren Tucker1b0dd172009-10-07 08:37:48 +1100705 tmp = xstrdup(g.gl_pathv[i]);
706 if ((filename = basename(tmp)) == NULL) {
707 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000708 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100709 err = -1;
710 goto out;
711 }
712
713 if (g.gl_matchc == 1 && tmp_dst) {
714 /* If directory specified, append filename */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100715 if (dst_is_dir)
716 abs_dst = path_append(tmp_dst, filename);
717 else
Damien Miller20e1fab2004-02-18 14:30:55 +1100718 abs_dst = xstrdup(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100719 } else if (tmp_dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100720 abs_dst = path_append(tmp_dst, filename);
721 } else {
722 abs_dst = make_absolute(xstrdup(filename), pwd);
723 }
Darren Tuckera627d422013-06-02 07:31:17 +1000724 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100725
Damien Miller9303e652013-04-23 15:22:40 +1000726 if (!quiet)
727 printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100728 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
729 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
Damien Millerf29238e2013-10-17 11:48:52 +1100730 pflag || global_pflag, 1,
731 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100732 err = -1;
733 } else {
734 if (do_upload(conn, g.gl_pathv[i], abs_dst,
Damien Millerf29238e2013-10-17 11:48:52 +1100735 pflag || global_pflag,
736 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100737 err = -1;
738 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100739 }
740
741out:
Darren Tuckera627d422013-06-02 07:31:17 +1000742 free(abs_dst);
743 free(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100744 globfree(&g);
745 return(err);
746}
747
748static int
749sdirent_comp(const void *aa, const void *bb)
750{
751 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
752 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000753 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100754
Darren Tuckerb9123452004-06-22 13:06:45 +1000755#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000756 if (sort_flag & LS_NAME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000757 return (rmul * strcmp(a->filename, b->filename));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000758 else if (sort_flag & LS_TIME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000759 return (rmul * NCMP(a->a.mtime, b->a.mtime));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000760 else if (sort_flag & LS_SIZE_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000761 return (rmul * NCMP(a->a.size, b->a.size));
762
763 fatal("Unknown ls sort type");
Damien Miller20e1fab2004-02-18 14:30:55 +1100764}
765
766/* sftp ls.1 replacement for directories */
767static int
768do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
769{
Damien Millereccb9de2005-06-17 12:59:34 +1000770 int n;
771 u_int c = 1, colspace = 0, columns = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100772 SFTP_DIRENT **d;
773
774 if ((n = do_readdir(conn, path, &d)) != 0)
775 return (n);
776
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000777 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000778 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100779 struct winsize ws;
780 char *tmp;
781
782 /* Count entries for sort and find longest filename */
Darren Tucker9a526452004-06-22 13:09:55 +1000783 for (n = 0; d[n] != NULL; n++) {
784 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
785 m = MAX(m, strlen(d[n]->filename));
786 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100787
788 /* Add any subpath that also needs to be counted */
789 tmp = path_strip(path, strip_path);
790 m += strlen(tmp);
Darren Tuckera627d422013-06-02 07:31:17 +1000791 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100792
793 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
794 width = ws.ws_col;
795
796 columns = width / (m + 2);
797 columns = MAX(columns, 1);
798 colspace = width / columns;
799 colspace = MIN(colspace, width);
800 }
801
Darren Tuckerb9123452004-06-22 13:06:45 +1000802 if (lflag & SORT_FLAGS) {
Damien Miller653b93b2005-11-05 15:15:23 +1100803 for (n = 0; d[n] != NULL; n++)
804 ; /* count entries */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000805 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
Darren Tuckerb9123452004-06-22 13:06:45 +1000806 qsort(d, n, sizeof(*d), sdirent_comp);
807 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100808
Darren Tuckercdf547a2004-05-24 10:12:19 +1000809 for (n = 0; d[n] != NULL && !interrupted; n++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100810 char *tmp, *fname;
811
Darren Tucker9a526452004-06-22 13:09:55 +1000812 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
813 continue;
814
Damien Miller20e1fab2004-02-18 14:30:55 +1100815 tmp = path_append(path, d[n]->filename);
816 fname = path_strip(tmp, strip_path);
Darren Tuckera627d422013-06-02 07:31:17 +1000817 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100818
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000819 if (lflag & LS_LONG_VIEW) {
Darren Tucker2901e2d2010-01-13 22:44:06 +1100820 if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000821 char *lname;
822 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100823
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000824 memset(&sb, 0, sizeof(sb));
825 attrib_to_stat(&d[n]->a, &sb);
Darren Tucker2901e2d2010-01-13 22:44:06 +1100826 lname = ls_file(fname, &sb, 1,
827 (lflag & LS_SI_UNITS));
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000828 printf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000829 free(lname);
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000830 } else
831 printf("%s\n", d[n]->longname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100832 } else {
833 printf("%-*s", colspace, fname);
834 if (c >= columns) {
835 printf("\n");
836 c = 1;
837 } else
838 c++;
839 }
840
Darren Tuckera627d422013-06-02 07:31:17 +1000841 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100842 }
843
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000844 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100845 printf("\n");
846
847 free_sftp_dirents(d);
848 return (0);
849}
850
851/* sftp ls.1 replacement which handles path globs */
852static int
853do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
854 int lflag)
855{
Damien Millera6e121a2010-10-07 21:39:17 +1100856 char *fname, *lname;
Damien Miller68e2e562010-10-07 21:39:55 +1100857 glob_t g;
858 int err;
859 struct winsize ws;
860 u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100861
862 memset(&g, 0, sizeof(g));
863
Damien Millera6e121a2010-10-07 21:39:17 +1100864 if (remote_glob(conn, path,
Damien Millerd7be70d2011-09-22 21:43:06 +1000865 GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
866 NULL, &g) ||
Damien Millera6e121a2010-10-07 21:39:17 +1100867 (g.gl_pathc && !g.gl_matchc)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100868 if (g.gl_pathc)
869 globfree(&g);
Damien Miller20e1fab2004-02-18 14:30:55 +1100870 error("Can't ls: \"%s\" not found", path);
Damien Millera6e121a2010-10-07 21:39:17 +1100871 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100872 }
873
Darren Tuckercdf547a2004-05-24 10:12:19 +1000874 if (interrupted)
875 goto out;
876
Damien Miller20e1fab2004-02-18 14:30:55 +1100877 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100878 * If the glob returns a single match and it is a directory,
879 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100880 */
Damien Millera6e121a2010-10-07 21:39:17 +1100881 if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
882 S_ISDIR(g.gl_statv[0]->st_mode)) {
883 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
884 globfree(&g);
885 return err;
Damien Miller20e1fab2004-02-18 14:30:55 +1100886 }
887
Damien Miller68e2e562010-10-07 21:39:55 +1100888 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
889 width = ws.ws_col;
Damien Miller20e1fab2004-02-18 14:30:55 +1100890
Damien Miller68e2e562010-10-07 21:39:55 +1100891 if (!(lflag & LS_SHORT_VIEW)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100892 /* Count entries for sort and find longest filename */
893 for (i = 0; g.gl_pathv[i]; i++)
894 m = MAX(m, strlen(g.gl_pathv[i]));
895
Damien Miller20e1fab2004-02-18 14:30:55 +1100896 columns = width / (m + 2);
897 columns = MAX(columns, 1);
898 colspace = width / columns;
899 }
900
Damien Millerea858292012-06-30 08:33:32 +1000901 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100902 fname = path_strip(g.gl_pathv[i], strip_path);
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000903 if (lflag & LS_LONG_VIEW) {
Damien Millera6e121a2010-10-07 21:39:17 +1100904 if (g.gl_statv[i] == NULL) {
905 error("no stat information for %s", fname);
906 continue;
907 }
908 lname = ls_file(fname, g.gl_statv[i], 1,
909 (lflag & LS_SI_UNITS));
Damien Miller20e1fab2004-02-18 14:30:55 +1100910 printf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000911 free(lname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100912 } else {
913 printf("%-*s", colspace, fname);
914 if (c >= columns) {
915 printf("\n");
916 c = 1;
917 } else
918 c++;
919 }
Darren Tuckera627d422013-06-02 07:31:17 +1000920 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100921 }
922
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000923 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100924 printf("\n");
925
Darren Tuckercdf547a2004-05-24 10:12:19 +1000926 out:
Damien Miller20e1fab2004-02-18 14:30:55 +1100927 if (g.gl_pathc)
928 globfree(&g);
929
Damien Millera6e121a2010-10-07 21:39:17 +1100930 return 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100931}
932
Damien Millerd671e5a2008-05-19 14:53:33 +1000933static int
934do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
935{
Darren Tucker7b598892008-06-09 22:49:36 +1000936 struct sftp_statvfs st;
Damien Millerd671e5a2008-05-19 14:53:33 +1000937 char s_used[FMT_SCALED_STRSIZE];
938 char s_avail[FMT_SCALED_STRSIZE];
939 char s_root[FMT_SCALED_STRSIZE];
940 char s_total[FMT_SCALED_STRSIZE];
Darren Tuckerb5082e92010-01-08 18:51:47 +1100941 unsigned long long ffree;
Damien Millerd671e5a2008-05-19 14:53:33 +1000942
943 if (do_statvfs(conn, path, &st, 1) == -1)
944 return -1;
945 if (iflag) {
Darren Tuckerb5082e92010-01-08 18:51:47 +1100946 ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
Damien Millerd671e5a2008-05-19 14:53:33 +1000947 printf(" Inodes Used Avail "
948 "(root) %%Capacity\n");
949 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
950 (unsigned long long)st.f_files,
951 (unsigned long long)(st.f_files - st.f_ffree),
952 (unsigned long long)st.f_favail,
Darren Tuckerb5082e92010-01-08 18:51:47 +1100953 (unsigned long long)st.f_ffree, ffree);
Damien Millerd671e5a2008-05-19 14:53:33 +1000954 } else if (hflag) {
955 strlcpy(s_used, "error", sizeof(s_used));
956 strlcpy(s_avail, "error", sizeof(s_avail));
957 strlcpy(s_root, "error", sizeof(s_root));
958 strlcpy(s_total, "error", sizeof(s_total));
959 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
960 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
961 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
962 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
963 printf(" Size Used Avail (root) %%Capacity\n");
964 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
965 s_total, s_used, s_avail, s_root,
966 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
967 st.f_blocks));
968 } else {
969 printf(" Size Used Avail "
970 "(root) %%Capacity\n");
971 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
972 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
973 (unsigned long long)(st.f_frsize *
974 (st.f_blocks - st.f_bfree) / 1024),
975 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
976 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
977 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
978 st.f_blocks));
979 }
980 return 0;
981}
982
Damien Miller1cbc2922007-10-26 14:27:45 +1000983/*
984 * Undo escaping of glob sequences in place. Used to undo extra escaping
985 * applied in makeargv() when the string is destined for a function that
986 * does not glob it.
987 */
988static void
989undo_glob_escape(char *s)
990{
991 size_t i, j;
992
993 for (i = j = 0;;) {
994 if (s[i] == '\0') {
995 s[j] = '\0';
996 return;
997 }
998 if (s[i] != '\\') {
999 s[j++] = s[i++];
1000 continue;
1001 }
1002 /* s[i] == '\\' */
1003 ++i;
1004 switch (s[i]) {
1005 case '?':
1006 case '[':
1007 case '*':
1008 case '\\':
1009 s[j++] = s[i++];
1010 break;
1011 case '\0':
1012 s[j++] = '\\';
1013 s[j] = '\0';
1014 return;
1015 default:
1016 s[j++] = '\\';
1017 s[j++] = s[i++];
1018 break;
1019 }
1020 }
1021}
1022
1023/*
1024 * Split a string into an argument vector using sh(1)-style quoting,
1025 * comment and escaping rules, but with some tweaks to handle glob(3)
1026 * wildcards.
Darren Tucker909d8582010-01-08 19:02:40 +11001027 * The "sloppy" flag allows for recovery from missing terminating quote, for
1028 * use in parsing incomplete commandlines during tab autocompletion.
1029 *
Damien Miller1cbc2922007-10-26 14:27:45 +10001030 * Returns NULL on error or a NULL-terminated array of arguments.
Darren Tucker909d8582010-01-08 19:02:40 +11001031 *
1032 * If "lastquote" is not NULL, the quoting character used for the last
1033 * argument is placed in *lastquote ("\0", "'" or "\"").
Damien Miller02e87802013-08-21 02:38:51 +10001034 *
Darren Tucker909d8582010-01-08 19:02:40 +11001035 * If "terminated" is not NULL, *terminated will be set to 1 when the
1036 * last argument's quote has been properly terminated or 0 otherwise.
1037 * This parameter is only of use if "sloppy" is set.
Damien Miller1cbc2922007-10-26 14:27:45 +10001038 */
1039#define MAXARGS 128
1040#define MAXARGLEN 8192
1041static char **
Darren Tucker909d8582010-01-08 19:02:40 +11001042makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
1043 u_int *terminated)
Damien Miller1cbc2922007-10-26 14:27:45 +10001044{
1045 int argc, quot;
1046 size_t i, j;
1047 static char argvs[MAXARGLEN];
1048 static char *argv[MAXARGS + 1];
1049 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
1050
1051 *argcp = argc = 0;
1052 if (strlen(arg) > sizeof(argvs) - 1) {
1053 args_too_longs:
1054 error("string too long");
1055 return NULL;
1056 }
Darren Tucker909d8582010-01-08 19:02:40 +11001057 if (terminated != NULL)
1058 *terminated = 1;
1059 if (lastquote != NULL)
1060 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001061 state = MA_START;
1062 i = j = 0;
1063 for (;;) {
Damien Miller07daed52012-10-31 08:57:55 +11001064 if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){
Darren Tucker063018d2012-10-05 10:43:58 +10001065 error("Too many arguments.");
1066 return NULL;
1067 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001068 if (isspace(arg[i])) {
1069 if (state == MA_UNQUOTED) {
1070 /* Terminate current argument */
1071 argvs[j++] = '\0';
1072 argc++;
1073 state = MA_START;
1074 } else if (state != MA_START)
1075 argvs[j++] = arg[i];
1076 } else if (arg[i] == '"' || arg[i] == '\'') {
1077 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1078 if (state == MA_START) {
1079 argv[argc] = argvs + j;
1080 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +11001081 if (lastquote != NULL)
1082 *lastquote = arg[i];
Damien Miller02e87802013-08-21 02:38:51 +10001083 } else if (state == MA_UNQUOTED)
Damien Miller1cbc2922007-10-26 14:27:45 +10001084 state = q;
1085 else if (state == q)
1086 state = MA_UNQUOTED;
1087 else
1088 argvs[j++] = arg[i];
1089 } else if (arg[i] == '\\') {
1090 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1091 quot = state == MA_SQUOTE ? '\'' : '"';
1092 /* Unescape quote we are in */
1093 /* XXX support \n and friends? */
1094 if (arg[i + 1] == quot) {
1095 i++;
1096 argvs[j++] = arg[i];
1097 } else if (arg[i + 1] == '?' ||
1098 arg[i + 1] == '[' || arg[i + 1] == '*') {
1099 /*
1100 * Special case for sftp: append
1101 * double-escaped glob sequence -
1102 * glob will undo one level of
1103 * escaping. NB. string can grow here.
1104 */
1105 if (j >= sizeof(argvs) - 5)
1106 goto args_too_longs;
1107 argvs[j++] = '\\';
1108 argvs[j++] = arg[i++];
1109 argvs[j++] = '\\';
1110 argvs[j++] = arg[i];
1111 } else {
1112 argvs[j++] = arg[i++];
1113 argvs[j++] = arg[i];
1114 }
1115 } else {
1116 if (state == MA_START) {
1117 argv[argc] = argvs + j;
1118 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001119 if (lastquote != NULL)
1120 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001121 }
1122 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1123 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1124 /*
1125 * Special case for sftp: append
1126 * escaped glob sequence -
1127 * glob will undo one level of
1128 * escaping.
1129 */
1130 argvs[j++] = arg[i++];
1131 argvs[j++] = arg[i];
1132 } else {
1133 /* Unescape everything */
1134 /* XXX support \n and friends? */
1135 i++;
1136 argvs[j++] = arg[i];
1137 }
1138 }
1139 } else if (arg[i] == '#') {
1140 if (state == MA_SQUOTE || state == MA_DQUOTE)
1141 argvs[j++] = arg[i];
1142 else
1143 goto string_done;
1144 } else if (arg[i] == '\0') {
1145 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001146 if (sloppy) {
1147 state = MA_UNQUOTED;
1148 if (terminated != NULL)
1149 *terminated = 0;
1150 goto string_done;
1151 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001152 error("Unterminated quoted argument");
1153 return NULL;
1154 }
1155 string_done:
1156 if (state == MA_UNQUOTED) {
1157 argvs[j++] = '\0';
1158 argc++;
1159 }
1160 break;
1161 } else {
1162 if (state == MA_START) {
1163 argv[argc] = argvs + j;
1164 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001165 if (lastquote != NULL)
1166 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001167 }
1168 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1169 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1170 /*
1171 * Special case for sftp: escape quoted
1172 * glob(3) wildcards. NB. string can grow
1173 * here.
1174 */
1175 if (j >= sizeof(argvs) - 3)
1176 goto args_too_longs;
1177 argvs[j++] = '\\';
1178 argvs[j++] = arg[i];
1179 } else
1180 argvs[j++] = arg[i];
1181 }
1182 i++;
1183 }
1184 *argcp = argc;
1185 return argv;
1186}
1187
Damien Miller20e1fab2004-02-18 14:30:55 +11001188static int
Damien Millerf29238e2013-10-17 11:48:52 +11001189parse_args(const char **cpp, int *ignore_errors, int *aflag, int *fflag,
1190 int *hflag, int *iflag, int *lflag, int *pflag, int *rflag, int *sflag,
1191 unsigned long *n_arg, char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001192{
1193 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001194 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001195 int base = 0;
1196 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001197 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001198
1199 /* Skip leading whitespace */
1200 cp = cp + strspn(cp, WHITESPACE);
1201
Damien Miller20e1fab2004-02-18 14:30:55 +11001202 /* Check for leading '-' (disable error processing) */
Damien Millerf29238e2013-10-17 11:48:52 +11001203 *ignore_errors = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001204 if (*cp == '-') {
Damien Millerf29238e2013-10-17 11:48:52 +11001205 *ignore_errors = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001206 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001207 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001208 }
1209
Darren Tucker70cc0922010-01-09 22:28:03 +11001210 /* Ignore blank lines and lines which begin with comment '#' char */
1211 if (*cp == '\0' || *cp == '#')
1212 return (0);
1213
Darren Tucker909d8582010-01-08 19:02:40 +11001214 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001215 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001216
Damien Miller1cbc2922007-10-26 14:27:45 +10001217 /* Figure out which command we have */
1218 for (i = 0; cmds[i].c != NULL; i++) {
Damien Millerd6d9fa02013-02-12 11:02:46 +11001219 if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001220 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001221 }
1222 cmdnum = cmds[i].n;
1223 cmd = cmds[i].c;
1224
1225 /* Special case */
1226 if (*cp == '!') {
1227 cp++;
1228 cmdnum = I_SHELL;
1229 } else if (cmdnum == -1) {
1230 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001231 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001232 }
1233
1234 /* Get arguments and parse flags */
Damien Millerf29238e2013-10-17 11:48:52 +11001235 *aflag = *fflag = *hflag = *iflag = *lflag = *pflag = 0;
1236 *rflag = *sflag = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001237 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001238 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001239 switch (cmdnum) {
1240 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001241 case I_REGET:
Damien Miller20e1fab2004-02-18 14:30:55 +11001242 case I_PUT:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001243 if ((optidx = parse_getput_flags(cmd, argv, argc,
Damien Millerf29238e2013-10-17 11:48:52 +11001244 aflag, fflag, pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001245 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001246 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001247 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001248 error("You must specify at least one path after a "
1249 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001250 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001251 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001252 *path1 = xstrdup(argv[optidx]);
1253 /* Get second pathname (optional) */
1254 if (argc - optidx > 1) {
1255 *path2 = xstrdup(argv[optidx + 1]);
1256 /* Destination is not globbed */
1257 undo_glob_escape(*path2);
1258 }
Damien Miller0d032412013-07-25 11:56:52 +10001259 if (*aflag && cmdnum == I_PUT) {
1260 /* XXX implement resume for uploads */
1261 error("Resume is not supported for uploads");
1262 return -1;
1263 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001264 break;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001265 case I_LINK:
1266 if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1267 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001268 goto parse_two_paths;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001269 case I_RENAME:
Damien Millerc7dba122013-08-21 02:41:15 +10001270 if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1)
1271 return -1;
1272 goto parse_two_paths;
1273 case I_SYMLINK:
Damien Miller036d3072013-08-21 02:41:46 +10001274 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1275 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001276 parse_two_paths:
Damien Miller1cbc2922007-10-26 14:27:45 +10001277 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001278 error("You must specify two paths after a %s "
1279 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001280 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001281 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001282 *path1 = xstrdup(argv[optidx]);
1283 *path2 = xstrdup(argv[optidx + 1]);
1284 /* Paths are not globbed */
1285 undo_glob_escape(*path1);
1286 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001287 break;
1288 case I_RM:
1289 case I_MKDIR:
1290 case I_RMDIR:
1291 case I_CHDIR:
1292 case I_LCHDIR:
1293 case I_LMKDIR:
Damien Miller036d3072013-08-21 02:41:46 +10001294 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1295 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001296 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001297 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001298 error("You must specify a path after a %s command.",
1299 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001300 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001301 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001302 *path1 = xstrdup(argv[optidx]);
1303 /* Only "rm" globs */
1304 if (cmdnum != I_RM)
1305 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001306 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001307 case I_DF:
1308 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1309 iflag)) == -1)
1310 return -1;
1311 /* Default to current directory if no path specified */
1312 if (argc - optidx < 1)
1313 *path1 = NULL;
1314 else {
1315 *path1 = xstrdup(argv[optidx]);
1316 undo_glob_escape(*path1);
1317 }
1318 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001319 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001320 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001321 return(-1);
1322 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001323 if (argc - optidx > 0)
1324 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001325 break;
1326 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001327 /* Skip ls command and following whitespace */
1328 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001329 case I_SHELL:
1330 /* Uses the rest of the line */
1331 break;
1332 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001333 case I_CHMOD:
1334 base = 8;
1335 case I_CHOWN:
1336 case I_CHGRP:
Damien Miller036d3072013-08-21 02:41:46 +10001337 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1338 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001339 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001340 if (argc - optidx < 1)
1341 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001342 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001343 l = strtol(argv[optidx], &cp2, base);
1344 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1345 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1346 l < 0) {
1347 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001348 error("You must supply a numeric argument "
1349 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001350 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001351 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001352 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001353 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001354 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001355 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001356 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001357 error("You must specify a path after a %s command.",
1358 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001359 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001360 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001361 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001362 break;
1363 case I_QUIT:
1364 case I_PWD:
1365 case I_LPWD:
1366 case I_HELP:
1367 case I_VERSION:
1368 case I_PROGRESS:
Damien Miller036d3072013-08-21 02:41:46 +10001369 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1370 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001371 break;
1372 default:
1373 fatal("Command not implemented");
1374 }
1375
1376 *cpp = cp;
1377 return(cmdnum);
1378}
1379
1380static int
1381parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1382 int err_abort)
1383{
1384 char *path1, *path2, *tmp;
Damien Millerf29238e2013-10-17 11:48:52 +11001385 int ignore_errors = 0, aflag = 0, fflag = 0, hflag = 0, iflag = 0;
1386 int lflag = 0, pflag = 0, rflag = 0, sflag = 0;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001387 int cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001388 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001389 Attrib a, *aa;
1390 char path_buf[MAXPATHLEN];
1391 int err = 0;
1392 glob_t g;
1393
1394 path1 = path2 = NULL;
Damien Millerf29238e2013-10-17 11:48:52 +11001395 cmdnum = parse_args(&cmd, &ignore_errors, &aflag, &fflag, &hflag,
1396 &iflag, &lflag, &pflag, &rflag, &sflag, &n_arg, &path1, &path2);
1397 if (ignore_errors != 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001398 err_abort = 0;
1399
1400 memset(&g, 0, sizeof(g));
1401
1402 /* Perform command */
1403 switch (cmdnum) {
1404 case 0:
1405 /* Blank line */
1406 break;
1407 case -1:
1408 /* Unrecognized command */
1409 err = -1;
1410 break;
Damien Miller0d032412013-07-25 11:56:52 +10001411 case I_REGET:
1412 aflag = 1;
1413 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001414 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001415 err = process_get(conn, path1, path2, *pwd, pflag,
Damien Millerf29238e2013-10-17 11:48:52 +11001416 rflag, aflag, fflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001417 break;
1418 case I_PUT:
Damien Millerf29238e2013-10-17 11:48:52 +11001419 err = process_put(conn, path1, path2, *pwd, pflag,
1420 rflag, fflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001421 break;
1422 case I_RENAME:
1423 path1 = make_absolute(path1, *pwd);
1424 path2 = make_absolute(path2, *pwd);
Damien Millerc7dba122013-08-21 02:41:15 +10001425 err = do_rename(conn, path1, path2, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001426 break;
1427 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001428 sflag = 1;
1429 case I_LINK:
Damien Miller034f27a2013-08-21 02:40:44 +10001430 if (!sflag)
1431 path1 = make_absolute(path1, *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001432 path2 = make_absolute(path2, *pwd);
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001433 err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001434 break;
1435 case I_RM:
1436 path1 = make_absolute(path1, *pwd);
1437 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001438 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001439 if (!quiet)
1440 printf("Removing %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001441 err = do_rm(conn, g.gl_pathv[i]);
1442 if (err != 0 && err_abort)
1443 break;
1444 }
1445 break;
1446 case I_MKDIR:
1447 path1 = make_absolute(path1, *pwd);
1448 attrib_clear(&a);
1449 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1450 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001451 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001452 break;
1453 case I_RMDIR:
1454 path1 = make_absolute(path1, *pwd);
1455 err = do_rmdir(conn, path1);
1456 break;
1457 case I_CHDIR:
1458 path1 = make_absolute(path1, *pwd);
1459 if ((tmp = do_realpath(conn, path1)) == NULL) {
1460 err = 1;
1461 break;
1462 }
1463 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
Darren Tuckera627d422013-06-02 07:31:17 +10001464 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001465 err = 1;
1466 break;
1467 }
1468 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1469 error("Can't change directory: Can't check target");
Darren Tuckera627d422013-06-02 07:31:17 +10001470 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001471 err = 1;
1472 break;
1473 }
1474 if (!S_ISDIR(aa->perm)) {
1475 error("Can't change directory: \"%s\" is not "
1476 "a directory", tmp);
Darren Tuckera627d422013-06-02 07:31:17 +10001477 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001478 err = 1;
1479 break;
1480 }
Darren Tuckera627d422013-06-02 07:31:17 +10001481 free(*pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001482 *pwd = tmp;
1483 break;
1484 case I_LS:
1485 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001486 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001487 break;
1488 }
1489
1490 /* Strip pwd off beginning of non-absolute paths */
1491 tmp = NULL;
1492 if (*path1 != '/')
1493 tmp = *pwd;
1494
1495 path1 = make_absolute(path1, *pwd);
1496 err = do_globbed_ls(conn, path1, tmp, lflag);
1497 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001498 case I_DF:
1499 /* Default to current directory if no path specified */
1500 if (path1 == NULL)
1501 path1 = xstrdup(*pwd);
1502 path1 = make_absolute(path1, *pwd);
1503 err = do_df(conn, path1, hflag, iflag);
1504 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001505 case I_LCHDIR:
1506 if (chdir(path1) == -1) {
1507 error("Couldn't change local directory to "
1508 "\"%s\": %s", path1, strerror(errno));
1509 err = 1;
1510 }
1511 break;
1512 case I_LMKDIR:
1513 if (mkdir(path1, 0777) == -1) {
1514 error("Couldn't create local directory "
1515 "\"%s\": %s", path1, strerror(errno));
1516 err = 1;
1517 }
1518 break;
1519 case I_LLS:
1520 local_do_ls(cmd);
1521 break;
1522 case I_SHELL:
1523 local_do_shell(cmd);
1524 break;
1525 case I_LUMASK:
1526 umask(n_arg);
1527 printf("Local umask: %03lo\n", n_arg);
1528 break;
1529 case I_CHMOD:
1530 path1 = make_absolute(path1, *pwd);
1531 attrib_clear(&a);
1532 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1533 a.perm = n_arg;
1534 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001535 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001536 if (!quiet)
1537 printf("Changing mode on %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001538 err = do_setstat(conn, g.gl_pathv[i], &a);
1539 if (err != 0 && err_abort)
1540 break;
1541 }
1542 break;
1543 case I_CHOWN:
1544 case I_CHGRP:
1545 path1 = make_absolute(path1, *pwd);
1546 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001547 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001548 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001549 if (err_abort) {
1550 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001551 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001552 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001553 continue;
1554 }
1555 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1556 error("Can't get current ownership of "
1557 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001558 if (err_abort) {
1559 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001560 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001561 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001562 continue;
1563 }
1564 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1565 if (cmdnum == I_CHOWN) {
Damien Miller9303e652013-04-23 15:22:40 +10001566 if (!quiet)
1567 printf("Changing owner on %s\n",
1568 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001569 aa->uid = n_arg;
1570 } else {
Damien Miller9303e652013-04-23 15:22:40 +10001571 if (!quiet)
1572 printf("Changing group on %s\n",
1573 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001574 aa->gid = n_arg;
1575 }
1576 err = do_setstat(conn, g.gl_pathv[i], aa);
1577 if (err != 0 && err_abort)
1578 break;
1579 }
1580 break;
1581 case I_PWD:
1582 printf("Remote working directory: %s\n", *pwd);
1583 break;
1584 case I_LPWD:
1585 if (!getcwd(path_buf, sizeof(path_buf))) {
1586 error("Couldn't get local cwd: %s", strerror(errno));
1587 err = -1;
1588 break;
1589 }
1590 printf("Local working directory: %s\n", path_buf);
1591 break;
1592 case I_QUIT:
1593 /* Processed below */
1594 break;
1595 case I_HELP:
1596 help();
1597 break;
1598 case I_VERSION:
1599 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1600 break;
1601 case I_PROGRESS:
1602 showprogress = !showprogress;
1603 if (showprogress)
1604 printf("Progress meter enabled\n");
1605 else
1606 printf("Progress meter disabled\n");
1607 break;
1608 default:
1609 fatal("%d is not implemented", cmdnum);
1610 }
1611
1612 if (g.gl_pathc)
1613 globfree(&g);
Darren Tuckera627d422013-06-02 07:31:17 +10001614 free(path1);
1615 free(path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001616
1617 /* If an unignored error occurs in batch mode we should abort. */
1618 if (err_abort && err != 0)
1619 return (-1);
1620 else if (cmdnum == I_QUIT)
1621 return (1);
1622
1623 return (0);
1624}
1625
Darren Tucker2d963d82004-11-07 20:04:10 +11001626#ifdef USE_LIBEDIT
1627static char *
1628prompt(EditLine *el)
1629{
1630 return ("sftp> ");
1631}
Darren Tucker2d963d82004-11-07 20:04:10 +11001632
Darren Tucker909d8582010-01-08 19:02:40 +11001633/* Display entries in 'list' after skipping the first 'len' chars */
1634static void
1635complete_display(char **list, u_int len)
1636{
1637 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1638 struct winsize ws;
1639 char *tmp;
1640
1641 /* Count entries for sort and find longest */
Damien Miller02e87802013-08-21 02:38:51 +10001642 for (y = 0; list[y]; y++)
Darren Tucker909d8582010-01-08 19:02:40 +11001643 m = MAX(m, strlen(list[y]));
1644
1645 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1646 width = ws.ws_col;
1647
1648 m = m > len ? m - len : 0;
1649 columns = width / (m + 2);
1650 columns = MAX(columns, 1);
1651 colspace = width / columns;
1652 colspace = MIN(colspace, width);
1653
1654 printf("\n");
1655 m = 1;
1656 for (y = 0; list[y]; y++) {
1657 llen = strlen(list[y]);
1658 tmp = llen > len ? list[y] + len : "";
1659 printf("%-*s", colspace, tmp);
1660 if (m >= columns) {
1661 printf("\n");
1662 m = 1;
1663 } else
1664 m++;
1665 }
1666 printf("\n");
1667}
1668
1669/*
1670 * Given a "list" of words that begin with a common prefix of "word",
1671 * attempt to find an autocompletion to extends "word" by the next
1672 * characters common to all entries in "list".
1673 */
1674static char *
1675complete_ambiguous(const char *word, char **list, size_t count)
1676{
1677 if (word == NULL)
1678 return NULL;
1679
1680 if (count > 0) {
1681 u_int y, matchlen = strlen(list[0]);
1682
1683 /* Find length of common stem */
1684 for (y = 1; list[y]; y++) {
1685 u_int x;
1686
Damien Miller02e87802013-08-21 02:38:51 +10001687 for (x = 0; x < matchlen; x++)
1688 if (list[0][x] != list[y][x])
Darren Tucker909d8582010-01-08 19:02:40 +11001689 break;
1690
1691 matchlen = x;
1692 }
1693
1694 if (matchlen > strlen(word)) {
1695 char *tmp = xstrdup(list[0]);
1696
Darren Tucker340d1682010-01-09 08:54:31 +11001697 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001698 return tmp;
1699 }
Damien Miller02e87802013-08-21 02:38:51 +10001700 }
Darren Tucker909d8582010-01-08 19:02:40 +11001701
1702 return xstrdup(word);
1703}
1704
1705/* Autocomplete a sftp command */
1706static int
1707complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1708 int terminated)
1709{
1710 u_int y, count = 0, cmdlen, tmplen;
1711 char *tmp, **list, argterm[3];
1712 const LineInfo *lf;
1713
1714 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1715
1716 /* No command specified: display all available commands */
1717 if (cmd == NULL) {
1718 for (y = 0; cmds[y].c; y++)
1719 list[count++] = xstrdup(cmds[y].c);
Damien Miller02e87802013-08-21 02:38:51 +10001720
Darren Tucker909d8582010-01-08 19:02:40 +11001721 list[count] = NULL;
1722 complete_display(list, 0);
1723
Damien Miller02e87802013-08-21 02:38:51 +10001724 for (y = 0; list[y] != NULL; y++)
1725 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001726 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001727 return count;
1728 }
1729
1730 /* Prepare subset of commands that start with "cmd" */
1731 cmdlen = strlen(cmd);
1732 for (y = 0; cmds[y].c; y++) {
Damien Miller02e87802013-08-21 02:38:51 +10001733 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
Darren Tucker909d8582010-01-08 19:02:40 +11001734 list[count++] = xstrdup(cmds[y].c);
1735 }
1736 list[count] = NULL;
1737
Damien Miller47d81152011-11-25 13:53:48 +11001738 if (count == 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10001739 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001740 return 0;
Damien Miller47d81152011-11-25 13:53:48 +11001741 }
Darren Tucker909d8582010-01-08 19:02:40 +11001742
1743 /* Complete ambigious command */
1744 tmp = complete_ambiguous(cmd, list, count);
1745 if (count > 1)
1746 complete_display(list, 0);
1747
Damien Miller02e87802013-08-21 02:38:51 +10001748 for (y = 0; list[y]; y++)
1749 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001750 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001751
1752 if (tmp != NULL) {
1753 tmplen = strlen(tmp);
1754 cmdlen = strlen(cmd);
1755 /* If cmd may be extended then do so */
1756 if (tmplen > cmdlen)
1757 if (el_insertstr(el, tmp + cmdlen) == -1)
1758 fatal("el_insertstr failed.");
1759 lf = el_line(el);
1760 /* Terminate argument cleanly */
1761 if (count == 1) {
1762 y = 0;
1763 if (!terminated)
1764 argterm[y++] = quote;
1765 if (lastarg || *(lf->cursor) != ' ')
1766 argterm[y++] = ' ';
1767 argterm[y] = '\0';
1768 if (y > 0 && el_insertstr(el, argterm) == -1)
1769 fatal("el_insertstr failed.");
1770 }
Darren Tuckera627d422013-06-02 07:31:17 +10001771 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001772 }
1773
1774 return count;
1775}
1776
1777/*
1778 * Determine whether a particular sftp command's arguments (if any)
1779 * represent local or remote files.
1780 */
1781static int
1782complete_is_remote(char *cmd) {
1783 int i;
1784
1785 if (cmd == NULL)
1786 return -1;
1787
1788 for (i = 0; cmds[i].c; i++) {
Damien Miller02e87802013-08-21 02:38:51 +10001789 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
Darren Tucker909d8582010-01-08 19:02:40 +11001790 return cmds[i].t;
1791 }
1792
1793 return -1;
1794}
1795
1796/* Autocomplete a filename "file" */
1797static int
1798complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1799 char *file, int remote, int lastarg, char quote, int terminated)
1800{
1801 glob_t g;
Darren Tuckerea647212013-06-06 08:19:09 +10001802 char *tmp, *tmp2, ins[8];
Darren Tucker17146d32012-10-05 10:46:16 +10001803 u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
Darren Tuckerea647212013-06-06 08:19:09 +10001804 int clen;
Darren Tucker909d8582010-01-08 19:02:40 +11001805 const LineInfo *lf;
Damien Miller02e87802013-08-21 02:38:51 +10001806
Darren Tucker909d8582010-01-08 19:02:40 +11001807 /* Glob from "file" location */
1808 if (file == NULL)
1809 tmp = xstrdup("*");
1810 else
1811 xasprintf(&tmp, "%s*", file);
1812
Darren Tucker191fcc62012-10-05 10:45:01 +10001813 /* Check if the path is absolute. */
1814 isabs = tmp[0] == '/';
1815
Darren Tucker909d8582010-01-08 19:02:40 +11001816 memset(&g, 0, sizeof(g));
1817 if (remote != LOCAL) {
1818 tmp = make_absolute(tmp, remote_path);
1819 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001820 } else
Darren Tucker909d8582010-01-08 19:02:40 +11001821 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001822
Darren Tucker909d8582010-01-08 19:02:40 +11001823 /* Determine length of pwd so we can trim completion display */
1824 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1825 /* Terminate counting on first unescaped glob metacharacter */
1826 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1827 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1828 hadglob = 1;
1829 break;
1830 }
1831 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1832 tmplen++;
1833 if (tmp[tmplen] == '/')
1834 pwdlen = tmplen + 1; /* track last seen '/' */
1835 }
Darren Tuckera627d422013-06-02 07:31:17 +10001836 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001837
Damien Miller02e87802013-08-21 02:38:51 +10001838 if (g.gl_matchc == 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001839 goto out;
1840
1841 if (g.gl_matchc > 1)
1842 complete_display(g.gl_pathv, pwdlen);
1843
1844 tmp = NULL;
1845 /* Don't try to extend globs */
1846 if (file == NULL || hadglob)
1847 goto out;
1848
1849 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
Darren Tucker191fcc62012-10-05 10:45:01 +10001850 tmp = path_strip(tmp2, isabs ? NULL : remote_path);
Darren Tuckera627d422013-06-02 07:31:17 +10001851 free(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001852
1853 if (tmp == NULL)
1854 goto out;
1855
1856 tmplen = strlen(tmp);
1857 filelen = strlen(file);
1858
Darren Tucker17146d32012-10-05 10:46:16 +10001859 /* Count the number of escaped characters in the input string. */
1860 cesc = isesc = 0;
1861 for (i = 0; i < filelen; i++) {
1862 if (!isesc && file[i] == '\\' && i + 1 < filelen){
1863 isesc = 1;
1864 cesc++;
1865 } else
1866 isesc = 0;
1867 }
1868
1869 if (tmplen > (filelen - cesc)) {
1870 tmp2 = tmp + filelen - cesc;
Damien Miller02e87802013-08-21 02:38:51 +10001871 len = strlen(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001872 /* quote argument on way out */
Darren Tuckerea647212013-06-06 08:19:09 +10001873 for (i = 0; i < len; i += clen) {
1874 if ((clen = mblen(tmp2 + i, len - i)) < 0 ||
1875 (size_t)clen > sizeof(ins) - 2)
1876 fatal("invalid multibyte character");
Darren Tucker909d8582010-01-08 19:02:40 +11001877 ins[0] = '\\';
Darren Tuckerea647212013-06-06 08:19:09 +10001878 memcpy(ins + 1, tmp2 + i, clen);
1879 ins[clen + 1] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001880 switch (tmp2[i]) {
1881 case '\'':
1882 case '"':
1883 case '\\':
1884 case '\t':
Darren Tuckerd78739a2010-10-24 10:56:32 +11001885 case '[':
Darren Tucker909d8582010-01-08 19:02:40 +11001886 case ' ':
Darren Tucker17146d32012-10-05 10:46:16 +10001887 case '#':
1888 case '*':
Darren Tucker909d8582010-01-08 19:02:40 +11001889 if (quote == '\0' || tmp2[i] == quote) {
1890 if (el_insertstr(el, ins) == -1)
1891 fatal("el_insertstr "
1892 "failed.");
1893 break;
1894 }
1895 /* FALLTHROUGH */
1896 default:
1897 if (el_insertstr(el, ins + 1) == -1)
1898 fatal("el_insertstr failed.");
1899 break;
1900 }
1901 }
1902 }
1903
1904 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11001905 if (g.gl_matchc == 1) {
1906 i = 0;
1907 if (!terminated)
1908 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11001909 if (*(lf->cursor - 1) != '/' &&
1910 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11001911 ins[i++] = ' ';
1912 ins[i] = '\0';
1913 if (i > 0 && el_insertstr(el, ins) == -1)
1914 fatal("el_insertstr failed.");
1915 }
Darren Tuckera627d422013-06-02 07:31:17 +10001916 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001917
1918 out:
1919 globfree(&g);
1920 return g.gl_matchc;
1921}
1922
1923/* tab-completion hook function, called via libedit */
1924static unsigned char
1925complete(EditLine *el, int ch)
1926{
Damien Miller02e87802013-08-21 02:38:51 +10001927 char **argv, *line, quote;
Damien Miller746d1a62013-07-18 16:13:02 +10001928 int argc, carg;
1929 u_int cursor, len, terminated, ret = CC_ERROR;
Darren Tucker909d8582010-01-08 19:02:40 +11001930 const LineInfo *lf;
1931 struct complete_ctx *complete_ctx;
1932
1933 lf = el_line(el);
1934 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1935 fatal("%s: el_get failed", __func__);
1936
1937 /* Figure out which argument the cursor points to */
1938 cursor = lf->cursor - lf->buffer;
1939 line = (char *)xmalloc(cursor + 1);
1940 memcpy(line, lf->buffer, cursor);
1941 line[cursor] = '\0';
1942 argv = makeargv(line, &carg, 1, &quote, &terminated);
Darren Tuckera627d422013-06-02 07:31:17 +10001943 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001944
1945 /* Get all the arguments on the line */
1946 len = lf->lastchar - lf->buffer;
1947 line = (char *)xmalloc(len + 1);
1948 memcpy(line, lf->buffer, len);
1949 line[len] = '\0';
1950 argv = makeargv(line, &argc, 1, NULL, NULL);
1951
1952 /* Ensure cursor is at EOL or a argument boundary */
1953 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1954 line[cursor] != '\n') {
Darren Tuckera627d422013-06-02 07:31:17 +10001955 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001956 return ret;
1957 }
1958
1959 if (carg == 0) {
1960 /* Show all available commands */
1961 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1962 ret = CC_REDISPLAY;
1963 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1964 /* Handle the command parsing */
1965 if (complete_cmd_parse(el, argv[0], argc == carg,
Damien Miller02e87802013-08-21 02:38:51 +10001966 quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001967 ret = CC_REDISPLAY;
1968 } else if (carg >= 1) {
1969 /* Handle file parsing */
1970 int remote = complete_is_remote(argv[0]);
1971 char *filematch = NULL;
1972
1973 if (carg > 1 && line[cursor-1] != ' ')
1974 filematch = argv[carg - 1];
1975
1976 if (remote != 0 &&
1977 complete_match(el, complete_ctx->conn,
1978 *complete_ctx->remote_pathp, filematch,
Damien Miller02e87802013-08-21 02:38:51 +10001979 remote, carg == argc, quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001980 ret = CC_REDISPLAY;
1981 }
1982
Damien Miller02e87802013-08-21 02:38:51 +10001983 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001984 return ret;
1985}
Darren Tuckere67f7db2010-01-08 19:50:02 +11001986#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11001987
Damien Miller20e1fab2004-02-18 14:30:55 +11001988int
Darren Tucker21063192010-01-08 17:10:36 +11001989interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001990{
Darren Tucker909d8582010-01-08 19:02:40 +11001991 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11001992 char *dir = NULL;
1993 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10001994 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11001995 EditLine *el = NULL;
1996#ifdef USE_LIBEDIT
1997 History *hl = NULL;
1998 HistEvent hev;
1999 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11002000 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11002001
2002 if (!batchmode && isatty(STDIN_FILENO)) {
2003 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
2004 fatal("Couldn't initialise editline");
2005 if ((hl = history_init()) == NULL)
2006 fatal("Couldn't initialise editline history");
2007 history(hl, &hev, H_SETSIZE, 100);
2008 el_set(el, EL_HIST, history, hl);
2009
2010 el_set(el, EL_PROMPT, prompt);
2011 el_set(el, EL_EDITOR, "emacs");
2012 el_set(el, EL_TERMINAL, NULL);
2013 el_set(el, EL_SIGNAL, 1);
2014 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11002015
2016 /* Tab Completion */
Damien Miller02e87802013-08-21 02:38:51 +10002017 el_set(el, EL_ADDFN, "ftp-complete",
Darren Tuckerd78739a2010-10-24 10:56:32 +11002018 "Context sensitive argument completion", complete);
Darren Tucker909d8582010-01-08 19:02:40 +11002019 complete_ctx.conn = conn;
2020 complete_ctx.remote_pathp = &remote_path;
2021 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
2022 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Damien Millere0ee7272013-08-21 02:42:35 +10002023 /* enable ctrl-left-arrow and ctrl-right-arrow */
2024 el_set(el, EL_BIND, "\\e[1;5C", "em-next-word", NULL);
2025 el_set(el, EL_BIND, "\\e[5C", "em-next-word", NULL);
2026 el_set(el, EL_BIND, "\\e[1;5D", "ed-prev-word", NULL);
2027 el_set(el, EL_BIND, "\\e\\e[D", "ed-prev-word", NULL);
Damien Miller61353b32013-09-14 09:45:32 +10002028 /* make ^w match ksh behaviour */
2029 el_set(el, EL_BIND, "^w", "ed-delete-prev-word", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11002030 }
2031#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002032
Darren Tucker909d8582010-01-08 19:02:40 +11002033 remote_path = do_realpath(conn, ".");
2034 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11002035 fatal("Need cwd");
2036
2037 if (file1 != NULL) {
2038 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11002039 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11002040
2041 if (remote_is_dir(conn, dir) && file2 == NULL) {
Damien Miller9303e652013-04-23 15:22:40 +10002042 if (!quiet)
2043 printf("Changing to: %s\n", dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002044 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11002045 if (parse_dispatch_command(conn, cmd,
2046 &remote_path, 1) != 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10002047 free(dir);
2048 free(remote_path);
2049 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002050 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11002051 }
Damien Miller20e1fab2004-02-18 14:30:55 +11002052 } else {
Darren Tucker0af24052012-10-05 10:41:25 +10002053 /* XXX this is wrong wrt quoting */
Damien Miller0d032412013-07-25 11:56:52 +10002054 snprintf(cmd, sizeof cmd, "get%s %s%s%s",
2055 global_aflag ? " -a" : "", dir,
2056 file2 == NULL ? "" : " ",
2057 file2 == NULL ? "" : file2);
Darren Tucker909d8582010-01-08 19:02:40 +11002058 err = parse_dispatch_command(conn, cmd,
2059 &remote_path, 1);
Darren Tuckera627d422013-06-02 07:31:17 +10002060 free(dir);
2061 free(remote_path);
2062 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002063 return (err);
2064 }
Darren Tuckera627d422013-06-02 07:31:17 +10002065 free(dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002066 }
2067
Damien Miller37294fb2005-07-17 17:18:49 +10002068 setlinebuf(stdout);
2069 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11002070
Damien Miller0e2c1022005-08-12 22:16:22 +10002071 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11002072 err = 0;
2073 for (;;) {
2074 char *cp;
2075
Darren Tuckercdf547a2004-05-24 10:12:19 +10002076 signal(SIGINT, SIG_IGN);
2077
Darren Tucker2d963d82004-11-07 20:04:10 +11002078 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002079 if (interactive)
2080 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11002081 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002082 if (interactive)
2083 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11002084 break;
2085 }
Damien Miller0e2c1022005-08-12 22:16:22 +10002086 if (!interactive) { /* Echo command */
2087 printf("sftp> %s", cmd);
2088 if (strlen(cmd) > 0 &&
2089 cmd[strlen(cmd) - 1] != '\n')
2090 printf("\n");
2091 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002092 } else {
2093#ifdef USE_LIBEDIT
2094 const char *line;
2095 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11002096
Darren Tucker909d8582010-01-08 19:02:40 +11002097 if ((line = el_gets(el, &count)) == NULL ||
2098 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002099 printf("\n");
2100 break;
2101 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002102 history(hl, &hev, H_ENTER, line);
2103 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
2104 fprintf(stderr, "Error: input line too long\n");
2105 continue;
2106 }
2107#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002108 }
2109
Damien Miller20e1fab2004-02-18 14:30:55 +11002110 cp = strrchr(cmd, '\n');
2111 if (cp)
2112 *cp = '\0';
2113
Darren Tuckercdf547a2004-05-24 10:12:19 +10002114 /* Handle user interrupts gracefully during commands */
2115 interrupted = 0;
2116 signal(SIGINT, cmd_interrupt);
2117
Darren Tucker909d8582010-01-08 19:02:40 +11002118 err = parse_dispatch_command(conn, cmd, &remote_path,
2119 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11002120 if (err != 0)
2121 break;
2122 }
Darren Tuckera627d422013-06-02 07:31:17 +10002123 free(remote_path);
2124 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002125
Tim Rice027e8b12005-08-15 14:52:50 -07002126#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10002127 if (el != NULL)
2128 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07002129#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10002130
Damien Miller20e1fab2004-02-18 14:30:55 +11002131 /* err == 1 signifies normal "quit" exit */
2132 return (err >= 0 ? 0 : -1);
2133}
Damien Miller62d57f62003-01-10 21:43:24 +11002134
Ben Lindstrombba81212001-06-25 05:01:22 +00002135static void
Damien Millercc685c12003-06-04 22:51:38 +10002136connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11002137{
2138 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002139
Damien Miller33804262001-02-04 23:20:18 +11002140#ifdef USE_PIPES
2141 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002142
Damien Miller33804262001-02-04 23:20:18 +11002143 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2144 fatal("pipe: %s", strerror(errno));
2145 *in = pin[0];
2146 *out = pout[1];
2147 c_in = pout[0];
2148 c_out = pin[1];
2149#else /* USE_PIPES */
2150 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002151
Damien Miller33804262001-02-04 23:20:18 +11002152 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2153 fatal("socketpair: %s", strerror(errno));
2154 *in = *out = inout[0];
2155 c_in = c_out = inout[1];
2156#endif /* USE_PIPES */
2157
Damien Millercc685c12003-06-04 22:51:38 +10002158 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002159 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002160 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002161 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2162 (dup2(c_out, STDOUT_FILENO) == -1)) {
2163 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002164 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002165 }
2166 close(*in);
2167 close(*out);
2168 close(c_in);
2169 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002170
2171 /*
2172 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002173 * ignore SIGINT if we want to gracefully abort commands,
2174 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002175 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2176 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002177 */
2178 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002179 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002180 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002181 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002182 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002183 }
2184
Damien Millercc685c12003-06-04 22:51:38 +10002185 signal(SIGTERM, killchild);
2186 signal(SIGINT, killchild);
2187 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11002188 close(c_in);
2189 close(c_out);
2190}
2191
Ben Lindstrombba81212001-06-25 05:01:22 +00002192static void
Damien Miller33804262001-02-04 23:20:18 +11002193usage(void)
2194{
Damien Miller025e01c2002-02-08 22:06:29 +11002195 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002196
Ben Lindstrom1e243242001-09-18 05:38:44 +00002197 fprintf(stderr,
Damien Miller1edcbf62013-10-18 10:17:17 +11002198 "usage: %s [-1246aCfpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002199 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002200 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002201 " [-o ssh_option] [-P port] [-R num_requests] "
2202 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002203 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002204 " %s [user@]host[:file ...]\n"
2205 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002206 " %s -b batchfile [user@]host\n",
2207 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002208 exit(1);
2209}
2210
Kevin Stevesef4eea92001-02-05 12:42:17 +00002211int
Damien Miller33804262001-02-04 23:20:18 +11002212main(int argc, char **argv)
2213{
Damien Miller956f3fb2003-01-10 21:40:00 +11002214 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002215 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002216 int debug_level = 0, sshver = 2;
2217 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002218 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002219 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002220 LogLevel ll = SYSLOG_LEVEL_INFO;
2221 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002222 extern int optind;
2223 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002224 struct sftp_conn *conn;
2225 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2226 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002227 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002228
Darren Tuckerce321d82005-10-03 18:11:24 +10002229 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2230 sanitise_stdfd();
Darren Tuckerea647212013-06-06 08:19:09 +10002231 setlocale(LC_CTYPE, "");
Darren Tuckerce321d82005-10-03 18:11:24 +10002232
Damien Miller59d3d5b2003-08-22 09:34:41 +10002233 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002234 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002235 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002236 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002237 addargs(&args, "-oForwardX11 no");
2238 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002239 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002240 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002241
Ben Lindstrom387c4722001-05-08 20:27:25 +00002242 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002243 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002244
Darren Tucker282b4022009-10-07 08:23:06 +11002245 while ((ch = getopt(argc, argv,
Damien Millerf29238e2013-10-17 11:48:52 +11002246 "1246afhpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002247 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002248 /* Passed through to ssh(1) */
2249 case '4':
2250 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002251 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002252 addargs(&args, "-%c", ch);
2253 break;
2254 /* Passed through to ssh(1) with argument */
2255 case 'F':
2256 case 'c':
2257 case 'i':
2258 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002259 addargs(&args, "-%c", ch);
2260 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002261 break;
2262 case 'q':
Damien Miller9303e652013-04-23 15:22:40 +10002263 ll = SYSLOG_LEVEL_ERROR;
2264 quiet = 1;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002265 showprogress = 0;
2266 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002267 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002268 case 'P':
2269 addargs(&args, "-oPort %s", optarg);
2270 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002271 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002272 if (debug_level < 3) {
2273 addargs(&args, "-v");
2274 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2275 }
2276 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002277 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002278 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002279 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002280 if (sftp_server == NULL)
2281 sftp_server = _PATH_SFTP_SERVER;
2282 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002283 case '2':
2284 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002285 break;
Damien Miller0d032412013-07-25 11:56:52 +10002286 case 'a':
2287 global_aflag = 1;
2288 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002289 case 'B':
2290 copy_buffer_len = strtol(optarg, &cp, 10);
2291 if (copy_buffer_len == 0 || *cp != '\0')
2292 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002293 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002294 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002295 if (batchmode)
2296 fatal("Batch file already specified.");
2297
2298 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002299 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002300 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002301 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002302 showprogress = 0;
Damien Miller9303e652013-04-23 15:22:40 +10002303 quiet = batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002304 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002305 break;
Damien Millerf29238e2013-10-17 11:48:52 +11002306 case 'f':
2307 global_fflag = 1;
2308 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002309 case 'p':
2310 global_pflag = 1;
2311 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002312 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002313 sftp_direct = optarg;
2314 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002315 case 'l':
2316 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2317 &errstr);
2318 if (errstr != NULL)
2319 usage();
2320 limit_kbps *= 1024; /* kbps */
2321 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002322 case 'r':
2323 global_rflag = 1;
2324 break;
Damien Miller16a13332002-02-13 14:03:56 +11002325 case 'R':
2326 num_requests = strtol(optarg, &cp, 10);
2327 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002328 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002329 optarg);
2330 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002331 case 's':
2332 sftp_server = optarg;
2333 break;
2334 case 'S':
2335 ssh_program = optarg;
2336 replacearg(&args, 0, "%s", ssh_program);
2337 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002338 case 'h':
2339 default:
Damien Miller33804262001-02-04 23:20:18 +11002340 usage();
2341 }
2342 }
2343
Damien Millerc0f27d82004-03-08 23:12:19 +11002344 if (!isatty(STDERR_FILENO))
2345 showprogress = 0;
2346
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002347 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2348
Damien Millerd14ee1e2002-02-05 12:27:31 +11002349 if (sftp_direct == NULL) {
2350 if (optind == argc || argc > (optind + 2))
2351 usage();
Damien Miller33804262001-02-04 23:20:18 +11002352
Damien Millerd14ee1e2002-02-05 12:27:31 +11002353 userhost = xstrdup(argv[optind]);
2354 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002355
Ben Lindstromc276c122002-12-23 02:14:51 +00002356 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002357 host = userhost;
2358 else {
2359 *host++ = '\0';
2360 if (!userhost[0]) {
2361 fprintf(stderr, "Missing username\n");
2362 usage();
2363 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002364 addargs(&args, "-l");
2365 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002366 }
2367
Damien Millerec692032004-01-27 21:22:00 +11002368 if ((cp = colon(host)) != NULL) {
2369 *cp++ = '\0';
2370 file1 = cp;
2371 }
2372
Damien Millerd14ee1e2002-02-05 12:27:31 +11002373 host = cleanhostname(host);
2374 if (!*host) {
2375 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002376 usage();
2377 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002378
Damien Millerd14ee1e2002-02-05 12:27:31 +11002379 addargs(&args, "-oProtocol %d", sshver);
2380
2381 /* no subsystem if the server-spec contains a '/' */
2382 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2383 addargs(&args, "-s");
2384
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002385 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002386 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002387 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002388 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002389
Damien Millercc685c12003-06-04 22:51:38 +10002390 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002391 } else {
2392 args.list = NULL;
2393 addargs(&args, "sftp-server");
2394
Damien Millercc685c12003-06-04 22:51:38 +10002395 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002396 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002397 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002398
Damien Miller65e42f82010-09-24 22:15:11 +10002399 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002400 if (conn == NULL)
2401 fatal("Couldn't initialise connection to server");
2402
Damien Miller9303e652013-04-23 15:22:40 +10002403 if (!quiet) {
Darren Tucker21063192010-01-08 17:10:36 +11002404 if (sftp_direct == NULL)
2405 fprintf(stderr, "Connected to %s.\n", host);
2406 else
2407 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2408 }
2409
2410 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002411
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002412#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002413 shutdown(in, SHUT_RDWR);
2414 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002415#endif
2416
Damien Miller33804262001-02-04 23:20:18 +11002417 close(in);
2418 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002419 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002420 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002421
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002422 while (waitpid(sshpid, NULL, 0) == -1)
2423 if (errno != EINTR)
2424 fatal("Couldn't wait for ssh process: %s",
2425 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002426
Damien Miller956f3fb2003-01-10 21:40:00 +11002427 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002428}