blob: cb9b967edc99f9b98cc86a1bc4d818ab58adbe6a [file] [log] [blame]
deraadt@openbsd.org087266e2015-01-20 23:14:00 +00001/* $OpenBSD: sftp.c,v 1.170 2015/01/20 23:14:00 deraadt 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
deraadt@openbsd.org087266e2015-01-20 23:14:00 +000020#include <sys/param.h> /* MIN MAX */
Damien Miller9cf6d072006-03-15 11:29:24 +110021#include <sys/types.h>
Damien Millerd7834352006-08-05 12:39:39 +100022#include <sys/ioctl.h>
Damien Millerf17883e2006-03-15 11:45:54 +110023#ifdef HAVE_SYS_STAT_H
24# include <sys/stat.h>
25#endif
Damien Miller8dbffe72006-08-05 11:02:17 +100026#include <sys/param.h>
Damien Millere3b60b52006-07-10 21:08:03 +100027#include <sys/socket.h>
Damien Miller9cf6d072006-03-15 11:29:24 +110028#include <sys/wait.h>
Darren Tucker5b2e2ba2008-06-08 09:25:28 +100029#ifdef HAVE_SYS_STATVFS_H
Damien Millerd671e5a2008-05-19 14:53:33 +100030#include <sys/statvfs.h>
Darren Tucker5b2e2ba2008-06-08 09:25:28 +100031#endif
Darren Tucker2d963d82004-11-07 20:04:10 +110032
Damien Miller1cbc2922007-10-26 14:27:45 +100033#include <ctype.h>
Darren Tucker39972492006-07-12 22:22:46 +100034#include <errno.h>
35
Damien Miller03e20032006-03-15 11:16:59 +110036#ifdef HAVE_PATHS_H
Damien Millera9263d02006-03-15 11:18:26 +110037# include <paths.h>
Damien Miller03e20032006-03-15 11:16:59 +110038#endif
Darren Tucker1b0dd172009-10-07 08:37:48 +110039#ifdef HAVE_LIBGEN_H
40#include <libgen.h>
41#endif
Darren Tuckerae133d42013-06-06 08:30:20 +100042#ifdef HAVE_LOCALE_H
43# include <locale.h>
44#endif
Darren Tucker2d963d82004-11-07 20:04:10 +110045#ifdef USE_LIBEDIT
46#include <histedit.h>
47#else
48typedef void EditLine;
49#endif
deraadt@openbsd.org087266e2015-01-20 23:14:00 +000050#include <limits.h>
Damien Miller6ff3cad2006-03-15 11:52:09 +110051#include <signal.h>
Damien Millere7a1e5c2006-08-05 11:34:19 +100052#include <stdlib.h>
Damien Millera7a73ee2006-08-05 11:37:59 +100053#include <stdio.h>
Damien Millere3476ed2006-07-24 14:13:33 +100054#include <string.h>
Damien Millere6b3b612006-07-24 14:01:23 +100055#include <unistd.h>
Damien Millerd7834352006-08-05 12:39:39 +100056#include <stdarg.h>
Damien Miller33804262001-02-04 23:20:18 +110057
Damien Millera7058ec2008-05-20 08:57:06 +100058#ifdef HAVE_UTIL_H
59# include <util.h>
60#endif
61
Damien Miller33804262001-02-04 23:20:18 +110062#include "xmalloc.h"
63#include "log.h"
64#include "pathnames.h"
Ben Lindstrom4529b702001-05-03 23:39:53 +000065#include "misc.h"
Damien Miller33804262001-02-04 23:20:18 +110066
67#include "sftp.h"
djm@openbsd.org7d845f42015-01-14 13:54:13 +000068#include "ssherr.h"
69#include "sshbuf.h"
Damien Miller33804262001-02-04 23:20:18 +110070#include "sftp-common.h"
71#include "sftp-client.h"
Damien Millerd7d46bb2004-02-18 14:11:13 +110072
Darren Tucker21063192010-01-08 17:10:36 +110073#define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */
74#define DEFAULT_NUM_REQUESTS 64 /* # concurrent outstanding requests */
75
Damien Miller20e1fab2004-02-18 14:30:55 +110076/* File to read commands from */
77FILE* infile;
78
79/* Are we in batchfile mode? */
80int batchmode = 0;
81
Damien Miller20e1fab2004-02-18 14:30:55 +110082/* PID of ssh transport process */
83static pid_t sshpid = -1;
84
Damien Miller9303e652013-04-23 15:22:40 +100085/* Suppress diagnositic messages */
86int quiet = 0;
87
Damien Miller20e1fab2004-02-18 14:30:55 +110088/* This is set to 0 if the progressmeter is not desired. */
Damien Millerc0f27d82004-03-08 23:12:19 +110089int showprogress = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +110090
Darren Tucker1b0dd172009-10-07 08:37:48 +110091/* When this option is set, we always recursively download/upload directories */
92int global_rflag = 0;
93
Damien Millerd8accc02014-05-15 13:46:25 +100094/* When this option is set, we resume download or upload if possible */
Damien Miller0d032412013-07-25 11:56:52 +100095int global_aflag = 0;
96
Darren Tucker1b0dd172009-10-07 08:37:48 +110097/* When this option is set, the file transfers will always preserve times */
98int global_pflag = 0;
99
Damien Millerf29238e2013-10-17 11:48:52 +1100100/* When this option is set, transfers will have fsync() called on each file */
101int global_fflag = 0;
102
Darren Tuckercdf547a2004-05-24 10:12:19 +1000103/* SIGINT received during command processing */
104volatile sig_atomic_t interrupted = 0;
105
Darren Tuckerb9123452004-06-22 13:06:45 +1000106/* I wish qsort() took a separate ctx for the comparison function...*/
107int sort_flag;
108
Darren Tucker909d8582010-01-08 19:02:40 +1100109/* Context used for commandline completion */
110struct complete_ctx {
111 struct sftp_conn *conn;
112 char **remote_pathp;
113};
114
Damien Miller20e1fab2004-02-18 14:30:55 +1100115int remote_glob(struct sftp_conn *, const char *, int,
116 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
Damien Miller33804262001-02-04 23:20:18 +1100117
Kevin Steves12888d12001-03-05 19:50:57 +0000118extern char *__progname;
Kevin Steves12888d12001-03-05 19:50:57 +0000119
Damien Miller20e1fab2004-02-18 14:30:55 +1100120/* Separators for interactive commands */
121#define WHITESPACE " \t\r\n"
Damien Millerd7686fd2001-02-10 00:40:03 +1100122
Darren Tuckerb9123452004-06-22 13:06:45 +1000123/* ls flags */
Darren Tucker2901e2d2010-01-13 22:44:06 +1100124#define LS_LONG_VIEW 0x0001 /* Full view ala ls -l */
125#define LS_SHORT_VIEW 0x0002 /* Single row view ala ls -1 */
126#define LS_NUMERIC_VIEW 0x0004 /* Long view with numeric uid/gid */
127#define LS_NAME_SORT 0x0008 /* Sort by name (default) */
128#define LS_TIME_SORT 0x0010 /* Sort by mtime */
129#define LS_SIZE_SORT 0x0020 /* Sort by file size */
130#define LS_REVERSE_SORT 0x0040 /* Reverse sort order */
131#define LS_SHOW_ALL 0x0080 /* Don't skip filenames starting with '.' */
132#define LS_SI_UNITS 0x0100 /* Display sizes as K, M, G, etc. */
Darren Tuckerb9123452004-06-22 13:06:45 +1000133
Darren Tucker2901e2d2010-01-13 22:44:06 +1100134#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS)
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000135#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
Damien Miller20e1fab2004-02-18 14:30:55 +1100136
137/* Commands for interactive mode */
Damien Miller02e87802013-08-21 02:38:51 +1000138enum sftp_command {
139 I_CHDIR = 1,
140 I_CHGRP,
141 I_CHMOD,
142 I_CHOWN,
143 I_DF,
144 I_GET,
145 I_HELP,
146 I_LCHDIR,
147 I_LINK,
148 I_LLS,
149 I_LMKDIR,
150 I_LPWD,
151 I_LS,
152 I_LUMASK,
153 I_MKDIR,
154 I_PUT,
155 I_PWD,
156 I_QUIT,
Damien Millerb15cd7b2014-05-15 13:46:52 +1000157 I_REGET,
Damien Miller02e87802013-08-21 02:38:51 +1000158 I_RENAME,
Damien Millerb15cd7b2014-05-15 13:46:52 +1000159 I_REPUT,
Damien Miller02e87802013-08-21 02:38:51 +1000160 I_RM,
161 I_RMDIR,
162 I_SHELL,
163 I_SYMLINK,
164 I_VERSION,
165 I_PROGRESS,
Damien Miller02e87802013-08-21 02:38:51 +1000166};
Damien Miller20e1fab2004-02-18 14:30:55 +1100167
168struct CMD {
169 const char *c;
170 const int n;
Darren Tucker909d8582010-01-08 19:02:40 +1100171 const int t;
Damien Miller20e1fab2004-02-18 14:30:55 +1100172};
173
Darren Tucker909d8582010-01-08 19:02:40 +1100174/* Type of completion */
175#define NOARGS 0
176#define REMOTE 1
177#define LOCAL 2
178
Damien Miller20e1fab2004-02-18 14:30:55 +1100179static const struct CMD cmds[] = {
Darren Tucker909d8582010-01-08 19:02:40 +1100180 { "bye", I_QUIT, NOARGS },
181 { "cd", I_CHDIR, REMOTE },
182 { "chdir", I_CHDIR, REMOTE },
183 { "chgrp", I_CHGRP, REMOTE },
184 { "chmod", I_CHMOD, REMOTE },
185 { "chown", I_CHOWN, REMOTE },
186 { "df", I_DF, REMOTE },
187 { "dir", I_LS, REMOTE },
188 { "exit", I_QUIT, NOARGS },
189 { "get", I_GET, REMOTE },
190 { "help", I_HELP, NOARGS },
191 { "lcd", I_LCHDIR, LOCAL },
192 { "lchdir", I_LCHDIR, LOCAL },
193 { "lls", I_LLS, LOCAL },
194 { "lmkdir", I_LMKDIR, LOCAL },
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100195 { "ln", I_LINK, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100196 { "lpwd", I_LPWD, LOCAL },
197 { "ls", I_LS, REMOTE },
198 { "lumask", I_LUMASK, NOARGS },
199 { "mkdir", I_MKDIR, REMOTE },
Damien Miller099fc162010-05-10 11:56:50 +1000200 { "mget", I_GET, REMOTE },
201 { "mput", I_PUT, LOCAL },
Darren Tucker909d8582010-01-08 19:02:40 +1100202 { "progress", I_PROGRESS, NOARGS },
203 { "put", I_PUT, LOCAL },
204 { "pwd", I_PWD, REMOTE },
205 { "quit", I_QUIT, NOARGS },
Damien Miller0d032412013-07-25 11:56:52 +1000206 { "reget", I_REGET, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100207 { "rename", I_RENAME, REMOTE },
djm@openbsd.org4a459222014-10-06 00:47:15 +0000208 { "reput", I_REPUT, LOCAL },
Darren Tucker909d8582010-01-08 19:02:40 +1100209 { "rm", I_RM, REMOTE },
210 { "rmdir", I_RMDIR, REMOTE },
211 { "symlink", I_SYMLINK, REMOTE },
212 { "version", I_VERSION, NOARGS },
213 { "!", I_SHELL, NOARGS },
214 { "?", I_HELP, NOARGS },
215 { NULL, -1, -1 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100216};
217
Darren Tucker21063192010-01-08 17:10:36 +1100218int interactive_loop(struct sftp_conn *, char *file1, char *file2);
Damien Miller20e1fab2004-02-18 14:30:55 +1100219
Damien Millerb6c85fc2007-01-05 16:30:41 +1100220/* ARGSUSED */
Damien Miller20e1fab2004-02-18 14:30:55 +1100221static void
Darren Tuckercdf547a2004-05-24 10:12:19 +1000222killchild(int signo)
223{
Darren Tuckerba66df82005-01-24 21:57:40 +1100224 if (sshpid > 1) {
Darren Tuckercdf547a2004-05-24 10:12:19 +1000225 kill(sshpid, SIGTERM);
Darren Tuckerba66df82005-01-24 21:57:40 +1100226 waitpid(sshpid, NULL, 0);
227 }
Darren Tuckercdf547a2004-05-24 10:12:19 +1000228
229 _exit(1);
230}
231
Damien Millerb6c85fc2007-01-05 16:30:41 +1100232/* ARGSUSED */
Darren Tuckercdf547a2004-05-24 10:12:19 +1000233static void
234cmd_interrupt(int signo)
235{
236 const char msg[] = "\rInterrupt \n";
Darren Tuckere2f189a2004-12-06 22:45:53 +1100237 int olderrno = errno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000238
Darren Tuckerdbee3082013-05-16 20:32:29 +1000239 (void)write(STDERR_FILENO, msg, sizeof(msg) - 1);
Darren Tuckercdf547a2004-05-24 10:12:19 +1000240 interrupted = 1;
Darren Tuckere2f189a2004-12-06 22:45:53 +1100241 errno = olderrno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000242}
243
244static void
Damien Miller20e1fab2004-02-18 14:30:55 +1100245help(void)
246{
Damien Miller62fd18a2009-01-28 16:14:09 +1100247 printf("Available commands:\n"
248 "bye Quit sftp\n"
249 "cd path Change remote directory to 'path'\n"
250 "chgrp grp path Change group of file 'path' to 'grp'\n"
251 "chmod mode path Change permissions of file 'path' to 'mode'\n"
252 "chown own path Change owner of file 'path' to 'own'\n"
253 "df [-hi] [path] Display statistics for current directory or\n"
254 " filesystem containing 'path'\n"
255 "exit Quit sftp\n"
djm@openbsd.org4a459222014-10-06 00:47:15 +0000256 "get [-afPpRr] remote [local] Download file\n"
257 "reget [-fPpRr] remote [local] Resume download file\n"
258 "reput [-fPpRr] [local] remote Resume upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100259 "help Display this help text\n"
260 "lcd path Change local directory to 'path'\n"
261 "lls [ls-options [path]] Display local directory listing\n"
262 "lmkdir path Create local directory\n"
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100263 "ln [-s] oldpath newpath Link remote file (-s for symlink)\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100264 "lpwd Print local working directory\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100265 "ls [-1afhlnrSt] [path] Display remote directory listing\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100266 "lumask umask Set local umask to 'umask'\n"
267 "mkdir path Create remote directory\n"
268 "progress Toggle display of progress meter\n"
djm@openbsd.org4a459222014-10-06 00:47:15 +0000269 "put [-afPpRr] local [remote] Upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100270 "pwd Display remote working directory\n"
271 "quit Quit sftp\n"
272 "rename oldpath newpath Rename remote file\n"
273 "rm path Delete remote file\n"
274 "rmdir path Remove remote directory\n"
275 "symlink oldpath newpath Symlink remote file\n"
276 "version Show SFTP version\n"
277 "!command Execute 'command' in local shell\n"
278 "! Escape to local shell\n"
279 "? Synonym for help\n");
Damien Miller20e1fab2004-02-18 14:30:55 +1100280}
281
282static void
283local_do_shell(const char *args)
284{
285 int status;
286 char *shell;
287 pid_t pid;
288
289 if (!*args)
290 args = NULL;
291
Damien Miller38d9a962010-10-07 22:07:11 +1100292 if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
Damien Miller20e1fab2004-02-18 14:30:55 +1100293 shell = _PATH_BSHELL;
294
295 if ((pid = fork()) == -1)
296 fatal("Couldn't fork: %s", strerror(errno));
297
298 if (pid == 0) {
299 /* XXX: child has pipe fds to ssh subproc open - issue? */
300 if (args) {
301 debug3("Executing %s -c \"%s\"", shell, args);
302 execl(shell, shell, "-c", args, (char *)NULL);
303 } else {
304 debug3("Executing %s", shell);
305 execl(shell, shell, (char *)NULL);
306 }
307 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
308 strerror(errno));
309 _exit(1);
310 }
311 while (waitpid(pid, &status, 0) == -1)
312 if (errno != EINTR)
313 fatal("Couldn't wait for child: %s", strerror(errno));
314 if (!WIFEXITED(status))
Damien Miller55b04f12006-03-26 14:23:17 +1100315 error("Shell exited abnormally");
Damien Miller20e1fab2004-02-18 14:30:55 +1100316 else if (WEXITSTATUS(status))
317 error("Shell exited with status %d", WEXITSTATUS(status));
318}
319
320static void
321local_do_ls(const char *args)
322{
323 if (!args || !*args)
324 local_do_shell(_PATH_LS);
325 else {
326 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
327 char *buf = xmalloc(len);
328
329 /* XXX: quoting - rip quoting code from ftp? */
330 snprintf(buf, len, _PATH_LS " %s", args);
331 local_do_shell(buf);
Darren Tuckera627d422013-06-02 07:31:17 +1000332 free(buf);
Damien Miller20e1fab2004-02-18 14:30:55 +1100333 }
334}
335
336/* Strip one path (usually the pwd) from the start of another */
337static char *
338path_strip(char *path, char *strip)
339{
340 size_t len;
341
342 if (strip == NULL)
343 return (xstrdup(path));
344
345 len = strlen(strip);
Darren Tuckere2f189a2004-12-06 22:45:53 +1100346 if (strncmp(path, strip, len) == 0) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100347 if (strip[len - 1] != '/' && path[len] == '/')
348 len++;
349 return (xstrdup(path + len));
350 }
351
352 return (xstrdup(path));
353}
354
355static char *
Damien Miller20e1fab2004-02-18 14:30:55 +1100356make_absolute(char *p, char *pwd)
357{
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000358 char *abs_str;
Damien Miller20e1fab2004-02-18 14:30:55 +1100359
360 /* Derelativise */
361 if (p && p[0] != '/') {
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000362 abs_str = path_append(pwd, p);
Darren Tuckera627d422013-06-02 07:31:17 +1000363 free(p);
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000364 return(abs_str);
Damien Miller20e1fab2004-02-18 14:30:55 +1100365 } else
366 return(p);
367}
368
369static int
Damien Miller0d032412013-07-25 11:56:52 +1000370parse_getput_flags(const char *cmd, char **argv, int argc,
Damien Millerf29238e2013-10-17 11:48:52 +1100371 int *aflag, int *fflag, int *pflag, int *rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100372{
Damien Millerf184bcf2008-06-29 22:45:13 +1000373 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000374 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100375
Damien Miller1cbc2922007-10-26 14:27:45 +1000376 optind = optreset = 1;
377 opterr = 0;
378
Damien Millerf29238e2013-10-17 11:48:52 +1100379 *aflag = *fflag = *rflag = *pflag = 0;
380 while ((ch = getopt(argc, argv, "afPpRr")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000381 switch (ch) {
Damien Miller0d032412013-07-25 11:56:52 +1000382 case 'a':
383 *aflag = 1;
384 break;
Damien Millerf29238e2013-10-17 11:48:52 +1100385 case 'f':
386 *fflag = 1;
387 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100388 case 'p':
389 case 'P':
390 *pflag = 1;
391 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100392 case 'r':
393 case 'R':
394 *rflag = 1;
395 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100396 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000397 error("%s: Invalid flag -%c", cmd, optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000398 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100399 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100400 }
401
Damien Miller1cbc2922007-10-26 14:27:45 +1000402 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100403}
404
405static int
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100406parse_link_flags(const char *cmd, char **argv, int argc, int *sflag)
407{
408 extern int opterr, optind, optopt, optreset;
409 int ch;
410
411 optind = optreset = 1;
412 opterr = 0;
413
414 *sflag = 0;
415 while ((ch = getopt(argc, argv, "s")) != -1) {
416 switch (ch) {
417 case 's':
418 *sflag = 1;
419 break;
420 default:
421 error("%s: Invalid flag -%c", cmd, optopt);
422 return -1;
423 }
424 }
425
426 return optind;
427}
428
429static int
Damien Millerc7dba122013-08-21 02:41:15 +1000430parse_rename_flags(const char *cmd, char **argv, int argc, int *lflag)
431{
432 extern int opterr, optind, optopt, optreset;
433 int ch;
434
435 optind = optreset = 1;
436 opterr = 0;
437
438 *lflag = 0;
439 while ((ch = getopt(argc, argv, "l")) != -1) {
440 switch (ch) {
441 case 'l':
442 *lflag = 1;
443 break;
444 default:
445 error("%s: Invalid flag -%c", cmd, optopt);
446 return -1;
447 }
448 }
449
450 return optind;
451}
452
453static int
Damien Miller1cbc2922007-10-26 14:27:45 +1000454parse_ls_flags(char **argv, int argc, int *lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100455{
Damien Millerf184bcf2008-06-29 22:45:13 +1000456 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000457 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100458
Damien Miller1cbc2922007-10-26 14:27:45 +1000459 optind = optreset = 1;
460 opterr = 0;
461
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000462 *lflag = LS_NAME_SORT;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100463 while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000464 switch (ch) {
465 case '1':
466 *lflag &= ~VIEW_FLAGS;
467 *lflag |= LS_SHORT_VIEW;
468 break;
469 case 'S':
470 *lflag &= ~SORT_FLAGS;
471 *lflag |= LS_SIZE_SORT;
472 break;
473 case 'a':
474 *lflag |= LS_SHOW_ALL;
475 break;
476 case 'f':
477 *lflag &= ~SORT_FLAGS;
478 break;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100479 case 'h':
480 *lflag |= LS_SI_UNITS;
481 break;
Damien Miller1cbc2922007-10-26 14:27:45 +1000482 case 'l':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100483 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000484 *lflag |= LS_LONG_VIEW;
485 break;
486 case 'n':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100487 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000488 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
489 break;
490 case 'r':
491 *lflag |= LS_REVERSE_SORT;
492 break;
493 case 't':
494 *lflag &= ~SORT_FLAGS;
495 *lflag |= LS_TIME_SORT;
496 break;
497 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000498 error("ls: Invalid flag -%c", optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000499 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100500 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100501 }
502
Damien Miller1cbc2922007-10-26 14:27:45 +1000503 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100504}
505
506static int
Damien Millerd671e5a2008-05-19 14:53:33 +1000507parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
508{
Damien Millerf184bcf2008-06-29 22:45:13 +1000509 extern int opterr, optind, optopt, optreset;
Damien Millerd671e5a2008-05-19 14:53:33 +1000510 int ch;
511
512 optind = optreset = 1;
513 opterr = 0;
514
515 *hflag = *iflag = 0;
516 while ((ch = getopt(argc, argv, "hi")) != -1) {
517 switch (ch) {
518 case 'h':
519 *hflag = 1;
520 break;
521 case 'i':
522 *iflag = 1;
523 break;
524 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000525 error("%s: Invalid flag -%c", cmd, optopt);
Damien Millerd671e5a2008-05-19 14:53:33 +1000526 return -1;
527 }
528 }
529
530 return optind;
531}
532
533static int
Damien Miller036d3072013-08-21 02:41:46 +1000534parse_no_flags(const char *cmd, char **argv, int argc)
535{
536 extern int opterr, optind, optopt, optreset;
537 int ch;
538
539 optind = optreset = 1;
540 opterr = 0;
541
542 while ((ch = getopt(argc, argv, "")) != -1) {
543 switch (ch) {
544 default:
545 error("%s: Invalid flag -%c", cmd, optopt);
546 return -1;
547 }
548 }
549
550 return optind;
551}
552
553static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100554is_dir(char *path)
555{
556 struct stat sb;
557
558 /* XXX: report errors? */
559 if (stat(path, &sb) == -1)
560 return(0);
561
Darren Tucker1e80e402006-09-21 12:59:33 +1000562 return(S_ISDIR(sb.st_mode));
Damien Miller20e1fab2004-02-18 14:30:55 +1100563}
564
565static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100566remote_is_dir(struct sftp_conn *conn, char *path)
567{
568 Attrib *a;
569
570 /* XXX: report errors? */
571 if ((a = do_stat(conn, path, 1)) == NULL)
572 return(0);
573 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
574 return(0);
Darren Tucker1e80e402006-09-21 12:59:33 +1000575 return(S_ISDIR(a->perm));
Damien Miller20e1fab2004-02-18 14:30:55 +1100576}
577
Darren Tucker1b0dd172009-10-07 08:37:48 +1100578/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
Damien Miller20e1fab2004-02-18 14:30:55 +1100579static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100580pathname_is_dir(char *pathname)
581{
582 size_t l = strlen(pathname);
583
584 return l > 0 && pathname[l - 1] == '/';
585}
586
587static int
588process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
Damien Millerf29238e2013-10-17 11:48:52 +1100589 int pflag, int rflag, int resume, int fflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100590{
591 char *abs_src = NULL;
592 char *abs_dst = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100593 glob_t g;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100594 char *filename, *tmp=NULL;
Damien Miller00707762014-07-09 13:07:06 +1000595 int i, r, err = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100596
597 abs_src = xstrdup(src);
598 abs_src = make_absolute(abs_src, pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +1100599 memset(&g, 0, sizeof(g));
Darren Tucker1b0dd172009-10-07 08:37:48 +1100600
Damien Miller20e1fab2004-02-18 14:30:55 +1100601 debug3("Looking up %s", abs_src);
Damien Miller00707762014-07-09 13:07:06 +1000602 if ((r = remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) != 0) {
603 if (r == GLOB_NOSPACE) {
604 error("Too many matches for \"%s\".", abs_src);
605 } else {
606 error("File \"%s\" not found.", abs_src);
607 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100608 err = -1;
609 goto out;
610 }
611
Darren Tucker1b0dd172009-10-07 08:37:48 +1100612 /*
613 * If multiple matches then dst must be a directory or
614 * unspecified.
615 */
616 if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
617 error("Multiple source paths, but destination "
618 "\"%s\" is not a directory", dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100619 err = -1;
620 goto out;
621 }
622
Darren Tuckercdf547a2004-05-24 10:12:19 +1000623 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100624 tmp = xstrdup(g.gl_pathv[i]);
625 if ((filename = basename(tmp)) == NULL) {
626 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000627 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100628 err = -1;
629 goto out;
630 }
631
632 if (g.gl_matchc == 1 && dst) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100633 if (is_dir(dst)) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100634 abs_dst = path_append(dst, filename);
635 } else {
Damien Miller20e1fab2004-02-18 14:30:55 +1100636 abs_dst = xstrdup(dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100637 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100638 } else if (dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100639 abs_dst = path_append(dst, filename);
640 } else {
641 abs_dst = xstrdup(filename);
642 }
Darren Tuckera627d422013-06-02 07:31:17 +1000643 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100644
Damien Miller0d032412013-07-25 11:56:52 +1000645 resume |= global_aflag;
646 if (!quiet && resume)
647 printf("Resuming %s to %s\n", g.gl_pathv[i], abs_dst);
648 else if (!quiet && !resume)
Damien Miller9303e652013-04-23 15:22:40 +1000649 printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100650 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
Damien Miller0d032412013-07-25 11:56:52 +1000651 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
Damien Millerf29238e2013-10-17 11:48:52 +1100652 pflag || global_pflag, 1, resume,
653 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100654 err = -1;
655 } else {
656 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
Damien Millerf29238e2013-10-17 11:48:52 +1100657 pflag || global_pflag, resume,
658 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100659 err = -1;
660 }
Darren Tuckera627d422013-06-02 07:31:17 +1000661 free(abs_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100662 abs_dst = NULL;
663 }
664
665out:
Darren Tuckera627d422013-06-02 07:31:17 +1000666 free(abs_src);
Damien Miller20e1fab2004-02-18 14:30:55 +1100667 globfree(&g);
668 return(err);
669}
670
671static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100672process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
Damien Millerd8accc02014-05-15 13:46:25 +1000673 int pflag, int rflag, int resume, int fflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100674{
675 char *tmp_dst = NULL;
676 char *abs_dst = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100677 char *tmp = NULL, *filename = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100678 glob_t g;
679 int err = 0;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100680 int i, dst_is_dir = 1;
Damien Milleraec5cf82008-02-10 22:26:24 +1100681 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100682
683 if (dst) {
684 tmp_dst = xstrdup(dst);
685 tmp_dst = make_absolute(tmp_dst, pwd);
686 }
687
688 memset(&g, 0, sizeof(g));
689 debug3("Looking up %s", src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100690 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100691 error("File \"%s\" not found.", src);
692 err = -1;
693 goto out;
694 }
695
Darren Tucker1b0dd172009-10-07 08:37:48 +1100696 /* If we aren't fetching to pwd then stash this status for later */
697 if (tmp_dst != NULL)
698 dst_is_dir = remote_is_dir(conn, tmp_dst);
699
Damien Miller20e1fab2004-02-18 14:30:55 +1100700 /* If multiple matches, dst may be directory or unspecified */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100701 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
702 error("Multiple paths match, but destination "
703 "\"%s\" is not a directory", tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100704 err = -1;
705 goto out;
706 }
707
Darren Tuckercdf547a2004-05-24 10:12:19 +1000708 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Milleraec5cf82008-02-10 22:26:24 +1100709 if (stat(g.gl_pathv[i], &sb) == -1) {
710 err = -1;
711 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
712 continue;
713 }
Damien Miller02e87802013-08-21 02:38:51 +1000714
Darren Tucker1b0dd172009-10-07 08:37:48 +1100715 tmp = xstrdup(g.gl_pathv[i]);
716 if ((filename = basename(tmp)) == NULL) {
717 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000718 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100719 err = -1;
720 goto out;
721 }
722
723 if (g.gl_matchc == 1 && tmp_dst) {
724 /* If directory specified, append filename */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100725 if (dst_is_dir)
726 abs_dst = path_append(tmp_dst, filename);
727 else
Damien Miller20e1fab2004-02-18 14:30:55 +1100728 abs_dst = xstrdup(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100729 } else if (tmp_dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100730 abs_dst = path_append(tmp_dst, filename);
731 } else {
732 abs_dst = make_absolute(xstrdup(filename), pwd);
733 }
Darren Tuckera627d422013-06-02 07:31:17 +1000734 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100735
Damien Millerd8accc02014-05-15 13:46:25 +1000736 resume |= global_aflag;
737 if (!quiet && resume)
Damien Miller3dc27172014-05-15 14:37:59 +1000738 printf("Resuming upload of %s to %s\n", g.gl_pathv[i],
Damien Millerd8accc02014-05-15 13:46:25 +1000739 abs_dst);
740 else if (!quiet && !resume)
Damien Miller9303e652013-04-23 15:22:40 +1000741 printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100742 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
743 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
Damien Millerd8accc02014-05-15 13:46:25 +1000744 pflag || global_pflag, 1, resume,
Damien Millerf29238e2013-10-17 11:48:52 +1100745 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100746 err = -1;
747 } else {
748 if (do_upload(conn, g.gl_pathv[i], abs_dst,
Damien Millerd8accc02014-05-15 13:46:25 +1000749 pflag || global_pflag, resume,
Damien Millerf29238e2013-10-17 11:48:52 +1100750 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100751 err = -1;
752 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100753 }
754
755out:
Darren Tuckera627d422013-06-02 07:31:17 +1000756 free(abs_dst);
757 free(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100758 globfree(&g);
759 return(err);
760}
761
762static int
763sdirent_comp(const void *aa, const void *bb)
764{
765 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
766 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000767 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100768
Darren Tuckerb9123452004-06-22 13:06:45 +1000769#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000770 if (sort_flag & LS_NAME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000771 return (rmul * strcmp(a->filename, b->filename));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000772 else if (sort_flag & LS_TIME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000773 return (rmul * NCMP(a->a.mtime, b->a.mtime));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000774 else if (sort_flag & LS_SIZE_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000775 return (rmul * NCMP(a->a.size, b->a.size));
776
777 fatal("Unknown ls sort type");
Damien Miller20e1fab2004-02-18 14:30:55 +1100778}
779
780/* sftp ls.1 replacement for directories */
781static int
782do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
783{
Damien Millereccb9de2005-06-17 12:59:34 +1000784 int n;
785 u_int c = 1, colspace = 0, columns = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100786 SFTP_DIRENT **d;
787
788 if ((n = do_readdir(conn, path, &d)) != 0)
789 return (n);
790
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000791 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000792 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100793 struct winsize ws;
794 char *tmp;
795
796 /* Count entries for sort and find longest filename */
Darren Tucker9a526452004-06-22 13:09:55 +1000797 for (n = 0; d[n] != NULL; n++) {
798 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
799 m = MAX(m, strlen(d[n]->filename));
800 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100801
802 /* Add any subpath that also needs to be counted */
803 tmp = path_strip(path, strip_path);
804 m += strlen(tmp);
Darren Tuckera627d422013-06-02 07:31:17 +1000805 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100806
807 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
808 width = ws.ws_col;
809
810 columns = width / (m + 2);
811 columns = MAX(columns, 1);
812 colspace = width / columns;
813 colspace = MIN(colspace, width);
814 }
815
Darren Tuckerb9123452004-06-22 13:06:45 +1000816 if (lflag & SORT_FLAGS) {
Damien Miller653b93b2005-11-05 15:15:23 +1100817 for (n = 0; d[n] != NULL; n++)
818 ; /* count entries */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000819 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
Darren Tuckerb9123452004-06-22 13:06:45 +1000820 qsort(d, n, sizeof(*d), sdirent_comp);
821 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100822
Darren Tuckercdf547a2004-05-24 10:12:19 +1000823 for (n = 0; d[n] != NULL && !interrupted; n++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100824 char *tmp, *fname;
825
Darren Tucker9a526452004-06-22 13:09:55 +1000826 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
827 continue;
828
Damien Miller20e1fab2004-02-18 14:30:55 +1100829 tmp = path_append(path, d[n]->filename);
830 fname = path_strip(tmp, strip_path);
Darren Tuckera627d422013-06-02 07:31:17 +1000831 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100832
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000833 if (lflag & LS_LONG_VIEW) {
Darren Tucker2901e2d2010-01-13 22:44:06 +1100834 if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000835 char *lname;
836 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100837
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000838 memset(&sb, 0, sizeof(sb));
839 attrib_to_stat(&d[n]->a, &sb);
Darren Tucker2901e2d2010-01-13 22:44:06 +1100840 lname = ls_file(fname, &sb, 1,
841 (lflag & LS_SI_UNITS));
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000842 printf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000843 free(lname);
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000844 } else
845 printf("%s\n", d[n]->longname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100846 } else {
847 printf("%-*s", colspace, fname);
848 if (c >= columns) {
849 printf("\n");
850 c = 1;
851 } else
852 c++;
853 }
854
Darren Tuckera627d422013-06-02 07:31:17 +1000855 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100856 }
857
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000858 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100859 printf("\n");
860
861 free_sftp_dirents(d);
862 return (0);
863}
864
865/* sftp ls.1 replacement which handles path globs */
866static int
867do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
868 int lflag)
869{
Damien Millera6e121a2010-10-07 21:39:17 +1100870 char *fname, *lname;
Damien Miller68e2e562010-10-07 21:39:55 +1100871 glob_t g;
Damien Miller00707762014-07-09 13:07:06 +1000872 int err, r;
Damien Miller68e2e562010-10-07 21:39:55 +1100873 struct winsize ws;
874 u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100875
876 memset(&g, 0, sizeof(g));
877
Damien Miller00707762014-07-09 13:07:06 +1000878 if ((r = remote_glob(conn, path,
Damien Millerd7be70d2011-09-22 21:43:06 +1000879 GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
Damien Miller00707762014-07-09 13:07:06 +1000880 NULL, &g)) != 0 ||
Damien Millera6e121a2010-10-07 21:39:17 +1100881 (g.gl_pathc && !g.gl_matchc)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100882 if (g.gl_pathc)
883 globfree(&g);
Damien Miller00707762014-07-09 13:07:06 +1000884 if (r == GLOB_NOSPACE) {
885 error("Can't ls: Too many matches for \"%s\"", path);
886 } else {
887 error("Can't ls: \"%s\" not found", path);
888 }
Damien Millera6e121a2010-10-07 21:39:17 +1100889 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100890 }
891
Darren Tuckercdf547a2004-05-24 10:12:19 +1000892 if (interrupted)
893 goto out;
894
Damien Miller20e1fab2004-02-18 14:30:55 +1100895 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100896 * If the glob returns a single match and it is a directory,
897 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100898 */
Damien Millera6e121a2010-10-07 21:39:17 +1100899 if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
900 S_ISDIR(g.gl_statv[0]->st_mode)) {
901 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
902 globfree(&g);
903 return err;
Damien Miller20e1fab2004-02-18 14:30:55 +1100904 }
905
Damien Miller68e2e562010-10-07 21:39:55 +1100906 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
907 width = ws.ws_col;
Damien Miller20e1fab2004-02-18 14:30:55 +1100908
Damien Miller68e2e562010-10-07 21:39:55 +1100909 if (!(lflag & LS_SHORT_VIEW)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100910 /* Count entries for sort and find longest filename */
911 for (i = 0; g.gl_pathv[i]; i++)
912 m = MAX(m, strlen(g.gl_pathv[i]));
913
Damien Miller20e1fab2004-02-18 14:30:55 +1100914 columns = width / (m + 2);
915 columns = MAX(columns, 1);
916 colspace = width / columns;
917 }
918
Damien Millerea858292012-06-30 08:33:32 +1000919 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100920 fname = path_strip(g.gl_pathv[i], strip_path);
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000921 if (lflag & LS_LONG_VIEW) {
Damien Millera6e121a2010-10-07 21:39:17 +1100922 if (g.gl_statv[i] == NULL) {
923 error("no stat information for %s", fname);
924 continue;
925 }
926 lname = ls_file(fname, g.gl_statv[i], 1,
927 (lflag & LS_SI_UNITS));
Damien Miller20e1fab2004-02-18 14:30:55 +1100928 printf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000929 free(lname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100930 } else {
931 printf("%-*s", colspace, fname);
932 if (c >= columns) {
933 printf("\n");
934 c = 1;
935 } else
936 c++;
937 }
Darren Tuckera627d422013-06-02 07:31:17 +1000938 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100939 }
940
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000941 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100942 printf("\n");
943
Darren Tuckercdf547a2004-05-24 10:12:19 +1000944 out:
Damien Miller20e1fab2004-02-18 14:30:55 +1100945 if (g.gl_pathc)
946 globfree(&g);
947
Damien Millera6e121a2010-10-07 21:39:17 +1100948 return 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100949}
950
Damien Millerd671e5a2008-05-19 14:53:33 +1000951static int
952do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
953{
Darren Tucker7b598892008-06-09 22:49:36 +1000954 struct sftp_statvfs st;
Damien Millerd671e5a2008-05-19 14:53:33 +1000955 char s_used[FMT_SCALED_STRSIZE];
956 char s_avail[FMT_SCALED_STRSIZE];
957 char s_root[FMT_SCALED_STRSIZE];
958 char s_total[FMT_SCALED_STRSIZE];
Darren Tuckerb5082e92010-01-08 18:51:47 +1100959 unsigned long long ffree;
Damien Millerd671e5a2008-05-19 14:53:33 +1000960
961 if (do_statvfs(conn, path, &st, 1) == -1)
962 return -1;
963 if (iflag) {
Darren Tuckerb5082e92010-01-08 18:51:47 +1100964 ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
Damien Millerd671e5a2008-05-19 14:53:33 +1000965 printf(" Inodes Used Avail "
966 "(root) %%Capacity\n");
967 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
968 (unsigned long long)st.f_files,
969 (unsigned long long)(st.f_files - st.f_ffree),
970 (unsigned long long)st.f_favail,
Darren Tuckerb5082e92010-01-08 18:51:47 +1100971 (unsigned long long)st.f_ffree, ffree);
Damien Millerd671e5a2008-05-19 14:53:33 +1000972 } else if (hflag) {
973 strlcpy(s_used, "error", sizeof(s_used));
974 strlcpy(s_avail, "error", sizeof(s_avail));
975 strlcpy(s_root, "error", sizeof(s_root));
976 strlcpy(s_total, "error", sizeof(s_total));
977 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
978 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
979 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
980 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
981 printf(" Size Used Avail (root) %%Capacity\n");
982 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
983 s_total, s_used, s_avail, s_root,
984 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
985 st.f_blocks));
986 } else {
987 printf(" Size Used Avail "
988 "(root) %%Capacity\n");
989 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
990 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
991 (unsigned long long)(st.f_frsize *
992 (st.f_blocks - st.f_bfree) / 1024),
993 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
994 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
995 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
996 st.f_blocks));
997 }
998 return 0;
999}
1000
Damien Miller1cbc2922007-10-26 14:27:45 +10001001/*
1002 * Undo escaping of glob sequences in place. Used to undo extra escaping
1003 * applied in makeargv() when the string is destined for a function that
1004 * does not glob it.
1005 */
1006static void
1007undo_glob_escape(char *s)
1008{
1009 size_t i, j;
1010
1011 for (i = j = 0;;) {
1012 if (s[i] == '\0') {
1013 s[j] = '\0';
1014 return;
1015 }
1016 if (s[i] != '\\') {
1017 s[j++] = s[i++];
1018 continue;
1019 }
1020 /* s[i] == '\\' */
1021 ++i;
1022 switch (s[i]) {
1023 case '?':
1024 case '[':
1025 case '*':
1026 case '\\':
1027 s[j++] = s[i++];
1028 break;
1029 case '\0':
1030 s[j++] = '\\';
1031 s[j] = '\0';
1032 return;
1033 default:
1034 s[j++] = '\\';
1035 s[j++] = s[i++];
1036 break;
1037 }
1038 }
1039}
1040
1041/*
1042 * Split a string into an argument vector using sh(1)-style quoting,
1043 * comment and escaping rules, but with some tweaks to handle glob(3)
1044 * wildcards.
Darren Tucker909d8582010-01-08 19:02:40 +11001045 * The "sloppy" flag allows for recovery from missing terminating quote, for
1046 * use in parsing incomplete commandlines during tab autocompletion.
1047 *
Damien Miller1cbc2922007-10-26 14:27:45 +10001048 * Returns NULL on error or a NULL-terminated array of arguments.
Darren Tucker909d8582010-01-08 19:02:40 +11001049 *
1050 * If "lastquote" is not NULL, the quoting character used for the last
1051 * argument is placed in *lastquote ("\0", "'" or "\"").
Damien Miller02e87802013-08-21 02:38:51 +10001052 *
Darren Tucker909d8582010-01-08 19:02:40 +11001053 * If "terminated" is not NULL, *terminated will be set to 1 when the
1054 * last argument's quote has been properly terminated or 0 otherwise.
1055 * This parameter is only of use if "sloppy" is set.
Damien Miller1cbc2922007-10-26 14:27:45 +10001056 */
1057#define MAXARGS 128
1058#define MAXARGLEN 8192
1059static char **
Darren Tucker909d8582010-01-08 19:02:40 +11001060makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
1061 u_int *terminated)
Damien Miller1cbc2922007-10-26 14:27:45 +10001062{
1063 int argc, quot;
1064 size_t i, j;
1065 static char argvs[MAXARGLEN];
1066 static char *argv[MAXARGS + 1];
1067 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
1068
1069 *argcp = argc = 0;
1070 if (strlen(arg) > sizeof(argvs) - 1) {
1071 args_too_longs:
1072 error("string too long");
1073 return NULL;
1074 }
Darren Tucker909d8582010-01-08 19:02:40 +11001075 if (terminated != NULL)
1076 *terminated = 1;
1077 if (lastquote != NULL)
1078 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001079 state = MA_START;
1080 i = j = 0;
1081 for (;;) {
Damien Miller07daed52012-10-31 08:57:55 +11001082 if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){
Darren Tucker063018d2012-10-05 10:43:58 +10001083 error("Too many arguments.");
1084 return NULL;
1085 }
Damien Millerfdb23062013-11-21 13:57:15 +11001086 if (isspace((unsigned char)arg[i])) {
Damien Miller1cbc2922007-10-26 14:27:45 +10001087 if (state == MA_UNQUOTED) {
1088 /* Terminate current argument */
1089 argvs[j++] = '\0';
1090 argc++;
1091 state = MA_START;
1092 } else if (state != MA_START)
1093 argvs[j++] = arg[i];
1094 } else if (arg[i] == '"' || arg[i] == '\'') {
1095 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1096 if (state == MA_START) {
1097 argv[argc] = argvs + j;
1098 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +11001099 if (lastquote != NULL)
1100 *lastquote = arg[i];
Damien Miller02e87802013-08-21 02:38:51 +10001101 } else if (state == MA_UNQUOTED)
Damien Miller1cbc2922007-10-26 14:27:45 +10001102 state = q;
1103 else if (state == q)
1104 state = MA_UNQUOTED;
1105 else
1106 argvs[j++] = arg[i];
1107 } else if (arg[i] == '\\') {
1108 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1109 quot = state == MA_SQUOTE ? '\'' : '"';
1110 /* Unescape quote we are in */
1111 /* XXX support \n and friends? */
1112 if (arg[i + 1] == quot) {
1113 i++;
1114 argvs[j++] = arg[i];
1115 } else if (arg[i + 1] == '?' ||
1116 arg[i + 1] == '[' || arg[i + 1] == '*') {
1117 /*
1118 * Special case for sftp: append
1119 * double-escaped glob sequence -
1120 * glob will undo one level of
1121 * escaping. NB. string can grow here.
1122 */
1123 if (j >= sizeof(argvs) - 5)
1124 goto args_too_longs;
1125 argvs[j++] = '\\';
1126 argvs[j++] = arg[i++];
1127 argvs[j++] = '\\';
1128 argvs[j++] = arg[i];
1129 } else {
1130 argvs[j++] = arg[i++];
1131 argvs[j++] = arg[i];
1132 }
1133 } else {
1134 if (state == MA_START) {
1135 argv[argc] = argvs + j;
1136 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001137 if (lastquote != NULL)
1138 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001139 }
1140 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1141 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1142 /*
1143 * Special case for sftp: append
1144 * escaped glob sequence -
1145 * glob will undo one level of
1146 * escaping.
1147 */
1148 argvs[j++] = arg[i++];
1149 argvs[j++] = arg[i];
1150 } else {
1151 /* Unescape everything */
1152 /* XXX support \n and friends? */
1153 i++;
1154 argvs[j++] = arg[i];
1155 }
1156 }
1157 } else if (arg[i] == '#') {
1158 if (state == MA_SQUOTE || state == MA_DQUOTE)
1159 argvs[j++] = arg[i];
1160 else
1161 goto string_done;
1162 } else if (arg[i] == '\0') {
1163 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001164 if (sloppy) {
1165 state = MA_UNQUOTED;
1166 if (terminated != NULL)
1167 *terminated = 0;
1168 goto string_done;
1169 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001170 error("Unterminated quoted argument");
1171 return NULL;
1172 }
1173 string_done:
1174 if (state == MA_UNQUOTED) {
1175 argvs[j++] = '\0';
1176 argc++;
1177 }
1178 break;
1179 } else {
1180 if (state == MA_START) {
1181 argv[argc] = argvs + j;
1182 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001183 if (lastquote != NULL)
1184 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001185 }
1186 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1187 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1188 /*
1189 * Special case for sftp: escape quoted
1190 * glob(3) wildcards. NB. string can grow
1191 * here.
1192 */
1193 if (j >= sizeof(argvs) - 3)
1194 goto args_too_longs;
1195 argvs[j++] = '\\';
1196 argvs[j++] = arg[i];
1197 } else
1198 argvs[j++] = arg[i];
1199 }
1200 i++;
1201 }
1202 *argcp = argc;
1203 return argv;
1204}
1205
Damien Miller20e1fab2004-02-18 14:30:55 +11001206static int
Damien Millerd8accc02014-05-15 13:46:25 +10001207parse_args(const char **cpp, int *ignore_errors, int *aflag,
1208 int *fflag, int *hflag, int *iflag, int *lflag, int *pflag,
1209 int *rflag, int *sflag,
Damien Millerf29238e2013-10-17 11:48:52 +11001210 unsigned long *n_arg, char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001211{
1212 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001213 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001214 int base = 0;
1215 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001216 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001217
1218 /* Skip leading whitespace */
1219 cp = cp + strspn(cp, WHITESPACE);
1220
Damien Miller20e1fab2004-02-18 14:30:55 +11001221 /* Check for leading '-' (disable error processing) */
Damien Millerf29238e2013-10-17 11:48:52 +11001222 *ignore_errors = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001223 if (*cp == '-') {
Damien Millerf29238e2013-10-17 11:48:52 +11001224 *ignore_errors = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001225 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001226 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001227 }
1228
Darren Tucker70cc0922010-01-09 22:28:03 +11001229 /* Ignore blank lines and lines which begin with comment '#' char */
1230 if (*cp == '\0' || *cp == '#')
1231 return (0);
1232
Darren Tucker909d8582010-01-08 19:02:40 +11001233 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001234 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001235
Damien Miller1cbc2922007-10-26 14:27:45 +10001236 /* Figure out which command we have */
1237 for (i = 0; cmds[i].c != NULL; i++) {
Damien Millerd6d9fa02013-02-12 11:02:46 +11001238 if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001239 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001240 }
1241 cmdnum = cmds[i].n;
1242 cmd = cmds[i].c;
1243
1244 /* Special case */
1245 if (*cp == '!') {
1246 cp++;
1247 cmdnum = I_SHELL;
1248 } else if (cmdnum == -1) {
1249 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001250 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001251 }
1252
1253 /* Get arguments and parse flags */
Damien Millerf29238e2013-10-17 11:48:52 +11001254 *aflag = *fflag = *hflag = *iflag = *lflag = *pflag = 0;
1255 *rflag = *sflag = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001256 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001257 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001258 switch (cmdnum) {
1259 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001260 case I_REGET:
Damien Millerd8accc02014-05-15 13:46:25 +10001261 case I_REPUT:
Damien Miller20e1fab2004-02-18 14:30:55 +11001262 case I_PUT:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001263 if ((optidx = parse_getput_flags(cmd, argv, argc,
Damien Millerf29238e2013-10-17 11:48:52 +11001264 aflag, fflag, pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001265 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001266 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001267 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001268 error("You must specify at least one path after a "
1269 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001270 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001271 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001272 *path1 = xstrdup(argv[optidx]);
1273 /* Get second pathname (optional) */
1274 if (argc - optidx > 1) {
1275 *path2 = xstrdup(argv[optidx + 1]);
1276 /* Destination is not globbed */
1277 undo_glob_escape(*path2);
1278 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001279 break;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001280 case I_LINK:
1281 if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1282 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001283 goto parse_two_paths;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001284 case I_RENAME:
Damien Millerc7dba122013-08-21 02:41:15 +10001285 if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1)
1286 return -1;
1287 goto parse_two_paths;
1288 case I_SYMLINK:
Damien Miller036d3072013-08-21 02:41:46 +10001289 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1290 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001291 parse_two_paths:
Damien Miller1cbc2922007-10-26 14:27:45 +10001292 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001293 error("You must specify two paths after a %s "
1294 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001295 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001296 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001297 *path1 = xstrdup(argv[optidx]);
1298 *path2 = xstrdup(argv[optidx + 1]);
1299 /* Paths are not globbed */
1300 undo_glob_escape(*path1);
1301 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001302 break;
1303 case I_RM:
1304 case I_MKDIR:
1305 case I_RMDIR:
1306 case I_CHDIR:
1307 case I_LCHDIR:
1308 case I_LMKDIR:
Damien Miller036d3072013-08-21 02:41:46 +10001309 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1310 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001311 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001312 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001313 error("You must specify a path after a %s command.",
1314 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001315 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001316 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001317 *path1 = xstrdup(argv[optidx]);
1318 /* Only "rm" globs */
1319 if (cmdnum != I_RM)
1320 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001321 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001322 case I_DF:
1323 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1324 iflag)) == -1)
1325 return -1;
1326 /* Default to current directory if no path specified */
1327 if (argc - optidx < 1)
1328 *path1 = NULL;
1329 else {
1330 *path1 = xstrdup(argv[optidx]);
1331 undo_glob_escape(*path1);
1332 }
1333 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001334 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001335 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001336 return(-1);
1337 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001338 if (argc - optidx > 0)
1339 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001340 break;
1341 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001342 /* Skip ls command and following whitespace */
1343 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001344 case I_SHELL:
1345 /* Uses the rest of the line */
1346 break;
1347 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001348 case I_CHMOD:
1349 base = 8;
1350 case I_CHOWN:
1351 case I_CHGRP:
Damien Miller036d3072013-08-21 02:41:46 +10001352 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1353 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001354 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001355 if (argc - optidx < 1)
1356 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001357 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001358 l = strtol(argv[optidx], &cp2, base);
1359 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1360 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1361 l < 0) {
1362 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001363 error("You must supply a numeric argument "
1364 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001365 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001366 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001367 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001368 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001369 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001370 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001371 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001372 error("You must specify a path after a %s command.",
1373 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001374 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001375 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001376 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001377 break;
1378 case I_QUIT:
1379 case I_PWD:
1380 case I_LPWD:
1381 case I_HELP:
1382 case I_VERSION:
1383 case I_PROGRESS:
Damien Miller036d3072013-08-21 02:41:46 +10001384 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1385 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001386 break;
1387 default:
1388 fatal("Command not implemented");
1389 }
1390
1391 *cpp = cp;
1392 return(cmdnum);
1393}
1394
1395static int
1396parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1397 int err_abort)
1398{
1399 char *path1, *path2, *tmp;
Damien Millerd8accc02014-05-15 13:46:25 +10001400 int ignore_errors = 0, aflag = 0, fflag = 0, hflag = 0,
1401 iflag = 0;
Damien Millerf29238e2013-10-17 11:48:52 +11001402 int lflag = 0, pflag = 0, rflag = 0, sflag = 0;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001403 int cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001404 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001405 Attrib a, *aa;
deraadt@openbsd.org087266e2015-01-20 23:14:00 +00001406 char path_buf[PATH_MAX];
Damien Miller20e1fab2004-02-18 14:30:55 +11001407 int err = 0;
1408 glob_t g;
1409
1410 path1 = path2 = NULL;
Damien Millerf29238e2013-10-17 11:48:52 +11001411 cmdnum = parse_args(&cmd, &ignore_errors, &aflag, &fflag, &hflag,
1412 &iflag, &lflag, &pflag, &rflag, &sflag, &n_arg, &path1, &path2);
1413 if (ignore_errors != 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001414 err_abort = 0;
1415
1416 memset(&g, 0, sizeof(g));
1417
1418 /* Perform command */
1419 switch (cmdnum) {
1420 case 0:
1421 /* Blank line */
1422 break;
1423 case -1:
1424 /* Unrecognized command */
1425 err = -1;
1426 break;
Damien Miller0d032412013-07-25 11:56:52 +10001427 case I_REGET:
1428 aflag = 1;
1429 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001430 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001431 err = process_get(conn, path1, path2, *pwd, pflag,
Damien Millerf29238e2013-10-17 11:48:52 +11001432 rflag, aflag, fflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001433 break;
Damien Millerd8accc02014-05-15 13:46:25 +10001434 case I_REPUT:
1435 aflag = 1;
1436 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001437 case I_PUT:
Damien Millerf29238e2013-10-17 11:48:52 +11001438 err = process_put(conn, path1, path2, *pwd, pflag,
Damien Millerd8accc02014-05-15 13:46:25 +10001439 rflag, aflag, fflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001440 break;
1441 case I_RENAME:
1442 path1 = make_absolute(path1, *pwd);
1443 path2 = make_absolute(path2, *pwd);
Damien Millerc7dba122013-08-21 02:41:15 +10001444 err = do_rename(conn, path1, path2, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001445 break;
1446 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001447 sflag = 1;
1448 case I_LINK:
Damien Miller034f27a2013-08-21 02:40:44 +10001449 if (!sflag)
1450 path1 = make_absolute(path1, *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001451 path2 = make_absolute(path2, *pwd);
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001452 err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001453 break;
1454 case I_RM:
1455 path1 = make_absolute(path1, *pwd);
1456 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001457 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001458 if (!quiet)
1459 printf("Removing %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001460 err = do_rm(conn, g.gl_pathv[i]);
1461 if (err != 0 && err_abort)
1462 break;
1463 }
1464 break;
1465 case I_MKDIR:
1466 path1 = make_absolute(path1, *pwd);
1467 attrib_clear(&a);
1468 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1469 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001470 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001471 break;
1472 case I_RMDIR:
1473 path1 = make_absolute(path1, *pwd);
1474 err = do_rmdir(conn, path1);
1475 break;
1476 case I_CHDIR:
1477 path1 = make_absolute(path1, *pwd);
1478 if ((tmp = do_realpath(conn, path1)) == NULL) {
1479 err = 1;
1480 break;
1481 }
1482 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
Darren Tuckera627d422013-06-02 07:31:17 +10001483 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001484 err = 1;
1485 break;
1486 }
1487 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1488 error("Can't change directory: Can't check target");
Darren Tuckera627d422013-06-02 07:31:17 +10001489 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001490 err = 1;
1491 break;
1492 }
1493 if (!S_ISDIR(aa->perm)) {
1494 error("Can't change directory: \"%s\" is not "
1495 "a directory", tmp);
Darren Tuckera627d422013-06-02 07:31:17 +10001496 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001497 err = 1;
1498 break;
1499 }
Darren Tuckera627d422013-06-02 07:31:17 +10001500 free(*pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001501 *pwd = tmp;
1502 break;
1503 case I_LS:
1504 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001505 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001506 break;
1507 }
1508
1509 /* Strip pwd off beginning of non-absolute paths */
1510 tmp = NULL;
1511 if (*path1 != '/')
1512 tmp = *pwd;
1513
1514 path1 = make_absolute(path1, *pwd);
1515 err = do_globbed_ls(conn, path1, tmp, lflag);
1516 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001517 case I_DF:
1518 /* Default to current directory if no path specified */
1519 if (path1 == NULL)
1520 path1 = xstrdup(*pwd);
1521 path1 = make_absolute(path1, *pwd);
1522 err = do_df(conn, path1, hflag, iflag);
1523 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001524 case I_LCHDIR:
deraadt@openbsd.org40ba4c92014-08-20 01:28:55 +00001525 tmp = tilde_expand_filename(path1, getuid());
djm@openbsd.org7ff880e2014-08-19 23:57:18 +00001526 free(path1);
1527 path1 = tmp;
Damien Miller20e1fab2004-02-18 14:30:55 +11001528 if (chdir(path1) == -1) {
1529 error("Couldn't change local directory to "
1530 "\"%s\": %s", path1, strerror(errno));
1531 err = 1;
1532 }
1533 break;
1534 case I_LMKDIR:
1535 if (mkdir(path1, 0777) == -1) {
1536 error("Couldn't create local directory "
1537 "\"%s\": %s", path1, strerror(errno));
1538 err = 1;
1539 }
1540 break;
1541 case I_LLS:
1542 local_do_ls(cmd);
1543 break;
1544 case I_SHELL:
1545 local_do_shell(cmd);
1546 break;
1547 case I_LUMASK:
1548 umask(n_arg);
1549 printf("Local umask: %03lo\n", n_arg);
1550 break;
1551 case I_CHMOD:
1552 path1 = make_absolute(path1, *pwd);
1553 attrib_clear(&a);
1554 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1555 a.perm = n_arg;
1556 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001557 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001558 if (!quiet)
1559 printf("Changing mode on %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001560 err = do_setstat(conn, g.gl_pathv[i], &a);
1561 if (err != 0 && err_abort)
1562 break;
1563 }
1564 break;
1565 case I_CHOWN:
1566 case I_CHGRP:
1567 path1 = make_absolute(path1, *pwd);
1568 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001569 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001570 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001571 if (err_abort) {
1572 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001573 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001574 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001575 continue;
1576 }
1577 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1578 error("Can't get current ownership of "
1579 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001580 if (err_abort) {
1581 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001582 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001583 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001584 continue;
1585 }
1586 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1587 if (cmdnum == I_CHOWN) {
Damien Miller9303e652013-04-23 15:22:40 +10001588 if (!quiet)
1589 printf("Changing owner on %s\n",
1590 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001591 aa->uid = n_arg;
1592 } else {
Damien Miller9303e652013-04-23 15:22:40 +10001593 if (!quiet)
1594 printf("Changing group on %s\n",
1595 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001596 aa->gid = n_arg;
1597 }
1598 err = do_setstat(conn, g.gl_pathv[i], aa);
1599 if (err != 0 && err_abort)
1600 break;
1601 }
1602 break;
1603 case I_PWD:
1604 printf("Remote working directory: %s\n", *pwd);
1605 break;
1606 case I_LPWD:
1607 if (!getcwd(path_buf, sizeof(path_buf))) {
1608 error("Couldn't get local cwd: %s", strerror(errno));
1609 err = -1;
1610 break;
1611 }
1612 printf("Local working directory: %s\n", path_buf);
1613 break;
1614 case I_QUIT:
1615 /* Processed below */
1616 break;
1617 case I_HELP:
1618 help();
1619 break;
1620 case I_VERSION:
1621 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1622 break;
1623 case I_PROGRESS:
1624 showprogress = !showprogress;
1625 if (showprogress)
1626 printf("Progress meter enabled\n");
1627 else
1628 printf("Progress meter disabled\n");
1629 break;
1630 default:
1631 fatal("%d is not implemented", cmdnum);
1632 }
1633
1634 if (g.gl_pathc)
1635 globfree(&g);
Darren Tuckera627d422013-06-02 07:31:17 +10001636 free(path1);
1637 free(path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001638
1639 /* If an unignored error occurs in batch mode we should abort. */
1640 if (err_abort && err != 0)
1641 return (-1);
1642 else if (cmdnum == I_QUIT)
1643 return (1);
1644
1645 return (0);
1646}
1647
Darren Tucker2d963d82004-11-07 20:04:10 +11001648#ifdef USE_LIBEDIT
1649static char *
1650prompt(EditLine *el)
1651{
1652 return ("sftp> ");
1653}
Darren Tucker2d963d82004-11-07 20:04:10 +11001654
Darren Tucker909d8582010-01-08 19:02:40 +11001655/* Display entries in 'list' after skipping the first 'len' chars */
1656static void
1657complete_display(char **list, u_int len)
1658{
1659 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1660 struct winsize ws;
1661 char *tmp;
1662
1663 /* Count entries for sort and find longest */
Damien Miller02e87802013-08-21 02:38:51 +10001664 for (y = 0; list[y]; y++)
Darren Tucker909d8582010-01-08 19:02:40 +11001665 m = MAX(m, strlen(list[y]));
1666
1667 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1668 width = ws.ws_col;
1669
1670 m = m > len ? m - len : 0;
1671 columns = width / (m + 2);
1672 columns = MAX(columns, 1);
1673 colspace = width / columns;
1674 colspace = MIN(colspace, width);
1675
1676 printf("\n");
1677 m = 1;
1678 for (y = 0; list[y]; y++) {
1679 llen = strlen(list[y]);
1680 tmp = llen > len ? list[y] + len : "";
1681 printf("%-*s", colspace, tmp);
1682 if (m >= columns) {
1683 printf("\n");
1684 m = 1;
1685 } else
1686 m++;
1687 }
1688 printf("\n");
1689}
1690
1691/*
1692 * Given a "list" of words that begin with a common prefix of "word",
1693 * attempt to find an autocompletion to extends "word" by the next
1694 * characters common to all entries in "list".
1695 */
1696static char *
1697complete_ambiguous(const char *word, char **list, size_t count)
1698{
1699 if (word == NULL)
1700 return NULL;
1701
1702 if (count > 0) {
1703 u_int y, matchlen = strlen(list[0]);
1704
1705 /* Find length of common stem */
1706 for (y = 1; list[y]; y++) {
1707 u_int x;
1708
Damien Miller02e87802013-08-21 02:38:51 +10001709 for (x = 0; x < matchlen; x++)
1710 if (list[0][x] != list[y][x])
Darren Tucker909d8582010-01-08 19:02:40 +11001711 break;
1712
1713 matchlen = x;
1714 }
1715
1716 if (matchlen > strlen(word)) {
1717 char *tmp = xstrdup(list[0]);
1718
Darren Tucker340d1682010-01-09 08:54:31 +11001719 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001720 return tmp;
1721 }
Damien Miller02e87802013-08-21 02:38:51 +10001722 }
Darren Tucker909d8582010-01-08 19:02:40 +11001723
1724 return xstrdup(word);
1725}
1726
1727/* Autocomplete a sftp command */
1728static int
1729complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1730 int terminated)
1731{
1732 u_int y, count = 0, cmdlen, tmplen;
1733 char *tmp, **list, argterm[3];
1734 const LineInfo *lf;
1735
1736 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1737
1738 /* No command specified: display all available commands */
1739 if (cmd == NULL) {
1740 for (y = 0; cmds[y].c; y++)
1741 list[count++] = xstrdup(cmds[y].c);
Damien Miller02e87802013-08-21 02:38:51 +10001742
Darren Tucker909d8582010-01-08 19:02:40 +11001743 list[count] = NULL;
1744 complete_display(list, 0);
1745
Damien Miller02e87802013-08-21 02:38:51 +10001746 for (y = 0; list[y] != NULL; y++)
1747 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001748 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001749 return count;
1750 }
1751
1752 /* Prepare subset of commands that start with "cmd" */
1753 cmdlen = strlen(cmd);
1754 for (y = 0; cmds[y].c; y++) {
Damien Miller02e87802013-08-21 02:38:51 +10001755 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
Darren Tucker909d8582010-01-08 19:02:40 +11001756 list[count++] = xstrdup(cmds[y].c);
1757 }
1758 list[count] = NULL;
1759
Damien Miller47d81152011-11-25 13:53:48 +11001760 if (count == 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10001761 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001762 return 0;
Damien Miller47d81152011-11-25 13:53:48 +11001763 }
Darren Tucker909d8582010-01-08 19:02:40 +11001764
1765 /* Complete ambigious command */
1766 tmp = complete_ambiguous(cmd, list, count);
1767 if (count > 1)
1768 complete_display(list, 0);
1769
Damien Miller02e87802013-08-21 02:38:51 +10001770 for (y = 0; list[y]; y++)
1771 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001772 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001773
1774 if (tmp != NULL) {
1775 tmplen = strlen(tmp);
1776 cmdlen = strlen(cmd);
1777 /* If cmd may be extended then do so */
1778 if (tmplen > cmdlen)
1779 if (el_insertstr(el, tmp + cmdlen) == -1)
1780 fatal("el_insertstr failed.");
1781 lf = el_line(el);
1782 /* Terminate argument cleanly */
1783 if (count == 1) {
1784 y = 0;
1785 if (!terminated)
1786 argterm[y++] = quote;
1787 if (lastarg || *(lf->cursor) != ' ')
1788 argterm[y++] = ' ';
1789 argterm[y] = '\0';
1790 if (y > 0 && el_insertstr(el, argterm) == -1)
1791 fatal("el_insertstr failed.");
1792 }
Darren Tuckera627d422013-06-02 07:31:17 +10001793 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001794 }
1795
1796 return count;
1797}
1798
1799/*
1800 * Determine whether a particular sftp command's arguments (if any)
1801 * represent local or remote files.
1802 */
1803static int
1804complete_is_remote(char *cmd) {
1805 int i;
1806
1807 if (cmd == NULL)
1808 return -1;
1809
1810 for (i = 0; cmds[i].c; i++) {
Damien Miller02e87802013-08-21 02:38:51 +10001811 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
Darren Tucker909d8582010-01-08 19:02:40 +11001812 return cmds[i].t;
1813 }
1814
1815 return -1;
1816}
1817
1818/* Autocomplete a filename "file" */
1819static int
1820complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1821 char *file, int remote, int lastarg, char quote, int terminated)
1822{
1823 glob_t g;
Darren Tuckerea647212013-06-06 08:19:09 +10001824 char *tmp, *tmp2, ins[8];
Darren Tucker17146d32012-10-05 10:46:16 +10001825 u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
Darren Tuckerea647212013-06-06 08:19:09 +10001826 int clen;
Darren Tucker909d8582010-01-08 19:02:40 +11001827 const LineInfo *lf;
Damien Miller02e87802013-08-21 02:38:51 +10001828
Darren Tucker909d8582010-01-08 19:02:40 +11001829 /* Glob from "file" location */
1830 if (file == NULL)
1831 tmp = xstrdup("*");
1832 else
1833 xasprintf(&tmp, "%s*", file);
1834
Darren Tucker191fcc62012-10-05 10:45:01 +10001835 /* Check if the path is absolute. */
1836 isabs = tmp[0] == '/';
1837
Darren Tucker909d8582010-01-08 19:02:40 +11001838 memset(&g, 0, sizeof(g));
1839 if (remote != LOCAL) {
1840 tmp = make_absolute(tmp, remote_path);
1841 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001842 } else
Darren Tucker909d8582010-01-08 19:02:40 +11001843 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001844
Darren Tucker909d8582010-01-08 19:02:40 +11001845 /* Determine length of pwd so we can trim completion display */
1846 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1847 /* Terminate counting on first unescaped glob metacharacter */
1848 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1849 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1850 hadglob = 1;
1851 break;
1852 }
1853 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1854 tmplen++;
1855 if (tmp[tmplen] == '/')
1856 pwdlen = tmplen + 1; /* track last seen '/' */
1857 }
Darren Tuckera627d422013-06-02 07:31:17 +10001858 free(tmp);
Damien Millerd7fd8be2014-05-15 14:24:59 +10001859 tmp = NULL;
Darren Tucker909d8582010-01-08 19:02:40 +11001860
Damien Miller02e87802013-08-21 02:38:51 +10001861 if (g.gl_matchc == 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001862 goto out;
1863
1864 if (g.gl_matchc > 1)
1865 complete_display(g.gl_pathv, pwdlen);
1866
Darren Tucker909d8582010-01-08 19:02:40 +11001867 /* Don't try to extend globs */
1868 if (file == NULL || hadglob)
1869 goto out;
1870
1871 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
Darren Tucker191fcc62012-10-05 10:45:01 +10001872 tmp = path_strip(tmp2, isabs ? NULL : remote_path);
Darren Tuckera627d422013-06-02 07:31:17 +10001873 free(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001874
1875 if (tmp == NULL)
1876 goto out;
1877
1878 tmplen = strlen(tmp);
1879 filelen = strlen(file);
1880
Darren Tucker17146d32012-10-05 10:46:16 +10001881 /* Count the number of escaped characters in the input string. */
1882 cesc = isesc = 0;
1883 for (i = 0; i < filelen; i++) {
1884 if (!isesc && file[i] == '\\' && i + 1 < filelen){
1885 isesc = 1;
1886 cesc++;
1887 } else
1888 isesc = 0;
1889 }
1890
1891 if (tmplen > (filelen - cesc)) {
1892 tmp2 = tmp + filelen - cesc;
Damien Miller02e87802013-08-21 02:38:51 +10001893 len = strlen(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001894 /* quote argument on way out */
Darren Tuckerea647212013-06-06 08:19:09 +10001895 for (i = 0; i < len; i += clen) {
1896 if ((clen = mblen(tmp2 + i, len - i)) < 0 ||
1897 (size_t)clen > sizeof(ins) - 2)
1898 fatal("invalid multibyte character");
Darren Tucker909d8582010-01-08 19:02:40 +11001899 ins[0] = '\\';
Darren Tuckerea647212013-06-06 08:19:09 +10001900 memcpy(ins + 1, tmp2 + i, clen);
1901 ins[clen + 1] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001902 switch (tmp2[i]) {
1903 case '\'':
1904 case '"':
1905 case '\\':
1906 case '\t':
Darren Tuckerd78739a2010-10-24 10:56:32 +11001907 case '[':
Darren Tucker909d8582010-01-08 19:02:40 +11001908 case ' ':
Darren Tucker17146d32012-10-05 10:46:16 +10001909 case '#':
1910 case '*':
Darren Tucker909d8582010-01-08 19:02:40 +11001911 if (quote == '\0' || tmp2[i] == quote) {
1912 if (el_insertstr(el, ins) == -1)
1913 fatal("el_insertstr "
1914 "failed.");
1915 break;
1916 }
1917 /* FALLTHROUGH */
1918 default:
1919 if (el_insertstr(el, ins + 1) == -1)
1920 fatal("el_insertstr failed.");
1921 break;
1922 }
1923 }
1924 }
1925
1926 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11001927 if (g.gl_matchc == 1) {
1928 i = 0;
Damien Miller38094812014-05-15 14:25:18 +10001929 if (!terminated && quote != '\0')
Darren Tucker909d8582010-01-08 19:02:40 +11001930 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11001931 if (*(lf->cursor - 1) != '/' &&
1932 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11001933 ins[i++] = ' ';
1934 ins[i] = '\0';
1935 if (i > 0 && el_insertstr(el, ins) == -1)
1936 fatal("el_insertstr failed.");
1937 }
Darren Tuckera627d422013-06-02 07:31:17 +10001938 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001939
1940 out:
1941 globfree(&g);
1942 return g.gl_matchc;
1943}
1944
1945/* tab-completion hook function, called via libedit */
1946static unsigned char
1947complete(EditLine *el, int ch)
1948{
Damien Miller02e87802013-08-21 02:38:51 +10001949 char **argv, *line, quote;
Damien Miller746d1a62013-07-18 16:13:02 +10001950 int argc, carg;
1951 u_int cursor, len, terminated, ret = CC_ERROR;
Darren Tucker909d8582010-01-08 19:02:40 +11001952 const LineInfo *lf;
1953 struct complete_ctx *complete_ctx;
1954
1955 lf = el_line(el);
1956 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1957 fatal("%s: el_get failed", __func__);
1958
1959 /* Figure out which argument the cursor points to */
1960 cursor = lf->cursor - lf->buffer;
1961 line = (char *)xmalloc(cursor + 1);
1962 memcpy(line, lf->buffer, cursor);
1963 line[cursor] = '\0';
1964 argv = makeargv(line, &carg, 1, &quote, &terminated);
Darren Tuckera627d422013-06-02 07:31:17 +10001965 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001966
1967 /* Get all the arguments on the line */
1968 len = lf->lastchar - lf->buffer;
1969 line = (char *)xmalloc(len + 1);
1970 memcpy(line, lf->buffer, len);
1971 line[len] = '\0';
1972 argv = makeargv(line, &argc, 1, NULL, NULL);
1973
1974 /* Ensure cursor is at EOL or a argument boundary */
1975 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1976 line[cursor] != '\n') {
Darren Tuckera627d422013-06-02 07:31:17 +10001977 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001978 return ret;
1979 }
1980
1981 if (carg == 0) {
1982 /* Show all available commands */
1983 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1984 ret = CC_REDISPLAY;
1985 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1986 /* Handle the command parsing */
1987 if (complete_cmd_parse(el, argv[0], argc == carg,
Damien Miller02e87802013-08-21 02:38:51 +10001988 quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001989 ret = CC_REDISPLAY;
1990 } else if (carg >= 1) {
1991 /* Handle file parsing */
1992 int remote = complete_is_remote(argv[0]);
1993 char *filematch = NULL;
1994
1995 if (carg > 1 && line[cursor-1] != ' ')
1996 filematch = argv[carg - 1];
1997
1998 if (remote != 0 &&
1999 complete_match(el, complete_ctx->conn,
2000 *complete_ctx->remote_pathp, filematch,
Damien Miller02e87802013-08-21 02:38:51 +10002001 remote, carg == argc, quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11002002 ret = CC_REDISPLAY;
2003 }
2004
Damien Miller02e87802013-08-21 02:38:51 +10002005 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11002006 return ret;
2007}
Darren Tuckere67f7db2010-01-08 19:50:02 +11002008#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11002009
Damien Miller20e1fab2004-02-18 14:30:55 +11002010int
Darren Tucker21063192010-01-08 17:10:36 +11002011interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11002012{
Darren Tucker909d8582010-01-08 19:02:40 +11002013 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11002014 char *dir = NULL;
2015 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10002016 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11002017 EditLine *el = NULL;
2018#ifdef USE_LIBEDIT
2019 History *hl = NULL;
2020 HistEvent hev;
2021 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11002022 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11002023
2024 if (!batchmode && isatty(STDIN_FILENO)) {
2025 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
2026 fatal("Couldn't initialise editline");
2027 if ((hl = history_init()) == NULL)
2028 fatal("Couldn't initialise editline history");
2029 history(hl, &hev, H_SETSIZE, 100);
2030 el_set(el, EL_HIST, history, hl);
2031
2032 el_set(el, EL_PROMPT, prompt);
2033 el_set(el, EL_EDITOR, "emacs");
2034 el_set(el, EL_TERMINAL, NULL);
2035 el_set(el, EL_SIGNAL, 1);
2036 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11002037
2038 /* Tab Completion */
Damien Miller02e87802013-08-21 02:38:51 +10002039 el_set(el, EL_ADDFN, "ftp-complete",
Darren Tuckerd78739a2010-10-24 10:56:32 +11002040 "Context sensitive argument completion", complete);
Darren Tucker909d8582010-01-08 19:02:40 +11002041 complete_ctx.conn = conn;
2042 complete_ctx.remote_pathp = &remote_path;
2043 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
2044 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Damien Millere0ee7272013-08-21 02:42:35 +10002045 /* enable ctrl-left-arrow and ctrl-right-arrow */
2046 el_set(el, EL_BIND, "\\e[1;5C", "em-next-word", NULL);
2047 el_set(el, EL_BIND, "\\e[5C", "em-next-word", NULL);
2048 el_set(el, EL_BIND, "\\e[1;5D", "ed-prev-word", NULL);
2049 el_set(el, EL_BIND, "\\e\\e[D", "ed-prev-word", NULL);
Damien Miller61353b32013-09-14 09:45:32 +10002050 /* make ^w match ksh behaviour */
2051 el_set(el, EL_BIND, "^w", "ed-delete-prev-word", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11002052 }
2053#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002054
Darren Tucker909d8582010-01-08 19:02:40 +11002055 remote_path = do_realpath(conn, ".");
2056 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11002057 fatal("Need cwd");
2058
2059 if (file1 != NULL) {
2060 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11002061 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11002062
2063 if (remote_is_dir(conn, dir) && file2 == NULL) {
Damien Miller9303e652013-04-23 15:22:40 +10002064 if (!quiet)
2065 printf("Changing to: %s\n", dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002066 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11002067 if (parse_dispatch_command(conn, cmd,
2068 &remote_path, 1) != 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10002069 free(dir);
2070 free(remote_path);
2071 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002072 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11002073 }
Damien Miller20e1fab2004-02-18 14:30:55 +11002074 } else {
Darren Tucker0af24052012-10-05 10:41:25 +10002075 /* XXX this is wrong wrt quoting */
Damien Miller0d032412013-07-25 11:56:52 +10002076 snprintf(cmd, sizeof cmd, "get%s %s%s%s",
2077 global_aflag ? " -a" : "", dir,
2078 file2 == NULL ? "" : " ",
2079 file2 == NULL ? "" : file2);
Darren Tucker909d8582010-01-08 19:02:40 +11002080 err = parse_dispatch_command(conn, cmd,
2081 &remote_path, 1);
Darren Tuckera627d422013-06-02 07:31:17 +10002082 free(dir);
2083 free(remote_path);
2084 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002085 return (err);
2086 }
Darren Tuckera627d422013-06-02 07:31:17 +10002087 free(dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002088 }
2089
millert@openbsd.orgdb995f22014-11-26 18:34:51 +00002090 setvbuf(stdout, NULL, _IOLBF, 0);
2091 setvbuf(infile, NULL, _IOLBF, 0);
Damien Miller20e1fab2004-02-18 14:30:55 +11002092
Damien Miller0e2c1022005-08-12 22:16:22 +10002093 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11002094 err = 0;
2095 for (;;) {
2096 char *cp;
2097
Darren Tuckercdf547a2004-05-24 10:12:19 +10002098 signal(SIGINT, SIG_IGN);
2099
Darren Tucker2d963d82004-11-07 20:04:10 +11002100 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002101 if (interactive)
2102 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11002103 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002104 if (interactive)
2105 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11002106 break;
2107 }
Damien Miller0e2c1022005-08-12 22:16:22 +10002108 if (!interactive) { /* Echo command */
2109 printf("sftp> %s", cmd);
2110 if (strlen(cmd) > 0 &&
2111 cmd[strlen(cmd) - 1] != '\n')
2112 printf("\n");
2113 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002114 } else {
2115#ifdef USE_LIBEDIT
2116 const char *line;
2117 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11002118
Darren Tucker909d8582010-01-08 19:02:40 +11002119 if ((line = el_gets(el, &count)) == NULL ||
2120 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002121 printf("\n");
2122 break;
2123 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002124 history(hl, &hev, H_ENTER, line);
2125 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
2126 fprintf(stderr, "Error: input line too long\n");
2127 continue;
2128 }
2129#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002130 }
2131
Damien Miller20e1fab2004-02-18 14:30:55 +11002132 cp = strrchr(cmd, '\n');
2133 if (cp)
2134 *cp = '\0';
2135
Darren Tuckercdf547a2004-05-24 10:12:19 +10002136 /* Handle user interrupts gracefully during commands */
2137 interrupted = 0;
2138 signal(SIGINT, cmd_interrupt);
2139
Darren Tucker909d8582010-01-08 19:02:40 +11002140 err = parse_dispatch_command(conn, cmd, &remote_path,
2141 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11002142 if (err != 0)
2143 break;
2144 }
Darren Tuckera627d422013-06-02 07:31:17 +10002145 free(remote_path);
2146 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002147
Tim Rice027e8b12005-08-15 14:52:50 -07002148#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10002149 if (el != NULL)
2150 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07002151#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10002152
Damien Miller20e1fab2004-02-18 14:30:55 +11002153 /* err == 1 signifies normal "quit" exit */
2154 return (err >= 0 ? 0 : -1);
2155}
Damien Miller62d57f62003-01-10 21:43:24 +11002156
Ben Lindstrombba81212001-06-25 05:01:22 +00002157static void
Damien Millercc685c12003-06-04 22:51:38 +10002158connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11002159{
2160 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002161
Damien Miller33804262001-02-04 23:20:18 +11002162#ifdef USE_PIPES
2163 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002164
Damien Miller33804262001-02-04 23:20:18 +11002165 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2166 fatal("pipe: %s", strerror(errno));
2167 *in = pin[0];
2168 *out = pout[1];
2169 c_in = pout[0];
2170 c_out = pin[1];
2171#else /* USE_PIPES */
2172 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002173
Damien Miller33804262001-02-04 23:20:18 +11002174 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2175 fatal("socketpair: %s", strerror(errno));
2176 *in = *out = inout[0];
2177 c_in = c_out = inout[1];
2178#endif /* USE_PIPES */
2179
Damien Millercc685c12003-06-04 22:51:38 +10002180 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002181 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002182 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002183 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2184 (dup2(c_out, STDOUT_FILENO) == -1)) {
2185 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002186 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002187 }
2188 close(*in);
2189 close(*out);
2190 close(c_in);
2191 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002192
2193 /*
2194 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002195 * ignore SIGINT if we want to gracefully abort commands,
2196 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002197 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2198 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002199 */
2200 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002201 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002202 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002203 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002204 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002205 }
2206
Damien Millercc685c12003-06-04 22:51:38 +10002207 signal(SIGTERM, killchild);
2208 signal(SIGINT, killchild);
2209 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11002210 close(c_in);
2211 close(c_out);
2212}
2213
Ben Lindstrombba81212001-06-25 05:01:22 +00002214static void
Damien Miller33804262001-02-04 23:20:18 +11002215usage(void)
2216{
Damien Miller025e01c2002-02-08 22:06:29 +11002217 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002218
Ben Lindstrom1e243242001-09-18 05:38:44 +00002219 fprintf(stderr,
Damien Miller1edcbf62013-10-18 10:17:17 +11002220 "usage: %s [-1246aCfpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002221 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002222 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002223 " [-o ssh_option] [-P port] [-R num_requests] "
2224 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002225 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002226 " %s [user@]host[:file ...]\n"
2227 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002228 " %s -b batchfile [user@]host\n",
2229 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002230 exit(1);
2231}
2232
Kevin Stevesef4eea92001-02-05 12:42:17 +00002233int
Damien Miller33804262001-02-04 23:20:18 +11002234main(int argc, char **argv)
2235{
Damien Miller956f3fb2003-01-10 21:40:00 +11002236 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002237 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002238 int debug_level = 0, sshver = 2;
2239 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002240 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002241 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002242 LogLevel ll = SYSLOG_LEVEL_INFO;
2243 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002244 extern int optind;
2245 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002246 struct sftp_conn *conn;
2247 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2248 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002249 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002250
Darren Tuckerce321d82005-10-03 18:11:24 +10002251 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2252 sanitise_stdfd();
Darren Tuckerea647212013-06-06 08:19:09 +10002253 setlocale(LC_CTYPE, "");
Darren Tuckerce321d82005-10-03 18:11:24 +10002254
Damien Miller59d3d5b2003-08-22 09:34:41 +10002255 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002256 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002257 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002258 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002259 addargs(&args, "-oForwardX11 no");
2260 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002261 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002262 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002263
Ben Lindstrom387c4722001-05-08 20:27:25 +00002264 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002265 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002266
Darren Tucker282b4022009-10-07 08:23:06 +11002267 while ((ch = getopt(argc, argv,
Damien Millerf29238e2013-10-17 11:48:52 +11002268 "1246afhpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002269 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002270 /* Passed through to ssh(1) */
2271 case '4':
2272 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002273 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002274 addargs(&args, "-%c", ch);
2275 break;
2276 /* Passed through to ssh(1) with argument */
2277 case 'F':
2278 case 'c':
2279 case 'i':
2280 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002281 addargs(&args, "-%c", ch);
2282 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002283 break;
2284 case 'q':
Damien Miller9303e652013-04-23 15:22:40 +10002285 ll = SYSLOG_LEVEL_ERROR;
2286 quiet = 1;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002287 showprogress = 0;
2288 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002289 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002290 case 'P':
2291 addargs(&args, "-oPort %s", optarg);
2292 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002293 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002294 if (debug_level < 3) {
2295 addargs(&args, "-v");
2296 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2297 }
2298 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002299 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002300 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002301 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002302 if (sftp_server == NULL)
2303 sftp_server = _PATH_SFTP_SERVER;
2304 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002305 case '2':
2306 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002307 break;
Damien Miller0d032412013-07-25 11:56:52 +10002308 case 'a':
2309 global_aflag = 1;
2310 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002311 case 'B':
2312 copy_buffer_len = strtol(optarg, &cp, 10);
2313 if (copy_buffer_len == 0 || *cp != '\0')
2314 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002315 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002316 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002317 if (batchmode)
2318 fatal("Batch file already specified.");
2319
2320 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002321 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002322 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002323 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002324 showprogress = 0;
Damien Miller9303e652013-04-23 15:22:40 +10002325 quiet = batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002326 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002327 break;
Damien Millerf29238e2013-10-17 11:48:52 +11002328 case 'f':
2329 global_fflag = 1;
2330 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002331 case 'p':
2332 global_pflag = 1;
2333 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002334 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002335 sftp_direct = optarg;
2336 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002337 case 'l':
2338 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2339 &errstr);
2340 if (errstr != NULL)
2341 usage();
2342 limit_kbps *= 1024; /* kbps */
2343 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002344 case 'r':
2345 global_rflag = 1;
2346 break;
Damien Miller16a13332002-02-13 14:03:56 +11002347 case 'R':
2348 num_requests = strtol(optarg, &cp, 10);
2349 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002350 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002351 optarg);
2352 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002353 case 's':
2354 sftp_server = optarg;
2355 break;
2356 case 'S':
2357 ssh_program = optarg;
2358 replacearg(&args, 0, "%s", ssh_program);
2359 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002360 case 'h':
2361 default:
Damien Miller33804262001-02-04 23:20:18 +11002362 usage();
2363 }
2364 }
2365
Damien Millerc0f27d82004-03-08 23:12:19 +11002366 if (!isatty(STDERR_FILENO))
2367 showprogress = 0;
2368
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002369 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2370
Damien Millerd14ee1e2002-02-05 12:27:31 +11002371 if (sftp_direct == NULL) {
2372 if (optind == argc || argc > (optind + 2))
2373 usage();
Damien Miller33804262001-02-04 23:20:18 +11002374
Damien Millerd14ee1e2002-02-05 12:27:31 +11002375 userhost = xstrdup(argv[optind]);
2376 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002377
Ben Lindstromc276c122002-12-23 02:14:51 +00002378 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002379 host = userhost;
2380 else {
2381 *host++ = '\0';
2382 if (!userhost[0]) {
2383 fprintf(stderr, "Missing username\n");
2384 usage();
2385 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002386 addargs(&args, "-l");
2387 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002388 }
2389
Damien Millerec692032004-01-27 21:22:00 +11002390 if ((cp = colon(host)) != NULL) {
2391 *cp++ = '\0';
2392 file1 = cp;
2393 }
2394
Damien Millerd14ee1e2002-02-05 12:27:31 +11002395 host = cleanhostname(host);
2396 if (!*host) {
2397 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002398 usage();
2399 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002400
Damien Millerd14ee1e2002-02-05 12:27:31 +11002401 addargs(&args, "-oProtocol %d", sshver);
2402
2403 /* no subsystem if the server-spec contains a '/' */
2404 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2405 addargs(&args, "-s");
2406
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002407 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002408 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002409 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002410 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002411
Damien Millercc685c12003-06-04 22:51:38 +10002412 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002413 } else {
2414 args.list = NULL;
2415 addargs(&args, "sftp-server");
2416
Damien Millercc685c12003-06-04 22:51:38 +10002417 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002418 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002419 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002420
Damien Miller65e42f82010-09-24 22:15:11 +10002421 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002422 if (conn == NULL)
2423 fatal("Couldn't initialise connection to server");
2424
Damien Miller9303e652013-04-23 15:22:40 +10002425 if (!quiet) {
Darren Tucker21063192010-01-08 17:10:36 +11002426 if (sftp_direct == NULL)
2427 fprintf(stderr, "Connected to %s.\n", host);
2428 else
2429 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2430 }
2431
2432 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002433
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002434#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002435 shutdown(in, SHUT_RDWR);
2436 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002437#endif
2438
Damien Miller33804262001-02-04 23:20:18 +11002439 close(in);
2440 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002441 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002442 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002443
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002444 while (waitpid(sshpid, NULL, 0) == -1)
2445 if (errno != EINTR)
2446 fatal("Couldn't wait for ssh process: %s",
2447 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002448
Damien Miller956f3fb2003-01-10 21:40:00 +11002449 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002450}