blob: 67110f738f79e176d46627b4c15c4836b2363a44 [file] [log] [blame]
djm@openbsd.org72be5b22017-06-10 06:33:34 +00001/* $OpenBSD: sftp.c,v 1.180 2017/06/10 06:33:34 djm Exp $ */
Damien Miller33804262001-02-04 23:20:18 +11002/*
Damien Miller4e60ed72004-02-17 17:07:59 +11003 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
Damien Miller33804262001-02-04 23:20:18 +11004 *
Damien Miller4e60ed72004-02-17 17:07:59 +11005 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
Damien Miller33804262001-02-04 23:20:18 +11008 *
Damien Miller4e60ed72004-02-17 17:07:59 +11009 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Damien Miller33804262001-02-04 23:20:18 +110016 */
17
18#include "includes.h"
19
Damien Miller9cf6d072006-03-15 11:29:24 +110020#include <sys/types.h>
Damien Millerd7834352006-08-05 12:39:39 +100021#include <sys/ioctl.h>
Damien Millerf17883e2006-03-15 11:45:54 +110022#ifdef HAVE_SYS_STAT_H
23# include <sys/stat.h>
24#endif
Damien Miller8dbffe72006-08-05 11:02:17 +100025#include <sys/param.h>
Damien Millere3b60b52006-07-10 21:08:03 +100026#include <sys/socket.h>
Damien Miller9cf6d072006-03-15 11:29:24 +110027#include <sys/wait.h>
Darren Tucker5b2e2ba2008-06-08 09:25:28 +100028#ifdef HAVE_SYS_STATVFS_H
Damien Millerd671e5a2008-05-19 14:53:33 +100029#include <sys/statvfs.h>
Darren Tucker5b2e2ba2008-06-08 09:25:28 +100030#endif
Darren Tucker2d963d82004-11-07 20:04:10 +110031
Damien Miller1cbc2922007-10-26 14:27:45 +100032#include <ctype.h>
Darren Tucker39972492006-07-12 22:22:46 +100033#include <errno.h>
34
Damien Miller03e20032006-03-15 11:16:59 +110035#ifdef HAVE_PATHS_H
Damien Millera9263d02006-03-15 11:18:26 +110036# include <paths.h>
Damien Miller03e20032006-03-15 11:16:59 +110037#endif
Darren Tucker1b0dd172009-10-07 08:37:48 +110038#ifdef HAVE_LIBGEN_H
39#include <libgen.h>
40#endif
Darren Tuckerae133d42013-06-06 08:30:20 +100041#ifdef HAVE_LOCALE_H
42# include <locale.h>
43#endif
Darren Tucker2d963d82004-11-07 20:04:10 +110044#ifdef USE_LIBEDIT
45#include <histedit.h>
46#else
47typedef void EditLine;
48#endif
deraadt@openbsd.org087266e2015-01-20 23:14:00 +000049#include <limits.h>
Damien Miller6ff3cad2006-03-15 11:52:09 +110050#include <signal.h>
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +000051#include <stdarg.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"
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +000066#include "utf8.h"
Damien Miller33804262001-02-04 23:20:18 +110067
68#include "sftp.h"
djm@openbsd.org7d845f42015-01-14 13:54:13 +000069#include "ssherr.h"
70#include "sshbuf.h"
Damien Miller33804262001-02-04 23:20:18 +110071#include "sftp-common.h"
72#include "sftp-client.h"
Damien Millerd7d46bb2004-02-18 14:11:13 +110073
Darren Tucker21063192010-01-08 17:10:36 +110074#define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */
75#define DEFAULT_NUM_REQUESTS 64 /* # concurrent outstanding requests */
76
Damien Miller20e1fab2004-02-18 14:30:55 +110077/* File to read commands from */
78FILE* infile;
79
80/* Are we in batchfile mode? */
81int batchmode = 0;
82
Damien Miller20e1fab2004-02-18 14:30:55 +110083/* PID of ssh transport process */
84static pid_t sshpid = -1;
85
Damien Miller9303e652013-04-23 15:22:40 +100086/* Suppress diagnositic messages */
87int quiet = 0;
88
Damien Miller20e1fab2004-02-18 14:30:55 +110089/* This is set to 0 if the progressmeter is not desired. */
Damien Millerc0f27d82004-03-08 23:12:19 +110090int showprogress = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +110091
Darren Tucker1b0dd172009-10-07 08:37:48 +110092/* When this option is set, we always recursively download/upload directories */
93int global_rflag = 0;
94
Damien Millerd8accc02014-05-15 13:46:25 +100095/* When this option is set, we resume download or upload if possible */
Damien Miller0d032412013-07-25 11:56:52 +100096int global_aflag = 0;
97
Darren Tucker1b0dd172009-10-07 08:37:48 +110098/* When this option is set, the file transfers will always preserve times */
99int global_pflag = 0;
100
Damien Millerf29238e2013-10-17 11:48:52 +1100101/* When this option is set, transfers will have fsync() called on each file */
102int global_fflag = 0;
103
Darren Tuckercdf547a2004-05-24 10:12:19 +1000104/* SIGINT received during command processing */
105volatile sig_atomic_t interrupted = 0;
106
Darren Tuckerb9123452004-06-22 13:06:45 +1000107/* I wish qsort() took a separate ctx for the comparison function...*/
108int sort_flag;
djm@openbsd.org72be5b22017-06-10 06:33:34 +0000109glob_t *sort_glob;
Darren Tuckerb9123452004-06-22 13:06:45 +1000110
Darren Tucker909d8582010-01-08 19:02:40 +1100111/* Context used for commandline completion */
112struct complete_ctx {
113 struct sftp_conn *conn;
114 char **remote_pathp;
115};
116
Damien Miller20e1fab2004-02-18 14:30:55 +1100117int remote_glob(struct sftp_conn *, const char *, int,
118 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
Damien Miller33804262001-02-04 23:20:18 +1100119
Kevin Steves12888d12001-03-05 19:50:57 +0000120extern char *__progname;
Kevin Steves12888d12001-03-05 19:50:57 +0000121
Damien Miller20e1fab2004-02-18 14:30:55 +1100122/* Separators for interactive commands */
123#define WHITESPACE " \t\r\n"
Damien Millerd7686fd2001-02-10 00:40:03 +1100124
Darren Tuckerb9123452004-06-22 13:06:45 +1000125/* ls flags */
Darren Tucker2901e2d2010-01-13 22:44:06 +1100126#define LS_LONG_VIEW 0x0001 /* Full view ala ls -l */
127#define LS_SHORT_VIEW 0x0002 /* Single row view ala ls -1 */
128#define LS_NUMERIC_VIEW 0x0004 /* Long view with numeric uid/gid */
129#define LS_NAME_SORT 0x0008 /* Sort by name (default) */
130#define LS_TIME_SORT 0x0010 /* Sort by mtime */
131#define LS_SIZE_SORT 0x0020 /* Sort by file size */
132#define LS_REVERSE_SORT 0x0040 /* Reverse sort order */
133#define LS_SHOW_ALL 0x0080 /* Don't skip filenames starting with '.' */
134#define LS_SI_UNITS 0x0100 /* Display sizes as K, M, G, etc. */
Darren Tuckerb9123452004-06-22 13:06:45 +1000135
Darren Tucker2901e2d2010-01-13 22:44:06 +1100136#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS)
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000137#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
Damien Miller20e1fab2004-02-18 14:30:55 +1100138
139/* Commands for interactive mode */
Damien Miller02e87802013-08-21 02:38:51 +1000140enum sftp_command {
141 I_CHDIR = 1,
142 I_CHGRP,
143 I_CHMOD,
144 I_CHOWN,
145 I_DF,
146 I_GET,
147 I_HELP,
148 I_LCHDIR,
149 I_LINK,
150 I_LLS,
151 I_LMKDIR,
152 I_LPWD,
153 I_LS,
154 I_LUMASK,
155 I_MKDIR,
156 I_PUT,
157 I_PWD,
158 I_QUIT,
Damien Millerb15cd7b2014-05-15 13:46:52 +1000159 I_REGET,
Damien Miller02e87802013-08-21 02:38:51 +1000160 I_RENAME,
Damien Millerb15cd7b2014-05-15 13:46:52 +1000161 I_REPUT,
Damien Miller02e87802013-08-21 02:38:51 +1000162 I_RM,
163 I_RMDIR,
164 I_SHELL,
165 I_SYMLINK,
166 I_VERSION,
167 I_PROGRESS,
Damien Miller02e87802013-08-21 02:38:51 +1000168};
Damien Miller20e1fab2004-02-18 14:30:55 +1100169
170struct CMD {
171 const char *c;
172 const int n;
Darren Tucker909d8582010-01-08 19:02:40 +1100173 const int t;
Damien Miller20e1fab2004-02-18 14:30:55 +1100174};
175
Darren Tucker909d8582010-01-08 19:02:40 +1100176/* Type of completion */
177#define NOARGS 0
178#define REMOTE 1
179#define LOCAL 2
180
Damien Miller20e1fab2004-02-18 14:30:55 +1100181static const struct CMD cmds[] = {
Darren Tucker909d8582010-01-08 19:02:40 +1100182 { "bye", I_QUIT, NOARGS },
183 { "cd", I_CHDIR, REMOTE },
184 { "chdir", I_CHDIR, REMOTE },
185 { "chgrp", I_CHGRP, REMOTE },
186 { "chmod", I_CHMOD, REMOTE },
187 { "chown", I_CHOWN, REMOTE },
188 { "df", I_DF, REMOTE },
189 { "dir", I_LS, REMOTE },
190 { "exit", I_QUIT, NOARGS },
191 { "get", I_GET, REMOTE },
192 { "help", I_HELP, NOARGS },
193 { "lcd", I_LCHDIR, LOCAL },
194 { "lchdir", I_LCHDIR, LOCAL },
195 { "lls", I_LLS, LOCAL },
196 { "lmkdir", I_LMKDIR, LOCAL },
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100197 { "ln", I_LINK, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100198 { "lpwd", I_LPWD, LOCAL },
199 { "ls", I_LS, REMOTE },
200 { "lumask", I_LUMASK, NOARGS },
201 { "mkdir", I_MKDIR, REMOTE },
Damien Miller099fc162010-05-10 11:56:50 +1000202 { "mget", I_GET, REMOTE },
203 { "mput", I_PUT, LOCAL },
Darren Tucker909d8582010-01-08 19:02:40 +1100204 { "progress", I_PROGRESS, NOARGS },
205 { "put", I_PUT, LOCAL },
206 { "pwd", I_PWD, REMOTE },
207 { "quit", I_QUIT, NOARGS },
Damien Miller0d032412013-07-25 11:56:52 +1000208 { "reget", I_REGET, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100209 { "rename", I_RENAME, REMOTE },
djm@openbsd.org4a459222014-10-06 00:47:15 +0000210 { "reput", I_REPUT, LOCAL },
Darren Tucker909d8582010-01-08 19:02:40 +1100211 { "rm", I_RM, REMOTE },
212 { "rmdir", I_RMDIR, REMOTE },
213 { "symlink", I_SYMLINK, REMOTE },
214 { "version", I_VERSION, NOARGS },
215 { "!", I_SHELL, NOARGS },
216 { "?", I_HELP, NOARGS },
217 { NULL, -1, -1 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100218};
219
Darren Tucker21063192010-01-08 17:10:36 +1100220int interactive_loop(struct sftp_conn *, char *file1, char *file2);
Damien Miller20e1fab2004-02-18 14:30:55 +1100221
Damien Millerb6c85fc2007-01-05 16:30:41 +1100222/* ARGSUSED */
Damien Miller20e1fab2004-02-18 14:30:55 +1100223static void
Darren Tuckercdf547a2004-05-24 10:12:19 +1000224killchild(int signo)
225{
Darren Tuckerba66df82005-01-24 21:57:40 +1100226 if (sshpid > 1) {
Darren Tuckercdf547a2004-05-24 10:12:19 +1000227 kill(sshpid, SIGTERM);
Darren Tuckerba66df82005-01-24 21:57:40 +1100228 waitpid(sshpid, NULL, 0);
229 }
Darren Tuckercdf547a2004-05-24 10:12:19 +1000230
231 _exit(1);
232}
233
Damien Millerb6c85fc2007-01-05 16:30:41 +1100234/* ARGSUSED */
Darren Tuckercdf547a2004-05-24 10:12:19 +1000235static void
millert@openbsd.org2c6697c2016-10-18 12:41:22 +0000236suspchild(int signo)
237{
238 if (sshpid > 1) {
239 kill(sshpid, signo);
240 while (waitpid(sshpid, NULL, WUNTRACED) == -1 && errno == EINTR)
241 continue;
242 }
243 kill(getpid(), SIGSTOP);
244}
245
246/* ARGSUSED */
247static void
Darren Tuckercdf547a2004-05-24 10:12:19 +1000248cmd_interrupt(int signo)
249{
250 const char msg[] = "\rInterrupt \n";
Darren Tuckere2f189a2004-12-06 22:45:53 +1100251 int olderrno = errno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000252
Darren Tuckerdbee3082013-05-16 20:32:29 +1000253 (void)write(STDERR_FILENO, msg, sizeof(msg) - 1);
Darren Tuckercdf547a2004-05-24 10:12:19 +1000254 interrupted = 1;
Darren Tuckere2f189a2004-12-06 22:45:53 +1100255 errno = olderrno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000256}
257
258static void
Damien Miller20e1fab2004-02-18 14:30:55 +1100259help(void)
260{
Damien Miller62fd18a2009-01-28 16:14:09 +1100261 printf("Available commands:\n"
262 "bye Quit sftp\n"
263 "cd path Change remote directory to 'path'\n"
264 "chgrp grp path Change group of file 'path' to 'grp'\n"
265 "chmod mode path Change permissions of file 'path' to 'mode'\n"
266 "chown own path Change owner of file 'path' to 'own'\n"
267 "df [-hi] [path] Display statistics for current directory or\n"
268 " filesystem containing 'path'\n"
269 "exit Quit sftp\n"
djm@openbsd.org4a459222014-10-06 00:47:15 +0000270 "get [-afPpRr] remote [local] Download file\n"
271 "reget [-fPpRr] remote [local] Resume download file\n"
272 "reput [-fPpRr] [local] remote Resume upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100273 "help Display this help text\n"
274 "lcd path Change local directory to 'path'\n"
275 "lls [ls-options [path]] Display local directory listing\n"
276 "lmkdir path Create local directory\n"
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100277 "ln [-s] oldpath newpath Link remote file (-s for symlink)\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100278 "lpwd Print local working directory\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100279 "ls [-1afhlnrSt] [path] Display remote directory listing\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100280 "lumask umask Set local umask to 'umask'\n"
281 "mkdir path Create remote directory\n"
282 "progress Toggle display of progress meter\n"
djm@openbsd.org4a459222014-10-06 00:47:15 +0000283 "put [-afPpRr] local [remote] Upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100284 "pwd Display remote working directory\n"
285 "quit Quit sftp\n"
286 "rename oldpath newpath Rename remote file\n"
287 "rm path Delete remote file\n"
288 "rmdir path Remove remote directory\n"
289 "symlink oldpath newpath Symlink remote file\n"
290 "version Show SFTP version\n"
291 "!command Execute 'command' in local shell\n"
292 "! Escape to local shell\n"
293 "? Synonym for help\n");
Damien Miller20e1fab2004-02-18 14:30:55 +1100294}
295
296static void
297local_do_shell(const char *args)
298{
299 int status;
300 char *shell;
301 pid_t pid;
302
303 if (!*args)
304 args = NULL;
305
Damien Miller38d9a962010-10-07 22:07:11 +1100306 if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
Damien Miller20e1fab2004-02-18 14:30:55 +1100307 shell = _PATH_BSHELL;
308
309 if ((pid = fork()) == -1)
310 fatal("Couldn't fork: %s", strerror(errno));
311
312 if (pid == 0) {
313 /* XXX: child has pipe fds to ssh subproc open - issue? */
314 if (args) {
315 debug3("Executing %s -c \"%s\"", shell, args);
316 execl(shell, shell, "-c", args, (char *)NULL);
317 } else {
318 debug3("Executing %s", shell);
319 execl(shell, shell, (char *)NULL);
320 }
321 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
322 strerror(errno));
323 _exit(1);
324 }
325 while (waitpid(pid, &status, 0) == -1)
326 if (errno != EINTR)
327 fatal("Couldn't wait for child: %s", strerror(errno));
328 if (!WIFEXITED(status))
Damien Miller55b04f12006-03-26 14:23:17 +1100329 error("Shell exited abnormally");
Damien Miller20e1fab2004-02-18 14:30:55 +1100330 else if (WEXITSTATUS(status))
331 error("Shell exited with status %d", WEXITSTATUS(status));
332}
333
334static void
335local_do_ls(const char *args)
336{
337 if (!args || !*args)
338 local_do_shell(_PATH_LS);
339 else {
340 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
341 char *buf = xmalloc(len);
342
343 /* XXX: quoting - rip quoting code from ftp? */
344 snprintf(buf, len, _PATH_LS " %s", args);
345 local_do_shell(buf);
Darren Tuckera627d422013-06-02 07:31:17 +1000346 free(buf);
Damien Miller20e1fab2004-02-18 14:30:55 +1100347 }
348}
349
350/* Strip one path (usually the pwd) from the start of another */
351static char *
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000352path_strip(const char *path, const char *strip)
Damien Miller20e1fab2004-02-18 14:30:55 +1100353{
354 size_t len;
355
356 if (strip == NULL)
357 return (xstrdup(path));
358
359 len = strlen(strip);
Darren Tuckere2f189a2004-12-06 22:45:53 +1100360 if (strncmp(path, strip, len) == 0) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100361 if (strip[len - 1] != '/' && path[len] == '/')
362 len++;
363 return (xstrdup(path + len));
364 }
365
366 return (xstrdup(path));
367}
368
369static char *
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000370make_absolute(char *p, const char *pwd)
Damien Miller20e1fab2004-02-18 14:30:55 +1100371{
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000372 char *abs_str;
Damien Miller20e1fab2004-02-18 14:30:55 +1100373
374 /* Derelativise */
375 if (p && p[0] != '/') {
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000376 abs_str = path_append(pwd, p);
Darren Tuckera627d422013-06-02 07:31:17 +1000377 free(p);
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000378 return(abs_str);
Damien Miller20e1fab2004-02-18 14:30:55 +1100379 } else
380 return(p);
381}
382
383static int
Damien Miller0d032412013-07-25 11:56:52 +1000384parse_getput_flags(const char *cmd, char **argv, int argc,
Damien Millerf29238e2013-10-17 11:48:52 +1100385 int *aflag, int *fflag, int *pflag, int *rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100386{
Damien Millerf184bcf2008-06-29 22:45:13 +1000387 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000388 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100389
Damien Miller1cbc2922007-10-26 14:27:45 +1000390 optind = optreset = 1;
391 opterr = 0;
392
Damien Millerf29238e2013-10-17 11:48:52 +1100393 *aflag = *fflag = *rflag = *pflag = 0;
394 while ((ch = getopt(argc, argv, "afPpRr")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000395 switch (ch) {
Damien Miller0d032412013-07-25 11:56:52 +1000396 case 'a':
397 *aflag = 1;
398 break;
Damien Millerf29238e2013-10-17 11:48:52 +1100399 case 'f':
400 *fflag = 1;
401 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100402 case 'p':
403 case 'P':
404 *pflag = 1;
405 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100406 case 'r':
407 case 'R':
408 *rflag = 1;
409 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100410 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000411 error("%s: Invalid flag -%c", cmd, optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000412 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100413 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100414 }
415
Damien Miller1cbc2922007-10-26 14:27:45 +1000416 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100417}
418
419static int
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100420parse_link_flags(const char *cmd, char **argv, int argc, int *sflag)
421{
422 extern int opterr, optind, optopt, optreset;
423 int ch;
424
425 optind = optreset = 1;
426 opterr = 0;
427
428 *sflag = 0;
429 while ((ch = getopt(argc, argv, "s")) != -1) {
430 switch (ch) {
431 case 's':
432 *sflag = 1;
433 break;
434 default:
435 error("%s: Invalid flag -%c", cmd, optopt);
436 return -1;
437 }
438 }
439
440 return optind;
441}
442
443static int
Damien Millerc7dba122013-08-21 02:41:15 +1000444parse_rename_flags(const char *cmd, char **argv, int argc, int *lflag)
445{
446 extern int opterr, optind, optopt, optreset;
447 int ch;
448
449 optind = optreset = 1;
450 opterr = 0;
451
452 *lflag = 0;
453 while ((ch = getopt(argc, argv, "l")) != -1) {
454 switch (ch) {
455 case 'l':
456 *lflag = 1;
457 break;
458 default:
459 error("%s: Invalid flag -%c", cmd, optopt);
460 return -1;
461 }
462 }
463
464 return optind;
465}
466
467static int
Damien Miller1cbc2922007-10-26 14:27:45 +1000468parse_ls_flags(char **argv, int argc, int *lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100469{
Damien Millerf184bcf2008-06-29 22:45:13 +1000470 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000471 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100472
Damien Miller1cbc2922007-10-26 14:27:45 +1000473 optind = optreset = 1;
474 opterr = 0;
475
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000476 *lflag = LS_NAME_SORT;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100477 while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000478 switch (ch) {
479 case '1':
480 *lflag &= ~VIEW_FLAGS;
481 *lflag |= LS_SHORT_VIEW;
482 break;
483 case 'S':
484 *lflag &= ~SORT_FLAGS;
485 *lflag |= LS_SIZE_SORT;
486 break;
487 case 'a':
488 *lflag |= LS_SHOW_ALL;
489 break;
490 case 'f':
491 *lflag &= ~SORT_FLAGS;
492 break;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100493 case 'h':
494 *lflag |= LS_SI_UNITS;
495 break;
Damien Miller1cbc2922007-10-26 14:27:45 +1000496 case 'l':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100497 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000498 *lflag |= LS_LONG_VIEW;
499 break;
500 case 'n':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100501 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000502 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
503 break;
504 case 'r':
505 *lflag |= LS_REVERSE_SORT;
506 break;
507 case 't':
508 *lflag &= ~SORT_FLAGS;
509 *lflag |= LS_TIME_SORT;
510 break;
511 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000512 error("ls: Invalid flag -%c", optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000513 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100514 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100515 }
516
Damien Miller1cbc2922007-10-26 14:27:45 +1000517 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100518}
519
520static int
Damien Millerd671e5a2008-05-19 14:53:33 +1000521parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
522{
Damien Millerf184bcf2008-06-29 22:45:13 +1000523 extern int opterr, optind, optopt, optreset;
Damien Millerd671e5a2008-05-19 14:53:33 +1000524 int ch;
525
526 optind = optreset = 1;
527 opterr = 0;
528
529 *hflag = *iflag = 0;
530 while ((ch = getopt(argc, argv, "hi")) != -1) {
531 switch (ch) {
532 case 'h':
533 *hflag = 1;
534 break;
535 case 'i':
536 *iflag = 1;
537 break;
538 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000539 error("%s: Invalid flag -%c", cmd, optopt);
Damien Millerd671e5a2008-05-19 14:53:33 +1000540 return -1;
541 }
542 }
543
544 return optind;
545}
546
547static int
Damien Miller036d3072013-08-21 02:41:46 +1000548parse_no_flags(const char *cmd, char **argv, int argc)
549{
550 extern int opterr, optind, optopt, optreset;
551 int ch;
552
553 optind = optreset = 1;
554 opterr = 0;
555
556 while ((ch = getopt(argc, argv, "")) != -1) {
557 switch (ch) {
558 default:
559 error("%s: Invalid flag -%c", cmd, optopt);
560 return -1;
561 }
562 }
563
564 return optind;
565}
566
567static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000568is_dir(const char *path)
Damien Miller20e1fab2004-02-18 14:30:55 +1100569{
570 struct stat sb;
571
572 /* XXX: report errors? */
573 if (stat(path, &sb) == -1)
574 return(0);
575
Darren Tucker1e80e402006-09-21 12:59:33 +1000576 return(S_ISDIR(sb.st_mode));
Damien Miller20e1fab2004-02-18 14:30:55 +1100577}
578
579static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000580remote_is_dir(struct sftp_conn *conn, const char *path)
Damien Miller20e1fab2004-02-18 14:30:55 +1100581{
582 Attrib *a;
583
584 /* XXX: report errors? */
585 if ((a = do_stat(conn, path, 1)) == NULL)
586 return(0);
587 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
588 return(0);
Darren Tucker1e80e402006-09-21 12:59:33 +1000589 return(S_ISDIR(a->perm));
Damien Miller20e1fab2004-02-18 14:30:55 +1100590}
591
Darren Tucker1b0dd172009-10-07 08:37:48 +1100592/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
Damien Miller20e1fab2004-02-18 14:30:55 +1100593static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000594pathname_is_dir(const char *pathname)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100595{
596 size_t l = strlen(pathname);
597
598 return l > 0 && pathname[l - 1] == '/';
599}
600
601static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000602process_get(struct sftp_conn *conn, const char *src, const char *dst,
603 const char *pwd, int pflag, int rflag, int resume, int fflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100604{
605 char *abs_src = NULL;
606 char *abs_dst = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100607 glob_t g;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100608 char *filename, *tmp=NULL;
Damien Miller00707762014-07-09 13:07:06 +1000609 int i, r, err = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100610
611 abs_src = xstrdup(src);
612 abs_src = make_absolute(abs_src, pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +1100613 memset(&g, 0, sizeof(g));
Darren Tucker1b0dd172009-10-07 08:37:48 +1100614
Damien Miller20e1fab2004-02-18 14:30:55 +1100615 debug3("Looking up %s", abs_src);
Damien Miller00707762014-07-09 13:07:06 +1000616 if ((r = remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) != 0) {
617 if (r == GLOB_NOSPACE) {
618 error("Too many matches for \"%s\".", abs_src);
619 } else {
620 error("File \"%s\" not found.", abs_src);
621 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100622 err = -1;
623 goto out;
624 }
625
Darren Tucker1b0dd172009-10-07 08:37:48 +1100626 /*
627 * If multiple matches then dst must be a directory or
628 * unspecified.
629 */
630 if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
631 error("Multiple source paths, but destination "
632 "\"%s\" is not a directory", dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100633 err = -1;
634 goto out;
635 }
636
Darren Tuckercdf547a2004-05-24 10:12:19 +1000637 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100638 tmp = xstrdup(g.gl_pathv[i]);
639 if ((filename = basename(tmp)) == NULL) {
640 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000641 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100642 err = -1;
643 goto out;
644 }
645
646 if (g.gl_matchc == 1 && dst) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100647 if (is_dir(dst)) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100648 abs_dst = path_append(dst, filename);
649 } else {
Damien Miller20e1fab2004-02-18 14:30:55 +1100650 abs_dst = xstrdup(dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100651 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100652 } else if (dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100653 abs_dst = path_append(dst, filename);
654 } else {
655 abs_dst = xstrdup(filename);
656 }
Darren Tuckera627d422013-06-02 07:31:17 +1000657 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100658
Damien Miller0d032412013-07-25 11:56:52 +1000659 resume |= global_aflag;
660 if (!quiet && resume)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000661 mprintf("Resuming %s to %s\n",
662 g.gl_pathv[i], abs_dst);
Damien Miller0d032412013-07-25 11:56:52 +1000663 else if (!quiet && !resume)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000664 mprintf("Fetching %s to %s\n",
665 g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100666 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
Damien Miller0d032412013-07-25 11:56:52 +1000667 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
Damien Millerf29238e2013-10-17 11:48:52 +1100668 pflag || global_pflag, 1, resume,
669 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100670 err = -1;
671 } else {
672 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
Damien Millerf29238e2013-10-17 11:48:52 +1100673 pflag || global_pflag, resume,
674 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100675 err = -1;
676 }
Darren Tuckera627d422013-06-02 07:31:17 +1000677 free(abs_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100678 abs_dst = NULL;
679 }
680
681out:
Darren Tuckera627d422013-06-02 07:31:17 +1000682 free(abs_src);
Damien Miller20e1fab2004-02-18 14:30:55 +1100683 globfree(&g);
684 return(err);
685}
686
687static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000688process_put(struct sftp_conn *conn, const char *src, const char *dst,
689 const char *pwd, int pflag, int rflag, int resume, int fflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100690{
691 char *tmp_dst = NULL;
692 char *abs_dst = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100693 char *tmp = NULL, *filename = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100694 glob_t g;
695 int err = 0;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100696 int i, dst_is_dir = 1;
Damien Milleraec5cf82008-02-10 22:26:24 +1100697 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100698
699 if (dst) {
700 tmp_dst = xstrdup(dst);
701 tmp_dst = make_absolute(tmp_dst, pwd);
702 }
703
704 memset(&g, 0, sizeof(g));
705 debug3("Looking up %s", src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100706 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100707 error("File \"%s\" not found.", src);
708 err = -1;
709 goto out;
710 }
711
Darren Tucker1b0dd172009-10-07 08:37:48 +1100712 /* If we aren't fetching to pwd then stash this status for later */
713 if (tmp_dst != NULL)
714 dst_is_dir = remote_is_dir(conn, tmp_dst);
715
Damien Miller20e1fab2004-02-18 14:30:55 +1100716 /* If multiple matches, dst may be directory or unspecified */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100717 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
718 error("Multiple paths match, but destination "
719 "\"%s\" is not a directory", tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100720 err = -1;
721 goto out;
722 }
723
Darren Tuckercdf547a2004-05-24 10:12:19 +1000724 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Milleraec5cf82008-02-10 22:26:24 +1100725 if (stat(g.gl_pathv[i], &sb) == -1) {
726 err = -1;
727 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
728 continue;
729 }
Damien Miller02e87802013-08-21 02:38:51 +1000730
Darren Tucker1b0dd172009-10-07 08:37:48 +1100731 tmp = xstrdup(g.gl_pathv[i]);
732 if ((filename = basename(tmp)) == NULL) {
733 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000734 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100735 err = -1;
736 goto out;
737 }
738
739 if (g.gl_matchc == 1 && tmp_dst) {
740 /* If directory specified, append filename */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100741 if (dst_is_dir)
742 abs_dst = path_append(tmp_dst, filename);
743 else
Damien Miller20e1fab2004-02-18 14:30:55 +1100744 abs_dst = xstrdup(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100745 } else if (tmp_dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100746 abs_dst = path_append(tmp_dst, filename);
747 } else {
748 abs_dst = make_absolute(xstrdup(filename), pwd);
749 }
Darren Tuckera627d422013-06-02 07:31:17 +1000750 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100751
Damien Millerd8accc02014-05-15 13:46:25 +1000752 resume |= global_aflag;
753 if (!quiet && resume)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000754 mprintf("Resuming upload of %s to %s\n",
755 g.gl_pathv[i], abs_dst);
Damien Millerd8accc02014-05-15 13:46:25 +1000756 else if (!quiet && !resume)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000757 mprintf("Uploading %s to %s\n",
758 g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100759 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
760 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
Damien Millerd8accc02014-05-15 13:46:25 +1000761 pflag || global_pflag, 1, resume,
Damien Millerf29238e2013-10-17 11:48:52 +1100762 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100763 err = -1;
764 } else {
765 if (do_upload(conn, g.gl_pathv[i], abs_dst,
Damien Millerd8accc02014-05-15 13:46:25 +1000766 pflag || global_pflag, resume,
Damien Millerf29238e2013-10-17 11:48:52 +1100767 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100768 err = -1;
769 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100770 }
771
772out:
Darren Tuckera627d422013-06-02 07:31:17 +1000773 free(abs_dst);
774 free(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100775 globfree(&g);
776 return(err);
777}
778
779static int
780sdirent_comp(const void *aa, const void *bb)
781{
782 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
783 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000784 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100785
Darren Tuckerb9123452004-06-22 13:06:45 +1000786#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000787 if (sort_flag & LS_NAME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000788 return (rmul * strcmp(a->filename, b->filename));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000789 else if (sort_flag & LS_TIME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000790 return (rmul * NCMP(a->a.mtime, b->a.mtime));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000791 else if (sort_flag & LS_SIZE_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000792 return (rmul * NCMP(a->a.size, b->a.size));
793
794 fatal("Unknown ls sort type");
Damien Miller20e1fab2004-02-18 14:30:55 +1100795}
796
797/* sftp ls.1 replacement for directories */
798static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000799do_ls_dir(struct sftp_conn *conn, const char *path,
800 const char *strip_path, int lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100801{
Damien Millereccb9de2005-06-17 12:59:34 +1000802 int n;
803 u_int c = 1, colspace = 0, columns = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100804 SFTP_DIRENT **d;
805
806 if ((n = do_readdir(conn, path, &d)) != 0)
807 return (n);
808
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000809 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000810 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100811 struct winsize ws;
812 char *tmp;
813
814 /* Count entries for sort and find longest filename */
Darren Tucker9a526452004-06-22 13:09:55 +1000815 for (n = 0; d[n] != NULL; n++) {
816 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +0000817 m = MAXIMUM(m, strlen(d[n]->filename));
Darren Tucker9a526452004-06-22 13:09:55 +1000818 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100819
820 /* Add any subpath that also needs to be counted */
821 tmp = path_strip(path, strip_path);
822 m += strlen(tmp);
Darren Tuckera627d422013-06-02 07:31:17 +1000823 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100824
825 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
826 width = ws.ws_col;
827
828 columns = width / (m + 2);
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +0000829 columns = MAXIMUM(columns, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +1100830 colspace = width / columns;
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +0000831 colspace = MINIMUM(colspace, width);
Damien Miller20e1fab2004-02-18 14:30:55 +1100832 }
833
Darren Tuckerb9123452004-06-22 13:06:45 +1000834 if (lflag & SORT_FLAGS) {
Damien Miller653b93b2005-11-05 15:15:23 +1100835 for (n = 0; d[n] != NULL; n++)
836 ; /* count entries */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000837 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
Darren Tuckerb9123452004-06-22 13:06:45 +1000838 qsort(d, n, sizeof(*d), sdirent_comp);
839 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100840
Darren Tuckercdf547a2004-05-24 10:12:19 +1000841 for (n = 0; d[n] != NULL && !interrupted; n++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100842 char *tmp, *fname;
843
Darren Tucker9a526452004-06-22 13:09:55 +1000844 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
845 continue;
846
Damien Miller20e1fab2004-02-18 14:30:55 +1100847 tmp = path_append(path, d[n]->filename);
848 fname = path_strip(tmp, strip_path);
Darren Tuckera627d422013-06-02 07:31:17 +1000849 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100850
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000851 if (lflag & LS_LONG_VIEW) {
Darren Tucker2901e2d2010-01-13 22:44:06 +1100852 if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000853 char *lname;
854 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100855
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000856 memset(&sb, 0, sizeof(sb));
857 attrib_to_stat(&d[n]->a, &sb);
Darren Tucker2901e2d2010-01-13 22:44:06 +1100858 lname = ls_file(fname, &sb, 1,
859 (lflag & LS_SI_UNITS));
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000860 mprintf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000861 free(lname);
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000862 } else
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000863 mprintf("%s\n", d[n]->longname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100864 } else {
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000865 mprintf("%-*s", colspace, fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100866 if (c >= columns) {
867 printf("\n");
868 c = 1;
869 } else
870 c++;
871 }
872
Darren Tuckera627d422013-06-02 07:31:17 +1000873 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100874 }
875
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000876 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100877 printf("\n");
878
879 free_sftp_dirents(d);
880 return (0);
881}
882
djm@openbsd.org72be5b22017-06-10 06:33:34 +0000883static int
884sglob_comp(const void *aa, const void *bb)
885{
886 u_int a = *(const u_int *)aa;
887 u_int b = *(const u_int *)bb;
888 const char *ap = sort_glob->gl_pathv[a];
889 const char *bp = sort_glob->gl_pathv[b];
890 const struct stat *as = sort_glob->gl_statv[a];
891 const struct stat *bs = sort_glob->gl_statv[b];
892 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
893
894#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
895 if (sort_flag & LS_NAME_SORT)
896 return (rmul * strcmp(ap, bp));
Damien Millerbcd14852017-06-10 23:41:25 +1000897 else if (sort_flag & LS_TIME_SORT) {
898#if defined(HAVE_STRUCT_STAT_ST_MTIM)
djm@openbsd.org72be5b22017-06-10 06:33:34 +0000899 return (rmul * timespeccmp(&as->st_mtim, &bs->st_mtim, <));
Damien Millerbcd14852017-06-10 23:41:25 +1000900#elif defined(HAVE_STRUCT_STAT_ST_MTIME)
901 return (rmul * NCMP(as->st_mtime, bs->st_mtime));
902#else
903 return rmul * 1;
904#endif
905 } else if (sort_flag & LS_SIZE_SORT)
djm@openbsd.org72be5b22017-06-10 06:33:34 +0000906 return (rmul * NCMP(as->st_size, bs->st_size));
907
908 fatal("Unknown ls sort type");
909}
910
Damien Miller20e1fab2004-02-18 14:30:55 +1100911/* sftp ls.1 replacement which handles path globs */
912static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000913do_globbed_ls(struct sftp_conn *conn, const char *path,
914 const char *strip_path, int lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100915{
Damien Millera6e121a2010-10-07 21:39:17 +1100916 char *fname, *lname;
Damien Miller68e2e562010-10-07 21:39:55 +1100917 glob_t g;
Damien Miller00707762014-07-09 13:07:06 +1000918 int err, r;
Damien Miller68e2e562010-10-07 21:39:55 +1100919 struct winsize ws;
djm@openbsd.org72be5b22017-06-10 06:33:34 +0000920 u_int i, j, nentries, *indices = NULL, c = 1;
921 u_int colspace = 0, columns = 1, m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100922
923 memset(&g, 0, sizeof(g));
924
Damien Miller00707762014-07-09 13:07:06 +1000925 if ((r = remote_glob(conn, path,
Damien Millerd7be70d2011-09-22 21:43:06 +1000926 GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
Damien Miller00707762014-07-09 13:07:06 +1000927 NULL, &g)) != 0 ||
Damien Millera6e121a2010-10-07 21:39:17 +1100928 (g.gl_pathc && !g.gl_matchc)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100929 if (g.gl_pathc)
930 globfree(&g);
Damien Miller00707762014-07-09 13:07:06 +1000931 if (r == GLOB_NOSPACE) {
932 error("Can't ls: Too many matches for \"%s\"", path);
933 } else {
934 error("Can't ls: \"%s\" not found", path);
935 }
Damien Millera6e121a2010-10-07 21:39:17 +1100936 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100937 }
938
Darren Tuckercdf547a2004-05-24 10:12:19 +1000939 if (interrupted)
940 goto out;
941
Damien Miller20e1fab2004-02-18 14:30:55 +1100942 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100943 * If the glob returns a single match and it is a directory,
944 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100945 */
Damien Millera6e121a2010-10-07 21:39:17 +1100946 if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
947 S_ISDIR(g.gl_statv[0]->st_mode)) {
948 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
949 globfree(&g);
950 return err;
Damien Miller20e1fab2004-02-18 14:30:55 +1100951 }
952
Damien Miller68e2e562010-10-07 21:39:55 +1100953 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
954 width = ws.ws_col;
Damien Miller20e1fab2004-02-18 14:30:55 +1100955
Damien Miller68e2e562010-10-07 21:39:55 +1100956 if (!(lflag & LS_SHORT_VIEW)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100957 /* Count entries for sort and find longest filename */
958 for (i = 0; g.gl_pathv[i]; i++)
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +0000959 m = MAXIMUM(m, strlen(g.gl_pathv[i]));
Damien Miller20e1fab2004-02-18 14:30:55 +1100960
Damien Miller20e1fab2004-02-18 14:30:55 +1100961 columns = width / (m + 2);
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +0000962 columns = MAXIMUM(columns, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +1100963 colspace = width / columns;
964 }
965
djm@openbsd.org72be5b22017-06-10 06:33:34 +0000966 /*
967 * Sorting: rather than mess with the contents of glob_t, prepare
968 * an array of indices into it and sort that. For the usual
969 * unsorted case, the indices are just the identity 1=1, 2=2, etc.
970 */
971 for (nentries = 0; g.gl_pathv[nentries] != NULL; nentries++)
972 ; /* count entries */
973 indices = calloc(nentries, sizeof(*indices));
974 for (i = 0; i < nentries; i++)
975 indices[i] = i;
976
977 if (lflag & SORT_FLAGS) {
978 sort_glob = &g;
979 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
980 qsort(indices, nentries, sizeof(*indices), sglob_comp);
981 sort_glob = NULL;
982 }
983
984 for (j = 0; j < nentries && !interrupted; j++) {
985 i = indices[j];
Damien Miller20e1fab2004-02-18 14:30:55 +1100986 fname = path_strip(g.gl_pathv[i], strip_path);
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000987 if (lflag & LS_LONG_VIEW) {
Damien Millera6e121a2010-10-07 21:39:17 +1100988 if (g.gl_statv[i] == NULL) {
989 error("no stat information for %s", fname);
990 continue;
991 }
992 lname = ls_file(fname, g.gl_statv[i], 1,
993 (lflag & LS_SI_UNITS));
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000994 mprintf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000995 free(lname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100996 } else {
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000997 mprintf("%-*s", colspace, fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100998 if (c >= columns) {
999 printf("\n");
1000 c = 1;
1001 } else
1002 c++;
1003 }
Darren Tuckera627d422013-06-02 07:31:17 +10001004 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +11001005 }
1006
Darren Tuckera4e9ffa2004-06-22 13:07:58 +10001007 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +11001008 printf("\n");
1009
Darren Tuckercdf547a2004-05-24 10:12:19 +10001010 out:
Damien Miller20e1fab2004-02-18 14:30:55 +11001011 if (g.gl_pathc)
1012 globfree(&g);
djm@openbsd.org72be5b22017-06-10 06:33:34 +00001013 free(indices);
Damien Miller20e1fab2004-02-18 14:30:55 +11001014
Damien Millera6e121a2010-10-07 21:39:17 +11001015 return 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001016}
1017
Damien Millerd671e5a2008-05-19 14:53:33 +10001018static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +00001019do_df(struct sftp_conn *conn, const char *path, int hflag, int iflag)
Damien Millerd671e5a2008-05-19 14:53:33 +10001020{
Darren Tucker7b598892008-06-09 22:49:36 +10001021 struct sftp_statvfs st;
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +00001022 char s_used[FMT_SCALED_STRSIZE], s_avail[FMT_SCALED_STRSIZE];
1023 char s_root[FMT_SCALED_STRSIZE], s_total[FMT_SCALED_STRSIZE];
1024 char s_icapacity[16], s_dcapacity[16];
Damien Millerd671e5a2008-05-19 14:53:33 +10001025
1026 if (do_statvfs(conn, path, &st, 1) == -1)
1027 return -1;
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +00001028 if (st.f_files == 0)
1029 strlcpy(s_icapacity, "ERR", sizeof(s_icapacity));
1030 else {
1031 snprintf(s_icapacity, sizeof(s_icapacity), "%3llu%%",
1032 (unsigned long long)(100 * (st.f_files - st.f_ffree) /
1033 st.f_files));
1034 }
1035 if (st.f_blocks == 0)
1036 strlcpy(s_dcapacity, "ERR", sizeof(s_dcapacity));
1037 else {
1038 snprintf(s_dcapacity, sizeof(s_dcapacity), "%3llu%%",
1039 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
1040 st.f_blocks));
1041 }
Damien Millerd671e5a2008-05-19 14:53:33 +10001042 if (iflag) {
1043 printf(" Inodes Used Avail "
1044 "(root) %%Capacity\n");
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +00001045 printf("%11llu %11llu %11llu %11llu %s\n",
Damien Millerd671e5a2008-05-19 14:53:33 +10001046 (unsigned long long)st.f_files,
1047 (unsigned long long)(st.f_files - st.f_ffree),
1048 (unsigned long long)st.f_favail,
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +00001049 (unsigned long long)st.f_ffree, s_icapacity);
Damien Millerd671e5a2008-05-19 14:53:33 +10001050 } else if (hflag) {
1051 strlcpy(s_used, "error", sizeof(s_used));
1052 strlcpy(s_avail, "error", sizeof(s_avail));
1053 strlcpy(s_root, "error", sizeof(s_root));
1054 strlcpy(s_total, "error", sizeof(s_total));
1055 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
1056 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
1057 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
1058 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
1059 printf(" Size Used Avail (root) %%Capacity\n");
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +00001060 printf("%7sB %7sB %7sB %7sB %s\n",
1061 s_total, s_used, s_avail, s_root, s_dcapacity);
Damien Millerd671e5a2008-05-19 14:53:33 +10001062 } else {
1063 printf(" Size Used Avail "
1064 "(root) %%Capacity\n");
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +00001065 printf("%12llu %12llu %12llu %12llu %s\n",
Damien Millerd671e5a2008-05-19 14:53:33 +10001066 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
1067 (unsigned long long)(st.f_frsize *
1068 (st.f_blocks - st.f_bfree) / 1024),
1069 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
1070 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +00001071 s_dcapacity);
Damien Millerd671e5a2008-05-19 14:53:33 +10001072 }
1073 return 0;
1074}
1075
Damien Miller1cbc2922007-10-26 14:27:45 +10001076/*
1077 * Undo escaping of glob sequences in place. Used to undo extra escaping
1078 * applied in makeargv() when the string is destined for a function that
1079 * does not glob it.
1080 */
1081static void
1082undo_glob_escape(char *s)
1083{
1084 size_t i, j;
1085
1086 for (i = j = 0;;) {
1087 if (s[i] == '\0') {
1088 s[j] = '\0';
1089 return;
1090 }
1091 if (s[i] != '\\') {
1092 s[j++] = s[i++];
1093 continue;
1094 }
1095 /* s[i] == '\\' */
1096 ++i;
1097 switch (s[i]) {
1098 case '?':
1099 case '[':
1100 case '*':
1101 case '\\':
1102 s[j++] = s[i++];
1103 break;
1104 case '\0':
1105 s[j++] = '\\';
1106 s[j] = '\0';
1107 return;
1108 default:
1109 s[j++] = '\\';
1110 s[j++] = s[i++];
1111 break;
1112 }
1113 }
1114}
1115
1116/*
1117 * Split a string into an argument vector using sh(1)-style quoting,
1118 * comment and escaping rules, but with some tweaks to handle glob(3)
1119 * wildcards.
Darren Tucker909d8582010-01-08 19:02:40 +11001120 * The "sloppy" flag allows for recovery from missing terminating quote, for
1121 * use in parsing incomplete commandlines during tab autocompletion.
1122 *
Damien Miller1cbc2922007-10-26 14:27:45 +10001123 * Returns NULL on error or a NULL-terminated array of arguments.
Darren Tucker909d8582010-01-08 19:02:40 +11001124 *
1125 * If "lastquote" is not NULL, the quoting character used for the last
1126 * argument is placed in *lastquote ("\0", "'" or "\"").
Damien Miller02e87802013-08-21 02:38:51 +10001127 *
Darren Tucker909d8582010-01-08 19:02:40 +11001128 * If "terminated" is not NULL, *terminated will be set to 1 when the
1129 * last argument's quote has been properly terminated or 0 otherwise.
1130 * This parameter is only of use if "sloppy" is set.
Damien Miller1cbc2922007-10-26 14:27:45 +10001131 */
1132#define MAXARGS 128
1133#define MAXARGLEN 8192
1134static char **
Darren Tucker909d8582010-01-08 19:02:40 +11001135makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
1136 u_int *terminated)
Damien Miller1cbc2922007-10-26 14:27:45 +10001137{
1138 int argc, quot;
1139 size_t i, j;
1140 static char argvs[MAXARGLEN];
1141 static char *argv[MAXARGS + 1];
1142 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
1143
1144 *argcp = argc = 0;
1145 if (strlen(arg) > sizeof(argvs) - 1) {
1146 args_too_longs:
1147 error("string too long");
1148 return NULL;
1149 }
Darren Tucker909d8582010-01-08 19:02:40 +11001150 if (terminated != NULL)
1151 *terminated = 1;
1152 if (lastquote != NULL)
1153 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001154 state = MA_START;
1155 i = j = 0;
1156 for (;;) {
Damien Miller07daed52012-10-31 08:57:55 +11001157 if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){
Darren Tucker063018d2012-10-05 10:43:58 +10001158 error("Too many arguments.");
1159 return NULL;
1160 }
Damien Millerfdb23062013-11-21 13:57:15 +11001161 if (isspace((unsigned char)arg[i])) {
Damien Miller1cbc2922007-10-26 14:27:45 +10001162 if (state == MA_UNQUOTED) {
1163 /* Terminate current argument */
1164 argvs[j++] = '\0';
1165 argc++;
1166 state = MA_START;
1167 } else if (state != MA_START)
1168 argvs[j++] = arg[i];
1169 } else if (arg[i] == '"' || arg[i] == '\'') {
1170 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1171 if (state == MA_START) {
1172 argv[argc] = argvs + j;
1173 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +11001174 if (lastquote != NULL)
1175 *lastquote = arg[i];
Damien Miller02e87802013-08-21 02:38:51 +10001176 } else if (state == MA_UNQUOTED)
Damien Miller1cbc2922007-10-26 14:27:45 +10001177 state = q;
1178 else if (state == q)
1179 state = MA_UNQUOTED;
1180 else
1181 argvs[j++] = arg[i];
1182 } else if (arg[i] == '\\') {
1183 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1184 quot = state == MA_SQUOTE ? '\'' : '"';
1185 /* Unescape quote we are in */
1186 /* XXX support \n and friends? */
1187 if (arg[i + 1] == quot) {
1188 i++;
1189 argvs[j++] = arg[i];
1190 } else if (arg[i + 1] == '?' ||
1191 arg[i + 1] == '[' || arg[i + 1] == '*') {
1192 /*
1193 * Special case for sftp: append
1194 * double-escaped glob sequence -
1195 * glob will undo one level of
1196 * escaping. NB. string can grow here.
1197 */
1198 if (j >= sizeof(argvs) - 5)
1199 goto args_too_longs;
1200 argvs[j++] = '\\';
1201 argvs[j++] = arg[i++];
1202 argvs[j++] = '\\';
1203 argvs[j++] = arg[i];
1204 } else {
1205 argvs[j++] = arg[i++];
1206 argvs[j++] = arg[i];
1207 }
1208 } else {
1209 if (state == MA_START) {
1210 argv[argc] = argvs + j;
1211 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001212 if (lastquote != NULL)
1213 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001214 }
1215 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1216 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1217 /*
1218 * Special case for sftp: append
1219 * escaped glob sequence -
1220 * glob will undo one level of
1221 * escaping.
1222 */
1223 argvs[j++] = arg[i++];
1224 argvs[j++] = arg[i];
1225 } else {
1226 /* Unescape everything */
1227 /* XXX support \n and friends? */
1228 i++;
1229 argvs[j++] = arg[i];
1230 }
1231 }
1232 } else if (arg[i] == '#') {
1233 if (state == MA_SQUOTE || state == MA_DQUOTE)
1234 argvs[j++] = arg[i];
1235 else
1236 goto string_done;
1237 } else if (arg[i] == '\0') {
1238 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001239 if (sloppy) {
1240 state = MA_UNQUOTED;
1241 if (terminated != NULL)
1242 *terminated = 0;
1243 goto string_done;
1244 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001245 error("Unterminated quoted argument");
1246 return NULL;
1247 }
1248 string_done:
1249 if (state == MA_UNQUOTED) {
1250 argvs[j++] = '\0';
1251 argc++;
1252 }
1253 break;
1254 } else {
1255 if (state == MA_START) {
1256 argv[argc] = argvs + j;
1257 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001258 if (lastquote != NULL)
1259 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001260 }
1261 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1262 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1263 /*
1264 * Special case for sftp: escape quoted
1265 * glob(3) wildcards. NB. string can grow
1266 * here.
1267 */
1268 if (j >= sizeof(argvs) - 3)
1269 goto args_too_longs;
1270 argvs[j++] = '\\';
1271 argvs[j++] = arg[i];
1272 } else
1273 argvs[j++] = arg[i];
1274 }
1275 i++;
1276 }
1277 *argcp = argc;
1278 return argv;
1279}
1280
Damien Miller20e1fab2004-02-18 14:30:55 +11001281static int
Damien Millerd8accc02014-05-15 13:46:25 +10001282parse_args(const char **cpp, int *ignore_errors, int *aflag,
djm@openbsd.org34a01b22016-04-08 08:19:17 +00001283 int *fflag, int *hflag, int *iflag, int *lflag, int *pflag,
Damien Millerd8accc02014-05-15 13:46:25 +10001284 int *rflag, int *sflag,
Damien Millerf29238e2013-10-17 11:48:52 +11001285 unsigned long *n_arg, char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001286{
1287 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001288 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001289 int base = 0;
1290 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001291 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001292
1293 /* Skip leading whitespace */
1294 cp = cp + strspn(cp, WHITESPACE);
1295
Damien Miller20e1fab2004-02-18 14:30:55 +11001296 /* Check for leading '-' (disable error processing) */
Damien Millerf29238e2013-10-17 11:48:52 +11001297 *ignore_errors = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001298 if (*cp == '-') {
Damien Millerf29238e2013-10-17 11:48:52 +11001299 *ignore_errors = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001300 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001301 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001302 }
1303
Darren Tucker70cc0922010-01-09 22:28:03 +11001304 /* Ignore blank lines and lines which begin with comment '#' char */
1305 if (*cp == '\0' || *cp == '#')
1306 return (0);
1307
Darren Tucker909d8582010-01-08 19:02:40 +11001308 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001309 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001310
Damien Miller1cbc2922007-10-26 14:27:45 +10001311 /* Figure out which command we have */
1312 for (i = 0; cmds[i].c != NULL; i++) {
Damien Millerd6d9fa02013-02-12 11:02:46 +11001313 if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001314 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001315 }
1316 cmdnum = cmds[i].n;
1317 cmd = cmds[i].c;
1318
1319 /* Special case */
1320 if (*cp == '!') {
1321 cp++;
1322 cmdnum = I_SHELL;
1323 } else if (cmdnum == -1) {
1324 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001325 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001326 }
1327
1328 /* Get arguments and parse flags */
Damien Millerf29238e2013-10-17 11:48:52 +11001329 *aflag = *fflag = *hflag = *iflag = *lflag = *pflag = 0;
1330 *rflag = *sflag = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001331 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001332 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001333 switch (cmdnum) {
1334 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001335 case I_REGET:
Damien Millerd8accc02014-05-15 13:46:25 +10001336 case I_REPUT:
Damien Miller20e1fab2004-02-18 14:30:55 +11001337 case I_PUT:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001338 if ((optidx = parse_getput_flags(cmd, argv, argc,
Damien Millerf29238e2013-10-17 11:48:52 +11001339 aflag, fflag, pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001340 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001341 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001342 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001343 error("You must specify at least one path after a "
1344 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001345 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001346 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001347 *path1 = xstrdup(argv[optidx]);
1348 /* Get second pathname (optional) */
1349 if (argc - optidx > 1) {
1350 *path2 = xstrdup(argv[optidx + 1]);
1351 /* Destination is not globbed */
1352 undo_glob_escape(*path2);
1353 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001354 break;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001355 case I_LINK:
1356 if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1357 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001358 goto parse_two_paths;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001359 case I_RENAME:
Damien Millerc7dba122013-08-21 02:41:15 +10001360 if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1)
1361 return -1;
1362 goto parse_two_paths;
1363 case I_SYMLINK:
Damien Miller036d3072013-08-21 02:41:46 +10001364 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1365 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001366 parse_two_paths:
Damien Miller1cbc2922007-10-26 14:27:45 +10001367 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001368 error("You must specify two paths after a %s "
1369 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001370 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001371 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001372 *path1 = xstrdup(argv[optidx]);
1373 *path2 = xstrdup(argv[optidx + 1]);
1374 /* Paths are not globbed */
1375 undo_glob_escape(*path1);
1376 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001377 break;
1378 case I_RM:
1379 case I_MKDIR:
1380 case I_RMDIR:
1381 case I_CHDIR:
1382 case I_LCHDIR:
1383 case I_LMKDIR:
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 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001387 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001388 error("You must specify a path after a %s command.",
1389 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001390 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001391 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001392 *path1 = xstrdup(argv[optidx]);
1393 /* Only "rm" globs */
1394 if (cmdnum != I_RM)
1395 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001396 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001397 case I_DF:
1398 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1399 iflag)) == -1)
1400 return -1;
1401 /* Default to current directory if no path specified */
1402 if (argc - optidx < 1)
1403 *path1 = NULL;
1404 else {
1405 *path1 = xstrdup(argv[optidx]);
1406 undo_glob_escape(*path1);
1407 }
1408 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001409 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001410 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001411 return(-1);
1412 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001413 if (argc - optidx > 0)
1414 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001415 break;
1416 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001417 /* Skip ls command and following whitespace */
1418 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001419 case I_SHELL:
1420 /* Uses the rest of the line */
1421 break;
1422 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001423 case I_CHMOD:
1424 base = 8;
1425 case I_CHOWN:
1426 case I_CHGRP:
Damien Miller036d3072013-08-21 02:41:46 +10001427 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1428 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001429 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001430 if (argc - optidx < 1)
1431 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001432 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001433 l = strtol(argv[optidx], &cp2, base);
1434 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1435 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1436 l < 0) {
1437 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001438 error("You must supply a numeric argument "
1439 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001440 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001441 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001442 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001443 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001444 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001445 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001446 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001447 error("You must specify a path after a %s command.",
1448 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001449 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001450 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001451 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001452 break;
1453 case I_QUIT:
1454 case I_PWD:
1455 case I_LPWD:
1456 case I_HELP:
1457 case I_VERSION:
1458 case I_PROGRESS:
Damien Miller036d3072013-08-21 02:41:46 +10001459 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1460 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001461 break;
1462 default:
1463 fatal("Command not implemented");
1464 }
1465
1466 *cpp = cp;
1467 return(cmdnum);
1468}
1469
1470static int
1471parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1472 int err_abort)
1473{
1474 char *path1, *path2, *tmp;
djm@openbsd.org34a01b22016-04-08 08:19:17 +00001475 int ignore_errors = 0, aflag = 0, fflag = 0, hflag = 0,
Damien Millerd8accc02014-05-15 13:46:25 +10001476 iflag = 0;
Damien Millerf29238e2013-10-17 11:48:52 +11001477 int lflag = 0, pflag = 0, rflag = 0, sflag = 0;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001478 int cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001479 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001480 Attrib a, *aa;
deraadt@openbsd.org087266e2015-01-20 23:14:00 +00001481 char path_buf[PATH_MAX];
Damien Miller20e1fab2004-02-18 14:30:55 +11001482 int err = 0;
1483 glob_t g;
1484
1485 path1 = path2 = NULL;
Damien Millerf29238e2013-10-17 11:48:52 +11001486 cmdnum = parse_args(&cmd, &ignore_errors, &aflag, &fflag, &hflag,
1487 &iflag, &lflag, &pflag, &rflag, &sflag, &n_arg, &path1, &path2);
1488 if (ignore_errors != 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001489 err_abort = 0;
1490
1491 memset(&g, 0, sizeof(g));
1492
1493 /* Perform command */
1494 switch (cmdnum) {
1495 case 0:
1496 /* Blank line */
1497 break;
1498 case -1:
1499 /* Unrecognized command */
1500 err = -1;
1501 break;
Damien Miller0d032412013-07-25 11:56:52 +10001502 case I_REGET:
1503 aflag = 1;
1504 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001505 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001506 err = process_get(conn, path1, path2, *pwd, pflag,
Damien Millerf29238e2013-10-17 11:48:52 +11001507 rflag, aflag, fflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001508 break;
Damien Millerd8accc02014-05-15 13:46:25 +10001509 case I_REPUT:
1510 aflag = 1;
1511 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001512 case I_PUT:
Damien Millerf29238e2013-10-17 11:48:52 +11001513 err = process_put(conn, path1, path2, *pwd, pflag,
Damien Millerd8accc02014-05-15 13:46:25 +10001514 rflag, aflag, fflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001515 break;
1516 case I_RENAME:
1517 path1 = make_absolute(path1, *pwd);
1518 path2 = make_absolute(path2, *pwd);
Damien Millerc7dba122013-08-21 02:41:15 +10001519 err = do_rename(conn, path1, path2, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001520 break;
1521 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001522 sflag = 1;
1523 case I_LINK:
Damien Miller034f27a2013-08-21 02:40:44 +10001524 if (!sflag)
1525 path1 = make_absolute(path1, *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001526 path2 = make_absolute(path2, *pwd);
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001527 err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001528 break;
1529 case I_RM:
1530 path1 = make_absolute(path1, *pwd);
1531 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001532 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001533 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001534 mprintf("Removing %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001535 err = do_rm(conn, g.gl_pathv[i]);
1536 if (err != 0 && err_abort)
1537 break;
1538 }
1539 break;
1540 case I_MKDIR:
1541 path1 = make_absolute(path1, *pwd);
1542 attrib_clear(&a);
1543 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1544 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001545 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001546 break;
1547 case I_RMDIR:
1548 path1 = make_absolute(path1, *pwd);
1549 err = do_rmdir(conn, path1);
1550 break;
1551 case I_CHDIR:
1552 path1 = make_absolute(path1, *pwd);
1553 if ((tmp = do_realpath(conn, path1)) == NULL) {
1554 err = 1;
1555 break;
1556 }
1557 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
Darren Tuckera627d422013-06-02 07:31:17 +10001558 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001559 err = 1;
1560 break;
1561 }
1562 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1563 error("Can't change directory: Can't check target");
Darren Tuckera627d422013-06-02 07:31:17 +10001564 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001565 err = 1;
1566 break;
1567 }
1568 if (!S_ISDIR(aa->perm)) {
1569 error("Can't change directory: \"%s\" is not "
1570 "a directory", tmp);
Darren Tuckera627d422013-06-02 07:31:17 +10001571 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001572 err = 1;
1573 break;
1574 }
Darren Tuckera627d422013-06-02 07:31:17 +10001575 free(*pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001576 *pwd = tmp;
1577 break;
1578 case I_LS:
1579 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001580 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001581 break;
1582 }
1583
1584 /* Strip pwd off beginning of non-absolute paths */
1585 tmp = NULL;
1586 if (*path1 != '/')
1587 tmp = *pwd;
1588
1589 path1 = make_absolute(path1, *pwd);
1590 err = do_globbed_ls(conn, path1, tmp, lflag);
1591 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001592 case I_DF:
1593 /* Default to current directory if no path specified */
1594 if (path1 == NULL)
1595 path1 = xstrdup(*pwd);
1596 path1 = make_absolute(path1, *pwd);
1597 err = do_df(conn, path1, hflag, iflag);
1598 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001599 case I_LCHDIR:
deraadt@openbsd.org40ba4c92014-08-20 01:28:55 +00001600 tmp = tilde_expand_filename(path1, getuid());
djm@openbsd.org7ff880e2014-08-19 23:57:18 +00001601 free(path1);
1602 path1 = tmp;
Damien Miller20e1fab2004-02-18 14:30:55 +11001603 if (chdir(path1) == -1) {
1604 error("Couldn't change local directory to "
1605 "\"%s\": %s", path1, strerror(errno));
1606 err = 1;
1607 }
1608 break;
1609 case I_LMKDIR:
1610 if (mkdir(path1, 0777) == -1) {
1611 error("Couldn't create local directory "
1612 "\"%s\": %s", path1, strerror(errno));
1613 err = 1;
1614 }
1615 break;
1616 case I_LLS:
1617 local_do_ls(cmd);
1618 break;
1619 case I_SHELL:
1620 local_do_shell(cmd);
1621 break;
1622 case I_LUMASK:
1623 umask(n_arg);
1624 printf("Local umask: %03lo\n", n_arg);
1625 break;
1626 case I_CHMOD:
1627 path1 = make_absolute(path1, *pwd);
1628 attrib_clear(&a);
1629 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1630 a.perm = n_arg;
1631 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001632 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001633 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001634 mprintf("Changing mode on %s\n",
1635 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001636 err = do_setstat(conn, g.gl_pathv[i], &a);
1637 if (err != 0 && err_abort)
1638 break;
1639 }
1640 break;
1641 case I_CHOWN:
1642 case I_CHGRP:
1643 path1 = make_absolute(path1, *pwd);
1644 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001645 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001646 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001647 if (err_abort) {
1648 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001649 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001650 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001651 continue;
1652 }
1653 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1654 error("Can't get current ownership of "
1655 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001656 if (err_abort) {
1657 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001658 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001659 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001660 continue;
1661 }
1662 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1663 if (cmdnum == I_CHOWN) {
Damien Miller9303e652013-04-23 15:22:40 +10001664 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001665 mprintf("Changing owner on %s\n",
Damien Miller9303e652013-04-23 15:22:40 +10001666 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001667 aa->uid = n_arg;
1668 } else {
Damien Miller9303e652013-04-23 15:22:40 +10001669 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001670 mprintf("Changing group on %s\n",
Damien Miller9303e652013-04-23 15:22:40 +10001671 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001672 aa->gid = n_arg;
1673 }
1674 err = do_setstat(conn, g.gl_pathv[i], aa);
1675 if (err != 0 && err_abort)
1676 break;
1677 }
1678 break;
1679 case I_PWD:
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001680 mprintf("Remote working directory: %s\n", *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001681 break;
1682 case I_LPWD:
1683 if (!getcwd(path_buf, sizeof(path_buf))) {
1684 error("Couldn't get local cwd: %s", strerror(errno));
1685 err = -1;
1686 break;
1687 }
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001688 mprintf("Local working directory: %s\n", path_buf);
Damien Miller20e1fab2004-02-18 14:30:55 +11001689 break;
1690 case I_QUIT:
1691 /* Processed below */
1692 break;
1693 case I_HELP:
1694 help();
1695 break;
1696 case I_VERSION:
1697 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1698 break;
1699 case I_PROGRESS:
1700 showprogress = !showprogress;
1701 if (showprogress)
1702 printf("Progress meter enabled\n");
1703 else
1704 printf("Progress meter disabled\n");
1705 break;
1706 default:
1707 fatal("%d is not implemented", cmdnum);
1708 }
1709
1710 if (g.gl_pathc)
1711 globfree(&g);
Darren Tuckera627d422013-06-02 07:31:17 +10001712 free(path1);
1713 free(path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001714
1715 /* If an unignored error occurs in batch mode we should abort. */
1716 if (err_abort && err != 0)
1717 return (-1);
1718 else if (cmdnum == I_QUIT)
1719 return (1);
1720
1721 return (0);
1722}
1723
Darren Tucker2d963d82004-11-07 20:04:10 +11001724#ifdef USE_LIBEDIT
1725static char *
1726prompt(EditLine *el)
1727{
1728 return ("sftp> ");
1729}
Darren Tucker2d963d82004-11-07 20:04:10 +11001730
Darren Tucker909d8582010-01-08 19:02:40 +11001731/* Display entries in 'list' after skipping the first 'len' chars */
1732static void
1733complete_display(char **list, u_int len)
1734{
1735 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1736 struct winsize ws;
1737 char *tmp;
1738
1739 /* Count entries for sort and find longest */
Damien Miller02e87802013-08-21 02:38:51 +10001740 for (y = 0; list[y]; y++)
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +00001741 m = MAXIMUM(m, strlen(list[y]));
Darren Tucker909d8582010-01-08 19:02:40 +11001742
1743 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1744 width = ws.ws_col;
1745
1746 m = m > len ? m - len : 0;
1747 columns = width / (m + 2);
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +00001748 columns = MAXIMUM(columns, 1);
Darren Tucker909d8582010-01-08 19:02:40 +11001749 colspace = width / columns;
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +00001750 colspace = MINIMUM(colspace, width);
Darren Tucker909d8582010-01-08 19:02:40 +11001751
1752 printf("\n");
1753 m = 1;
1754 for (y = 0; list[y]; y++) {
1755 llen = strlen(list[y]);
1756 tmp = llen > len ? list[y] + len : "";
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001757 mprintf("%-*s", colspace, tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001758 if (m >= columns) {
1759 printf("\n");
1760 m = 1;
1761 } else
1762 m++;
1763 }
1764 printf("\n");
1765}
1766
1767/*
1768 * Given a "list" of words that begin with a common prefix of "word",
1769 * attempt to find an autocompletion to extends "word" by the next
1770 * characters common to all entries in "list".
1771 */
1772static char *
1773complete_ambiguous(const char *word, char **list, size_t count)
1774{
1775 if (word == NULL)
1776 return NULL;
1777
1778 if (count > 0) {
1779 u_int y, matchlen = strlen(list[0]);
1780
1781 /* Find length of common stem */
1782 for (y = 1; list[y]; y++) {
1783 u_int x;
1784
Damien Miller02e87802013-08-21 02:38:51 +10001785 for (x = 0; x < matchlen; x++)
1786 if (list[0][x] != list[y][x])
Darren Tucker909d8582010-01-08 19:02:40 +11001787 break;
1788
1789 matchlen = x;
1790 }
1791
1792 if (matchlen > strlen(word)) {
1793 char *tmp = xstrdup(list[0]);
1794
Darren Tucker340d1682010-01-09 08:54:31 +11001795 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001796 return tmp;
1797 }
Damien Miller02e87802013-08-21 02:38:51 +10001798 }
Darren Tucker909d8582010-01-08 19:02:40 +11001799
1800 return xstrdup(word);
1801}
1802
1803/* Autocomplete a sftp command */
1804static int
1805complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1806 int terminated)
1807{
1808 u_int y, count = 0, cmdlen, tmplen;
1809 char *tmp, **list, argterm[3];
1810 const LineInfo *lf;
1811
1812 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1813
1814 /* No command specified: display all available commands */
1815 if (cmd == NULL) {
1816 for (y = 0; cmds[y].c; y++)
1817 list[count++] = xstrdup(cmds[y].c);
Damien Miller02e87802013-08-21 02:38:51 +10001818
Darren Tucker909d8582010-01-08 19:02:40 +11001819 list[count] = NULL;
1820 complete_display(list, 0);
1821
Damien Miller02e87802013-08-21 02:38:51 +10001822 for (y = 0; list[y] != NULL; y++)
1823 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001824 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001825 return count;
1826 }
1827
1828 /* Prepare subset of commands that start with "cmd" */
1829 cmdlen = strlen(cmd);
1830 for (y = 0; cmds[y].c; y++) {
Damien Miller02e87802013-08-21 02:38:51 +10001831 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
Darren Tucker909d8582010-01-08 19:02:40 +11001832 list[count++] = xstrdup(cmds[y].c);
1833 }
1834 list[count] = NULL;
1835
Damien Miller47d81152011-11-25 13:53:48 +11001836 if (count == 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10001837 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001838 return 0;
Damien Miller47d81152011-11-25 13:53:48 +11001839 }
Darren Tucker909d8582010-01-08 19:02:40 +11001840
1841 /* Complete ambigious command */
1842 tmp = complete_ambiguous(cmd, list, count);
1843 if (count > 1)
1844 complete_display(list, 0);
1845
Damien Miller02e87802013-08-21 02:38:51 +10001846 for (y = 0; list[y]; y++)
1847 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001848 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001849
1850 if (tmp != NULL) {
1851 tmplen = strlen(tmp);
1852 cmdlen = strlen(cmd);
1853 /* If cmd may be extended then do so */
1854 if (tmplen > cmdlen)
1855 if (el_insertstr(el, tmp + cmdlen) == -1)
1856 fatal("el_insertstr failed.");
1857 lf = el_line(el);
1858 /* Terminate argument cleanly */
1859 if (count == 1) {
1860 y = 0;
1861 if (!terminated)
1862 argterm[y++] = quote;
1863 if (lastarg || *(lf->cursor) != ' ')
1864 argterm[y++] = ' ';
1865 argterm[y] = '\0';
1866 if (y > 0 && el_insertstr(el, argterm) == -1)
1867 fatal("el_insertstr failed.");
1868 }
Darren Tuckera627d422013-06-02 07:31:17 +10001869 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001870 }
1871
1872 return count;
1873}
1874
1875/*
1876 * Determine whether a particular sftp command's arguments (if any)
1877 * represent local or remote files.
1878 */
1879static int
1880complete_is_remote(char *cmd) {
1881 int i;
1882
1883 if (cmd == NULL)
1884 return -1;
1885
1886 for (i = 0; cmds[i].c; i++) {
Damien Miller02e87802013-08-21 02:38:51 +10001887 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
Darren Tucker909d8582010-01-08 19:02:40 +11001888 return cmds[i].t;
1889 }
1890
1891 return -1;
1892}
1893
1894/* Autocomplete a filename "file" */
1895static int
1896complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1897 char *file, int remote, int lastarg, char quote, int terminated)
1898{
1899 glob_t g;
Darren Tuckerea647212013-06-06 08:19:09 +10001900 char *tmp, *tmp2, ins[8];
Darren Tucker17146d32012-10-05 10:46:16 +10001901 u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
Darren Tuckerea647212013-06-06 08:19:09 +10001902 int clen;
Darren Tucker909d8582010-01-08 19:02:40 +11001903 const LineInfo *lf;
Damien Miller02e87802013-08-21 02:38:51 +10001904
Darren Tucker909d8582010-01-08 19:02:40 +11001905 /* Glob from "file" location */
1906 if (file == NULL)
1907 tmp = xstrdup("*");
1908 else
1909 xasprintf(&tmp, "%s*", file);
1910
Darren Tucker191fcc62012-10-05 10:45:01 +10001911 /* Check if the path is absolute. */
1912 isabs = tmp[0] == '/';
1913
Darren Tucker909d8582010-01-08 19:02:40 +11001914 memset(&g, 0, sizeof(g));
1915 if (remote != LOCAL) {
1916 tmp = make_absolute(tmp, remote_path);
1917 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001918 } else
Darren Tucker909d8582010-01-08 19:02:40 +11001919 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001920
Darren Tucker909d8582010-01-08 19:02:40 +11001921 /* Determine length of pwd so we can trim completion display */
1922 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1923 /* Terminate counting on first unescaped glob metacharacter */
1924 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1925 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1926 hadglob = 1;
1927 break;
1928 }
1929 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1930 tmplen++;
1931 if (tmp[tmplen] == '/')
1932 pwdlen = tmplen + 1; /* track last seen '/' */
1933 }
Darren Tuckera627d422013-06-02 07:31:17 +10001934 free(tmp);
Damien Millerd7fd8be2014-05-15 14:24:59 +10001935 tmp = NULL;
Darren Tucker909d8582010-01-08 19:02:40 +11001936
Damien Miller02e87802013-08-21 02:38:51 +10001937 if (g.gl_matchc == 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001938 goto out;
1939
1940 if (g.gl_matchc > 1)
1941 complete_display(g.gl_pathv, pwdlen);
1942
Darren Tucker909d8582010-01-08 19:02:40 +11001943 /* Don't try to extend globs */
1944 if (file == NULL || hadglob)
1945 goto out;
1946
1947 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
Darren Tucker191fcc62012-10-05 10:45:01 +10001948 tmp = path_strip(tmp2, isabs ? NULL : remote_path);
Darren Tuckera627d422013-06-02 07:31:17 +10001949 free(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001950
1951 if (tmp == NULL)
1952 goto out;
1953
1954 tmplen = strlen(tmp);
1955 filelen = strlen(file);
1956
Darren Tucker17146d32012-10-05 10:46:16 +10001957 /* Count the number of escaped characters in the input string. */
1958 cesc = isesc = 0;
1959 for (i = 0; i < filelen; i++) {
1960 if (!isesc && file[i] == '\\' && i + 1 < filelen){
1961 isesc = 1;
1962 cesc++;
1963 } else
1964 isesc = 0;
1965 }
1966
1967 if (tmplen > (filelen - cesc)) {
1968 tmp2 = tmp + filelen - cesc;
Damien Miller02e87802013-08-21 02:38:51 +10001969 len = strlen(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001970 /* quote argument on way out */
Darren Tuckerea647212013-06-06 08:19:09 +10001971 for (i = 0; i < len; i += clen) {
1972 if ((clen = mblen(tmp2 + i, len - i)) < 0 ||
1973 (size_t)clen > sizeof(ins) - 2)
1974 fatal("invalid multibyte character");
Darren Tucker909d8582010-01-08 19:02:40 +11001975 ins[0] = '\\';
Darren Tuckerea647212013-06-06 08:19:09 +10001976 memcpy(ins + 1, tmp2 + i, clen);
1977 ins[clen + 1] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001978 switch (tmp2[i]) {
1979 case '\'':
1980 case '"':
1981 case '\\':
1982 case '\t':
Darren Tuckerd78739a2010-10-24 10:56:32 +11001983 case '[':
Darren Tucker909d8582010-01-08 19:02:40 +11001984 case ' ':
Darren Tucker17146d32012-10-05 10:46:16 +10001985 case '#':
1986 case '*':
Darren Tucker909d8582010-01-08 19:02:40 +11001987 if (quote == '\0' || tmp2[i] == quote) {
1988 if (el_insertstr(el, ins) == -1)
1989 fatal("el_insertstr "
1990 "failed.");
1991 break;
1992 }
1993 /* FALLTHROUGH */
1994 default:
1995 if (el_insertstr(el, ins + 1) == -1)
1996 fatal("el_insertstr failed.");
1997 break;
1998 }
1999 }
2000 }
2001
2002 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11002003 if (g.gl_matchc == 1) {
2004 i = 0;
Damien Miller38094812014-05-15 14:25:18 +10002005 if (!terminated && quote != '\0')
Darren Tucker909d8582010-01-08 19:02:40 +11002006 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11002007 if (*(lf->cursor - 1) != '/' &&
2008 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11002009 ins[i++] = ' ';
2010 ins[i] = '\0';
2011 if (i > 0 && el_insertstr(el, ins) == -1)
2012 fatal("el_insertstr failed.");
2013 }
Darren Tuckera627d422013-06-02 07:31:17 +10002014 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11002015
2016 out:
2017 globfree(&g);
2018 return g.gl_matchc;
2019}
2020
2021/* tab-completion hook function, called via libedit */
2022static unsigned char
2023complete(EditLine *el, int ch)
2024{
Damien Miller02e87802013-08-21 02:38:51 +10002025 char **argv, *line, quote;
Damien Miller746d1a62013-07-18 16:13:02 +10002026 int argc, carg;
2027 u_int cursor, len, terminated, ret = CC_ERROR;
Darren Tucker909d8582010-01-08 19:02:40 +11002028 const LineInfo *lf;
2029 struct complete_ctx *complete_ctx;
2030
2031 lf = el_line(el);
2032 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
2033 fatal("%s: el_get failed", __func__);
2034
2035 /* Figure out which argument the cursor points to */
2036 cursor = lf->cursor - lf->buffer;
deraadt@openbsd.orgce445b02015-08-20 22:32:42 +00002037 line = xmalloc(cursor + 1);
Darren Tucker909d8582010-01-08 19:02:40 +11002038 memcpy(line, lf->buffer, cursor);
2039 line[cursor] = '\0';
2040 argv = makeargv(line, &carg, 1, &quote, &terminated);
Darren Tuckera627d422013-06-02 07:31:17 +10002041 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11002042
2043 /* Get all the arguments on the line */
2044 len = lf->lastchar - lf->buffer;
deraadt@openbsd.orgce445b02015-08-20 22:32:42 +00002045 line = xmalloc(len + 1);
Darren Tucker909d8582010-01-08 19:02:40 +11002046 memcpy(line, lf->buffer, len);
2047 line[len] = '\0';
2048 argv = makeargv(line, &argc, 1, NULL, NULL);
2049
2050 /* Ensure cursor is at EOL or a argument boundary */
2051 if (line[cursor] != ' ' && line[cursor] != '\0' &&
2052 line[cursor] != '\n') {
Darren Tuckera627d422013-06-02 07:31:17 +10002053 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11002054 return ret;
2055 }
2056
2057 if (carg == 0) {
2058 /* Show all available commands */
2059 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
2060 ret = CC_REDISPLAY;
2061 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
2062 /* Handle the command parsing */
2063 if (complete_cmd_parse(el, argv[0], argc == carg,
Damien Miller02e87802013-08-21 02:38:51 +10002064 quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11002065 ret = CC_REDISPLAY;
2066 } else if (carg >= 1) {
2067 /* Handle file parsing */
2068 int remote = complete_is_remote(argv[0]);
2069 char *filematch = NULL;
2070
2071 if (carg > 1 && line[cursor-1] != ' ')
2072 filematch = argv[carg - 1];
2073
2074 if (remote != 0 &&
2075 complete_match(el, complete_ctx->conn,
2076 *complete_ctx->remote_pathp, filematch,
Damien Miller02e87802013-08-21 02:38:51 +10002077 remote, carg == argc, quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11002078 ret = CC_REDISPLAY;
2079 }
2080
Damien Miller02e87802013-08-21 02:38:51 +10002081 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11002082 return ret;
2083}
Darren Tuckere67f7db2010-01-08 19:50:02 +11002084#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11002085
Damien Miller20e1fab2004-02-18 14:30:55 +11002086int
Darren Tucker21063192010-01-08 17:10:36 +11002087interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11002088{
Darren Tucker909d8582010-01-08 19:02:40 +11002089 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11002090 char *dir = NULL;
2091 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10002092 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11002093 EditLine *el = NULL;
2094#ifdef USE_LIBEDIT
2095 History *hl = NULL;
2096 HistEvent hev;
2097 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11002098 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11002099
2100 if (!batchmode && isatty(STDIN_FILENO)) {
2101 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
2102 fatal("Couldn't initialise editline");
2103 if ((hl = history_init()) == NULL)
2104 fatal("Couldn't initialise editline history");
2105 history(hl, &hev, H_SETSIZE, 100);
2106 el_set(el, EL_HIST, history, hl);
2107
2108 el_set(el, EL_PROMPT, prompt);
2109 el_set(el, EL_EDITOR, "emacs");
2110 el_set(el, EL_TERMINAL, NULL);
2111 el_set(el, EL_SIGNAL, 1);
2112 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11002113
2114 /* Tab Completion */
Damien Miller02e87802013-08-21 02:38:51 +10002115 el_set(el, EL_ADDFN, "ftp-complete",
Darren Tuckerd78739a2010-10-24 10:56:32 +11002116 "Context sensitive argument completion", complete);
Darren Tucker909d8582010-01-08 19:02:40 +11002117 complete_ctx.conn = conn;
2118 complete_ctx.remote_pathp = &remote_path;
2119 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
2120 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Damien Millere0ee7272013-08-21 02:42:35 +10002121 /* enable ctrl-left-arrow and ctrl-right-arrow */
2122 el_set(el, EL_BIND, "\\e[1;5C", "em-next-word", NULL);
2123 el_set(el, EL_BIND, "\\e[5C", "em-next-word", NULL);
2124 el_set(el, EL_BIND, "\\e[1;5D", "ed-prev-word", NULL);
2125 el_set(el, EL_BIND, "\\e\\e[D", "ed-prev-word", NULL);
Damien Miller61353b32013-09-14 09:45:32 +10002126 /* make ^w match ksh behaviour */
2127 el_set(el, EL_BIND, "^w", "ed-delete-prev-word", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11002128 }
2129#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002130
Darren Tucker909d8582010-01-08 19:02:40 +11002131 remote_path = do_realpath(conn, ".");
2132 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11002133 fatal("Need cwd");
2134
2135 if (file1 != NULL) {
2136 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11002137 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11002138
2139 if (remote_is_dir(conn, dir) && file2 == NULL) {
Damien Miller9303e652013-04-23 15:22:40 +10002140 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00002141 mprintf("Changing to: %s\n", dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002142 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11002143 if (parse_dispatch_command(conn, cmd,
2144 &remote_path, 1) != 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10002145 free(dir);
2146 free(remote_path);
2147 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002148 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11002149 }
Damien Miller20e1fab2004-02-18 14:30:55 +11002150 } else {
Darren Tucker0af24052012-10-05 10:41:25 +10002151 /* XXX this is wrong wrt quoting */
Damien Miller0d032412013-07-25 11:56:52 +10002152 snprintf(cmd, sizeof cmd, "get%s %s%s%s",
2153 global_aflag ? " -a" : "", dir,
2154 file2 == NULL ? "" : " ",
2155 file2 == NULL ? "" : file2);
Darren Tucker909d8582010-01-08 19:02:40 +11002156 err = parse_dispatch_command(conn, cmd,
2157 &remote_path, 1);
Darren Tuckera627d422013-06-02 07:31:17 +10002158 free(dir);
2159 free(remote_path);
2160 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002161 return (err);
2162 }
Darren Tuckera627d422013-06-02 07:31:17 +10002163 free(dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002164 }
2165
millert@openbsd.orgdb995f22014-11-26 18:34:51 +00002166 setvbuf(stdout, NULL, _IOLBF, 0);
2167 setvbuf(infile, NULL, _IOLBF, 0);
Damien Miller20e1fab2004-02-18 14:30:55 +11002168
Damien Miller0e2c1022005-08-12 22:16:22 +10002169 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11002170 err = 0;
2171 for (;;) {
2172 char *cp;
2173
Darren Tuckercdf547a2004-05-24 10:12:19 +10002174 signal(SIGINT, SIG_IGN);
2175
Darren Tucker2d963d82004-11-07 20:04:10 +11002176 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002177 if (interactive)
2178 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11002179 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002180 if (interactive)
2181 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11002182 break;
2183 }
Damien Miller0e2c1022005-08-12 22:16:22 +10002184 if (!interactive) { /* Echo command */
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00002185 mprintf("sftp> %s", cmd);
Damien Miller0e2c1022005-08-12 22:16:22 +10002186 if (strlen(cmd) > 0 &&
2187 cmd[strlen(cmd) - 1] != '\n')
2188 printf("\n");
2189 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002190 } else {
2191#ifdef USE_LIBEDIT
2192 const char *line;
2193 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11002194
Darren Tucker909d8582010-01-08 19:02:40 +11002195 if ((line = el_gets(el, &count)) == NULL ||
2196 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002197 printf("\n");
2198 break;
2199 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002200 history(hl, &hev, H_ENTER, line);
2201 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
2202 fprintf(stderr, "Error: input line too long\n");
2203 continue;
2204 }
2205#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002206 }
2207
Damien Miller20e1fab2004-02-18 14:30:55 +11002208 cp = strrchr(cmd, '\n');
2209 if (cp)
2210 *cp = '\0';
2211
Darren Tuckercdf547a2004-05-24 10:12:19 +10002212 /* Handle user interrupts gracefully during commands */
2213 interrupted = 0;
2214 signal(SIGINT, cmd_interrupt);
2215
Darren Tucker909d8582010-01-08 19:02:40 +11002216 err = parse_dispatch_command(conn, cmd, &remote_path,
2217 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11002218 if (err != 0)
2219 break;
2220 }
Darren Tuckera627d422013-06-02 07:31:17 +10002221 free(remote_path);
2222 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002223
Tim Rice027e8b12005-08-15 14:52:50 -07002224#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10002225 if (el != NULL)
2226 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07002227#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10002228
Damien Miller20e1fab2004-02-18 14:30:55 +11002229 /* err == 1 signifies normal "quit" exit */
2230 return (err >= 0 ? 0 : -1);
2231}
Damien Miller62d57f62003-01-10 21:43:24 +11002232
Ben Lindstrombba81212001-06-25 05:01:22 +00002233static void
Damien Millercc685c12003-06-04 22:51:38 +10002234connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11002235{
2236 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002237
Damien Miller33804262001-02-04 23:20:18 +11002238#ifdef USE_PIPES
2239 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002240
Damien Miller33804262001-02-04 23:20:18 +11002241 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2242 fatal("pipe: %s", strerror(errno));
2243 *in = pin[0];
2244 *out = pout[1];
2245 c_in = pout[0];
2246 c_out = pin[1];
2247#else /* USE_PIPES */
2248 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002249
Damien Miller33804262001-02-04 23:20:18 +11002250 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2251 fatal("socketpair: %s", strerror(errno));
2252 *in = *out = inout[0];
2253 c_in = c_out = inout[1];
2254#endif /* USE_PIPES */
2255
Damien Millercc685c12003-06-04 22:51:38 +10002256 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002257 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002258 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002259 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2260 (dup2(c_out, STDOUT_FILENO) == -1)) {
2261 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002262 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002263 }
2264 close(*in);
2265 close(*out);
2266 close(c_in);
2267 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002268
2269 /*
2270 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002271 * ignore SIGINT if we want to gracefully abort commands,
2272 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002273 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2274 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002275 */
2276 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002277 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002278 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002279 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002280 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002281 }
2282
Damien Millercc685c12003-06-04 22:51:38 +10002283 signal(SIGTERM, killchild);
2284 signal(SIGINT, killchild);
2285 signal(SIGHUP, killchild);
millert@openbsd.org2c6697c2016-10-18 12:41:22 +00002286 signal(SIGTSTP, suspchild);
2287 signal(SIGTTIN, suspchild);
2288 signal(SIGTTOU, suspchild);
Damien Miller33804262001-02-04 23:20:18 +11002289 close(c_in);
2290 close(c_out);
2291}
2292
Ben Lindstrombba81212001-06-25 05:01:22 +00002293static void
Damien Miller33804262001-02-04 23:20:18 +11002294usage(void)
2295{
Damien Miller025e01c2002-02-08 22:06:29 +11002296 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002297
Ben Lindstrom1e243242001-09-18 05:38:44 +00002298 fprintf(stderr,
djm@openbsd.org3575f0b2017-05-02 08:54:19 +00002299 "usage: %s [-46aCfpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002300 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002301 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002302 " [-o ssh_option] [-P port] [-R num_requests] "
2303 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002304 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002305 " %s [user@]host[:file ...]\n"
2306 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002307 " %s -b batchfile [user@]host\n",
2308 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002309 exit(1);
2310}
2311
Kevin Stevesef4eea92001-02-05 12:42:17 +00002312int
Damien Miller33804262001-02-04 23:20:18 +11002313main(int argc, char **argv)
2314{
Damien Miller956f3fb2003-01-10 21:40:00 +11002315 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002316 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002317 int debug_level = 0, sshver = 2;
2318 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002319 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002320 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002321 LogLevel ll = SYSLOG_LEVEL_INFO;
2322 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002323 extern int optind;
2324 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002325 struct sftp_conn *conn;
2326 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2327 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002328 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002329
dtucker@openbsd.orgffb1e7e2016-02-15 09:47:49 +00002330 ssh_malloc_init(); /* must be called before any mallocs */
Darren Tuckerce321d82005-10-03 18:11:24 +10002331 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2332 sanitise_stdfd();
Damien Millerdda78a02016-12-12 13:57:10 +11002333 msetlocale();
Darren Tuckerce321d82005-10-03 18:11:24 +10002334
Damien Miller59d3d5b2003-08-22 09:34:41 +10002335 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002336 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002337 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002338 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002339 addargs(&args, "-oForwardX11 no");
2340 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002341 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002342 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002343
Ben Lindstrom387c4722001-05-08 20:27:25 +00002344 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002345 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002346
Darren Tucker282b4022009-10-07 08:23:06 +11002347 while ((ch = getopt(argc, argv,
Damien Millerf29238e2013-10-17 11:48:52 +11002348 "1246afhpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002349 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002350 /* Passed through to ssh(1) */
2351 case '4':
2352 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002353 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002354 addargs(&args, "-%c", ch);
2355 break;
2356 /* Passed through to ssh(1) with argument */
2357 case 'F':
2358 case 'c':
2359 case 'i':
2360 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002361 addargs(&args, "-%c", ch);
2362 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002363 break;
2364 case 'q':
Damien Miller9303e652013-04-23 15:22:40 +10002365 ll = SYSLOG_LEVEL_ERROR;
2366 quiet = 1;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002367 showprogress = 0;
2368 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002369 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002370 case 'P':
2371 addargs(&args, "-oPort %s", optarg);
2372 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002373 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002374 if (debug_level < 3) {
2375 addargs(&args, "-v");
2376 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2377 }
2378 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002379 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002380 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002381 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002382 if (sftp_server == NULL)
2383 sftp_server = _PATH_SFTP_SERVER;
2384 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002385 case '2':
2386 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002387 break;
Damien Miller0d032412013-07-25 11:56:52 +10002388 case 'a':
2389 global_aflag = 1;
2390 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002391 case 'B':
2392 copy_buffer_len = strtol(optarg, &cp, 10);
2393 if (copy_buffer_len == 0 || *cp != '\0')
2394 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002395 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002396 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002397 if (batchmode)
2398 fatal("Batch file already specified.");
2399
2400 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002401 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002402 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002403 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002404 showprogress = 0;
Damien Miller9303e652013-04-23 15:22:40 +10002405 quiet = batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002406 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002407 break;
Damien Millerf29238e2013-10-17 11:48:52 +11002408 case 'f':
2409 global_fflag = 1;
2410 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002411 case 'p':
2412 global_pflag = 1;
2413 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002414 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002415 sftp_direct = optarg;
2416 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002417 case 'l':
2418 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2419 &errstr);
2420 if (errstr != NULL)
2421 usage();
2422 limit_kbps *= 1024; /* kbps */
2423 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002424 case 'r':
2425 global_rflag = 1;
2426 break;
Damien Miller16a13332002-02-13 14:03:56 +11002427 case 'R':
2428 num_requests = strtol(optarg, &cp, 10);
2429 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002430 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002431 optarg);
2432 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002433 case 's':
2434 sftp_server = optarg;
2435 break;
2436 case 'S':
2437 ssh_program = optarg;
2438 replacearg(&args, 0, "%s", ssh_program);
2439 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002440 case 'h':
2441 default:
Damien Miller33804262001-02-04 23:20:18 +11002442 usage();
2443 }
2444 }
2445
Damien Millerc0f27d82004-03-08 23:12:19 +11002446 if (!isatty(STDERR_FILENO))
2447 showprogress = 0;
2448
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002449 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2450
Damien Millerd14ee1e2002-02-05 12:27:31 +11002451 if (sftp_direct == NULL) {
2452 if (optind == argc || argc > (optind + 2))
2453 usage();
Damien Miller33804262001-02-04 23:20:18 +11002454
Damien Millerd14ee1e2002-02-05 12:27:31 +11002455 userhost = xstrdup(argv[optind]);
2456 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002457
Ben Lindstromc276c122002-12-23 02:14:51 +00002458 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002459 host = userhost;
2460 else {
2461 *host++ = '\0';
2462 if (!userhost[0]) {
2463 fprintf(stderr, "Missing username\n");
2464 usage();
2465 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002466 addargs(&args, "-l");
2467 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002468 }
2469
Damien Millerec692032004-01-27 21:22:00 +11002470 if ((cp = colon(host)) != NULL) {
2471 *cp++ = '\0';
2472 file1 = cp;
2473 }
2474
Damien Millerd14ee1e2002-02-05 12:27:31 +11002475 host = cleanhostname(host);
2476 if (!*host) {
2477 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002478 usage();
2479 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002480
Damien Millerd14ee1e2002-02-05 12:27:31 +11002481 addargs(&args, "-oProtocol %d", sshver);
2482
2483 /* no subsystem if the server-spec contains a '/' */
2484 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2485 addargs(&args, "-s");
2486
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002487 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002488 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002489 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002490 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002491
Damien Millercc685c12003-06-04 22:51:38 +10002492 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002493 } else {
2494 args.list = NULL;
2495 addargs(&args, "sftp-server");
2496
Damien Millercc685c12003-06-04 22:51:38 +10002497 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002498 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002499 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002500
Damien Miller65e42f82010-09-24 22:15:11 +10002501 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002502 if (conn == NULL)
2503 fatal("Couldn't initialise connection to server");
2504
Damien Miller9303e652013-04-23 15:22:40 +10002505 if (!quiet) {
Darren Tucker21063192010-01-08 17:10:36 +11002506 if (sftp_direct == NULL)
2507 fprintf(stderr, "Connected to %s.\n", host);
2508 else
2509 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2510 }
2511
2512 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002513
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002514#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002515 shutdown(in, SHUT_RDWR);
2516 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002517#endif
2518
Damien Miller33804262001-02-04 23:20:18 +11002519 close(in);
2520 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002521 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002522 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002523
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002524 while (waitpid(sshpid, NULL, 0) == -1)
2525 if (errno != EINTR)
2526 fatal("Couldn't wait for ssh process: %s",
2527 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002528
Damien Miller956f3fb2003-01-10 21:40:00 +11002529 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002530}