blob: eee472d08bac950ad4c50eb28834d184e0d9f2b7 [file] [log] [blame]
djm@openbsd.org7d845f42015-01-14 13:54:13 +00001/* $OpenBSD: sftp.c,v 1.169 2015/01/14 13:54:13 djm Exp $ */
Damien Miller33804262001-02-04 23:20:18 +11002/*
Damien Miller4e60ed72004-02-17 17:07:59 +11003 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
Damien Miller33804262001-02-04 23:20:18 +11004 *
Damien Miller4e60ed72004-02-17 17:07:59 +11005 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
Damien Miller33804262001-02-04 23:20:18 +11008 *
Damien Miller4e60ed72004-02-17 17:07:59 +11009 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Damien Miller33804262001-02-04 23:20:18 +110016 */
17
18#include "includes.h"
19
Damien Miller9cf6d072006-03-15 11:29:24 +110020#include <sys/types.h>
Damien Millerd7834352006-08-05 12:39:39 +100021#include <sys/ioctl.h>
Damien Millerf17883e2006-03-15 11:45:54 +110022#ifdef HAVE_SYS_STAT_H
23# include <sys/stat.h>
24#endif
Damien Miller8dbffe72006-08-05 11:02:17 +100025#include <sys/param.h>
Damien Millere3b60b52006-07-10 21:08:03 +100026#include <sys/socket.h>
Damien Miller9cf6d072006-03-15 11:29:24 +110027#include <sys/wait.h>
Darren Tucker5b2e2ba2008-06-08 09:25:28 +100028#ifdef HAVE_SYS_STATVFS_H
Damien Millerd671e5a2008-05-19 14:53:33 +100029#include <sys/statvfs.h>
Darren Tucker5b2e2ba2008-06-08 09:25:28 +100030#endif
Darren Tucker2d963d82004-11-07 20:04:10 +110031
Damien Miller1cbc2922007-10-26 14:27:45 +100032#include <ctype.h>
Darren Tucker39972492006-07-12 22:22:46 +100033#include <errno.h>
34
Damien Miller03e20032006-03-15 11:16:59 +110035#ifdef HAVE_PATHS_H
Damien Millera9263d02006-03-15 11:18:26 +110036# include <paths.h>
Damien Miller03e20032006-03-15 11:16:59 +110037#endif
Darren Tucker1b0dd172009-10-07 08:37:48 +110038#ifdef HAVE_LIBGEN_H
39#include <libgen.h>
40#endif
Darren 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"
djm@openbsd.org7d845f42015-01-14 13:54:13 +000066#include "ssherr.h"
67#include "sshbuf.h"
Damien Miller33804262001-02-04 23:20:18 +110068#include "sftp-common.h"
69#include "sftp-client.h"
Damien Millerd7d46bb2004-02-18 14:11:13 +110070
Darren Tucker21063192010-01-08 17:10:36 +110071#define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */
72#define DEFAULT_NUM_REQUESTS 64 /* # concurrent outstanding requests */
73
Damien Miller20e1fab2004-02-18 14:30:55 +110074/* File to read commands from */
75FILE* infile;
76
77/* Are we in batchfile mode? */
78int batchmode = 0;
79
Damien Miller20e1fab2004-02-18 14:30:55 +110080/* PID of ssh transport process */
81static pid_t sshpid = -1;
82
Damien Miller9303e652013-04-23 15:22:40 +100083/* Suppress diagnositic messages */
84int quiet = 0;
85
Damien Miller20e1fab2004-02-18 14:30:55 +110086/* This is set to 0 if the progressmeter is not desired. */
Damien Millerc0f27d82004-03-08 23:12:19 +110087int showprogress = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +110088
Darren Tucker1b0dd172009-10-07 08:37:48 +110089/* When this option is set, we always recursively download/upload directories */
90int global_rflag = 0;
91
Damien Millerd8accc02014-05-15 13:46:25 +100092/* When this option is set, we resume download or upload if possible */
Damien Miller0d032412013-07-25 11:56:52 +100093int global_aflag = 0;
94
Darren Tucker1b0dd172009-10-07 08:37:48 +110095/* When this option is set, the file transfers will always preserve times */
96int global_pflag = 0;
97
Damien Millerf29238e2013-10-17 11:48:52 +110098/* When this option is set, transfers will have fsync() called on each file */
99int global_fflag = 0;
100
Darren Tuckercdf547a2004-05-24 10:12:19 +1000101/* SIGINT received during command processing */
102volatile sig_atomic_t interrupted = 0;
103
Darren Tuckerb9123452004-06-22 13:06:45 +1000104/* I wish qsort() took a separate ctx for the comparison function...*/
105int sort_flag;
106
Darren Tucker909d8582010-01-08 19:02:40 +1100107/* Context used for commandline completion */
108struct complete_ctx {
109 struct sftp_conn *conn;
110 char **remote_pathp;
111};
112
Damien Miller20e1fab2004-02-18 14:30:55 +1100113int remote_glob(struct sftp_conn *, const char *, int,
114 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
Damien Miller33804262001-02-04 23:20:18 +1100115
Kevin Steves12888d12001-03-05 19:50:57 +0000116extern char *__progname;
Kevin Steves12888d12001-03-05 19:50:57 +0000117
Damien Miller20e1fab2004-02-18 14:30:55 +1100118/* Separators for interactive commands */
119#define WHITESPACE " \t\r\n"
Damien Millerd7686fd2001-02-10 00:40:03 +1100120
Darren Tuckerb9123452004-06-22 13:06:45 +1000121/* ls flags */
Darren Tucker2901e2d2010-01-13 22:44:06 +1100122#define LS_LONG_VIEW 0x0001 /* Full view ala ls -l */
123#define LS_SHORT_VIEW 0x0002 /* Single row view ala ls -1 */
124#define LS_NUMERIC_VIEW 0x0004 /* Long view with numeric uid/gid */
125#define LS_NAME_SORT 0x0008 /* Sort by name (default) */
126#define LS_TIME_SORT 0x0010 /* Sort by mtime */
127#define LS_SIZE_SORT 0x0020 /* Sort by file size */
128#define LS_REVERSE_SORT 0x0040 /* Reverse sort order */
129#define LS_SHOW_ALL 0x0080 /* Don't skip filenames starting with '.' */
130#define LS_SI_UNITS 0x0100 /* Display sizes as K, M, G, etc. */
Darren Tuckerb9123452004-06-22 13:06:45 +1000131
Darren Tucker2901e2d2010-01-13 22:44:06 +1100132#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS)
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000133#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
Damien Miller20e1fab2004-02-18 14:30:55 +1100134
135/* Commands for interactive mode */
Damien Miller02e87802013-08-21 02:38:51 +1000136enum sftp_command {
137 I_CHDIR = 1,
138 I_CHGRP,
139 I_CHMOD,
140 I_CHOWN,
141 I_DF,
142 I_GET,
143 I_HELP,
144 I_LCHDIR,
145 I_LINK,
146 I_LLS,
147 I_LMKDIR,
148 I_LPWD,
149 I_LS,
150 I_LUMASK,
151 I_MKDIR,
152 I_PUT,
153 I_PWD,
154 I_QUIT,
Damien Millerb15cd7b2014-05-15 13:46:52 +1000155 I_REGET,
Damien Miller02e87802013-08-21 02:38:51 +1000156 I_RENAME,
Damien Millerb15cd7b2014-05-15 13:46:52 +1000157 I_REPUT,
Damien Miller02e87802013-08-21 02:38:51 +1000158 I_RM,
159 I_RMDIR,
160 I_SHELL,
161 I_SYMLINK,
162 I_VERSION,
163 I_PROGRESS,
Damien Miller02e87802013-08-21 02:38:51 +1000164};
Damien Miller20e1fab2004-02-18 14:30:55 +1100165
166struct CMD {
167 const char *c;
168 const int n;
Darren Tucker909d8582010-01-08 19:02:40 +1100169 const int t;
Damien Miller20e1fab2004-02-18 14:30:55 +1100170};
171
Darren Tucker909d8582010-01-08 19:02:40 +1100172/* Type of completion */
173#define NOARGS 0
174#define REMOTE 1
175#define LOCAL 2
176
Damien Miller20e1fab2004-02-18 14:30:55 +1100177static const struct CMD cmds[] = {
Darren Tucker909d8582010-01-08 19:02:40 +1100178 { "bye", I_QUIT, NOARGS },
179 { "cd", I_CHDIR, REMOTE },
180 { "chdir", I_CHDIR, REMOTE },
181 { "chgrp", I_CHGRP, REMOTE },
182 { "chmod", I_CHMOD, REMOTE },
183 { "chown", I_CHOWN, REMOTE },
184 { "df", I_DF, REMOTE },
185 { "dir", I_LS, REMOTE },
186 { "exit", I_QUIT, NOARGS },
187 { "get", I_GET, REMOTE },
188 { "help", I_HELP, NOARGS },
189 { "lcd", I_LCHDIR, LOCAL },
190 { "lchdir", I_LCHDIR, LOCAL },
191 { "lls", I_LLS, LOCAL },
192 { "lmkdir", I_LMKDIR, LOCAL },
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100193 { "ln", I_LINK, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100194 { "lpwd", I_LPWD, LOCAL },
195 { "ls", I_LS, REMOTE },
196 { "lumask", I_LUMASK, NOARGS },
197 { "mkdir", I_MKDIR, REMOTE },
Damien Miller099fc162010-05-10 11:56:50 +1000198 { "mget", I_GET, REMOTE },
199 { "mput", I_PUT, LOCAL },
Darren Tucker909d8582010-01-08 19:02:40 +1100200 { "progress", I_PROGRESS, NOARGS },
201 { "put", I_PUT, LOCAL },
202 { "pwd", I_PWD, REMOTE },
203 { "quit", I_QUIT, NOARGS },
Damien Miller0d032412013-07-25 11:56:52 +1000204 { "reget", I_REGET, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100205 { "rename", I_RENAME, REMOTE },
djm@openbsd.org4a459222014-10-06 00:47:15 +0000206 { "reput", I_REPUT, LOCAL },
Darren Tucker909d8582010-01-08 19:02:40 +1100207 { "rm", I_RM, REMOTE },
208 { "rmdir", I_RMDIR, REMOTE },
209 { "symlink", I_SYMLINK, REMOTE },
210 { "version", I_VERSION, NOARGS },
211 { "!", I_SHELL, NOARGS },
212 { "?", I_HELP, NOARGS },
213 { NULL, -1, -1 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100214};
215
Darren Tucker21063192010-01-08 17:10:36 +1100216int interactive_loop(struct sftp_conn *, char *file1, char *file2);
Damien Miller20e1fab2004-02-18 14:30:55 +1100217
Damien Millerb6c85fc2007-01-05 16:30:41 +1100218/* ARGSUSED */
Damien Miller20e1fab2004-02-18 14:30:55 +1100219static void
Darren Tuckercdf547a2004-05-24 10:12:19 +1000220killchild(int signo)
221{
Darren Tuckerba66df82005-01-24 21:57:40 +1100222 if (sshpid > 1) {
Darren Tuckercdf547a2004-05-24 10:12:19 +1000223 kill(sshpid, SIGTERM);
Darren Tuckerba66df82005-01-24 21:57:40 +1100224 waitpid(sshpid, NULL, 0);
225 }
Darren Tuckercdf547a2004-05-24 10:12:19 +1000226
227 _exit(1);
228}
229
Damien Millerb6c85fc2007-01-05 16:30:41 +1100230/* ARGSUSED */
Darren Tuckercdf547a2004-05-24 10:12:19 +1000231static void
232cmd_interrupt(int signo)
233{
234 const char msg[] = "\rInterrupt \n";
Darren Tuckere2f189a2004-12-06 22:45:53 +1100235 int olderrno = errno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000236
Darren Tuckerdbee3082013-05-16 20:32:29 +1000237 (void)write(STDERR_FILENO, msg, sizeof(msg) - 1);
Darren Tuckercdf547a2004-05-24 10:12:19 +1000238 interrupted = 1;
Darren Tuckere2f189a2004-12-06 22:45:53 +1100239 errno = olderrno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000240}
241
242static void
Damien Miller20e1fab2004-02-18 14:30:55 +1100243help(void)
244{
Damien Miller62fd18a2009-01-28 16:14:09 +1100245 printf("Available commands:\n"
246 "bye Quit sftp\n"
247 "cd path Change remote directory to 'path'\n"
248 "chgrp grp path Change group of file 'path' to 'grp'\n"
249 "chmod mode path Change permissions of file 'path' to 'mode'\n"
250 "chown own path Change owner of file 'path' to 'own'\n"
251 "df [-hi] [path] Display statistics for current directory or\n"
252 " filesystem containing 'path'\n"
253 "exit Quit sftp\n"
djm@openbsd.org4a459222014-10-06 00:47:15 +0000254 "get [-afPpRr] remote [local] Download file\n"
255 "reget [-fPpRr] remote [local] Resume download file\n"
256 "reput [-fPpRr] [local] remote Resume upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100257 "help Display this help text\n"
258 "lcd path Change local directory to 'path'\n"
259 "lls [ls-options [path]] Display local directory listing\n"
260 "lmkdir path Create local directory\n"
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100261 "ln [-s] oldpath newpath Link remote file (-s for symlink)\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100262 "lpwd Print local working directory\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100263 "ls [-1afhlnrSt] [path] Display remote directory listing\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100264 "lumask umask Set local umask to 'umask'\n"
265 "mkdir path Create remote directory\n"
266 "progress Toggle display of progress meter\n"
djm@openbsd.org4a459222014-10-06 00:47:15 +0000267 "put [-afPpRr] local [remote] Upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100268 "pwd Display remote working directory\n"
269 "quit Quit sftp\n"
270 "rename oldpath newpath Rename remote file\n"
271 "rm path Delete remote file\n"
272 "rmdir path Remove remote directory\n"
273 "symlink oldpath newpath Symlink remote file\n"
274 "version Show SFTP version\n"
275 "!command Execute 'command' in local shell\n"
276 "! Escape to local shell\n"
277 "? Synonym for help\n");
Damien Miller20e1fab2004-02-18 14:30:55 +1100278}
279
280static void
281local_do_shell(const char *args)
282{
283 int status;
284 char *shell;
285 pid_t pid;
286
287 if (!*args)
288 args = NULL;
289
Damien Miller38d9a962010-10-07 22:07:11 +1100290 if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
Damien Miller20e1fab2004-02-18 14:30:55 +1100291 shell = _PATH_BSHELL;
292
293 if ((pid = fork()) == -1)
294 fatal("Couldn't fork: %s", strerror(errno));
295
296 if (pid == 0) {
297 /* XXX: child has pipe fds to ssh subproc open - issue? */
298 if (args) {
299 debug3("Executing %s -c \"%s\"", shell, args);
300 execl(shell, shell, "-c", args, (char *)NULL);
301 } else {
302 debug3("Executing %s", shell);
303 execl(shell, shell, (char *)NULL);
304 }
305 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
306 strerror(errno));
307 _exit(1);
308 }
309 while (waitpid(pid, &status, 0) == -1)
310 if (errno != EINTR)
311 fatal("Couldn't wait for child: %s", strerror(errno));
312 if (!WIFEXITED(status))
Damien Miller55b04f12006-03-26 14:23:17 +1100313 error("Shell exited abnormally");
Damien Miller20e1fab2004-02-18 14:30:55 +1100314 else if (WEXITSTATUS(status))
315 error("Shell exited with status %d", WEXITSTATUS(status));
316}
317
318static void
319local_do_ls(const char *args)
320{
321 if (!args || !*args)
322 local_do_shell(_PATH_LS);
323 else {
324 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
325 char *buf = xmalloc(len);
326
327 /* XXX: quoting - rip quoting code from ftp? */
328 snprintf(buf, len, _PATH_LS " %s", args);
329 local_do_shell(buf);
Darren Tuckera627d422013-06-02 07:31:17 +1000330 free(buf);
Damien Miller20e1fab2004-02-18 14:30:55 +1100331 }
332}
333
334/* Strip one path (usually the pwd) from the start of another */
335static char *
336path_strip(char *path, char *strip)
337{
338 size_t len;
339
340 if (strip == NULL)
341 return (xstrdup(path));
342
343 len = strlen(strip);
Darren Tuckere2f189a2004-12-06 22:45:53 +1100344 if (strncmp(path, strip, len) == 0) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100345 if (strip[len - 1] != '/' && path[len] == '/')
346 len++;
347 return (xstrdup(path + len));
348 }
349
350 return (xstrdup(path));
351}
352
353static char *
Damien Miller20e1fab2004-02-18 14:30:55 +1100354make_absolute(char *p, char *pwd)
355{
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000356 char *abs_str;
Damien Miller20e1fab2004-02-18 14:30:55 +1100357
358 /* Derelativise */
359 if (p && p[0] != '/') {
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000360 abs_str = path_append(pwd, p);
Darren Tuckera627d422013-06-02 07:31:17 +1000361 free(p);
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000362 return(abs_str);
Damien Miller20e1fab2004-02-18 14:30:55 +1100363 } else
364 return(p);
365}
366
367static int
Damien Miller0d032412013-07-25 11:56:52 +1000368parse_getput_flags(const char *cmd, char **argv, int argc,
Damien Millerf29238e2013-10-17 11:48:52 +1100369 int *aflag, int *fflag, int *pflag, int *rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100370{
Damien Millerf184bcf2008-06-29 22:45:13 +1000371 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000372 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100373
Damien Miller1cbc2922007-10-26 14:27:45 +1000374 optind = optreset = 1;
375 opterr = 0;
376
Damien Millerf29238e2013-10-17 11:48:52 +1100377 *aflag = *fflag = *rflag = *pflag = 0;
378 while ((ch = getopt(argc, argv, "afPpRr")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000379 switch (ch) {
Damien Miller0d032412013-07-25 11:56:52 +1000380 case 'a':
381 *aflag = 1;
382 break;
Damien Millerf29238e2013-10-17 11:48:52 +1100383 case 'f':
384 *fflag = 1;
385 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100386 case 'p':
387 case 'P':
388 *pflag = 1;
389 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100390 case 'r':
391 case 'R':
392 *rflag = 1;
393 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100394 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000395 error("%s: Invalid flag -%c", cmd, optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000396 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100397 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100398 }
399
Damien Miller1cbc2922007-10-26 14:27:45 +1000400 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100401}
402
403static int
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100404parse_link_flags(const char *cmd, char **argv, int argc, int *sflag)
405{
406 extern int opterr, optind, optopt, optreset;
407 int ch;
408
409 optind = optreset = 1;
410 opterr = 0;
411
412 *sflag = 0;
413 while ((ch = getopt(argc, argv, "s")) != -1) {
414 switch (ch) {
415 case 's':
416 *sflag = 1;
417 break;
418 default:
419 error("%s: Invalid flag -%c", cmd, optopt);
420 return -1;
421 }
422 }
423
424 return optind;
425}
426
427static int
Damien Millerc7dba122013-08-21 02:41:15 +1000428parse_rename_flags(const char *cmd, char **argv, int argc, int *lflag)
429{
430 extern int opterr, optind, optopt, optreset;
431 int ch;
432
433 optind = optreset = 1;
434 opterr = 0;
435
436 *lflag = 0;
437 while ((ch = getopt(argc, argv, "l")) != -1) {
438 switch (ch) {
439 case 'l':
440 *lflag = 1;
441 break;
442 default:
443 error("%s: Invalid flag -%c", cmd, optopt);
444 return -1;
445 }
446 }
447
448 return optind;
449}
450
451static int
Damien Miller1cbc2922007-10-26 14:27:45 +1000452parse_ls_flags(char **argv, int argc, int *lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100453{
Damien Millerf184bcf2008-06-29 22:45:13 +1000454 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000455 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100456
Damien Miller1cbc2922007-10-26 14:27:45 +1000457 optind = optreset = 1;
458 opterr = 0;
459
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000460 *lflag = LS_NAME_SORT;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100461 while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000462 switch (ch) {
463 case '1':
464 *lflag &= ~VIEW_FLAGS;
465 *lflag |= LS_SHORT_VIEW;
466 break;
467 case 'S':
468 *lflag &= ~SORT_FLAGS;
469 *lflag |= LS_SIZE_SORT;
470 break;
471 case 'a':
472 *lflag |= LS_SHOW_ALL;
473 break;
474 case 'f':
475 *lflag &= ~SORT_FLAGS;
476 break;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100477 case 'h':
478 *lflag |= LS_SI_UNITS;
479 break;
Damien Miller1cbc2922007-10-26 14:27:45 +1000480 case 'l':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100481 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000482 *lflag |= LS_LONG_VIEW;
483 break;
484 case 'n':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100485 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000486 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
487 break;
488 case 'r':
489 *lflag |= LS_REVERSE_SORT;
490 break;
491 case 't':
492 *lflag &= ~SORT_FLAGS;
493 *lflag |= LS_TIME_SORT;
494 break;
495 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000496 error("ls: Invalid flag -%c", optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000497 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100498 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100499 }
500
Damien Miller1cbc2922007-10-26 14:27:45 +1000501 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100502}
503
504static int
Damien Millerd671e5a2008-05-19 14:53:33 +1000505parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
506{
Damien Millerf184bcf2008-06-29 22:45:13 +1000507 extern int opterr, optind, optopt, optreset;
Damien Millerd671e5a2008-05-19 14:53:33 +1000508 int ch;
509
510 optind = optreset = 1;
511 opterr = 0;
512
513 *hflag = *iflag = 0;
514 while ((ch = getopt(argc, argv, "hi")) != -1) {
515 switch (ch) {
516 case 'h':
517 *hflag = 1;
518 break;
519 case 'i':
520 *iflag = 1;
521 break;
522 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000523 error("%s: Invalid flag -%c", cmd, optopt);
Damien Millerd671e5a2008-05-19 14:53:33 +1000524 return -1;
525 }
526 }
527
528 return optind;
529}
530
531static int
Damien Miller036d3072013-08-21 02:41:46 +1000532parse_no_flags(const char *cmd, char **argv, int argc)
533{
534 extern int opterr, optind, optopt, optreset;
535 int ch;
536
537 optind = optreset = 1;
538 opterr = 0;
539
540 while ((ch = getopt(argc, argv, "")) != -1) {
541 switch (ch) {
542 default:
543 error("%s: Invalid flag -%c", cmd, optopt);
544 return -1;
545 }
546 }
547
548 return optind;
549}
550
551static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100552is_dir(char *path)
553{
554 struct stat sb;
555
556 /* XXX: report errors? */
557 if (stat(path, &sb) == -1)
558 return(0);
559
Darren Tucker1e80e402006-09-21 12:59:33 +1000560 return(S_ISDIR(sb.st_mode));
Damien Miller20e1fab2004-02-18 14:30:55 +1100561}
562
563static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100564remote_is_dir(struct sftp_conn *conn, char *path)
565{
566 Attrib *a;
567
568 /* XXX: report errors? */
569 if ((a = do_stat(conn, path, 1)) == NULL)
570 return(0);
571 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
572 return(0);
Darren Tucker1e80e402006-09-21 12:59:33 +1000573 return(S_ISDIR(a->perm));
Damien Miller20e1fab2004-02-18 14:30:55 +1100574}
575
Darren Tucker1b0dd172009-10-07 08:37:48 +1100576/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
Damien Miller20e1fab2004-02-18 14:30:55 +1100577static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100578pathname_is_dir(char *pathname)
579{
580 size_t l = strlen(pathname);
581
582 return l > 0 && pathname[l - 1] == '/';
583}
584
585static int
586process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
Damien Millerf29238e2013-10-17 11:48:52 +1100587 int pflag, int rflag, int resume, int fflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100588{
589 char *abs_src = NULL;
590 char *abs_dst = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100591 glob_t g;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100592 char *filename, *tmp=NULL;
Damien Miller00707762014-07-09 13:07:06 +1000593 int i, r, err = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100594
595 abs_src = xstrdup(src);
596 abs_src = make_absolute(abs_src, pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +1100597 memset(&g, 0, sizeof(g));
Darren Tucker1b0dd172009-10-07 08:37:48 +1100598
Damien Miller20e1fab2004-02-18 14:30:55 +1100599 debug3("Looking up %s", abs_src);
Damien Miller00707762014-07-09 13:07:06 +1000600 if ((r = remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) != 0) {
601 if (r == GLOB_NOSPACE) {
602 error("Too many matches for \"%s\".", abs_src);
603 } else {
604 error("File \"%s\" not found.", abs_src);
605 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100606 err = -1;
607 goto out;
608 }
609
Darren Tucker1b0dd172009-10-07 08:37:48 +1100610 /*
611 * If multiple matches then dst must be a directory or
612 * unspecified.
613 */
614 if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
615 error("Multiple source paths, but destination "
616 "\"%s\" is not a directory", dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100617 err = -1;
618 goto out;
619 }
620
Darren Tuckercdf547a2004-05-24 10:12:19 +1000621 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100622 tmp = xstrdup(g.gl_pathv[i]);
623 if ((filename = basename(tmp)) == NULL) {
624 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000625 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100626 err = -1;
627 goto out;
628 }
629
630 if (g.gl_matchc == 1 && dst) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100631 if (is_dir(dst)) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100632 abs_dst = path_append(dst, filename);
633 } else {
Damien Miller20e1fab2004-02-18 14:30:55 +1100634 abs_dst = xstrdup(dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100635 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100636 } else if (dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100637 abs_dst = path_append(dst, filename);
638 } else {
639 abs_dst = xstrdup(filename);
640 }
Darren Tuckera627d422013-06-02 07:31:17 +1000641 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100642
Damien Miller0d032412013-07-25 11:56:52 +1000643 resume |= global_aflag;
644 if (!quiet && resume)
645 printf("Resuming %s to %s\n", g.gl_pathv[i], abs_dst);
646 else if (!quiet && !resume)
Damien Miller9303e652013-04-23 15:22:40 +1000647 printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100648 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
Damien Miller0d032412013-07-25 11:56:52 +1000649 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
Damien Millerf29238e2013-10-17 11:48:52 +1100650 pflag || global_pflag, 1, resume,
651 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100652 err = -1;
653 } else {
654 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
Damien Millerf29238e2013-10-17 11:48:52 +1100655 pflag || global_pflag, resume,
656 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100657 err = -1;
658 }
Darren Tuckera627d422013-06-02 07:31:17 +1000659 free(abs_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100660 abs_dst = NULL;
661 }
662
663out:
Darren Tuckera627d422013-06-02 07:31:17 +1000664 free(abs_src);
Damien Miller20e1fab2004-02-18 14:30:55 +1100665 globfree(&g);
666 return(err);
667}
668
669static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100670process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
Damien Millerd8accc02014-05-15 13:46:25 +1000671 int pflag, int rflag, int resume, int fflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100672{
673 char *tmp_dst = NULL;
674 char *abs_dst = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100675 char *tmp = NULL, *filename = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100676 glob_t g;
677 int err = 0;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100678 int i, dst_is_dir = 1;
Damien Milleraec5cf82008-02-10 22:26:24 +1100679 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100680
681 if (dst) {
682 tmp_dst = xstrdup(dst);
683 tmp_dst = make_absolute(tmp_dst, pwd);
684 }
685
686 memset(&g, 0, sizeof(g));
687 debug3("Looking up %s", src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100688 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100689 error("File \"%s\" not found.", src);
690 err = -1;
691 goto out;
692 }
693
Darren Tucker1b0dd172009-10-07 08:37:48 +1100694 /* If we aren't fetching to pwd then stash this status for later */
695 if (tmp_dst != NULL)
696 dst_is_dir = remote_is_dir(conn, tmp_dst);
697
Damien Miller20e1fab2004-02-18 14:30:55 +1100698 /* If multiple matches, dst may be directory or unspecified */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100699 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
700 error("Multiple paths match, but destination "
701 "\"%s\" is not a directory", tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100702 err = -1;
703 goto out;
704 }
705
Darren Tuckercdf547a2004-05-24 10:12:19 +1000706 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Milleraec5cf82008-02-10 22:26:24 +1100707 if (stat(g.gl_pathv[i], &sb) == -1) {
708 err = -1;
709 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
710 continue;
711 }
Damien Miller02e87802013-08-21 02:38:51 +1000712
Darren Tucker1b0dd172009-10-07 08:37:48 +1100713 tmp = xstrdup(g.gl_pathv[i]);
714 if ((filename = basename(tmp)) == NULL) {
715 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000716 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100717 err = -1;
718 goto out;
719 }
720
721 if (g.gl_matchc == 1 && tmp_dst) {
722 /* If directory specified, append filename */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100723 if (dst_is_dir)
724 abs_dst = path_append(tmp_dst, filename);
725 else
Damien Miller20e1fab2004-02-18 14:30:55 +1100726 abs_dst = xstrdup(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100727 } else if (tmp_dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100728 abs_dst = path_append(tmp_dst, filename);
729 } else {
730 abs_dst = make_absolute(xstrdup(filename), pwd);
731 }
Darren Tuckera627d422013-06-02 07:31:17 +1000732 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100733
Damien Millerd8accc02014-05-15 13:46:25 +1000734 resume |= global_aflag;
735 if (!quiet && resume)
Damien Miller3dc27172014-05-15 14:37:59 +1000736 printf("Resuming upload of %s to %s\n", g.gl_pathv[i],
Damien Millerd8accc02014-05-15 13:46:25 +1000737 abs_dst);
738 else if (!quiet && !resume)
Damien Miller9303e652013-04-23 15:22:40 +1000739 printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100740 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
741 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
Damien Millerd8accc02014-05-15 13:46:25 +1000742 pflag || global_pflag, 1, resume,
Damien Millerf29238e2013-10-17 11:48:52 +1100743 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100744 err = -1;
745 } else {
746 if (do_upload(conn, g.gl_pathv[i], abs_dst,
Damien Millerd8accc02014-05-15 13:46:25 +1000747 pflag || global_pflag, resume,
Damien Millerf29238e2013-10-17 11:48:52 +1100748 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100749 err = -1;
750 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100751 }
752
753out:
Darren Tuckera627d422013-06-02 07:31:17 +1000754 free(abs_dst);
755 free(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100756 globfree(&g);
757 return(err);
758}
759
760static int
761sdirent_comp(const void *aa, const void *bb)
762{
763 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
764 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000765 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100766
Darren Tuckerb9123452004-06-22 13:06:45 +1000767#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000768 if (sort_flag & LS_NAME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000769 return (rmul * strcmp(a->filename, b->filename));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000770 else if (sort_flag & LS_TIME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000771 return (rmul * NCMP(a->a.mtime, b->a.mtime));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000772 else if (sort_flag & LS_SIZE_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000773 return (rmul * NCMP(a->a.size, b->a.size));
774
775 fatal("Unknown ls sort type");
Damien Miller20e1fab2004-02-18 14:30:55 +1100776}
777
778/* sftp ls.1 replacement for directories */
779static int
780do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
781{
Damien Millereccb9de2005-06-17 12:59:34 +1000782 int n;
783 u_int c = 1, colspace = 0, columns = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100784 SFTP_DIRENT **d;
785
786 if ((n = do_readdir(conn, path, &d)) != 0)
787 return (n);
788
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000789 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000790 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100791 struct winsize ws;
792 char *tmp;
793
794 /* Count entries for sort and find longest filename */
Darren Tucker9a526452004-06-22 13:09:55 +1000795 for (n = 0; d[n] != NULL; n++) {
796 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
797 m = MAX(m, strlen(d[n]->filename));
798 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100799
800 /* Add any subpath that also needs to be counted */
801 tmp = path_strip(path, strip_path);
802 m += strlen(tmp);
Darren Tuckera627d422013-06-02 07:31:17 +1000803 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100804
805 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
806 width = ws.ws_col;
807
808 columns = width / (m + 2);
809 columns = MAX(columns, 1);
810 colspace = width / columns;
811 colspace = MIN(colspace, width);
812 }
813
Darren Tuckerb9123452004-06-22 13:06:45 +1000814 if (lflag & SORT_FLAGS) {
Damien Miller653b93b2005-11-05 15:15:23 +1100815 for (n = 0; d[n] != NULL; n++)
816 ; /* count entries */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000817 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
Darren Tuckerb9123452004-06-22 13:06:45 +1000818 qsort(d, n, sizeof(*d), sdirent_comp);
819 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100820
Darren Tuckercdf547a2004-05-24 10:12:19 +1000821 for (n = 0; d[n] != NULL && !interrupted; n++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100822 char *tmp, *fname;
823
Darren Tucker9a526452004-06-22 13:09:55 +1000824 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
825 continue;
826
Damien Miller20e1fab2004-02-18 14:30:55 +1100827 tmp = path_append(path, d[n]->filename);
828 fname = path_strip(tmp, strip_path);
Darren Tuckera627d422013-06-02 07:31:17 +1000829 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100830
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000831 if (lflag & LS_LONG_VIEW) {
Darren Tucker2901e2d2010-01-13 22:44:06 +1100832 if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000833 char *lname;
834 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100835
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000836 memset(&sb, 0, sizeof(sb));
837 attrib_to_stat(&d[n]->a, &sb);
Darren Tucker2901e2d2010-01-13 22:44:06 +1100838 lname = ls_file(fname, &sb, 1,
839 (lflag & LS_SI_UNITS));
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000840 printf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000841 free(lname);
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000842 } else
843 printf("%s\n", d[n]->longname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100844 } else {
845 printf("%-*s", colspace, fname);
846 if (c >= columns) {
847 printf("\n");
848 c = 1;
849 } else
850 c++;
851 }
852
Darren Tuckera627d422013-06-02 07:31:17 +1000853 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100854 }
855
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000856 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100857 printf("\n");
858
859 free_sftp_dirents(d);
860 return (0);
861}
862
863/* sftp ls.1 replacement which handles path globs */
864static int
865do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
866 int lflag)
867{
Damien Millera6e121a2010-10-07 21:39:17 +1100868 char *fname, *lname;
Damien Miller68e2e562010-10-07 21:39:55 +1100869 glob_t g;
Damien Miller00707762014-07-09 13:07:06 +1000870 int err, r;
Damien Miller68e2e562010-10-07 21:39:55 +1100871 struct winsize ws;
872 u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100873
874 memset(&g, 0, sizeof(g));
875
Damien Miller00707762014-07-09 13:07:06 +1000876 if ((r = remote_glob(conn, path,
Damien Millerd7be70d2011-09-22 21:43:06 +1000877 GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
Damien Miller00707762014-07-09 13:07:06 +1000878 NULL, &g)) != 0 ||
Damien Millera6e121a2010-10-07 21:39:17 +1100879 (g.gl_pathc && !g.gl_matchc)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100880 if (g.gl_pathc)
881 globfree(&g);
Damien Miller00707762014-07-09 13:07:06 +1000882 if (r == GLOB_NOSPACE) {
883 error("Can't ls: Too many matches for \"%s\"", path);
884 } else {
885 error("Can't ls: \"%s\" not found", path);
886 }
Damien Millera6e121a2010-10-07 21:39:17 +1100887 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100888 }
889
Darren Tuckercdf547a2004-05-24 10:12:19 +1000890 if (interrupted)
891 goto out;
892
Damien Miller20e1fab2004-02-18 14:30:55 +1100893 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100894 * If the glob returns a single match and it is a directory,
895 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100896 */
Damien Millera6e121a2010-10-07 21:39:17 +1100897 if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
898 S_ISDIR(g.gl_statv[0]->st_mode)) {
899 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
900 globfree(&g);
901 return err;
Damien Miller20e1fab2004-02-18 14:30:55 +1100902 }
903
Damien Miller68e2e562010-10-07 21:39:55 +1100904 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
905 width = ws.ws_col;
Damien Miller20e1fab2004-02-18 14:30:55 +1100906
Damien Miller68e2e562010-10-07 21:39:55 +1100907 if (!(lflag & LS_SHORT_VIEW)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100908 /* Count entries for sort and find longest filename */
909 for (i = 0; g.gl_pathv[i]; i++)
910 m = MAX(m, strlen(g.gl_pathv[i]));
911
Damien Miller20e1fab2004-02-18 14:30:55 +1100912 columns = width / (m + 2);
913 columns = MAX(columns, 1);
914 colspace = width / columns;
915 }
916
Damien Millerea858292012-06-30 08:33:32 +1000917 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100918 fname = path_strip(g.gl_pathv[i], strip_path);
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000919 if (lflag & LS_LONG_VIEW) {
Damien Millera6e121a2010-10-07 21:39:17 +1100920 if (g.gl_statv[i] == NULL) {
921 error("no stat information for %s", fname);
922 continue;
923 }
924 lname = ls_file(fname, g.gl_statv[i], 1,
925 (lflag & LS_SI_UNITS));
Damien Miller20e1fab2004-02-18 14:30:55 +1100926 printf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000927 free(lname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100928 } else {
929 printf("%-*s", colspace, fname);
930 if (c >= columns) {
931 printf("\n");
932 c = 1;
933 } else
934 c++;
935 }
Darren Tuckera627d422013-06-02 07:31:17 +1000936 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100937 }
938
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000939 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100940 printf("\n");
941
Darren Tuckercdf547a2004-05-24 10:12:19 +1000942 out:
Damien Miller20e1fab2004-02-18 14:30:55 +1100943 if (g.gl_pathc)
944 globfree(&g);
945
Damien Millera6e121a2010-10-07 21:39:17 +1100946 return 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100947}
948
Damien Millerd671e5a2008-05-19 14:53:33 +1000949static int
950do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
951{
Darren Tucker7b598892008-06-09 22:49:36 +1000952 struct sftp_statvfs st;
Damien Millerd671e5a2008-05-19 14:53:33 +1000953 char s_used[FMT_SCALED_STRSIZE];
954 char s_avail[FMT_SCALED_STRSIZE];
955 char s_root[FMT_SCALED_STRSIZE];
956 char s_total[FMT_SCALED_STRSIZE];
Darren Tuckerb5082e92010-01-08 18:51:47 +1100957 unsigned long long ffree;
Damien Millerd671e5a2008-05-19 14:53:33 +1000958
959 if (do_statvfs(conn, path, &st, 1) == -1)
960 return -1;
961 if (iflag) {
Darren Tuckerb5082e92010-01-08 18:51:47 +1100962 ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
Damien Millerd671e5a2008-05-19 14:53:33 +1000963 printf(" Inodes Used Avail "
964 "(root) %%Capacity\n");
965 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
966 (unsigned long long)st.f_files,
967 (unsigned long long)(st.f_files - st.f_ffree),
968 (unsigned long long)st.f_favail,
Darren Tuckerb5082e92010-01-08 18:51:47 +1100969 (unsigned long long)st.f_ffree, ffree);
Damien Millerd671e5a2008-05-19 14:53:33 +1000970 } else if (hflag) {
971 strlcpy(s_used, "error", sizeof(s_used));
972 strlcpy(s_avail, "error", sizeof(s_avail));
973 strlcpy(s_root, "error", sizeof(s_root));
974 strlcpy(s_total, "error", sizeof(s_total));
975 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
976 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
977 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
978 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
979 printf(" Size Used Avail (root) %%Capacity\n");
980 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
981 s_total, s_used, s_avail, s_root,
982 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
983 st.f_blocks));
984 } else {
985 printf(" Size Used Avail "
986 "(root) %%Capacity\n");
987 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
988 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
989 (unsigned long long)(st.f_frsize *
990 (st.f_blocks - st.f_bfree) / 1024),
991 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
992 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
993 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
994 st.f_blocks));
995 }
996 return 0;
997}
998
Damien Miller1cbc2922007-10-26 14:27:45 +1000999/*
1000 * Undo escaping of glob sequences in place. Used to undo extra escaping
1001 * applied in makeargv() when the string is destined for a function that
1002 * does not glob it.
1003 */
1004static void
1005undo_glob_escape(char *s)
1006{
1007 size_t i, j;
1008
1009 for (i = j = 0;;) {
1010 if (s[i] == '\0') {
1011 s[j] = '\0';
1012 return;
1013 }
1014 if (s[i] != '\\') {
1015 s[j++] = s[i++];
1016 continue;
1017 }
1018 /* s[i] == '\\' */
1019 ++i;
1020 switch (s[i]) {
1021 case '?':
1022 case '[':
1023 case '*':
1024 case '\\':
1025 s[j++] = s[i++];
1026 break;
1027 case '\0':
1028 s[j++] = '\\';
1029 s[j] = '\0';
1030 return;
1031 default:
1032 s[j++] = '\\';
1033 s[j++] = s[i++];
1034 break;
1035 }
1036 }
1037}
1038
1039/*
1040 * Split a string into an argument vector using sh(1)-style quoting,
1041 * comment and escaping rules, but with some tweaks to handle glob(3)
1042 * wildcards.
Darren Tucker909d8582010-01-08 19:02:40 +11001043 * The "sloppy" flag allows for recovery from missing terminating quote, for
1044 * use in parsing incomplete commandlines during tab autocompletion.
1045 *
Damien Miller1cbc2922007-10-26 14:27:45 +10001046 * Returns NULL on error or a NULL-terminated array of arguments.
Darren Tucker909d8582010-01-08 19:02:40 +11001047 *
1048 * If "lastquote" is not NULL, the quoting character used for the last
1049 * argument is placed in *lastquote ("\0", "'" or "\"").
Damien Miller02e87802013-08-21 02:38:51 +10001050 *
Darren Tucker909d8582010-01-08 19:02:40 +11001051 * If "terminated" is not NULL, *terminated will be set to 1 when the
1052 * last argument's quote has been properly terminated or 0 otherwise.
1053 * This parameter is only of use if "sloppy" is set.
Damien Miller1cbc2922007-10-26 14:27:45 +10001054 */
1055#define MAXARGS 128
1056#define MAXARGLEN 8192
1057static char **
Darren Tucker909d8582010-01-08 19:02:40 +11001058makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
1059 u_int *terminated)
Damien Miller1cbc2922007-10-26 14:27:45 +10001060{
1061 int argc, quot;
1062 size_t i, j;
1063 static char argvs[MAXARGLEN];
1064 static char *argv[MAXARGS + 1];
1065 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
1066
1067 *argcp = argc = 0;
1068 if (strlen(arg) > sizeof(argvs) - 1) {
1069 args_too_longs:
1070 error("string too long");
1071 return NULL;
1072 }
Darren Tucker909d8582010-01-08 19:02:40 +11001073 if (terminated != NULL)
1074 *terminated = 1;
1075 if (lastquote != NULL)
1076 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001077 state = MA_START;
1078 i = j = 0;
1079 for (;;) {
Damien Miller07daed52012-10-31 08:57:55 +11001080 if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){
Darren Tucker063018d2012-10-05 10:43:58 +10001081 error("Too many arguments.");
1082 return NULL;
1083 }
Damien Millerfdb23062013-11-21 13:57:15 +11001084 if (isspace((unsigned char)arg[i])) {
Damien Miller1cbc2922007-10-26 14:27:45 +10001085 if (state == MA_UNQUOTED) {
1086 /* Terminate current argument */
1087 argvs[j++] = '\0';
1088 argc++;
1089 state = MA_START;
1090 } else if (state != MA_START)
1091 argvs[j++] = arg[i];
1092 } else if (arg[i] == '"' || arg[i] == '\'') {
1093 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1094 if (state == MA_START) {
1095 argv[argc] = argvs + j;
1096 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +11001097 if (lastquote != NULL)
1098 *lastquote = arg[i];
Damien Miller02e87802013-08-21 02:38:51 +10001099 } else if (state == MA_UNQUOTED)
Damien Miller1cbc2922007-10-26 14:27:45 +10001100 state = q;
1101 else if (state == q)
1102 state = MA_UNQUOTED;
1103 else
1104 argvs[j++] = arg[i];
1105 } else if (arg[i] == '\\') {
1106 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1107 quot = state == MA_SQUOTE ? '\'' : '"';
1108 /* Unescape quote we are in */
1109 /* XXX support \n and friends? */
1110 if (arg[i + 1] == quot) {
1111 i++;
1112 argvs[j++] = arg[i];
1113 } else if (arg[i + 1] == '?' ||
1114 arg[i + 1] == '[' || arg[i + 1] == '*') {
1115 /*
1116 * Special case for sftp: append
1117 * double-escaped glob sequence -
1118 * glob will undo one level of
1119 * escaping. NB. string can grow here.
1120 */
1121 if (j >= sizeof(argvs) - 5)
1122 goto args_too_longs;
1123 argvs[j++] = '\\';
1124 argvs[j++] = arg[i++];
1125 argvs[j++] = '\\';
1126 argvs[j++] = arg[i];
1127 } else {
1128 argvs[j++] = arg[i++];
1129 argvs[j++] = arg[i];
1130 }
1131 } else {
1132 if (state == MA_START) {
1133 argv[argc] = argvs + j;
1134 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001135 if (lastquote != NULL)
1136 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001137 }
1138 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1139 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1140 /*
1141 * Special case for sftp: append
1142 * escaped glob sequence -
1143 * glob will undo one level of
1144 * escaping.
1145 */
1146 argvs[j++] = arg[i++];
1147 argvs[j++] = arg[i];
1148 } else {
1149 /* Unescape everything */
1150 /* XXX support \n and friends? */
1151 i++;
1152 argvs[j++] = arg[i];
1153 }
1154 }
1155 } else if (arg[i] == '#') {
1156 if (state == MA_SQUOTE || state == MA_DQUOTE)
1157 argvs[j++] = arg[i];
1158 else
1159 goto string_done;
1160 } else if (arg[i] == '\0') {
1161 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001162 if (sloppy) {
1163 state = MA_UNQUOTED;
1164 if (terminated != NULL)
1165 *terminated = 0;
1166 goto string_done;
1167 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001168 error("Unterminated quoted argument");
1169 return NULL;
1170 }
1171 string_done:
1172 if (state == MA_UNQUOTED) {
1173 argvs[j++] = '\0';
1174 argc++;
1175 }
1176 break;
1177 } else {
1178 if (state == MA_START) {
1179 argv[argc] = argvs + j;
1180 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001181 if (lastquote != NULL)
1182 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001183 }
1184 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1185 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1186 /*
1187 * Special case for sftp: escape quoted
1188 * glob(3) wildcards. NB. string can grow
1189 * here.
1190 */
1191 if (j >= sizeof(argvs) - 3)
1192 goto args_too_longs;
1193 argvs[j++] = '\\';
1194 argvs[j++] = arg[i];
1195 } else
1196 argvs[j++] = arg[i];
1197 }
1198 i++;
1199 }
1200 *argcp = argc;
1201 return argv;
1202}
1203
Damien Miller20e1fab2004-02-18 14:30:55 +11001204static int
Damien Millerd8accc02014-05-15 13:46:25 +10001205parse_args(const char **cpp, int *ignore_errors, int *aflag,
1206 int *fflag, int *hflag, int *iflag, int *lflag, int *pflag,
1207 int *rflag, int *sflag,
Damien Millerf29238e2013-10-17 11:48:52 +11001208 unsigned long *n_arg, char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001209{
1210 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001211 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001212 int base = 0;
1213 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001214 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001215
1216 /* Skip leading whitespace */
1217 cp = cp + strspn(cp, WHITESPACE);
1218
Damien Miller20e1fab2004-02-18 14:30:55 +11001219 /* Check for leading '-' (disable error processing) */
Damien Millerf29238e2013-10-17 11:48:52 +11001220 *ignore_errors = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001221 if (*cp == '-') {
Damien Millerf29238e2013-10-17 11:48:52 +11001222 *ignore_errors = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001223 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001224 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001225 }
1226
Darren Tucker70cc0922010-01-09 22:28:03 +11001227 /* Ignore blank lines and lines which begin with comment '#' char */
1228 if (*cp == '\0' || *cp == '#')
1229 return (0);
1230
Darren Tucker909d8582010-01-08 19:02:40 +11001231 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001232 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001233
Damien Miller1cbc2922007-10-26 14:27:45 +10001234 /* Figure out which command we have */
1235 for (i = 0; cmds[i].c != NULL; i++) {
Damien Millerd6d9fa02013-02-12 11:02:46 +11001236 if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001237 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001238 }
1239 cmdnum = cmds[i].n;
1240 cmd = cmds[i].c;
1241
1242 /* Special case */
1243 if (*cp == '!') {
1244 cp++;
1245 cmdnum = I_SHELL;
1246 } else if (cmdnum == -1) {
1247 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001248 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001249 }
1250
1251 /* Get arguments and parse flags */
Damien Millerf29238e2013-10-17 11:48:52 +11001252 *aflag = *fflag = *hflag = *iflag = *lflag = *pflag = 0;
1253 *rflag = *sflag = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001254 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001255 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001256 switch (cmdnum) {
1257 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001258 case I_REGET:
Damien Millerd8accc02014-05-15 13:46:25 +10001259 case I_REPUT:
Damien Miller20e1fab2004-02-18 14:30:55 +11001260 case I_PUT:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001261 if ((optidx = parse_getput_flags(cmd, argv, argc,
Damien Millerf29238e2013-10-17 11:48:52 +11001262 aflag, fflag, pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001263 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001264 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001265 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001266 error("You must specify at least one path after a "
1267 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001268 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001269 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001270 *path1 = xstrdup(argv[optidx]);
1271 /* Get second pathname (optional) */
1272 if (argc - optidx > 1) {
1273 *path2 = xstrdup(argv[optidx + 1]);
1274 /* Destination is not globbed */
1275 undo_glob_escape(*path2);
1276 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001277 break;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001278 case I_LINK:
1279 if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1280 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001281 goto parse_two_paths;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001282 case I_RENAME:
Damien Millerc7dba122013-08-21 02:41:15 +10001283 if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1)
1284 return -1;
1285 goto parse_two_paths;
1286 case I_SYMLINK:
Damien Miller036d3072013-08-21 02:41:46 +10001287 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1288 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001289 parse_two_paths:
Damien Miller1cbc2922007-10-26 14:27:45 +10001290 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001291 error("You must specify two paths after a %s "
1292 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001293 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001294 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001295 *path1 = xstrdup(argv[optidx]);
1296 *path2 = xstrdup(argv[optidx + 1]);
1297 /* Paths are not globbed */
1298 undo_glob_escape(*path1);
1299 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001300 break;
1301 case I_RM:
1302 case I_MKDIR:
1303 case I_RMDIR:
1304 case I_CHDIR:
1305 case I_LCHDIR:
1306 case I_LMKDIR:
Damien Miller036d3072013-08-21 02:41:46 +10001307 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1308 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001309 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001310 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001311 error("You must specify a path after a %s command.",
1312 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001313 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001314 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001315 *path1 = xstrdup(argv[optidx]);
1316 /* Only "rm" globs */
1317 if (cmdnum != I_RM)
1318 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001319 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001320 case I_DF:
1321 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1322 iflag)) == -1)
1323 return -1;
1324 /* Default to current directory if no path specified */
1325 if (argc - optidx < 1)
1326 *path1 = NULL;
1327 else {
1328 *path1 = xstrdup(argv[optidx]);
1329 undo_glob_escape(*path1);
1330 }
1331 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001332 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001333 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001334 return(-1);
1335 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001336 if (argc - optidx > 0)
1337 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001338 break;
1339 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001340 /* Skip ls command and following whitespace */
1341 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001342 case I_SHELL:
1343 /* Uses the rest of the line */
1344 break;
1345 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001346 case I_CHMOD:
1347 base = 8;
1348 case I_CHOWN:
1349 case I_CHGRP:
Damien Miller036d3072013-08-21 02:41:46 +10001350 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1351 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001352 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001353 if (argc - optidx < 1)
1354 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001355 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001356 l = strtol(argv[optidx], &cp2, base);
1357 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1358 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1359 l < 0) {
1360 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001361 error("You must supply a numeric argument "
1362 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001363 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001364 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001365 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001366 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001367 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001368 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001369 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001370 error("You must specify a path after a %s command.",
1371 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001372 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001373 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001374 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001375 break;
1376 case I_QUIT:
1377 case I_PWD:
1378 case I_LPWD:
1379 case I_HELP:
1380 case I_VERSION:
1381 case I_PROGRESS:
Damien Miller036d3072013-08-21 02:41:46 +10001382 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1383 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001384 break;
1385 default:
1386 fatal("Command not implemented");
1387 }
1388
1389 *cpp = cp;
1390 return(cmdnum);
1391}
1392
1393static int
1394parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1395 int err_abort)
1396{
1397 char *path1, *path2, *tmp;
Damien Millerd8accc02014-05-15 13:46:25 +10001398 int ignore_errors = 0, aflag = 0, fflag = 0, hflag = 0,
1399 iflag = 0;
Damien Millerf29238e2013-10-17 11:48:52 +11001400 int lflag = 0, pflag = 0, rflag = 0, sflag = 0;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001401 int cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001402 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001403 Attrib a, *aa;
1404 char path_buf[MAXPATHLEN];
1405 int err = 0;
1406 glob_t g;
1407
1408 path1 = path2 = NULL;
Damien Millerf29238e2013-10-17 11:48:52 +11001409 cmdnum = parse_args(&cmd, &ignore_errors, &aflag, &fflag, &hflag,
1410 &iflag, &lflag, &pflag, &rflag, &sflag, &n_arg, &path1, &path2);
1411 if (ignore_errors != 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001412 err_abort = 0;
1413
1414 memset(&g, 0, sizeof(g));
1415
1416 /* Perform command */
1417 switch (cmdnum) {
1418 case 0:
1419 /* Blank line */
1420 break;
1421 case -1:
1422 /* Unrecognized command */
1423 err = -1;
1424 break;
Damien Miller0d032412013-07-25 11:56:52 +10001425 case I_REGET:
1426 aflag = 1;
1427 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001428 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001429 err = process_get(conn, path1, path2, *pwd, pflag,
Damien Millerf29238e2013-10-17 11:48:52 +11001430 rflag, aflag, fflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001431 break;
Damien Millerd8accc02014-05-15 13:46:25 +10001432 case I_REPUT:
1433 aflag = 1;
1434 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001435 case I_PUT:
Damien Millerf29238e2013-10-17 11:48:52 +11001436 err = process_put(conn, path1, path2, *pwd, pflag,
Damien Millerd8accc02014-05-15 13:46:25 +10001437 rflag, aflag, fflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001438 break;
1439 case I_RENAME:
1440 path1 = make_absolute(path1, *pwd);
1441 path2 = make_absolute(path2, *pwd);
Damien Millerc7dba122013-08-21 02:41:15 +10001442 err = do_rename(conn, path1, path2, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001443 break;
1444 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001445 sflag = 1;
1446 case I_LINK:
Damien Miller034f27a2013-08-21 02:40:44 +10001447 if (!sflag)
1448 path1 = make_absolute(path1, *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001449 path2 = make_absolute(path2, *pwd);
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001450 err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001451 break;
1452 case I_RM:
1453 path1 = make_absolute(path1, *pwd);
1454 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001455 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001456 if (!quiet)
1457 printf("Removing %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001458 err = do_rm(conn, g.gl_pathv[i]);
1459 if (err != 0 && err_abort)
1460 break;
1461 }
1462 break;
1463 case I_MKDIR:
1464 path1 = make_absolute(path1, *pwd);
1465 attrib_clear(&a);
1466 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1467 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001468 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001469 break;
1470 case I_RMDIR:
1471 path1 = make_absolute(path1, *pwd);
1472 err = do_rmdir(conn, path1);
1473 break;
1474 case I_CHDIR:
1475 path1 = make_absolute(path1, *pwd);
1476 if ((tmp = do_realpath(conn, path1)) == NULL) {
1477 err = 1;
1478 break;
1479 }
1480 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
Darren Tuckera627d422013-06-02 07:31:17 +10001481 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001482 err = 1;
1483 break;
1484 }
1485 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1486 error("Can't change directory: Can't check target");
Darren Tuckera627d422013-06-02 07:31:17 +10001487 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001488 err = 1;
1489 break;
1490 }
1491 if (!S_ISDIR(aa->perm)) {
1492 error("Can't change directory: \"%s\" is not "
1493 "a directory", tmp);
Darren Tuckera627d422013-06-02 07:31:17 +10001494 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001495 err = 1;
1496 break;
1497 }
Darren Tuckera627d422013-06-02 07:31:17 +10001498 free(*pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001499 *pwd = tmp;
1500 break;
1501 case I_LS:
1502 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001503 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001504 break;
1505 }
1506
1507 /* Strip pwd off beginning of non-absolute paths */
1508 tmp = NULL;
1509 if (*path1 != '/')
1510 tmp = *pwd;
1511
1512 path1 = make_absolute(path1, *pwd);
1513 err = do_globbed_ls(conn, path1, tmp, lflag);
1514 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001515 case I_DF:
1516 /* Default to current directory if no path specified */
1517 if (path1 == NULL)
1518 path1 = xstrdup(*pwd);
1519 path1 = make_absolute(path1, *pwd);
1520 err = do_df(conn, path1, hflag, iflag);
1521 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001522 case I_LCHDIR:
deraadt@openbsd.org40ba4c92014-08-20 01:28:55 +00001523 tmp = tilde_expand_filename(path1, getuid());
djm@openbsd.org7ff880e2014-08-19 23:57:18 +00001524 free(path1);
1525 path1 = tmp;
Damien Miller20e1fab2004-02-18 14:30:55 +11001526 if (chdir(path1) == -1) {
1527 error("Couldn't change local directory to "
1528 "\"%s\": %s", path1, strerror(errno));
1529 err = 1;
1530 }
1531 break;
1532 case I_LMKDIR:
1533 if (mkdir(path1, 0777) == -1) {
1534 error("Couldn't create local directory "
1535 "\"%s\": %s", path1, strerror(errno));
1536 err = 1;
1537 }
1538 break;
1539 case I_LLS:
1540 local_do_ls(cmd);
1541 break;
1542 case I_SHELL:
1543 local_do_shell(cmd);
1544 break;
1545 case I_LUMASK:
1546 umask(n_arg);
1547 printf("Local umask: %03lo\n", n_arg);
1548 break;
1549 case I_CHMOD:
1550 path1 = make_absolute(path1, *pwd);
1551 attrib_clear(&a);
1552 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1553 a.perm = n_arg;
1554 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001555 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001556 if (!quiet)
1557 printf("Changing mode on %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001558 err = do_setstat(conn, g.gl_pathv[i], &a);
1559 if (err != 0 && err_abort)
1560 break;
1561 }
1562 break;
1563 case I_CHOWN:
1564 case I_CHGRP:
1565 path1 = make_absolute(path1, *pwd);
1566 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001567 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001568 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001569 if (err_abort) {
1570 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001571 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001572 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001573 continue;
1574 }
1575 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1576 error("Can't get current ownership of "
1577 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001578 if (err_abort) {
1579 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001580 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001581 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001582 continue;
1583 }
1584 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1585 if (cmdnum == I_CHOWN) {
Damien Miller9303e652013-04-23 15:22:40 +10001586 if (!quiet)
1587 printf("Changing owner on %s\n",
1588 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001589 aa->uid = n_arg;
1590 } else {
Damien Miller9303e652013-04-23 15:22:40 +10001591 if (!quiet)
1592 printf("Changing group on %s\n",
1593 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001594 aa->gid = n_arg;
1595 }
1596 err = do_setstat(conn, g.gl_pathv[i], aa);
1597 if (err != 0 && err_abort)
1598 break;
1599 }
1600 break;
1601 case I_PWD:
1602 printf("Remote working directory: %s\n", *pwd);
1603 break;
1604 case I_LPWD:
1605 if (!getcwd(path_buf, sizeof(path_buf))) {
1606 error("Couldn't get local cwd: %s", strerror(errno));
1607 err = -1;
1608 break;
1609 }
1610 printf("Local working directory: %s\n", path_buf);
1611 break;
1612 case I_QUIT:
1613 /* Processed below */
1614 break;
1615 case I_HELP:
1616 help();
1617 break;
1618 case I_VERSION:
1619 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1620 break;
1621 case I_PROGRESS:
1622 showprogress = !showprogress;
1623 if (showprogress)
1624 printf("Progress meter enabled\n");
1625 else
1626 printf("Progress meter disabled\n");
1627 break;
1628 default:
1629 fatal("%d is not implemented", cmdnum);
1630 }
1631
1632 if (g.gl_pathc)
1633 globfree(&g);
Darren Tuckera627d422013-06-02 07:31:17 +10001634 free(path1);
1635 free(path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001636
1637 /* If an unignored error occurs in batch mode we should abort. */
1638 if (err_abort && err != 0)
1639 return (-1);
1640 else if (cmdnum == I_QUIT)
1641 return (1);
1642
1643 return (0);
1644}
1645
Darren Tucker2d963d82004-11-07 20:04:10 +11001646#ifdef USE_LIBEDIT
1647static char *
1648prompt(EditLine *el)
1649{
1650 return ("sftp> ");
1651}
Darren Tucker2d963d82004-11-07 20:04:10 +11001652
Darren Tucker909d8582010-01-08 19:02:40 +11001653/* Display entries in 'list' after skipping the first 'len' chars */
1654static void
1655complete_display(char **list, u_int len)
1656{
1657 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1658 struct winsize ws;
1659 char *tmp;
1660
1661 /* Count entries for sort and find longest */
Damien Miller02e87802013-08-21 02:38:51 +10001662 for (y = 0; list[y]; y++)
Darren Tucker909d8582010-01-08 19:02:40 +11001663 m = MAX(m, strlen(list[y]));
1664
1665 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1666 width = ws.ws_col;
1667
1668 m = m > len ? m - len : 0;
1669 columns = width / (m + 2);
1670 columns = MAX(columns, 1);
1671 colspace = width / columns;
1672 colspace = MIN(colspace, width);
1673
1674 printf("\n");
1675 m = 1;
1676 for (y = 0; list[y]; y++) {
1677 llen = strlen(list[y]);
1678 tmp = llen > len ? list[y] + len : "";
1679 printf("%-*s", colspace, tmp);
1680 if (m >= columns) {
1681 printf("\n");
1682 m = 1;
1683 } else
1684 m++;
1685 }
1686 printf("\n");
1687}
1688
1689/*
1690 * Given a "list" of words that begin with a common prefix of "word",
1691 * attempt to find an autocompletion to extends "word" by the next
1692 * characters common to all entries in "list".
1693 */
1694static char *
1695complete_ambiguous(const char *word, char **list, size_t count)
1696{
1697 if (word == NULL)
1698 return NULL;
1699
1700 if (count > 0) {
1701 u_int y, matchlen = strlen(list[0]);
1702
1703 /* Find length of common stem */
1704 for (y = 1; list[y]; y++) {
1705 u_int x;
1706
Damien Miller02e87802013-08-21 02:38:51 +10001707 for (x = 0; x < matchlen; x++)
1708 if (list[0][x] != list[y][x])
Darren Tucker909d8582010-01-08 19:02:40 +11001709 break;
1710
1711 matchlen = x;
1712 }
1713
1714 if (matchlen > strlen(word)) {
1715 char *tmp = xstrdup(list[0]);
1716
Darren Tucker340d1682010-01-09 08:54:31 +11001717 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001718 return tmp;
1719 }
Damien Miller02e87802013-08-21 02:38:51 +10001720 }
Darren Tucker909d8582010-01-08 19:02:40 +11001721
1722 return xstrdup(word);
1723}
1724
1725/* Autocomplete a sftp command */
1726static int
1727complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1728 int terminated)
1729{
1730 u_int y, count = 0, cmdlen, tmplen;
1731 char *tmp, **list, argterm[3];
1732 const LineInfo *lf;
1733
1734 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1735
1736 /* No command specified: display all available commands */
1737 if (cmd == NULL) {
1738 for (y = 0; cmds[y].c; y++)
1739 list[count++] = xstrdup(cmds[y].c);
Damien Miller02e87802013-08-21 02:38:51 +10001740
Darren Tucker909d8582010-01-08 19:02:40 +11001741 list[count] = NULL;
1742 complete_display(list, 0);
1743
Damien Miller02e87802013-08-21 02:38:51 +10001744 for (y = 0; list[y] != NULL; y++)
1745 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001746 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001747 return count;
1748 }
1749
1750 /* Prepare subset of commands that start with "cmd" */
1751 cmdlen = strlen(cmd);
1752 for (y = 0; cmds[y].c; y++) {
Damien Miller02e87802013-08-21 02:38:51 +10001753 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
Darren Tucker909d8582010-01-08 19:02:40 +11001754 list[count++] = xstrdup(cmds[y].c);
1755 }
1756 list[count] = NULL;
1757
Damien Miller47d81152011-11-25 13:53:48 +11001758 if (count == 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10001759 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001760 return 0;
Damien Miller47d81152011-11-25 13:53:48 +11001761 }
Darren Tucker909d8582010-01-08 19:02:40 +11001762
1763 /* Complete ambigious command */
1764 tmp = complete_ambiguous(cmd, list, count);
1765 if (count > 1)
1766 complete_display(list, 0);
1767
Damien Miller02e87802013-08-21 02:38:51 +10001768 for (y = 0; list[y]; y++)
1769 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001770 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001771
1772 if (tmp != NULL) {
1773 tmplen = strlen(tmp);
1774 cmdlen = strlen(cmd);
1775 /* If cmd may be extended then do so */
1776 if (tmplen > cmdlen)
1777 if (el_insertstr(el, tmp + cmdlen) == -1)
1778 fatal("el_insertstr failed.");
1779 lf = el_line(el);
1780 /* Terminate argument cleanly */
1781 if (count == 1) {
1782 y = 0;
1783 if (!terminated)
1784 argterm[y++] = quote;
1785 if (lastarg || *(lf->cursor) != ' ')
1786 argterm[y++] = ' ';
1787 argterm[y] = '\0';
1788 if (y > 0 && el_insertstr(el, argterm) == -1)
1789 fatal("el_insertstr failed.");
1790 }
Darren Tuckera627d422013-06-02 07:31:17 +10001791 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001792 }
1793
1794 return count;
1795}
1796
1797/*
1798 * Determine whether a particular sftp command's arguments (if any)
1799 * represent local or remote files.
1800 */
1801static int
1802complete_is_remote(char *cmd) {
1803 int i;
1804
1805 if (cmd == NULL)
1806 return -1;
1807
1808 for (i = 0; cmds[i].c; i++) {
Damien Miller02e87802013-08-21 02:38:51 +10001809 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
Darren Tucker909d8582010-01-08 19:02:40 +11001810 return cmds[i].t;
1811 }
1812
1813 return -1;
1814}
1815
1816/* Autocomplete a filename "file" */
1817static int
1818complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1819 char *file, int remote, int lastarg, char quote, int terminated)
1820{
1821 glob_t g;
Darren Tuckerea647212013-06-06 08:19:09 +10001822 char *tmp, *tmp2, ins[8];
Darren Tucker17146d32012-10-05 10:46:16 +10001823 u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
Darren Tuckerea647212013-06-06 08:19:09 +10001824 int clen;
Darren Tucker909d8582010-01-08 19:02:40 +11001825 const LineInfo *lf;
Damien Miller02e87802013-08-21 02:38:51 +10001826
Darren Tucker909d8582010-01-08 19:02:40 +11001827 /* Glob from "file" location */
1828 if (file == NULL)
1829 tmp = xstrdup("*");
1830 else
1831 xasprintf(&tmp, "%s*", file);
1832
Darren Tucker191fcc62012-10-05 10:45:01 +10001833 /* Check if the path is absolute. */
1834 isabs = tmp[0] == '/';
1835
Darren Tucker909d8582010-01-08 19:02:40 +11001836 memset(&g, 0, sizeof(g));
1837 if (remote != LOCAL) {
1838 tmp = make_absolute(tmp, remote_path);
1839 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001840 } else
Darren Tucker909d8582010-01-08 19:02:40 +11001841 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001842
Darren Tucker909d8582010-01-08 19:02:40 +11001843 /* Determine length of pwd so we can trim completion display */
1844 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1845 /* Terminate counting on first unescaped glob metacharacter */
1846 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1847 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1848 hadglob = 1;
1849 break;
1850 }
1851 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1852 tmplen++;
1853 if (tmp[tmplen] == '/')
1854 pwdlen = tmplen + 1; /* track last seen '/' */
1855 }
Darren Tuckera627d422013-06-02 07:31:17 +10001856 free(tmp);
Damien Millerd7fd8be2014-05-15 14:24:59 +10001857 tmp = NULL;
Darren Tucker909d8582010-01-08 19:02:40 +11001858
Damien Miller02e87802013-08-21 02:38:51 +10001859 if (g.gl_matchc == 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001860 goto out;
1861
1862 if (g.gl_matchc > 1)
1863 complete_display(g.gl_pathv, pwdlen);
1864
Darren Tucker909d8582010-01-08 19:02:40 +11001865 /* Don't try to extend globs */
1866 if (file == NULL || hadglob)
1867 goto out;
1868
1869 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
Darren Tucker191fcc62012-10-05 10:45:01 +10001870 tmp = path_strip(tmp2, isabs ? NULL : remote_path);
Darren Tuckera627d422013-06-02 07:31:17 +10001871 free(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001872
1873 if (tmp == NULL)
1874 goto out;
1875
1876 tmplen = strlen(tmp);
1877 filelen = strlen(file);
1878
Darren Tucker17146d32012-10-05 10:46:16 +10001879 /* Count the number of escaped characters in the input string. */
1880 cesc = isesc = 0;
1881 for (i = 0; i < filelen; i++) {
1882 if (!isesc && file[i] == '\\' && i + 1 < filelen){
1883 isesc = 1;
1884 cesc++;
1885 } else
1886 isesc = 0;
1887 }
1888
1889 if (tmplen > (filelen - cesc)) {
1890 tmp2 = tmp + filelen - cesc;
Damien Miller02e87802013-08-21 02:38:51 +10001891 len = strlen(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001892 /* quote argument on way out */
Darren Tuckerea647212013-06-06 08:19:09 +10001893 for (i = 0; i < len; i += clen) {
1894 if ((clen = mblen(tmp2 + i, len - i)) < 0 ||
1895 (size_t)clen > sizeof(ins) - 2)
1896 fatal("invalid multibyte character");
Darren Tucker909d8582010-01-08 19:02:40 +11001897 ins[0] = '\\';
Darren Tuckerea647212013-06-06 08:19:09 +10001898 memcpy(ins + 1, tmp2 + i, clen);
1899 ins[clen + 1] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001900 switch (tmp2[i]) {
1901 case '\'':
1902 case '"':
1903 case '\\':
1904 case '\t':
Darren Tuckerd78739a2010-10-24 10:56:32 +11001905 case '[':
Darren Tucker909d8582010-01-08 19:02:40 +11001906 case ' ':
Darren Tucker17146d32012-10-05 10:46:16 +10001907 case '#':
1908 case '*':
Darren Tucker909d8582010-01-08 19:02:40 +11001909 if (quote == '\0' || tmp2[i] == quote) {
1910 if (el_insertstr(el, ins) == -1)
1911 fatal("el_insertstr "
1912 "failed.");
1913 break;
1914 }
1915 /* FALLTHROUGH */
1916 default:
1917 if (el_insertstr(el, ins + 1) == -1)
1918 fatal("el_insertstr failed.");
1919 break;
1920 }
1921 }
1922 }
1923
1924 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11001925 if (g.gl_matchc == 1) {
1926 i = 0;
Damien Miller38094812014-05-15 14:25:18 +10001927 if (!terminated && quote != '\0')
Darren Tucker909d8582010-01-08 19:02:40 +11001928 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11001929 if (*(lf->cursor - 1) != '/' &&
1930 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11001931 ins[i++] = ' ';
1932 ins[i] = '\0';
1933 if (i > 0 && el_insertstr(el, ins) == -1)
1934 fatal("el_insertstr failed.");
1935 }
Darren Tuckera627d422013-06-02 07:31:17 +10001936 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001937
1938 out:
1939 globfree(&g);
1940 return g.gl_matchc;
1941}
1942
1943/* tab-completion hook function, called via libedit */
1944static unsigned char
1945complete(EditLine *el, int ch)
1946{
Damien Miller02e87802013-08-21 02:38:51 +10001947 char **argv, *line, quote;
Damien Miller746d1a62013-07-18 16:13:02 +10001948 int argc, carg;
1949 u_int cursor, len, terminated, ret = CC_ERROR;
Darren Tucker909d8582010-01-08 19:02:40 +11001950 const LineInfo *lf;
1951 struct complete_ctx *complete_ctx;
1952
1953 lf = el_line(el);
1954 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1955 fatal("%s: el_get failed", __func__);
1956
1957 /* Figure out which argument the cursor points to */
1958 cursor = lf->cursor - lf->buffer;
1959 line = (char *)xmalloc(cursor + 1);
1960 memcpy(line, lf->buffer, cursor);
1961 line[cursor] = '\0';
1962 argv = makeargv(line, &carg, 1, &quote, &terminated);
Darren Tuckera627d422013-06-02 07:31:17 +10001963 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001964
1965 /* Get all the arguments on the line */
1966 len = lf->lastchar - lf->buffer;
1967 line = (char *)xmalloc(len + 1);
1968 memcpy(line, lf->buffer, len);
1969 line[len] = '\0';
1970 argv = makeargv(line, &argc, 1, NULL, NULL);
1971
1972 /* Ensure cursor is at EOL or a argument boundary */
1973 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1974 line[cursor] != '\n') {
Darren Tuckera627d422013-06-02 07:31:17 +10001975 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001976 return ret;
1977 }
1978
1979 if (carg == 0) {
1980 /* Show all available commands */
1981 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1982 ret = CC_REDISPLAY;
1983 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1984 /* Handle the command parsing */
1985 if (complete_cmd_parse(el, argv[0], argc == carg,
Damien Miller02e87802013-08-21 02:38:51 +10001986 quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001987 ret = CC_REDISPLAY;
1988 } else if (carg >= 1) {
1989 /* Handle file parsing */
1990 int remote = complete_is_remote(argv[0]);
1991 char *filematch = NULL;
1992
1993 if (carg > 1 && line[cursor-1] != ' ')
1994 filematch = argv[carg - 1];
1995
1996 if (remote != 0 &&
1997 complete_match(el, complete_ctx->conn,
1998 *complete_ctx->remote_pathp, filematch,
Damien Miller02e87802013-08-21 02:38:51 +10001999 remote, carg == argc, quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11002000 ret = CC_REDISPLAY;
2001 }
2002
Damien Miller02e87802013-08-21 02:38:51 +10002003 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11002004 return ret;
2005}
Darren Tuckere67f7db2010-01-08 19:50:02 +11002006#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11002007
Damien Miller20e1fab2004-02-18 14:30:55 +11002008int
Darren Tucker21063192010-01-08 17:10:36 +11002009interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11002010{
Darren Tucker909d8582010-01-08 19:02:40 +11002011 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11002012 char *dir = NULL;
2013 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10002014 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11002015 EditLine *el = NULL;
2016#ifdef USE_LIBEDIT
2017 History *hl = NULL;
2018 HistEvent hev;
2019 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11002020 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11002021
2022 if (!batchmode && isatty(STDIN_FILENO)) {
2023 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
2024 fatal("Couldn't initialise editline");
2025 if ((hl = history_init()) == NULL)
2026 fatal("Couldn't initialise editline history");
2027 history(hl, &hev, H_SETSIZE, 100);
2028 el_set(el, EL_HIST, history, hl);
2029
2030 el_set(el, EL_PROMPT, prompt);
2031 el_set(el, EL_EDITOR, "emacs");
2032 el_set(el, EL_TERMINAL, NULL);
2033 el_set(el, EL_SIGNAL, 1);
2034 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11002035
2036 /* Tab Completion */
Damien Miller02e87802013-08-21 02:38:51 +10002037 el_set(el, EL_ADDFN, "ftp-complete",
Darren Tuckerd78739a2010-10-24 10:56:32 +11002038 "Context sensitive argument completion", complete);
Darren Tucker909d8582010-01-08 19:02:40 +11002039 complete_ctx.conn = conn;
2040 complete_ctx.remote_pathp = &remote_path;
2041 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
2042 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Damien Millere0ee7272013-08-21 02:42:35 +10002043 /* enable ctrl-left-arrow and ctrl-right-arrow */
2044 el_set(el, EL_BIND, "\\e[1;5C", "em-next-word", NULL);
2045 el_set(el, EL_BIND, "\\e[5C", "em-next-word", NULL);
2046 el_set(el, EL_BIND, "\\e[1;5D", "ed-prev-word", NULL);
2047 el_set(el, EL_BIND, "\\e\\e[D", "ed-prev-word", NULL);
Damien Miller61353b32013-09-14 09:45:32 +10002048 /* make ^w match ksh behaviour */
2049 el_set(el, EL_BIND, "^w", "ed-delete-prev-word", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11002050 }
2051#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002052
Darren Tucker909d8582010-01-08 19:02:40 +11002053 remote_path = do_realpath(conn, ".");
2054 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11002055 fatal("Need cwd");
2056
2057 if (file1 != NULL) {
2058 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11002059 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11002060
2061 if (remote_is_dir(conn, dir) && file2 == NULL) {
Damien Miller9303e652013-04-23 15:22:40 +10002062 if (!quiet)
2063 printf("Changing to: %s\n", dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002064 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11002065 if (parse_dispatch_command(conn, cmd,
2066 &remote_path, 1) != 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10002067 free(dir);
2068 free(remote_path);
2069 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002070 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11002071 }
Damien Miller20e1fab2004-02-18 14:30:55 +11002072 } else {
Darren Tucker0af24052012-10-05 10:41:25 +10002073 /* XXX this is wrong wrt quoting */
Damien Miller0d032412013-07-25 11:56:52 +10002074 snprintf(cmd, sizeof cmd, "get%s %s%s%s",
2075 global_aflag ? " -a" : "", dir,
2076 file2 == NULL ? "" : " ",
2077 file2 == NULL ? "" : file2);
Darren Tucker909d8582010-01-08 19:02:40 +11002078 err = parse_dispatch_command(conn, cmd,
2079 &remote_path, 1);
Darren Tuckera627d422013-06-02 07:31:17 +10002080 free(dir);
2081 free(remote_path);
2082 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002083 return (err);
2084 }
Darren Tuckera627d422013-06-02 07:31:17 +10002085 free(dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002086 }
2087
millert@openbsd.orgdb995f22014-11-26 18:34:51 +00002088 setvbuf(stdout, NULL, _IOLBF, 0);
2089 setvbuf(infile, NULL, _IOLBF, 0);
Damien Miller20e1fab2004-02-18 14:30:55 +11002090
Damien Miller0e2c1022005-08-12 22:16:22 +10002091 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11002092 err = 0;
2093 for (;;) {
2094 char *cp;
2095
Darren Tuckercdf547a2004-05-24 10:12:19 +10002096 signal(SIGINT, SIG_IGN);
2097
Darren Tucker2d963d82004-11-07 20:04:10 +11002098 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002099 if (interactive)
2100 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11002101 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002102 if (interactive)
2103 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11002104 break;
2105 }
Damien Miller0e2c1022005-08-12 22:16:22 +10002106 if (!interactive) { /* Echo command */
2107 printf("sftp> %s", cmd);
2108 if (strlen(cmd) > 0 &&
2109 cmd[strlen(cmd) - 1] != '\n')
2110 printf("\n");
2111 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002112 } else {
2113#ifdef USE_LIBEDIT
2114 const char *line;
2115 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11002116
Darren Tucker909d8582010-01-08 19:02:40 +11002117 if ((line = el_gets(el, &count)) == NULL ||
2118 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002119 printf("\n");
2120 break;
2121 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002122 history(hl, &hev, H_ENTER, line);
2123 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
2124 fprintf(stderr, "Error: input line too long\n");
2125 continue;
2126 }
2127#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002128 }
2129
Damien Miller20e1fab2004-02-18 14:30:55 +11002130 cp = strrchr(cmd, '\n');
2131 if (cp)
2132 *cp = '\0';
2133
Darren Tuckercdf547a2004-05-24 10:12:19 +10002134 /* Handle user interrupts gracefully during commands */
2135 interrupted = 0;
2136 signal(SIGINT, cmd_interrupt);
2137
Darren Tucker909d8582010-01-08 19:02:40 +11002138 err = parse_dispatch_command(conn, cmd, &remote_path,
2139 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11002140 if (err != 0)
2141 break;
2142 }
Darren Tuckera627d422013-06-02 07:31:17 +10002143 free(remote_path);
2144 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002145
Tim Rice027e8b12005-08-15 14:52:50 -07002146#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10002147 if (el != NULL)
2148 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07002149#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10002150
Damien Miller20e1fab2004-02-18 14:30:55 +11002151 /* err == 1 signifies normal "quit" exit */
2152 return (err >= 0 ? 0 : -1);
2153}
Damien Miller62d57f62003-01-10 21:43:24 +11002154
Ben Lindstrombba81212001-06-25 05:01:22 +00002155static void
Damien Millercc685c12003-06-04 22:51:38 +10002156connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11002157{
2158 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002159
Damien Miller33804262001-02-04 23:20:18 +11002160#ifdef USE_PIPES
2161 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002162
Damien Miller33804262001-02-04 23:20:18 +11002163 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2164 fatal("pipe: %s", strerror(errno));
2165 *in = pin[0];
2166 *out = pout[1];
2167 c_in = pout[0];
2168 c_out = pin[1];
2169#else /* USE_PIPES */
2170 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002171
Damien Miller33804262001-02-04 23:20:18 +11002172 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2173 fatal("socketpair: %s", strerror(errno));
2174 *in = *out = inout[0];
2175 c_in = c_out = inout[1];
2176#endif /* USE_PIPES */
2177
Damien Millercc685c12003-06-04 22:51:38 +10002178 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002179 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002180 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002181 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2182 (dup2(c_out, STDOUT_FILENO) == -1)) {
2183 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002184 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002185 }
2186 close(*in);
2187 close(*out);
2188 close(c_in);
2189 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002190
2191 /*
2192 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002193 * ignore SIGINT if we want to gracefully abort commands,
2194 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002195 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2196 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002197 */
2198 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002199 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002200 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002201 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002202 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002203 }
2204
Damien Millercc685c12003-06-04 22:51:38 +10002205 signal(SIGTERM, killchild);
2206 signal(SIGINT, killchild);
2207 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11002208 close(c_in);
2209 close(c_out);
2210}
2211
Ben Lindstrombba81212001-06-25 05:01:22 +00002212static void
Damien Miller33804262001-02-04 23:20:18 +11002213usage(void)
2214{
Damien Miller025e01c2002-02-08 22:06:29 +11002215 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002216
Ben Lindstrom1e243242001-09-18 05:38:44 +00002217 fprintf(stderr,
Damien Miller1edcbf62013-10-18 10:17:17 +11002218 "usage: %s [-1246aCfpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002219 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002220 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002221 " [-o ssh_option] [-P port] [-R num_requests] "
2222 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002223 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002224 " %s [user@]host[:file ...]\n"
2225 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002226 " %s -b batchfile [user@]host\n",
2227 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002228 exit(1);
2229}
2230
Kevin Stevesef4eea92001-02-05 12:42:17 +00002231int
Damien Miller33804262001-02-04 23:20:18 +11002232main(int argc, char **argv)
2233{
Damien Miller956f3fb2003-01-10 21:40:00 +11002234 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002235 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002236 int debug_level = 0, sshver = 2;
2237 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002238 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002239 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002240 LogLevel ll = SYSLOG_LEVEL_INFO;
2241 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002242 extern int optind;
2243 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002244 struct sftp_conn *conn;
2245 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2246 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002247 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002248
Darren Tuckerce321d82005-10-03 18:11:24 +10002249 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2250 sanitise_stdfd();
Darren Tuckerea647212013-06-06 08:19:09 +10002251 setlocale(LC_CTYPE, "");
Darren Tuckerce321d82005-10-03 18:11:24 +10002252
Damien Miller59d3d5b2003-08-22 09:34:41 +10002253 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002254 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002255 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002256 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002257 addargs(&args, "-oForwardX11 no");
2258 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002259 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002260 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002261
Ben Lindstrom387c4722001-05-08 20:27:25 +00002262 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002263 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002264
Darren Tucker282b4022009-10-07 08:23:06 +11002265 while ((ch = getopt(argc, argv,
Damien Millerf29238e2013-10-17 11:48:52 +11002266 "1246afhpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002267 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002268 /* Passed through to ssh(1) */
2269 case '4':
2270 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002271 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002272 addargs(&args, "-%c", ch);
2273 break;
2274 /* Passed through to ssh(1) with argument */
2275 case 'F':
2276 case 'c':
2277 case 'i':
2278 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002279 addargs(&args, "-%c", ch);
2280 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002281 break;
2282 case 'q':
Damien Miller9303e652013-04-23 15:22:40 +10002283 ll = SYSLOG_LEVEL_ERROR;
2284 quiet = 1;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002285 showprogress = 0;
2286 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002287 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002288 case 'P':
2289 addargs(&args, "-oPort %s", optarg);
2290 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002291 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002292 if (debug_level < 3) {
2293 addargs(&args, "-v");
2294 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2295 }
2296 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002297 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002298 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002299 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002300 if (sftp_server == NULL)
2301 sftp_server = _PATH_SFTP_SERVER;
2302 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002303 case '2':
2304 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002305 break;
Damien Miller0d032412013-07-25 11:56:52 +10002306 case 'a':
2307 global_aflag = 1;
2308 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002309 case 'B':
2310 copy_buffer_len = strtol(optarg, &cp, 10);
2311 if (copy_buffer_len == 0 || *cp != '\0')
2312 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002313 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002314 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002315 if (batchmode)
2316 fatal("Batch file already specified.");
2317
2318 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002319 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002320 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002321 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002322 showprogress = 0;
Damien Miller9303e652013-04-23 15:22:40 +10002323 quiet = batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002324 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002325 break;
Damien Millerf29238e2013-10-17 11:48:52 +11002326 case 'f':
2327 global_fflag = 1;
2328 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002329 case 'p':
2330 global_pflag = 1;
2331 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002332 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002333 sftp_direct = optarg;
2334 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002335 case 'l':
2336 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2337 &errstr);
2338 if (errstr != NULL)
2339 usage();
2340 limit_kbps *= 1024; /* kbps */
2341 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002342 case 'r':
2343 global_rflag = 1;
2344 break;
Damien Miller16a13332002-02-13 14:03:56 +11002345 case 'R':
2346 num_requests = strtol(optarg, &cp, 10);
2347 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002348 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002349 optarg);
2350 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002351 case 's':
2352 sftp_server = optarg;
2353 break;
2354 case 'S':
2355 ssh_program = optarg;
2356 replacearg(&args, 0, "%s", ssh_program);
2357 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002358 case 'h':
2359 default:
Damien Miller33804262001-02-04 23:20:18 +11002360 usage();
2361 }
2362 }
2363
Damien Millerc0f27d82004-03-08 23:12:19 +11002364 if (!isatty(STDERR_FILENO))
2365 showprogress = 0;
2366
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002367 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2368
Damien Millerd14ee1e2002-02-05 12:27:31 +11002369 if (sftp_direct == NULL) {
2370 if (optind == argc || argc > (optind + 2))
2371 usage();
Damien Miller33804262001-02-04 23:20:18 +11002372
Damien Millerd14ee1e2002-02-05 12:27:31 +11002373 userhost = xstrdup(argv[optind]);
2374 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002375
Ben Lindstromc276c122002-12-23 02:14:51 +00002376 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002377 host = userhost;
2378 else {
2379 *host++ = '\0';
2380 if (!userhost[0]) {
2381 fprintf(stderr, "Missing username\n");
2382 usage();
2383 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002384 addargs(&args, "-l");
2385 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002386 }
2387
Damien Millerec692032004-01-27 21:22:00 +11002388 if ((cp = colon(host)) != NULL) {
2389 *cp++ = '\0';
2390 file1 = cp;
2391 }
2392
Damien Millerd14ee1e2002-02-05 12:27:31 +11002393 host = cleanhostname(host);
2394 if (!*host) {
2395 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002396 usage();
2397 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002398
Damien Millerd14ee1e2002-02-05 12:27:31 +11002399 addargs(&args, "-oProtocol %d", sshver);
2400
2401 /* no subsystem if the server-spec contains a '/' */
2402 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2403 addargs(&args, "-s");
2404
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002405 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002406 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002407 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002408 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002409
Damien Millercc685c12003-06-04 22:51:38 +10002410 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002411 } else {
2412 args.list = NULL;
2413 addargs(&args, "sftp-server");
2414
Damien Millercc685c12003-06-04 22:51:38 +10002415 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002416 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002417 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002418
Damien Miller65e42f82010-09-24 22:15:11 +10002419 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002420 if (conn == NULL)
2421 fatal("Couldn't initialise connection to server");
2422
Damien Miller9303e652013-04-23 15:22:40 +10002423 if (!quiet) {
Darren Tucker21063192010-01-08 17:10:36 +11002424 if (sftp_direct == NULL)
2425 fprintf(stderr, "Connected to %s.\n", host);
2426 else
2427 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2428 }
2429
2430 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002431
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002432#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002433 shutdown(in, SHUT_RDWR);
2434 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002435#endif
2436
Damien Miller33804262001-02-04 23:20:18 +11002437 close(in);
2438 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002439 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002440 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002441
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002442 while (waitpid(sshpid, NULL, 0) == -1)
2443 if (errno != EINTR)
2444 fatal("Couldn't wait for ssh process: %s",
2445 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002446
Damien Miller956f3fb2003-01-10 21:40:00 +11002447 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002448}