blob: 5ce864eeb0febe644f6a32da4216c59325961503 [file] [log] [blame]
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00001/* $OpenBSD: sftp.c,v 1.182 2017/11/03 03:46:52 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
Damien Millerb6c85fc2007-01-05 16:30:41 +1100220/* ARGSUSED */
Damien Miller20e1fab2004-02-18 14:30:55 +1100221static void
Darren Tuckercdf547a2004-05-24 10:12:19 +1000222killchild(int signo)
223{
Darren Tuckerba66df82005-01-24 21:57:40 +1100224 if (sshpid > 1) {
Darren Tuckercdf547a2004-05-24 10:12:19 +1000225 kill(sshpid, SIGTERM);
Darren Tuckerba66df82005-01-24 21:57:40 +1100226 waitpid(sshpid, NULL, 0);
227 }
Darren Tuckercdf547a2004-05-24 10:12:19 +1000228
229 _exit(1);
230}
231
Damien Millerb6c85fc2007-01-05 16:30:41 +1100232/* ARGSUSED */
Darren Tuckercdf547a2004-05-24 10:12:19 +1000233static void
millert@openbsd.org2c6697c2016-10-18 12:41:22 +0000234suspchild(int signo)
235{
236 if (sshpid > 1) {
237 kill(sshpid, signo);
238 while (waitpid(sshpid, NULL, WUNTRACED) == -1 && errno == EINTR)
239 continue;
240 }
241 kill(getpid(), SIGSTOP);
242}
243
244/* ARGSUSED */
245static void
Darren Tuckercdf547a2004-05-24 10:12:19 +1000246cmd_interrupt(int signo)
247{
248 const char msg[] = "\rInterrupt \n";
Darren Tuckere2f189a2004-12-06 22:45:53 +1100249 int olderrno = errno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000250
Darren Tuckerdbee3082013-05-16 20:32:29 +1000251 (void)write(STDERR_FILENO, msg, sizeof(msg) - 1);
Darren Tuckercdf547a2004-05-24 10:12:19 +1000252 interrupted = 1;
Darren Tuckere2f189a2004-12-06 22:45:53 +1100253 errno = olderrno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000254}
255
256static void
Damien Miller20e1fab2004-02-18 14:30:55 +1100257help(void)
258{
Damien Miller62fd18a2009-01-28 16:14:09 +1100259 printf("Available commands:\n"
260 "bye Quit sftp\n"
261 "cd path Change remote directory to 'path'\n"
262 "chgrp grp path Change group of file 'path' to 'grp'\n"
263 "chmod mode path Change permissions of file 'path' to 'mode'\n"
264 "chown own path Change owner of file 'path' to 'own'\n"
265 "df [-hi] [path] Display statistics for current directory or\n"
266 " filesystem containing 'path'\n"
267 "exit Quit sftp\n"
djm@openbsd.org4a459222014-10-06 00:47:15 +0000268 "get [-afPpRr] remote [local] Download file\n"
269 "reget [-fPpRr] remote [local] Resume download file\n"
270 "reput [-fPpRr] [local] remote Resume upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100271 "help Display this help text\n"
272 "lcd path Change local directory to 'path'\n"
273 "lls [ls-options [path]] Display local directory listing\n"
274 "lmkdir path Create local directory\n"
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100275 "ln [-s] oldpath newpath Link remote file (-s for symlink)\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100276 "lpwd Print local working directory\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100277 "ls [-1afhlnrSt] [path] Display remote directory listing\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100278 "lumask umask Set local umask to 'umask'\n"
279 "mkdir path Create remote directory\n"
280 "progress Toggle display of progress meter\n"
djm@openbsd.org4a459222014-10-06 00:47:15 +0000281 "put [-afPpRr] local [remote] Upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100282 "pwd Display remote working directory\n"
283 "quit Quit sftp\n"
284 "rename oldpath newpath Rename remote file\n"
285 "rm path Delete remote file\n"
286 "rmdir path Remove remote directory\n"
287 "symlink oldpath newpath Symlink remote file\n"
288 "version Show SFTP version\n"
289 "!command Execute 'command' in local shell\n"
290 "! Escape to local shell\n"
291 "? Synonym for help\n");
Damien Miller20e1fab2004-02-18 14:30:55 +1100292}
293
294static void
295local_do_shell(const char *args)
296{
297 int status;
298 char *shell;
299 pid_t pid;
300
301 if (!*args)
302 args = NULL;
303
Damien Miller38d9a962010-10-07 22:07:11 +1100304 if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
Damien Miller20e1fab2004-02-18 14:30:55 +1100305 shell = _PATH_BSHELL;
306
307 if ((pid = fork()) == -1)
308 fatal("Couldn't fork: %s", strerror(errno));
309
310 if (pid == 0) {
311 /* XXX: child has pipe fds to ssh subproc open - issue? */
312 if (args) {
313 debug3("Executing %s -c \"%s\"", shell, args);
314 execl(shell, shell, "-c", args, (char *)NULL);
315 } else {
316 debug3("Executing %s", shell);
317 execl(shell, shell, (char *)NULL);
318 }
319 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
320 strerror(errno));
321 _exit(1);
322 }
323 while (waitpid(pid, &status, 0) == -1)
324 if (errno != EINTR)
325 fatal("Couldn't wait for child: %s", strerror(errno));
326 if (!WIFEXITED(status))
Damien Miller55b04f12006-03-26 14:23:17 +1100327 error("Shell exited abnormally");
Damien Miller20e1fab2004-02-18 14:30:55 +1100328 else if (WEXITSTATUS(status))
329 error("Shell exited with status %d", WEXITSTATUS(status));
330}
331
332static void
333local_do_ls(const char *args)
334{
335 if (!args || !*args)
336 local_do_shell(_PATH_LS);
337 else {
338 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
339 char *buf = xmalloc(len);
340
341 /* XXX: quoting - rip quoting code from ftp? */
342 snprintf(buf, len, _PATH_LS " %s", args);
343 local_do_shell(buf);
Darren Tuckera627d422013-06-02 07:31:17 +1000344 free(buf);
Damien Miller20e1fab2004-02-18 14:30:55 +1100345 }
346}
347
348/* Strip one path (usually the pwd) from the start of another */
349static char *
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000350path_strip(const char *path, const char *strip)
Damien Miller20e1fab2004-02-18 14:30:55 +1100351{
352 size_t len;
353
354 if (strip == NULL)
355 return (xstrdup(path));
356
357 len = strlen(strip);
Darren Tuckere2f189a2004-12-06 22:45:53 +1100358 if (strncmp(path, strip, len) == 0) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100359 if (strip[len - 1] != '/' && path[len] == '/')
360 len++;
361 return (xstrdup(path + len));
362 }
363
364 return (xstrdup(path));
365}
366
367static char *
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000368make_absolute(char *p, const char *pwd)
Damien Miller20e1fab2004-02-18 14:30:55 +1100369{
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000370 char *abs_str;
Damien Miller20e1fab2004-02-18 14:30:55 +1100371
372 /* Derelativise */
373 if (p && p[0] != '/') {
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000374 abs_str = path_append(pwd, p);
Darren Tuckera627d422013-06-02 07:31:17 +1000375 free(p);
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000376 return(abs_str);
Damien Miller20e1fab2004-02-18 14:30:55 +1100377 } else
378 return(p);
379}
380
381static int
Damien Miller0d032412013-07-25 11:56:52 +1000382parse_getput_flags(const char *cmd, char **argv, int argc,
Damien Millerf29238e2013-10-17 11:48:52 +1100383 int *aflag, int *fflag, int *pflag, int *rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100384{
Damien Millerf184bcf2008-06-29 22:45:13 +1000385 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000386 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100387
Damien Miller1cbc2922007-10-26 14:27:45 +1000388 optind = optreset = 1;
389 opterr = 0;
390
Damien Millerf29238e2013-10-17 11:48:52 +1100391 *aflag = *fflag = *rflag = *pflag = 0;
392 while ((ch = getopt(argc, argv, "afPpRr")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000393 switch (ch) {
Damien Miller0d032412013-07-25 11:56:52 +1000394 case 'a':
395 *aflag = 1;
396 break;
Damien Millerf29238e2013-10-17 11:48:52 +1100397 case 'f':
398 *fflag = 1;
399 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100400 case 'p':
401 case 'P':
402 *pflag = 1;
403 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100404 case 'r':
405 case 'R':
406 *rflag = 1;
407 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100408 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000409 error("%s: Invalid flag -%c", cmd, optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000410 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100411 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100412 }
413
Damien Miller1cbc2922007-10-26 14:27:45 +1000414 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100415}
416
417static int
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100418parse_link_flags(const char *cmd, char **argv, int argc, int *sflag)
419{
420 extern int opterr, optind, optopt, optreset;
421 int ch;
422
423 optind = optreset = 1;
424 opterr = 0;
425
426 *sflag = 0;
427 while ((ch = getopt(argc, argv, "s")) != -1) {
428 switch (ch) {
429 case 's':
430 *sflag = 1;
431 break;
432 default:
433 error("%s: Invalid flag -%c", cmd, optopt);
434 return -1;
435 }
436 }
437
438 return optind;
439}
440
441static int
Damien Millerc7dba122013-08-21 02:41:15 +1000442parse_rename_flags(const char *cmd, char **argv, int argc, int *lflag)
443{
444 extern int opterr, optind, optopt, optreset;
445 int ch;
446
447 optind = optreset = 1;
448 opterr = 0;
449
450 *lflag = 0;
451 while ((ch = getopt(argc, argv, "l")) != -1) {
452 switch (ch) {
453 case 'l':
454 *lflag = 1;
455 break;
456 default:
457 error("%s: Invalid flag -%c", cmd, optopt);
458 return -1;
459 }
460 }
461
462 return optind;
463}
464
465static int
Damien Miller1cbc2922007-10-26 14:27:45 +1000466parse_ls_flags(char **argv, int argc, int *lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100467{
Damien Millerf184bcf2008-06-29 22:45:13 +1000468 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000469 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100470
Damien Miller1cbc2922007-10-26 14:27:45 +1000471 optind = optreset = 1;
472 opterr = 0;
473
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000474 *lflag = LS_NAME_SORT;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100475 while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000476 switch (ch) {
477 case '1':
478 *lflag &= ~VIEW_FLAGS;
479 *lflag |= LS_SHORT_VIEW;
480 break;
481 case 'S':
482 *lflag &= ~SORT_FLAGS;
483 *lflag |= LS_SIZE_SORT;
484 break;
485 case 'a':
486 *lflag |= LS_SHOW_ALL;
487 break;
488 case 'f':
489 *lflag &= ~SORT_FLAGS;
490 break;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100491 case 'h':
492 *lflag |= LS_SI_UNITS;
493 break;
Damien Miller1cbc2922007-10-26 14:27:45 +1000494 case 'l':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100495 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000496 *lflag |= LS_LONG_VIEW;
497 break;
498 case 'n':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100499 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000500 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
501 break;
502 case 'r':
503 *lflag |= LS_REVERSE_SORT;
504 break;
505 case 't':
506 *lflag &= ~SORT_FLAGS;
507 *lflag |= LS_TIME_SORT;
508 break;
509 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000510 error("ls: Invalid flag -%c", optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000511 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100512 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100513 }
514
Damien Miller1cbc2922007-10-26 14:27:45 +1000515 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100516}
517
518static int
Damien Millerd671e5a2008-05-19 14:53:33 +1000519parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
520{
Damien Millerf184bcf2008-06-29 22:45:13 +1000521 extern int opterr, optind, optopt, optreset;
Damien Millerd671e5a2008-05-19 14:53:33 +1000522 int ch;
523
524 optind = optreset = 1;
525 opterr = 0;
526
527 *hflag = *iflag = 0;
528 while ((ch = getopt(argc, argv, "hi")) != -1) {
529 switch (ch) {
530 case 'h':
531 *hflag = 1;
532 break;
533 case 'i':
534 *iflag = 1;
535 break;
536 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000537 error("%s: Invalid flag -%c", cmd, optopt);
Damien Millerd671e5a2008-05-19 14:53:33 +1000538 return -1;
539 }
540 }
541
542 return optind;
543}
544
545static int
Damien Miller036d3072013-08-21 02:41:46 +1000546parse_no_flags(const char *cmd, char **argv, int argc)
547{
548 extern int opterr, optind, optopt, optreset;
549 int ch;
550
551 optind = optreset = 1;
552 opterr = 0;
553
554 while ((ch = getopt(argc, argv, "")) != -1) {
555 switch (ch) {
556 default:
557 error("%s: Invalid flag -%c", cmd, optopt);
558 return -1;
559 }
560 }
561
562 return optind;
563}
564
565static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000566is_dir(const char *path)
Damien Miller20e1fab2004-02-18 14:30:55 +1100567{
568 struct stat sb;
569
570 /* XXX: report errors? */
571 if (stat(path, &sb) == -1)
572 return(0);
573
Darren Tucker1e80e402006-09-21 12:59:33 +1000574 return(S_ISDIR(sb.st_mode));
Damien Miller20e1fab2004-02-18 14:30:55 +1100575}
576
577static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000578remote_is_dir(struct sftp_conn *conn, const char *path)
Damien Miller20e1fab2004-02-18 14:30:55 +1100579{
580 Attrib *a;
581
582 /* XXX: report errors? */
583 if ((a = do_stat(conn, path, 1)) == NULL)
584 return(0);
585 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
586 return(0);
Darren Tucker1e80e402006-09-21 12:59:33 +1000587 return(S_ISDIR(a->perm));
Damien Miller20e1fab2004-02-18 14:30:55 +1100588}
589
Darren Tucker1b0dd172009-10-07 08:37:48 +1100590/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
Damien Miller20e1fab2004-02-18 14:30:55 +1100591static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000592pathname_is_dir(const char *pathname)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100593{
594 size_t l = strlen(pathname);
595
596 return l > 0 && pathname[l - 1] == '/';
597}
598
599static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000600process_get(struct sftp_conn *conn, const char *src, const char *dst,
601 const char *pwd, int pflag, int rflag, int resume, int fflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100602{
603 char *abs_src = NULL;
604 char *abs_dst = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100605 glob_t g;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100606 char *filename, *tmp=NULL;
Damien Miller00707762014-07-09 13:07:06 +1000607 int i, r, err = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100608
609 abs_src = xstrdup(src);
610 abs_src = make_absolute(abs_src, pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +1100611 memset(&g, 0, sizeof(g));
Darren Tucker1b0dd172009-10-07 08:37:48 +1100612
Damien Miller20e1fab2004-02-18 14:30:55 +1100613 debug3("Looking up %s", abs_src);
Damien Miller00707762014-07-09 13:07:06 +1000614 if ((r = remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) != 0) {
615 if (r == GLOB_NOSPACE) {
616 error("Too many matches for \"%s\".", abs_src);
617 } else {
618 error("File \"%s\" not found.", abs_src);
619 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100620 err = -1;
621 goto out;
622 }
623
Darren Tucker1b0dd172009-10-07 08:37:48 +1100624 /*
625 * If multiple matches then dst must be a directory or
626 * unspecified.
627 */
628 if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
629 error("Multiple source paths, but destination "
630 "\"%s\" is not a directory", dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100631 err = -1;
632 goto out;
633 }
634
Darren Tuckercdf547a2004-05-24 10:12:19 +1000635 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100636 tmp = xstrdup(g.gl_pathv[i]);
637 if ((filename = basename(tmp)) == NULL) {
638 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000639 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100640 err = -1;
641 goto out;
642 }
643
644 if (g.gl_matchc == 1 && dst) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100645 if (is_dir(dst)) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100646 abs_dst = path_append(dst, filename);
647 } else {
Damien Miller20e1fab2004-02-18 14:30:55 +1100648 abs_dst = xstrdup(dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100649 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100650 } else if (dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100651 abs_dst = path_append(dst, filename);
652 } else {
653 abs_dst = xstrdup(filename);
654 }
Darren Tuckera627d422013-06-02 07:31:17 +1000655 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100656
Damien Miller0d032412013-07-25 11:56:52 +1000657 resume |= global_aflag;
658 if (!quiet && resume)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000659 mprintf("Resuming %s to %s\n",
660 g.gl_pathv[i], abs_dst);
Damien Miller0d032412013-07-25 11:56:52 +1000661 else if (!quiet && !resume)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000662 mprintf("Fetching %s to %s\n",
663 g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100664 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
Damien Miller0d032412013-07-25 11:56:52 +1000665 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
Damien Millerf29238e2013-10-17 11:48:52 +1100666 pflag || global_pflag, 1, resume,
667 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100668 err = -1;
669 } else {
670 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
Damien Millerf29238e2013-10-17 11:48:52 +1100671 pflag || global_pflag, resume,
672 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100673 err = -1;
674 }
Darren Tuckera627d422013-06-02 07:31:17 +1000675 free(abs_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100676 abs_dst = NULL;
677 }
678
679out:
Darren Tuckera627d422013-06-02 07:31:17 +1000680 free(abs_src);
Damien Miller20e1fab2004-02-18 14:30:55 +1100681 globfree(&g);
682 return(err);
683}
684
685static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000686process_put(struct sftp_conn *conn, const char *src, const char *dst,
687 const char *pwd, int pflag, int rflag, int resume, int fflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100688{
689 char *tmp_dst = NULL;
690 char *abs_dst = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100691 char *tmp = NULL, *filename = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100692 glob_t g;
693 int err = 0;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100694 int i, dst_is_dir = 1;
Damien Milleraec5cf82008-02-10 22:26:24 +1100695 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100696
697 if (dst) {
698 tmp_dst = xstrdup(dst);
699 tmp_dst = make_absolute(tmp_dst, pwd);
700 }
701
702 memset(&g, 0, sizeof(g));
703 debug3("Looking up %s", src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100704 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100705 error("File \"%s\" not found.", src);
706 err = -1;
707 goto out;
708 }
709
Darren Tucker1b0dd172009-10-07 08:37:48 +1100710 /* If we aren't fetching to pwd then stash this status for later */
711 if (tmp_dst != NULL)
712 dst_is_dir = remote_is_dir(conn, tmp_dst);
713
Damien Miller20e1fab2004-02-18 14:30:55 +1100714 /* If multiple matches, dst may be directory or unspecified */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100715 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
716 error("Multiple paths match, but destination "
717 "\"%s\" is not a directory", tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100718 err = -1;
719 goto out;
720 }
721
Darren Tuckercdf547a2004-05-24 10:12:19 +1000722 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Milleraec5cf82008-02-10 22:26:24 +1100723 if (stat(g.gl_pathv[i], &sb) == -1) {
724 err = -1;
725 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
726 continue;
727 }
Damien Miller02e87802013-08-21 02:38:51 +1000728
Darren Tucker1b0dd172009-10-07 08:37:48 +1100729 tmp = xstrdup(g.gl_pathv[i]);
730 if ((filename = basename(tmp)) == NULL) {
731 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000732 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100733 err = -1;
734 goto out;
735 }
736
737 if (g.gl_matchc == 1 && tmp_dst) {
738 /* If directory specified, append filename */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100739 if (dst_is_dir)
740 abs_dst = path_append(tmp_dst, filename);
741 else
Damien Miller20e1fab2004-02-18 14:30:55 +1100742 abs_dst = xstrdup(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100743 } else if (tmp_dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100744 abs_dst = path_append(tmp_dst, filename);
745 } else {
746 abs_dst = make_absolute(xstrdup(filename), pwd);
747 }
Darren Tuckera627d422013-06-02 07:31:17 +1000748 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100749
Damien Millerd8accc02014-05-15 13:46:25 +1000750 resume |= global_aflag;
751 if (!quiet && resume)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000752 mprintf("Resuming upload of %s to %s\n",
753 g.gl_pathv[i], abs_dst);
Damien Millerd8accc02014-05-15 13:46:25 +1000754 else if (!quiet && !resume)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000755 mprintf("Uploading %s to %s\n",
756 g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100757 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
758 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
Damien Millerd8accc02014-05-15 13:46:25 +1000759 pflag || global_pflag, 1, resume,
Damien Millerf29238e2013-10-17 11:48:52 +1100760 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100761 err = -1;
762 } else {
763 if (do_upload(conn, g.gl_pathv[i], abs_dst,
Damien Millerd8accc02014-05-15 13:46:25 +1000764 pflag || global_pflag, resume,
Damien Millerf29238e2013-10-17 11:48:52 +1100765 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100766 err = -1;
767 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100768 }
769
770out:
Darren Tuckera627d422013-06-02 07:31:17 +1000771 free(abs_dst);
772 free(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100773 globfree(&g);
774 return(err);
775}
776
777static int
778sdirent_comp(const void *aa, const void *bb)
779{
780 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
781 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000782 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100783
Darren Tuckerb9123452004-06-22 13:06:45 +1000784#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000785 if (sort_flag & LS_NAME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000786 return (rmul * strcmp(a->filename, b->filename));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000787 else if (sort_flag & LS_TIME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000788 return (rmul * NCMP(a->a.mtime, b->a.mtime));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000789 else if (sort_flag & LS_SIZE_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000790 return (rmul * NCMP(a->a.size, b->a.size));
791
792 fatal("Unknown ls sort type");
Damien Miller20e1fab2004-02-18 14:30:55 +1100793}
794
795/* sftp ls.1 replacement for directories */
796static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000797do_ls_dir(struct sftp_conn *conn, const char *path,
798 const char *strip_path, int lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100799{
Damien Millereccb9de2005-06-17 12:59:34 +1000800 int n;
801 u_int c = 1, colspace = 0, columns = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100802 SFTP_DIRENT **d;
803
804 if ((n = do_readdir(conn, path, &d)) != 0)
805 return (n);
806
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000807 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000808 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100809 struct winsize ws;
810 char *tmp;
811
812 /* Count entries for sort and find longest filename */
Darren Tucker9a526452004-06-22 13:09:55 +1000813 for (n = 0; d[n] != NULL; n++) {
814 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +0000815 m = MAXIMUM(m, strlen(d[n]->filename));
Darren Tucker9a526452004-06-22 13:09:55 +1000816 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100817
818 /* Add any subpath that also needs to be counted */
819 tmp = path_strip(path, strip_path);
820 m += strlen(tmp);
Darren Tuckera627d422013-06-02 07:31:17 +1000821 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100822
823 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
824 width = ws.ws_col;
825
826 columns = width / (m + 2);
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +0000827 columns = MAXIMUM(columns, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +1100828 colspace = width / columns;
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +0000829 colspace = MINIMUM(colspace, width);
Damien Miller20e1fab2004-02-18 14:30:55 +1100830 }
831
Darren Tuckerb9123452004-06-22 13:06:45 +1000832 if (lflag & SORT_FLAGS) {
Damien Miller653b93b2005-11-05 15:15:23 +1100833 for (n = 0; d[n] != NULL; n++)
834 ; /* count entries */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000835 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
Darren Tuckerb9123452004-06-22 13:06:45 +1000836 qsort(d, n, sizeof(*d), sdirent_comp);
837 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100838
Darren Tuckercdf547a2004-05-24 10:12:19 +1000839 for (n = 0; d[n] != NULL && !interrupted; n++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100840 char *tmp, *fname;
841
Darren Tucker9a526452004-06-22 13:09:55 +1000842 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
843 continue;
844
Damien Miller20e1fab2004-02-18 14:30:55 +1100845 tmp = path_append(path, d[n]->filename);
846 fname = path_strip(tmp, strip_path);
Darren Tuckera627d422013-06-02 07:31:17 +1000847 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100848
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000849 if (lflag & LS_LONG_VIEW) {
Darren Tucker2901e2d2010-01-13 22:44:06 +1100850 if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000851 char *lname;
852 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100853
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000854 memset(&sb, 0, sizeof(sb));
855 attrib_to_stat(&d[n]->a, &sb);
Darren Tucker2901e2d2010-01-13 22:44:06 +1100856 lname = ls_file(fname, &sb, 1,
857 (lflag & LS_SI_UNITS));
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000858 mprintf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000859 free(lname);
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000860 } else
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000861 mprintf("%s\n", d[n]->longname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100862 } else {
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000863 mprintf("%-*s", colspace, fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100864 if (c >= columns) {
865 printf("\n");
866 c = 1;
867 } else
868 c++;
869 }
870
Darren Tuckera627d422013-06-02 07:31:17 +1000871 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100872 }
873
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000874 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100875 printf("\n");
876
877 free_sftp_dirents(d);
878 return (0);
879}
880
djm@openbsd.org72be5b22017-06-10 06:33:34 +0000881static int
882sglob_comp(const void *aa, const void *bb)
883{
884 u_int a = *(const u_int *)aa;
885 u_int b = *(const u_int *)bb;
886 const char *ap = sort_glob->gl_pathv[a];
887 const char *bp = sort_glob->gl_pathv[b];
888 const struct stat *as = sort_glob->gl_statv[a];
889 const struct stat *bs = sort_glob->gl_statv[b];
890 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
891
892#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
893 if (sort_flag & LS_NAME_SORT)
894 return (rmul * strcmp(ap, bp));
Damien Millerbcd14852017-06-10 23:41:25 +1000895 else if (sort_flag & LS_TIME_SORT) {
896#if defined(HAVE_STRUCT_STAT_ST_MTIM)
djm@openbsd.org72be5b22017-06-10 06:33:34 +0000897 return (rmul * timespeccmp(&as->st_mtim, &bs->st_mtim, <));
Damien Millerbcd14852017-06-10 23:41:25 +1000898#elif defined(HAVE_STRUCT_STAT_ST_MTIME)
899 return (rmul * NCMP(as->st_mtime, bs->st_mtime));
900#else
901 return rmul * 1;
902#endif
903 } else if (sort_flag & LS_SIZE_SORT)
djm@openbsd.org72be5b22017-06-10 06:33:34 +0000904 return (rmul * NCMP(as->st_size, bs->st_size));
905
906 fatal("Unknown ls sort type");
907}
908
Damien Miller20e1fab2004-02-18 14:30:55 +1100909/* sftp ls.1 replacement which handles path globs */
910static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000911do_globbed_ls(struct sftp_conn *conn, const char *path,
912 const char *strip_path, int lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100913{
Damien Millera6e121a2010-10-07 21:39:17 +1100914 char *fname, *lname;
Damien Miller68e2e562010-10-07 21:39:55 +1100915 glob_t g;
Damien Miller00707762014-07-09 13:07:06 +1000916 int err, r;
Damien Miller68e2e562010-10-07 21:39:55 +1100917 struct winsize ws;
djm@openbsd.org72be5b22017-06-10 06:33:34 +0000918 u_int i, j, nentries, *indices = NULL, c = 1;
919 u_int colspace = 0, columns = 1, m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100920
921 memset(&g, 0, sizeof(g));
922
Damien Miller00707762014-07-09 13:07:06 +1000923 if ((r = remote_glob(conn, path,
Damien Millerd7be70d2011-09-22 21:43:06 +1000924 GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
Damien Miller00707762014-07-09 13:07:06 +1000925 NULL, &g)) != 0 ||
Damien Millera6e121a2010-10-07 21:39:17 +1100926 (g.gl_pathc && !g.gl_matchc)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100927 if (g.gl_pathc)
928 globfree(&g);
Damien Miller00707762014-07-09 13:07:06 +1000929 if (r == GLOB_NOSPACE) {
930 error("Can't ls: Too many matches for \"%s\"", path);
931 } else {
932 error("Can't ls: \"%s\" not found", path);
933 }
Damien Millera6e121a2010-10-07 21:39:17 +1100934 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100935 }
936
Darren Tuckercdf547a2004-05-24 10:12:19 +1000937 if (interrupted)
938 goto out;
939
Damien Miller20e1fab2004-02-18 14:30:55 +1100940 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100941 * If the glob returns a single match and it is a directory,
942 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100943 */
Damien Millera6e121a2010-10-07 21:39:17 +1100944 if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
945 S_ISDIR(g.gl_statv[0]->st_mode)) {
946 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
947 globfree(&g);
948 return err;
Damien Miller20e1fab2004-02-18 14:30:55 +1100949 }
950
Damien Miller68e2e562010-10-07 21:39:55 +1100951 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
952 width = ws.ws_col;
Damien Miller20e1fab2004-02-18 14:30:55 +1100953
Damien Miller68e2e562010-10-07 21:39:55 +1100954 if (!(lflag & LS_SHORT_VIEW)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100955 /* Count entries for sort and find longest filename */
956 for (i = 0; g.gl_pathv[i]; i++)
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +0000957 m = MAXIMUM(m, strlen(g.gl_pathv[i]));
Damien Miller20e1fab2004-02-18 14:30:55 +1100958
Damien Miller20e1fab2004-02-18 14:30:55 +1100959 columns = width / (m + 2);
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +0000960 columns = MAXIMUM(columns, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +1100961 colspace = width / columns;
962 }
963
djm@openbsd.org72be5b22017-06-10 06:33:34 +0000964 /*
965 * Sorting: rather than mess with the contents of glob_t, prepare
966 * an array of indices into it and sort that. For the usual
967 * unsorted case, the indices are just the identity 1=1, 2=2, etc.
968 */
969 for (nentries = 0; g.gl_pathv[nentries] != NULL; nentries++)
970 ; /* count entries */
971 indices = calloc(nentries, sizeof(*indices));
972 for (i = 0; i < nentries; i++)
973 indices[i] = i;
974
975 if (lflag & SORT_FLAGS) {
976 sort_glob = &g;
977 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
978 qsort(indices, nentries, sizeof(*indices), sglob_comp);
979 sort_glob = NULL;
980 }
981
982 for (j = 0; j < nentries && !interrupted; j++) {
983 i = indices[j];
Damien Miller20e1fab2004-02-18 14:30:55 +1100984 fname = path_strip(g.gl_pathv[i], strip_path);
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000985 if (lflag & LS_LONG_VIEW) {
Damien Millera6e121a2010-10-07 21:39:17 +1100986 if (g.gl_statv[i] == NULL) {
987 error("no stat information for %s", fname);
988 continue;
989 }
990 lname = ls_file(fname, g.gl_statv[i], 1,
991 (lflag & LS_SI_UNITS));
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000992 mprintf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000993 free(lname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100994 } else {
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000995 mprintf("%-*s", colspace, fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100996 if (c >= columns) {
997 printf("\n");
998 c = 1;
999 } else
1000 c++;
1001 }
Darren Tuckera627d422013-06-02 07:31:17 +10001002 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +11001003 }
1004
Darren Tuckera4e9ffa2004-06-22 13:07:58 +10001005 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +11001006 printf("\n");
1007
Darren Tuckercdf547a2004-05-24 10:12:19 +10001008 out:
Damien Miller20e1fab2004-02-18 14:30:55 +11001009 if (g.gl_pathc)
1010 globfree(&g);
djm@openbsd.org72be5b22017-06-10 06:33:34 +00001011 free(indices);
Damien Miller20e1fab2004-02-18 14:30:55 +11001012
Damien Millera6e121a2010-10-07 21:39:17 +11001013 return 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001014}
1015
Damien Millerd671e5a2008-05-19 14:53:33 +10001016static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +00001017do_df(struct sftp_conn *conn, const char *path, int hflag, int iflag)
Damien Millerd671e5a2008-05-19 14:53:33 +10001018{
Darren Tucker7b598892008-06-09 22:49:36 +10001019 struct sftp_statvfs st;
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +00001020 char s_used[FMT_SCALED_STRSIZE], s_avail[FMT_SCALED_STRSIZE];
1021 char s_root[FMT_SCALED_STRSIZE], s_total[FMT_SCALED_STRSIZE];
1022 char s_icapacity[16], s_dcapacity[16];
Damien Millerd671e5a2008-05-19 14:53:33 +10001023
1024 if (do_statvfs(conn, path, &st, 1) == -1)
1025 return -1;
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +00001026 if (st.f_files == 0)
1027 strlcpy(s_icapacity, "ERR", sizeof(s_icapacity));
1028 else {
1029 snprintf(s_icapacity, sizeof(s_icapacity), "%3llu%%",
1030 (unsigned long long)(100 * (st.f_files - st.f_ffree) /
1031 st.f_files));
1032 }
1033 if (st.f_blocks == 0)
1034 strlcpy(s_dcapacity, "ERR", sizeof(s_dcapacity));
1035 else {
1036 snprintf(s_dcapacity, sizeof(s_dcapacity), "%3llu%%",
1037 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
1038 st.f_blocks));
1039 }
Damien Millerd671e5a2008-05-19 14:53:33 +10001040 if (iflag) {
1041 printf(" Inodes Used Avail "
1042 "(root) %%Capacity\n");
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +00001043 printf("%11llu %11llu %11llu %11llu %s\n",
Damien Millerd671e5a2008-05-19 14:53:33 +10001044 (unsigned long long)st.f_files,
1045 (unsigned long long)(st.f_files - st.f_ffree),
1046 (unsigned long long)st.f_favail,
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +00001047 (unsigned long long)st.f_ffree, s_icapacity);
Damien Millerd671e5a2008-05-19 14:53:33 +10001048 } else if (hflag) {
1049 strlcpy(s_used, "error", sizeof(s_used));
1050 strlcpy(s_avail, "error", sizeof(s_avail));
1051 strlcpy(s_root, "error", sizeof(s_root));
1052 strlcpy(s_total, "error", sizeof(s_total));
1053 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
1054 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
1055 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
1056 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
1057 printf(" Size Used Avail (root) %%Capacity\n");
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +00001058 printf("%7sB %7sB %7sB %7sB %s\n",
1059 s_total, s_used, s_avail, s_root, s_dcapacity);
Damien Millerd671e5a2008-05-19 14:53:33 +10001060 } else {
1061 printf(" Size Used Avail "
1062 "(root) %%Capacity\n");
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +00001063 printf("%12llu %12llu %12llu %12llu %s\n",
Damien Millerd671e5a2008-05-19 14:53:33 +10001064 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
1065 (unsigned long long)(st.f_frsize *
1066 (st.f_blocks - st.f_bfree) / 1024),
1067 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
1068 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +00001069 s_dcapacity);
Damien Millerd671e5a2008-05-19 14:53:33 +10001070 }
1071 return 0;
1072}
1073
Damien Miller1cbc2922007-10-26 14:27:45 +10001074/*
1075 * Undo escaping of glob sequences in place. Used to undo extra escaping
1076 * applied in makeargv() when the string is destined for a function that
1077 * does not glob it.
1078 */
1079static void
1080undo_glob_escape(char *s)
1081{
1082 size_t i, j;
1083
1084 for (i = j = 0;;) {
1085 if (s[i] == '\0') {
1086 s[j] = '\0';
1087 return;
1088 }
1089 if (s[i] != '\\') {
1090 s[j++] = s[i++];
1091 continue;
1092 }
1093 /* s[i] == '\\' */
1094 ++i;
1095 switch (s[i]) {
1096 case '?':
1097 case '[':
1098 case '*':
1099 case '\\':
1100 s[j++] = s[i++];
1101 break;
1102 case '\0':
1103 s[j++] = '\\';
1104 s[j] = '\0';
1105 return;
1106 default:
1107 s[j++] = '\\';
1108 s[j++] = s[i++];
1109 break;
1110 }
1111 }
1112}
1113
1114/*
1115 * Split a string into an argument vector using sh(1)-style quoting,
1116 * comment and escaping rules, but with some tweaks to handle glob(3)
1117 * wildcards.
Darren Tucker909d8582010-01-08 19:02:40 +11001118 * The "sloppy" flag allows for recovery from missing terminating quote, for
1119 * use in parsing incomplete commandlines during tab autocompletion.
1120 *
Damien Miller1cbc2922007-10-26 14:27:45 +10001121 * Returns NULL on error or a NULL-terminated array of arguments.
Darren Tucker909d8582010-01-08 19:02:40 +11001122 *
1123 * If "lastquote" is not NULL, the quoting character used for the last
1124 * argument is placed in *lastquote ("\0", "'" or "\"").
Damien Miller02e87802013-08-21 02:38:51 +10001125 *
Darren Tucker909d8582010-01-08 19:02:40 +11001126 * If "terminated" is not NULL, *terminated will be set to 1 when the
1127 * last argument's quote has been properly terminated or 0 otherwise.
1128 * This parameter is only of use if "sloppy" is set.
Damien Miller1cbc2922007-10-26 14:27:45 +10001129 */
1130#define MAXARGS 128
1131#define MAXARGLEN 8192
1132static char **
Darren Tucker909d8582010-01-08 19:02:40 +11001133makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
1134 u_int *terminated)
Damien Miller1cbc2922007-10-26 14:27:45 +10001135{
1136 int argc, quot;
1137 size_t i, j;
1138 static char argvs[MAXARGLEN];
1139 static char *argv[MAXARGS + 1];
1140 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
1141
1142 *argcp = argc = 0;
1143 if (strlen(arg) > sizeof(argvs) - 1) {
1144 args_too_longs:
1145 error("string too long");
1146 return NULL;
1147 }
Darren Tucker909d8582010-01-08 19:02:40 +11001148 if (terminated != NULL)
1149 *terminated = 1;
1150 if (lastquote != NULL)
1151 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001152 state = MA_START;
1153 i = j = 0;
1154 for (;;) {
Damien Miller07daed52012-10-31 08:57:55 +11001155 if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){
Darren Tucker063018d2012-10-05 10:43:58 +10001156 error("Too many arguments.");
1157 return NULL;
1158 }
Damien Millerfdb23062013-11-21 13:57:15 +11001159 if (isspace((unsigned char)arg[i])) {
Damien Miller1cbc2922007-10-26 14:27:45 +10001160 if (state == MA_UNQUOTED) {
1161 /* Terminate current argument */
1162 argvs[j++] = '\0';
1163 argc++;
1164 state = MA_START;
1165 } else if (state != MA_START)
1166 argvs[j++] = arg[i];
1167 } else if (arg[i] == '"' || arg[i] == '\'') {
1168 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1169 if (state == MA_START) {
1170 argv[argc] = argvs + j;
1171 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +11001172 if (lastquote != NULL)
1173 *lastquote = arg[i];
Damien Miller02e87802013-08-21 02:38:51 +10001174 } else if (state == MA_UNQUOTED)
Damien Miller1cbc2922007-10-26 14:27:45 +10001175 state = q;
1176 else if (state == q)
1177 state = MA_UNQUOTED;
1178 else
1179 argvs[j++] = arg[i];
1180 } else if (arg[i] == '\\') {
1181 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1182 quot = state == MA_SQUOTE ? '\'' : '"';
1183 /* Unescape quote we are in */
1184 /* XXX support \n and friends? */
1185 if (arg[i + 1] == quot) {
1186 i++;
1187 argvs[j++] = arg[i];
1188 } else if (arg[i + 1] == '?' ||
1189 arg[i + 1] == '[' || arg[i + 1] == '*') {
1190 /*
1191 * Special case for sftp: append
1192 * double-escaped glob sequence -
1193 * glob will undo one level of
1194 * escaping. NB. string can grow here.
1195 */
1196 if (j >= sizeof(argvs) - 5)
1197 goto args_too_longs;
1198 argvs[j++] = '\\';
1199 argvs[j++] = arg[i++];
1200 argvs[j++] = '\\';
1201 argvs[j++] = arg[i];
1202 } else {
1203 argvs[j++] = arg[i++];
1204 argvs[j++] = arg[i];
1205 }
1206 } else {
1207 if (state == MA_START) {
1208 argv[argc] = argvs + j;
1209 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001210 if (lastquote != NULL)
1211 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001212 }
1213 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1214 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1215 /*
1216 * Special case for sftp: append
1217 * escaped glob sequence -
1218 * glob will undo one level of
1219 * escaping.
1220 */
1221 argvs[j++] = arg[i++];
1222 argvs[j++] = arg[i];
1223 } else {
1224 /* Unescape everything */
1225 /* XXX support \n and friends? */
1226 i++;
1227 argvs[j++] = arg[i];
1228 }
1229 }
1230 } else if (arg[i] == '#') {
1231 if (state == MA_SQUOTE || state == MA_DQUOTE)
1232 argvs[j++] = arg[i];
1233 else
1234 goto string_done;
1235 } else if (arg[i] == '\0') {
1236 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001237 if (sloppy) {
1238 state = MA_UNQUOTED;
1239 if (terminated != NULL)
1240 *terminated = 0;
1241 goto string_done;
1242 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001243 error("Unterminated quoted argument");
1244 return NULL;
1245 }
1246 string_done:
1247 if (state == MA_UNQUOTED) {
1248 argvs[j++] = '\0';
1249 argc++;
1250 }
1251 break;
1252 } else {
1253 if (state == MA_START) {
1254 argv[argc] = argvs + j;
1255 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001256 if (lastquote != NULL)
1257 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001258 }
1259 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1260 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1261 /*
1262 * Special case for sftp: escape quoted
1263 * glob(3) wildcards. NB. string can grow
1264 * here.
1265 */
1266 if (j >= sizeof(argvs) - 3)
1267 goto args_too_longs;
1268 argvs[j++] = '\\';
1269 argvs[j++] = arg[i];
1270 } else
1271 argvs[j++] = arg[i];
1272 }
1273 i++;
1274 }
1275 *argcp = argc;
1276 return argv;
1277}
1278
Damien Miller20e1fab2004-02-18 14:30:55 +11001279static int
Damien Millerd8accc02014-05-15 13:46:25 +10001280parse_args(const char **cpp, int *ignore_errors, int *aflag,
djm@openbsd.org34a01b22016-04-08 08:19:17 +00001281 int *fflag, int *hflag, int *iflag, int *lflag, int *pflag,
Damien Millerd8accc02014-05-15 13:46:25 +10001282 int *rflag, int *sflag,
Damien Millerf29238e2013-10-17 11:48:52 +11001283 unsigned long *n_arg, char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001284{
1285 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001286 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001287 int base = 0;
1288 long l;
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00001289 int path1_mandatory = 0, i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001290
1291 /* Skip leading whitespace */
1292 cp = cp + strspn(cp, WHITESPACE);
1293
Damien Miller20e1fab2004-02-18 14:30:55 +11001294 /* Check for leading '-' (disable error processing) */
Damien Millerf29238e2013-10-17 11:48:52 +11001295 *ignore_errors = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001296 if (*cp == '-') {
Damien Millerf29238e2013-10-17 11:48:52 +11001297 *ignore_errors = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001298 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001299 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001300 }
1301
Darren Tucker70cc0922010-01-09 22:28:03 +11001302 /* Ignore blank lines and lines which begin with comment '#' char */
1303 if (*cp == '\0' || *cp == '#')
1304 return (0);
1305
Darren Tucker909d8582010-01-08 19:02:40 +11001306 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001307 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001308
Damien Miller1cbc2922007-10-26 14:27:45 +10001309 /* Figure out which command we have */
1310 for (i = 0; cmds[i].c != NULL; i++) {
Damien Millerd6d9fa02013-02-12 11:02:46 +11001311 if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001312 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001313 }
1314 cmdnum = cmds[i].n;
1315 cmd = cmds[i].c;
1316
1317 /* Special case */
1318 if (*cp == '!') {
1319 cp++;
1320 cmdnum = I_SHELL;
1321 } else if (cmdnum == -1) {
1322 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001323 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001324 }
1325
1326 /* Get arguments and parse flags */
Damien Millerf29238e2013-10-17 11:48:52 +11001327 *aflag = *fflag = *hflag = *iflag = *lflag = *pflag = 0;
1328 *rflag = *sflag = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001329 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001330 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001331 switch (cmdnum) {
1332 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001333 case I_REGET:
Damien Millerd8accc02014-05-15 13:46:25 +10001334 case I_REPUT:
Damien Miller20e1fab2004-02-18 14:30:55 +11001335 case I_PUT:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001336 if ((optidx = parse_getput_flags(cmd, argv, argc,
Damien Millerf29238e2013-10-17 11:48:52 +11001337 aflag, fflag, pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001338 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001339 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001340 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001341 error("You must specify at least one path after a "
1342 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001343 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001344 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001345 *path1 = xstrdup(argv[optidx]);
1346 /* Get second pathname (optional) */
1347 if (argc - optidx > 1) {
1348 *path2 = xstrdup(argv[optidx + 1]);
1349 /* Destination is not globbed */
1350 undo_glob_escape(*path2);
1351 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001352 break;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001353 case I_LINK:
1354 if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1355 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001356 goto parse_two_paths;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001357 case I_RENAME:
Damien Millerc7dba122013-08-21 02:41:15 +10001358 if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1)
1359 return -1;
1360 goto parse_two_paths;
1361 case I_SYMLINK:
Damien Miller036d3072013-08-21 02:41:46 +10001362 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1363 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001364 parse_two_paths:
Damien Miller1cbc2922007-10-26 14:27:45 +10001365 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001366 error("You must specify two paths after a %s "
1367 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001368 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001369 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001370 *path1 = xstrdup(argv[optidx]);
1371 *path2 = xstrdup(argv[optidx + 1]);
1372 /* Paths are not globbed */
1373 undo_glob_escape(*path1);
1374 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001375 break;
1376 case I_RM:
1377 case I_MKDIR:
1378 case I_RMDIR:
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00001379 case I_LMKDIR:
1380 path1_mandatory = 1;
1381 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001382 case I_CHDIR:
1383 case I_LCHDIR:
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) {
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00001388 if (!path1_mandatory)
1389 break; /* return a NULL path1 */
Damien Miller20e1fab2004-02-18 14:30:55 +11001390 error("You must specify a path after a %s command.",
1391 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001392 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001393 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001394 *path1 = xstrdup(argv[optidx]);
1395 /* Only "rm" globs */
1396 if (cmdnum != I_RM)
1397 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001398 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001399 case I_DF:
1400 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1401 iflag)) == -1)
1402 return -1;
1403 /* Default to current directory if no path specified */
1404 if (argc - optidx < 1)
1405 *path1 = NULL;
1406 else {
1407 *path1 = xstrdup(argv[optidx]);
1408 undo_glob_escape(*path1);
1409 }
1410 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001411 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001412 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001413 return(-1);
1414 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001415 if (argc - optidx > 0)
1416 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001417 break;
1418 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001419 /* Skip ls command and following whitespace */
1420 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001421 case I_SHELL:
1422 /* Uses the rest of the line */
1423 break;
1424 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001425 case I_CHMOD:
1426 base = 8;
1427 case I_CHOWN:
1428 case I_CHGRP:
Damien Miller036d3072013-08-21 02:41:46 +10001429 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1430 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001431 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001432 if (argc - optidx < 1)
1433 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001434 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001435 l = strtol(argv[optidx], &cp2, base);
1436 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1437 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1438 l < 0) {
1439 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001440 error("You must supply a numeric argument "
1441 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001442 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001443 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001444 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001445 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001446 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001447 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001448 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001449 error("You must specify a path after a %s command.",
1450 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001451 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001452 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001453 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001454 break;
1455 case I_QUIT:
1456 case I_PWD:
1457 case I_LPWD:
1458 case I_HELP:
1459 case I_VERSION:
1460 case I_PROGRESS:
Damien Miller036d3072013-08-21 02:41:46 +10001461 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1462 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001463 break;
1464 default:
1465 fatal("Command not implemented");
1466 }
1467
1468 *cpp = cp;
1469 return(cmdnum);
1470}
1471
1472static int
1473parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00001474 const char *startdir, int err_abort)
Damien Miller20e1fab2004-02-18 14:30:55 +11001475{
1476 char *path1, *path2, *tmp;
djm@openbsd.org34a01b22016-04-08 08:19:17 +00001477 int ignore_errors = 0, aflag = 0, fflag = 0, hflag = 0,
Damien Millerd8accc02014-05-15 13:46:25 +10001478 iflag = 0;
Damien Millerf29238e2013-10-17 11:48:52 +11001479 int lflag = 0, pflag = 0, rflag = 0, sflag = 0;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001480 int cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001481 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001482 Attrib a, *aa;
deraadt@openbsd.org087266e2015-01-20 23:14:00 +00001483 char path_buf[PATH_MAX];
Damien Miller20e1fab2004-02-18 14:30:55 +11001484 int err = 0;
1485 glob_t g;
1486
1487 path1 = path2 = NULL;
Damien Millerf29238e2013-10-17 11:48:52 +11001488 cmdnum = parse_args(&cmd, &ignore_errors, &aflag, &fflag, &hflag,
1489 &iflag, &lflag, &pflag, &rflag, &sflag, &n_arg, &path1, &path2);
1490 if (ignore_errors != 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001491 err_abort = 0;
1492
1493 memset(&g, 0, sizeof(g));
1494
1495 /* Perform command */
1496 switch (cmdnum) {
1497 case 0:
1498 /* Blank line */
1499 break;
1500 case -1:
1501 /* Unrecognized command */
1502 err = -1;
1503 break;
Damien Miller0d032412013-07-25 11:56:52 +10001504 case I_REGET:
1505 aflag = 1;
1506 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001507 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001508 err = process_get(conn, path1, path2, *pwd, pflag,
Damien Millerf29238e2013-10-17 11:48:52 +11001509 rflag, aflag, fflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001510 break;
Damien Millerd8accc02014-05-15 13:46:25 +10001511 case I_REPUT:
1512 aflag = 1;
1513 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001514 case I_PUT:
Damien Millerf29238e2013-10-17 11:48:52 +11001515 err = process_put(conn, path1, path2, *pwd, pflag,
Damien Millerd8accc02014-05-15 13:46:25 +10001516 rflag, aflag, fflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001517 break;
1518 case I_RENAME:
1519 path1 = make_absolute(path1, *pwd);
1520 path2 = make_absolute(path2, *pwd);
Damien Millerc7dba122013-08-21 02:41:15 +10001521 err = do_rename(conn, path1, path2, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001522 break;
1523 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001524 sflag = 1;
1525 case I_LINK:
Damien Miller034f27a2013-08-21 02:40:44 +10001526 if (!sflag)
1527 path1 = make_absolute(path1, *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001528 path2 = make_absolute(path2, *pwd);
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001529 err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001530 break;
1531 case I_RM:
1532 path1 = make_absolute(path1, *pwd);
1533 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001534 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001535 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001536 mprintf("Removing %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001537 err = do_rm(conn, g.gl_pathv[i]);
1538 if (err != 0 && err_abort)
1539 break;
1540 }
1541 break;
1542 case I_MKDIR:
1543 path1 = make_absolute(path1, *pwd);
1544 attrib_clear(&a);
1545 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1546 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001547 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001548 break;
1549 case I_RMDIR:
1550 path1 = make_absolute(path1, *pwd);
1551 err = do_rmdir(conn, path1);
1552 break;
1553 case I_CHDIR:
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00001554 if (path1 == NULL || *path1 == '\0')
1555 path1 = xstrdup(startdir);
Damien Miller20e1fab2004-02-18 14:30:55 +11001556 path1 = make_absolute(path1, *pwd);
1557 if ((tmp = do_realpath(conn, path1)) == NULL) {
1558 err = 1;
1559 break;
1560 }
1561 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
Darren Tuckera627d422013-06-02 07:31:17 +10001562 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001563 err = 1;
1564 break;
1565 }
1566 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1567 error("Can't change directory: Can't check target");
Darren Tuckera627d422013-06-02 07:31:17 +10001568 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001569 err = 1;
1570 break;
1571 }
1572 if (!S_ISDIR(aa->perm)) {
1573 error("Can't change directory: \"%s\" is not "
1574 "a directory", tmp);
Darren Tuckera627d422013-06-02 07:31:17 +10001575 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001576 err = 1;
1577 break;
1578 }
Darren Tuckera627d422013-06-02 07:31:17 +10001579 free(*pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001580 *pwd = tmp;
1581 break;
1582 case I_LS:
1583 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001584 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001585 break;
1586 }
1587
1588 /* Strip pwd off beginning of non-absolute paths */
1589 tmp = NULL;
1590 if (*path1 != '/')
1591 tmp = *pwd;
1592
1593 path1 = make_absolute(path1, *pwd);
1594 err = do_globbed_ls(conn, path1, tmp, lflag);
1595 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001596 case I_DF:
1597 /* Default to current directory if no path specified */
1598 if (path1 == NULL)
1599 path1 = xstrdup(*pwd);
1600 path1 = make_absolute(path1, *pwd);
1601 err = do_df(conn, path1, hflag, iflag);
1602 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001603 case I_LCHDIR:
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00001604 if (path1 == NULL || *path1 == '\0')
1605 path1 = xstrdup("~");
deraadt@openbsd.org40ba4c92014-08-20 01:28:55 +00001606 tmp = tilde_expand_filename(path1, getuid());
djm@openbsd.org7ff880e2014-08-19 23:57:18 +00001607 free(path1);
1608 path1 = tmp;
Damien Miller20e1fab2004-02-18 14:30:55 +11001609 if (chdir(path1) == -1) {
1610 error("Couldn't change local directory to "
1611 "\"%s\": %s", path1, strerror(errno));
1612 err = 1;
1613 }
1614 break;
1615 case I_LMKDIR:
1616 if (mkdir(path1, 0777) == -1) {
1617 error("Couldn't create local directory "
1618 "\"%s\": %s", path1, strerror(errno));
1619 err = 1;
1620 }
1621 break;
1622 case I_LLS:
1623 local_do_ls(cmd);
1624 break;
1625 case I_SHELL:
1626 local_do_shell(cmd);
1627 break;
1628 case I_LUMASK:
1629 umask(n_arg);
1630 printf("Local umask: %03lo\n", n_arg);
1631 break;
1632 case I_CHMOD:
1633 path1 = make_absolute(path1, *pwd);
1634 attrib_clear(&a);
1635 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1636 a.perm = n_arg;
1637 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001638 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001639 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001640 mprintf("Changing mode on %s\n",
1641 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001642 err = do_setstat(conn, g.gl_pathv[i], &a);
1643 if (err != 0 && err_abort)
1644 break;
1645 }
1646 break;
1647 case I_CHOWN:
1648 case I_CHGRP:
1649 path1 = make_absolute(path1, *pwd);
1650 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001651 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001652 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001653 if (err_abort) {
1654 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001655 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001656 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001657 continue;
1658 }
1659 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1660 error("Can't get current ownership of "
1661 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001662 if (err_abort) {
1663 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001664 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001665 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001666 continue;
1667 }
1668 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1669 if (cmdnum == I_CHOWN) {
Damien Miller9303e652013-04-23 15:22:40 +10001670 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001671 mprintf("Changing owner on %s\n",
Damien Miller9303e652013-04-23 15:22:40 +10001672 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001673 aa->uid = n_arg;
1674 } else {
Damien Miller9303e652013-04-23 15:22:40 +10001675 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001676 mprintf("Changing group on %s\n",
Damien Miller9303e652013-04-23 15:22:40 +10001677 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001678 aa->gid = n_arg;
1679 }
1680 err = do_setstat(conn, g.gl_pathv[i], aa);
1681 if (err != 0 && err_abort)
1682 break;
1683 }
1684 break;
1685 case I_PWD:
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001686 mprintf("Remote working directory: %s\n", *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001687 break;
1688 case I_LPWD:
1689 if (!getcwd(path_buf, sizeof(path_buf))) {
1690 error("Couldn't get local cwd: %s", strerror(errno));
1691 err = -1;
1692 break;
1693 }
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001694 mprintf("Local working directory: %s\n", path_buf);
Damien Miller20e1fab2004-02-18 14:30:55 +11001695 break;
1696 case I_QUIT:
1697 /* Processed below */
1698 break;
1699 case I_HELP:
1700 help();
1701 break;
1702 case I_VERSION:
1703 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1704 break;
1705 case I_PROGRESS:
1706 showprogress = !showprogress;
1707 if (showprogress)
1708 printf("Progress meter enabled\n");
1709 else
1710 printf("Progress meter disabled\n");
1711 break;
1712 default:
1713 fatal("%d is not implemented", cmdnum);
1714 }
1715
1716 if (g.gl_pathc)
1717 globfree(&g);
Darren Tuckera627d422013-06-02 07:31:17 +10001718 free(path1);
1719 free(path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001720
1721 /* If an unignored error occurs in batch mode we should abort. */
1722 if (err_abort && err != 0)
1723 return (-1);
1724 else if (cmdnum == I_QUIT)
1725 return (1);
1726
1727 return (0);
1728}
1729
Darren Tucker2d963d82004-11-07 20:04:10 +11001730#ifdef USE_LIBEDIT
1731static char *
1732prompt(EditLine *el)
1733{
1734 return ("sftp> ");
1735}
Darren Tucker2d963d82004-11-07 20:04:10 +11001736
Darren Tucker909d8582010-01-08 19:02:40 +11001737/* Display entries in 'list' after skipping the first 'len' chars */
1738static void
1739complete_display(char **list, u_int len)
1740{
1741 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1742 struct winsize ws;
1743 char *tmp;
1744
1745 /* Count entries for sort and find longest */
Damien Miller02e87802013-08-21 02:38:51 +10001746 for (y = 0; list[y]; y++)
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +00001747 m = MAXIMUM(m, strlen(list[y]));
Darren Tucker909d8582010-01-08 19:02:40 +11001748
1749 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1750 width = ws.ws_col;
1751
1752 m = m > len ? m - len : 0;
1753 columns = width / (m + 2);
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +00001754 columns = MAXIMUM(columns, 1);
Darren Tucker909d8582010-01-08 19:02:40 +11001755 colspace = width / columns;
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +00001756 colspace = MINIMUM(colspace, width);
Darren Tucker909d8582010-01-08 19:02:40 +11001757
1758 printf("\n");
1759 m = 1;
1760 for (y = 0; list[y]; y++) {
1761 llen = strlen(list[y]);
1762 tmp = llen > len ? list[y] + len : "";
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001763 mprintf("%-*s", colspace, tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001764 if (m >= columns) {
1765 printf("\n");
1766 m = 1;
1767 } else
1768 m++;
1769 }
1770 printf("\n");
1771}
1772
1773/*
1774 * Given a "list" of words that begin with a common prefix of "word",
1775 * attempt to find an autocompletion to extends "word" by the next
1776 * characters common to all entries in "list".
1777 */
1778static char *
1779complete_ambiguous(const char *word, char **list, size_t count)
1780{
1781 if (word == NULL)
1782 return NULL;
1783
1784 if (count > 0) {
1785 u_int y, matchlen = strlen(list[0]);
1786
1787 /* Find length of common stem */
1788 for (y = 1; list[y]; y++) {
1789 u_int x;
1790
Damien Miller02e87802013-08-21 02:38:51 +10001791 for (x = 0; x < matchlen; x++)
1792 if (list[0][x] != list[y][x])
Darren Tucker909d8582010-01-08 19:02:40 +11001793 break;
1794
1795 matchlen = x;
1796 }
1797
1798 if (matchlen > strlen(word)) {
1799 char *tmp = xstrdup(list[0]);
1800
Darren Tucker340d1682010-01-09 08:54:31 +11001801 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001802 return tmp;
1803 }
Damien Miller02e87802013-08-21 02:38:51 +10001804 }
Darren Tucker909d8582010-01-08 19:02:40 +11001805
1806 return xstrdup(word);
1807}
1808
1809/* Autocomplete a sftp command */
1810static int
1811complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1812 int terminated)
1813{
1814 u_int y, count = 0, cmdlen, tmplen;
1815 char *tmp, **list, argterm[3];
1816 const LineInfo *lf;
1817
1818 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1819
1820 /* No command specified: display all available commands */
1821 if (cmd == NULL) {
1822 for (y = 0; cmds[y].c; y++)
1823 list[count++] = xstrdup(cmds[y].c);
Damien Miller02e87802013-08-21 02:38:51 +10001824
Darren Tucker909d8582010-01-08 19:02:40 +11001825 list[count] = NULL;
1826 complete_display(list, 0);
1827
Damien Miller02e87802013-08-21 02:38:51 +10001828 for (y = 0; list[y] != NULL; y++)
1829 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001830 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001831 return count;
1832 }
1833
1834 /* Prepare subset of commands that start with "cmd" */
1835 cmdlen = strlen(cmd);
1836 for (y = 0; cmds[y].c; y++) {
Damien Miller02e87802013-08-21 02:38:51 +10001837 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
Darren Tucker909d8582010-01-08 19:02:40 +11001838 list[count++] = xstrdup(cmds[y].c);
1839 }
1840 list[count] = NULL;
1841
Damien Miller47d81152011-11-25 13:53:48 +11001842 if (count == 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10001843 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001844 return 0;
Damien Miller47d81152011-11-25 13:53:48 +11001845 }
Darren Tucker909d8582010-01-08 19:02:40 +11001846
1847 /* Complete ambigious command */
1848 tmp = complete_ambiguous(cmd, list, count);
1849 if (count > 1)
1850 complete_display(list, 0);
1851
Damien Miller02e87802013-08-21 02:38:51 +10001852 for (y = 0; list[y]; y++)
1853 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001854 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001855
1856 if (tmp != NULL) {
1857 tmplen = strlen(tmp);
1858 cmdlen = strlen(cmd);
1859 /* If cmd may be extended then do so */
1860 if (tmplen > cmdlen)
1861 if (el_insertstr(el, tmp + cmdlen) == -1)
1862 fatal("el_insertstr failed.");
1863 lf = el_line(el);
1864 /* Terminate argument cleanly */
1865 if (count == 1) {
1866 y = 0;
1867 if (!terminated)
1868 argterm[y++] = quote;
1869 if (lastarg || *(lf->cursor) != ' ')
1870 argterm[y++] = ' ';
1871 argterm[y] = '\0';
1872 if (y > 0 && el_insertstr(el, argterm) == -1)
1873 fatal("el_insertstr failed.");
1874 }
Darren Tuckera627d422013-06-02 07:31:17 +10001875 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001876 }
1877
1878 return count;
1879}
1880
1881/*
1882 * Determine whether a particular sftp command's arguments (if any)
1883 * represent local or remote files.
1884 */
1885static int
1886complete_is_remote(char *cmd) {
1887 int i;
1888
1889 if (cmd == NULL)
1890 return -1;
1891
1892 for (i = 0; cmds[i].c; i++) {
Damien Miller02e87802013-08-21 02:38:51 +10001893 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
Darren Tucker909d8582010-01-08 19:02:40 +11001894 return cmds[i].t;
1895 }
1896
1897 return -1;
1898}
1899
1900/* Autocomplete a filename "file" */
1901static int
1902complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1903 char *file, int remote, int lastarg, char quote, int terminated)
1904{
1905 glob_t g;
Darren Tuckerea647212013-06-06 08:19:09 +10001906 char *tmp, *tmp2, ins[8];
Darren Tucker17146d32012-10-05 10:46:16 +10001907 u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
Darren Tuckerea647212013-06-06 08:19:09 +10001908 int clen;
Darren Tucker909d8582010-01-08 19:02:40 +11001909 const LineInfo *lf;
Damien Miller02e87802013-08-21 02:38:51 +10001910
Darren Tucker909d8582010-01-08 19:02:40 +11001911 /* Glob from "file" location */
1912 if (file == NULL)
1913 tmp = xstrdup("*");
1914 else
1915 xasprintf(&tmp, "%s*", file);
1916
Darren Tucker191fcc62012-10-05 10:45:01 +10001917 /* Check if the path is absolute. */
1918 isabs = tmp[0] == '/';
1919
Darren Tucker909d8582010-01-08 19:02:40 +11001920 memset(&g, 0, sizeof(g));
1921 if (remote != LOCAL) {
1922 tmp = make_absolute(tmp, remote_path);
1923 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001924 } else
Darren Tucker909d8582010-01-08 19:02:40 +11001925 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001926
Darren Tucker909d8582010-01-08 19:02:40 +11001927 /* Determine length of pwd so we can trim completion display */
1928 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1929 /* Terminate counting on first unescaped glob metacharacter */
1930 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1931 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1932 hadglob = 1;
1933 break;
1934 }
1935 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1936 tmplen++;
1937 if (tmp[tmplen] == '/')
1938 pwdlen = tmplen + 1; /* track last seen '/' */
1939 }
Darren Tuckera627d422013-06-02 07:31:17 +10001940 free(tmp);
Damien Millerd7fd8be2014-05-15 14:24:59 +10001941 tmp = NULL;
Darren Tucker909d8582010-01-08 19:02:40 +11001942
Damien Miller02e87802013-08-21 02:38:51 +10001943 if (g.gl_matchc == 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001944 goto out;
1945
1946 if (g.gl_matchc > 1)
1947 complete_display(g.gl_pathv, pwdlen);
1948
Darren Tucker909d8582010-01-08 19:02:40 +11001949 /* Don't try to extend globs */
1950 if (file == NULL || hadglob)
1951 goto out;
1952
1953 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
Darren Tucker191fcc62012-10-05 10:45:01 +10001954 tmp = path_strip(tmp2, isabs ? NULL : remote_path);
Darren Tuckera627d422013-06-02 07:31:17 +10001955 free(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001956
1957 if (tmp == NULL)
1958 goto out;
1959
1960 tmplen = strlen(tmp);
1961 filelen = strlen(file);
1962
Darren Tucker17146d32012-10-05 10:46:16 +10001963 /* Count the number of escaped characters in the input string. */
1964 cesc = isesc = 0;
1965 for (i = 0; i < filelen; i++) {
1966 if (!isesc && file[i] == '\\' && i + 1 < filelen){
1967 isesc = 1;
1968 cesc++;
1969 } else
1970 isesc = 0;
1971 }
1972
1973 if (tmplen > (filelen - cesc)) {
1974 tmp2 = tmp + filelen - cesc;
Damien Miller02e87802013-08-21 02:38:51 +10001975 len = strlen(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001976 /* quote argument on way out */
Darren Tuckerea647212013-06-06 08:19:09 +10001977 for (i = 0; i < len; i += clen) {
1978 if ((clen = mblen(tmp2 + i, len - i)) < 0 ||
1979 (size_t)clen > sizeof(ins) - 2)
1980 fatal("invalid multibyte character");
Darren Tucker909d8582010-01-08 19:02:40 +11001981 ins[0] = '\\';
Darren Tuckerea647212013-06-06 08:19:09 +10001982 memcpy(ins + 1, tmp2 + i, clen);
1983 ins[clen + 1] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001984 switch (tmp2[i]) {
1985 case '\'':
1986 case '"':
1987 case '\\':
1988 case '\t':
Darren Tuckerd78739a2010-10-24 10:56:32 +11001989 case '[':
Darren Tucker909d8582010-01-08 19:02:40 +11001990 case ' ':
Darren Tucker17146d32012-10-05 10:46:16 +10001991 case '#':
1992 case '*':
Darren Tucker909d8582010-01-08 19:02:40 +11001993 if (quote == '\0' || tmp2[i] == quote) {
1994 if (el_insertstr(el, ins) == -1)
1995 fatal("el_insertstr "
1996 "failed.");
1997 break;
1998 }
1999 /* FALLTHROUGH */
2000 default:
2001 if (el_insertstr(el, ins + 1) == -1)
2002 fatal("el_insertstr failed.");
2003 break;
2004 }
2005 }
2006 }
2007
2008 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11002009 if (g.gl_matchc == 1) {
2010 i = 0;
Damien Miller38094812014-05-15 14:25:18 +10002011 if (!terminated && quote != '\0')
Darren Tucker909d8582010-01-08 19:02:40 +11002012 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11002013 if (*(lf->cursor - 1) != '/' &&
2014 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11002015 ins[i++] = ' ';
2016 ins[i] = '\0';
2017 if (i > 0 && el_insertstr(el, ins) == -1)
2018 fatal("el_insertstr failed.");
2019 }
Darren Tuckera627d422013-06-02 07:31:17 +10002020 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11002021
2022 out:
2023 globfree(&g);
2024 return g.gl_matchc;
2025}
2026
2027/* tab-completion hook function, called via libedit */
2028static unsigned char
2029complete(EditLine *el, int ch)
2030{
Damien Miller02e87802013-08-21 02:38:51 +10002031 char **argv, *line, quote;
Damien Miller746d1a62013-07-18 16:13:02 +10002032 int argc, carg;
2033 u_int cursor, len, terminated, ret = CC_ERROR;
Darren Tucker909d8582010-01-08 19:02:40 +11002034 const LineInfo *lf;
2035 struct complete_ctx *complete_ctx;
2036
2037 lf = el_line(el);
2038 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
2039 fatal("%s: el_get failed", __func__);
2040
2041 /* Figure out which argument the cursor points to */
2042 cursor = lf->cursor - lf->buffer;
deraadt@openbsd.orgce445b02015-08-20 22:32:42 +00002043 line = xmalloc(cursor + 1);
Darren Tucker909d8582010-01-08 19:02:40 +11002044 memcpy(line, lf->buffer, cursor);
2045 line[cursor] = '\0';
2046 argv = makeargv(line, &carg, 1, &quote, &terminated);
Darren Tuckera627d422013-06-02 07:31:17 +10002047 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11002048
2049 /* Get all the arguments on the line */
2050 len = lf->lastchar - lf->buffer;
deraadt@openbsd.orgce445b02015-08-20 22:32:42 +00002051 line = xmalloc(len + 1);
Darren Tucker909d8582010-01-08 19:02:40 +11002052 memcpy(line, lf->buffer, len);
2053 line[len] = '\0';
2054 argv = makeargv(line, &argc, 1, NULL, NULL);
2055
2056 /* Ensure cursor is at EOL or a argument boundary */
2057 if (line[cursor] != ' ' && line[cursor] != '\0' &&
2058 line[cursor] != '\n') {
Darren Tuckera627d422013-06-02 07:31:17 +10002059 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11002060 return ret;
2061 }
2062
2063 if (carg == 0) {
2064 /* Show all available commands */
2065 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
2066 ret = CC_REDISPLAY;
2067 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
2068 /* Handle the command parsing */
2069 if (complete_cmd_parse(el, argv[0], argc == carg,
Damien Miller02e87802013-08-21 02:38:51 +10002070 quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11002071 ret = CC_REDISPLAY;
2072 } else if (carg >= 1) {
2073 /* Handle file parsing */
2074 int remote = complete_is_remote(argv[0]);
2075 char *filematch = NULL;
2076
2077 if (carg > 1 && line[cursor-1] != ' ')
2078 filematch = argv[carg - 1];
2079
2080 if (remote != 0 &&
2081 complete_match(el, complete_ctx->conn,
2082 *complete_ctx->remote_pathp, filematch,
Damien Miller02e87802013-08-21 02:38:51 +10002083 remote, carg == argc, quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11002084 ret = CC_REDISPLAY;
2085 }
2086
Damien Miller02e87802013-08-21 02:38:51 +10002087 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11002088 return ret;
2089}
Darren Tuckere67f7db2010-01-08 19:50:02 +11002090#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11002091
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002092static int
Darren Tucker21063192010-01-08 17:10:36 +11002093interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11002094{
Darren Tucker909d8582010-01-08 19:02:40 +11002095 char *remote_path;
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002096 char *dir = NULL, *startdir = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +11002097 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10002098 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11002099 EditLine *el = NULL;
2100#ifdef USE_LIBEDIT
2101 History *hl = NULL;
2102 HistEvent hev;
2103 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11002104 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11002105
2106 if (!batchmode && isatty(STDIN_FILENO)) {
2107 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
2108 fatal("Couldn't initialise editline");
2109 if ((hl = history_init()) == NULL)
2110 fatal("Couldn't initialise editline history");
2111 history(hl, &hev, H_SETSIZE, 100);
2112 el_set(el, EL_HIST, history, hl);
2113
2114 el_set(el, EL_PROMPT, prompt);
2115 el_set(el, EL_EDITOR, "emacs");
2116 el_set(el, EL_TERMINAL, NULL);
2117 el_set(el, EL_SIGNAL, 1);
2118 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11002119
2120 /* Tab Completion */
Damien Miller02e87802013-08-21 02:38:51 +10002121 el_set(el, EL_ADDFN, "ftp-complete",
Darren Tuckerd78739a2010-10-24 10:56:32 +11002122 "Context sensitive argument completion", complete);
Darren Tucker909d8582010-01-08 19:02:40 +11002123 complete_ctx.conn = conn;
2124 complete_ctx.remote_pathp = &remote_path;
2125 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
2126 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Damien Millere0ee7272013-08-21 02:42:35 +10002127 /* enable ctrl-left-arrow and ctrl-right-arrow */
2128 el_set(el, EL_BIND, "\\e[1;5C", "em-next-word", NULL);
2129 el_set(el, EL_BIND, "\\e[5C", "em-next-word", NULL);
2130 el_set(el, EL_BIND, "\\e[1;5D", "ed-prev-word", NULL);
2131 el_set(el, EL_BIND, "\\e\\e[D", "ed-prev-word", NULL);
Damien Miller61353b32013-09-14 09:45:32 +10002132 /* make ^w match ksh behaviour */
2133 el_set(el, EL_BIND, "^w", "ed-delete-prev-word", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11002134 }
2135#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002136
Darren Tucker909d8582010-01-08 19:02:40 +11002137 remote_path = do_realpath(conn, ".");
2138 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11002139 fatal("Need cwd");
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002140 startdir = xstrdup(remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11002141
2142 if (file1 != NULL) {
2143 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11002144 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11002145
2146 if (remote_is_dir(conn, dir) && file2 == NULL) {
Damien Miller9303e652013-04-23 15:22:40 +10002147 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00002148 mprintf("Changing to: %s\n", dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002149 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11002150 if (parse_dispatch_command(conn, cmd,
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002151 &remote_path, startdir, 1) != 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10002152 free(dir);
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002153 free(startdir);
Darren Tuckera627d422013-06-02 07:31:17 +10002154 free(remote_path);
2155 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002156 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11002157 }
Damien Miller20e1fab2004-02-18 14:30:55 +11002158 } else {
Darren Tucker0af24052012-10-05 10:41:25 +10002159 /* XXX this is wrong wrt quoting */
Damien Miller0d032412013-07-25 11:56:52 +10002160 snprintf(cmd, sizeof cmd, "get%s %s%s%s",
2161 global_aflag ? " -a" : "", dir,
2162 file2 == NULL ? "" : " ",
2163 file2 == NULL ? "" : file2);
Darren Tucker909d8582010-01-08 19:02:40 +11002164 err = parse_dispatch_command(conn, cmd,
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002165 &remote_path, startdir, 1);
Darren Tuckera627d422013-06-02 07:31:17 +10002166 free(dir);
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002167 free(startdir);
Darren Tuckera627d422013-06-02 07:31:17 +10002168 free(remote_path);
2169 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002170 return (err);
2171 }
Darren Tuckera627d422013-06-02 07:31:17 +10002172 free(dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002173 }
2174
millert@openbsd.orgdb995f22014-11-26 18:34:51 +00002175 setvbuf(stdout, NULL, _IOLBF, 0);
2176 setvbuf(infile, NULL, _IOLBF, 0);
Damien Miller20e1fab2004-02-18 14:30:55 +11002177
Damien Miller0e2c1022005-08-12 22:16:22 +10002178 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11002179 err = 0;
2180 for (;;) {
2181 char *cp;
2182
Darren Tuckercdf547a2004-05-24 10:12:19 +10002183 signal(SIGINT, SIG_IGN);
2184
Darren Tucker2d963d82004-11-07 20:04:10 +11002185 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002186 if (interactive)
2187 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11002188 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002189 if (interactive)
2190 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11002191 break;
2192 }
Damien Miller0e2c1022005-08-12 22:16:22 +10002193 if (!interactive) { /* Echo command */
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00002194 mprintf("sftp> %s", cmd);
Damien Miller0e2c1022005-08-12 22:16:22 +10002195 if (strlen(cmd) > 0 &&
2196 cmd[strlen(cmd) - 1] != '\n')
2197 printf("\n");
2198 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002199 } else {
2200#ifdef USE_LIBEDIT
2201 const char *line;
2202 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11002203
Darren Tucker909d8582010-01-08 19:02:40 +11002204 if ((line = el_gets(el, &count)) == NULL ||
2205 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002206 printf("\n");
2207 break;
2208 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002209 history(hl, &hev, H_ENTER, line);
2210 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
2211 fprintf(stderr, "Error: input line too long\n");
2212 continue;
2213 }
2214#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002215 }
2216
Damien Miller20e1fab2004-02-18 14:30:55 +11002217 cp = strrchr(cmd, '\n');
2218 if (cp)
2219 *cp = '\0';
2220
Darren Tuckercdf547a2004-05-24 10:12:19 +10002221 /* Handle user interrupts gracefully during commands */
2222 interrupted = 0;
2223 signal(SIGINT, cmd_interrupt);
2224
Darren Tucker909d8582010-01-08 19:02:40 +11002225 err = parse_dispatch_command(conn, cmd, &remote_path,
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002226 startdir, batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11002227 if (err != 0)
2228 break;
2229 }
Darren Tuckera627d422013-06-02 07:31:17 +10002230 free(remote_path);
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002231 free(startdir);
Darren Tuckera627d422013-06-02 07:31:17 +10002232 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002233
Tim Rice027e8b12005-08-15 14:52:50 -07002234#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10002235 if (el != NULL)
2236 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07002237#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10002238
Damien Miller20e1fab2004-02-18 14:30:55 +11002239 /* err == 1 signifies normal "quit" exit */
2240 return (err >= 0 ? 0 : -1);
2241}
Damien Miller62d57f62003-01-10 21:43:24 +11002242
Ben Lindstrombba81212001-06-25 05:01:22 +00002243static void
Damien Millercc685c12003-06-04 22:51:38 +10002244connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11002245{
2246 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002247
Damien Miller33804262001-02-04 23:20:18 +11002248#ifdef USE_PIPES
2249 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002250
Damien Miller33804262001-02-04 23:20:18 +11002251 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2252 fatal("pipe: %s", strerror(errno));
2253 *in = pin[0];
2254 *out = pout[1];
2255 c_in = pout[0];
2256 c_out = pin[1];
2257#else /* USE_PIPES */
2258 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002259
Damien Miller33804262001-02-04 23:20:18 +11002260 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2261 fatal("socketpair: %s", strerror(errno));
2262 *in = *out = inout[0];
2263 c_in = c_out = inout[1];
2264#endif /* USE_PIPES */
2265
Damien Millercc685c12003-06-04 22:51:38 +10002266 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002267 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002268 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002269 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2270 (dup2(c_out, STDOUT_FILENO) == -1)) {
2271 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002272 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002273 }
2274 close(*in);
2275 close(*out);
2276 close(c_in);
2277 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002278
2279 /*
2280 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002281 * ignore SIGINT if we want to gracefully abort commands,
2282 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002283 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2284 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002285 */
2286 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002287 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002288 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002289 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002290 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002291 }
2292
Damien Millercc685c12003-06-04 22:51:38 +10002293 signal(SIGTERM, killchild);
2294 signal(SIGINT, killchild);
2295 signal(SIGHUP, killchild);
millert@openbsd.org2c6697c2016-10-18 12:41:22 +00002296 signal(SIGTSTP, suspchild);
2297 signal(SIGTTIN, suspchild);
2298 signal(SIGTTOU, suspchild);
Damien Miller33804262001-02-04 23:20:18 +11002299 close(c_in);
2300 close(c_out);
2301}
2302
Ben Lindstrombba81212001-06-25 05:01:22 +00002303static void
Damien Miller33804262001-02-04 23:20:18 +11002304usage(void)
2305{
Damien Miller025e01c2002-02-08 22:06:29 +11002306 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002307
Ben Lindstrom1e243242001-09-18 05:38:44 +00002308 fprintf(stderr,
djm@openbsd.org3575f0b2017-05-02 08:54:19 +00002309 "usage: %s [-46aCfpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002310 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002311 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002312 " [-o ssh_option] [-P port] [-R num_requests] "
2313 "[-S program]\n"
millert@openbsd.org887669e2017-10-21 23:06:24 +00002314 " [-s subsystem | sftp_server] destination\n",
2315 __progname);
Damien Miller33804262001-02-04 23:20:18 +11002316 exit(1);
2317}
2318
Kevin Stevesef4eea92001-02-05 12:42:17 +00002319int
Damien Miller33804262001-02-04 23:20:18 +11002320main(int argc, char **argv)
2321{
millert@openbsd.org887669e2017-10-21 23:06:24 +00002322 int in, out, ch, err, tmp, port = -1;
2323 char *host = NULL, *user, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002324 int debug_level = 0, sshver = 2;
2325 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002326 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002327 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002328 LogLevel ll = SYSLOG_LEVEL_INFO;
2329 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002330 extern int optind;
2331 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002332 struct sftp_conn *conn;
2333 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2334 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002335 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002336
dtucker@openbsd.orgffb1e7e2016-02-15 09:47:49 +00002337 ssh_malloc_init(); /* must be called before any mallocs */
Darren Tuckerce321d82005-10-03 18:11:24 +10002338 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2339 sanitise_stdfd();
Damien Millerdda78a02016-12-12 13:57:10 +11002340 msetlocale();
Darren Tuckerce321d82005-10-03 18:11:24 +10002341
Damien Miller59d3d5b2003-08-22 09:34:41 +10002342 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002343 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002344 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002345 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002346 addargs(&args, "-oForwardX11 no");
2347 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002348 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002349 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002350
Ben Lindstrom387c4722001-05-08 20:27:25 +00002351 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002352 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002353
Darren Tucker282b4022009-10-07 08:23:06 +11002354 while ((ch = getopt(argc, argv,
Damien Millerf29238e2013-10-17 11:48:52 +11002355 "1246afhpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002356 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002357 /* Passed through to ssh(1) */
2358 case '4':
2359 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002360 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002361 addargs(&args, "-%c", ch);
2362 break;
2363 /* Passed through to ssh(1) with argument */
2364 case 'F':
2365 case 'c':
2366 case 'i':
2367 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002368 addargs(&args, "-%c", ch);
2369 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002370 break;
2371 case 'q':
Damien Miller9303e652013-04-23 15:22:40 +10002372 ll = SYSLOG_LEVEL_ERROR;
2373 quiet = 1;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002374 showprogress = 0;
2375 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002376 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002377 case 'P':
millert@openbsd.org887669e2017-10-21 23:06:24 +00002378 port = a2port(optarg);
2379 if (port <= 0)
2380 fatal("Bad port \"%s\"\n", optarg);
Darren Tucker282b4022009-10-07 08:23:06 +11002381 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002382 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002383 if (debug_level < 3) {
2384 addargs(&args, "-v");
2385 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2386 }
2387 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002388 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002389 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002390 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002391 if (sftp_server == NULL)
2392 sftp_server = _PATH_SFTP_SERVER;
2393 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002394 case '2':
2395 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002396 break;
Damien Miller0d032412013-07-25 11:56:52 +10002397 case 'a':
2398 global_aflag = 1;
2399 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002400 case 'B':
2401 copy_buffer_len = strtol(optarg, &cp, 10);
2402 if (copy_buffer_len == 0 || *cp != '\0')
2403 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002404 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002405 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002406 if (batchmode)
2407 fatal("Batch file already specified.");
2408
2409 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002410 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002411 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002412 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002413 showprogress = 0;
Damien Miller9303e652013-04-23 15:22:40 +10002414 quiet = batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002415 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002416 break;
Damien Millerf29238e2013-10-17 11:48:52 +11002417 case 'f':
2418 global_fflag = 1;
2419 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002420 case 'p':
2421 global_pflag = 1;
2422 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002423 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002424 sftp_direct = optarg;
2425 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002426 case 'l':
2427 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2428 &errstr);
2429 if (errstr != NULL)
2430 usage();
2431 limit_kbps *= 1024; /* kbps */
2432 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002433 case 'r':
2434 global_rflag = 1;
2435 break;
Damien Miller16a13332002-02-13 14:03:56 +11002436 case 'R':
2437 num_requests = strtol(optarg, &cp, 10);
2438 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002439 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002440 optarg);
2441 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002442 case 's':
2443 sftp_server = optarg;
2444 break;
2445 case 'S':
2446 ssh_program = optarg;
2447 replacearg(&args, 0, "%s", ssh_program);
2448 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002449 case 'h':
2450 default:
Damien Miller33804262001-02-04 23:20:18 +11002451 usage();
2452 }
2453 }
2454
Damien Millerc0f27d82004-03-08 23:12:19 +11002455 if (!isatty(STDERR_FILENO))
2456 showprogress = 0;
2457
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002458 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2459
Damien Millerd14ee1e2002-02-05 12:27:31 +11002460 if (sftp_direct == NULL) {
2461 if (optind == argc || argc > (optind + 2))
2462 usage();
millert@openbsd.org887669e2017-10-21 23:06:24 +00002463 argv += optind;
Damien Miller33804262001-02-04 23:20:18 +11002464
millert@openbsd.org887669e2017-10-21 23:06:24 +00002465 switch (parse_uri("sftp", *argv, &user, &host, &tmp, &file1)) {
2466 case -1:
2467 usage();
2468 break;
2469 case 0:
2470 if (tmp != -1)
2471 port = tmp;
2472 break;
2473 default:
2474 if (parse_user_host_path(*argv, &user, &host,
2475 &file1) == -1) {
2476 /* Treat as a plain hostname. */
2477 host = xstrdup(*argv);
2478 host = cleanhostname(host);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002479 }
millert@openbsd.org887669e2017-10-21 23:06:24 +00002480 break;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002481 }
millert@openbsd.org887669e2017-10-21 23:06:24 +00002482 file2 = *(argv + 1);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002483
Damien Millerd14ee1e2002-02-05 12:27:31 +11002484 if (!*host) {
2485 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002486 usage();
2487 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002488
millert@openbsd.org887669e2017-10-21 23:06:24 +00002489 if (port != -1)
2490 addargs(&args, "-oPort %d", port);
2491 if (user != NULL) {
2492 addargs(&args, "-l");
2493 addargs(&args, "%s", user);
2494 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002495 addargs(&args, "-oProtocol %d", sshver);
2496
2497 /* no subsystem if the server-spec contains a '/' */
2498 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2499 addargs(&args, "-s");
2500
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002501 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002502 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002503 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002504 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002505
Damien Millercc685c12003-06-04 22:51:38 +10002506 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002507 } else {
2508 args.list = NULL;
2509 addargs(&args, "sftp-server");
2510
Damien Millercc685c12003-06-04 22:51:38 +10002511 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002512 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002513 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002514
Damien Miller65e42f82010-09-24 22:15:11 +10002515 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002516 if (conn == NULL)
2517 fatal("Couldn't initialise connection to server");
2518
Damien Miller9303e652013-04-23 15:22:40 +10002519 if (!quiet) {
Darren Tucker21063192010-01-08 17:10:36 +11002520 if (sftp_direct == NULL)
2521 fprintf(stderr, "Connected to %s.\n", host);
2522 else
2523 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2524 }
2525
2526 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002527
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002528#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002529 shutdown(in, SHUT_RDWR);
2530 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002531#endif
2532
Damien Miller33804262001-02-04 23:20:18 +11002533 close(in);
2534 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002535 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002536 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002537
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002538 while (waitpid(sshpid, NULL, 0) == -1)
2539 if (errno != EINTR)
2540 fatal("Couldn't wait for ssh process: %s",
2541 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002542
Damien Miller956f3fb2003-01-10 21:40:00 +11002543 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002544}