blob: af6e3a69a3307e0bbbb7fcd453003ec91df8f1fb [file] [log] [blame]
millert@openbsd.org2c6697c2016-10-18 12:41:22 +00001/* $OpenBSD: sftp.c,v 1.177 2016/10/18 12:41:22 millert 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;
109
Darren Tucker909d8582010-01-08 19:02:40 +1100110/* Context used for commandline completion */
111struct complete_ctx {
112 struct sftp_conn *conn;
113 char **remote_pathp;
114};
115
Damien Miller20e1fab2004-02-18 14:30:55 +1100116int remote_glob(struct sftp_conn *, const char *, int,
117 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
Damien Miller33804262001-02-04 23:20:18 +1100118
Kevin Steves12888d12001-03-05 19:50:57 +0000119extern char *__progname;
Kevin Steves12888d12001-03-05 19:50:57 +0000120
Damien Miller20e1fab2004-02-18 14:30:55 +1100121/* Separators for interactive commands */
122#define WHITESPACE " \t\r\n"
Damien Millerd7686fd2001-02-10 00:40:03 +1100123
Darren Tuckerb9123452004-06-22 13:06:45 +1000124/* ls flags */
Darren Tucker2901e2d2010-01-13 22:44:06 +1100125#define LS_LONG_VIEW 0x0001 /* Full view ala ls -l */
126#define LS_SHORT_VIEW 0x0002 /* Single row view ala ls -1 */
127#define LS_NUMERIC_VIEW 0x0004 /* Long view with numeric uid/gid */
128#define LS_NAME_SORT 0x0008 /* Sort by name (default) */
129#define LS_TIME_SORT 0x0010 /* Sort by mtime */
130#define LS_SIZE_SORT 0x0020 /* Sort by file size */
131#define LS_REVERSE_SORT 0x0040 /* Reverse sort order */
132#define LS_SHOW_ALL 0x0080 /* Don't skip filenames starting with '.' */
133#define LS_SI_UNITS 0x0100 /* Display sizes as K, M, G, etc. */
Darren Tuckerb9123452004-06-22 13:06:45 +1000134
Darren Tucker2901e2d2010-01-13 22:44:06 +1100135#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS)
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000136#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
Damien Miller20e1fab2004-02-18 14:30:55 +1100137
138/* Commands for interactive mode */
Damien Miller02e87802013-08-21 02:38:51 +1000139enum sftp_command {
140 I_CHDIR = 1,
141 I_CHGRP,
142 I_CHMOD,
143 I_CHOWN,
144 I_DF,
145 I_GET,
146 I_HELP,
147 I_LCHDIR,
148 I_LINK,
149 I_LLS,
150 I_LMKDIR,
151 I_LPWD,
152 I_LS,
153 I_LUMASK,
154 I_MKDIR,
155 I_PUT,
156 I_PWD,
157 I_QUIT,
Damien Millerb15cd7b2014-05-15 13:46:52 +1000158 I_REGET,
Damien Miller02e87802013-08-21 02:38:51 +1000159 I_RENAME,
Damien Millerb15cd7b2014-05-15 13:46:52 +1000160 I_REPUT,
Damien Miller02e87802013-08-21 02:38:51 +1000161 I_RM,
162 I_RMDIR,
163 I_SHELL,
164 I_SYMLINK,
165 I_VERSION,
166 I_PROGRESS,
Damien Miller02e87802013-08-21 02:38:51 +1000167};
Damien Miller20e1fab2004-02-18 14:30:55 +1100168
169struct CMD {
170 const char *c;
171 const int n;
Darren Tucker909d8582010-01-08 19:02:40 +1100172 const int t;
Damien Miller20e1fab2004-02-18 14:30:55 +1100173};
174
Darren Tucker909d8582010-01-08 19:02:40 +1100175/* Type of completion */
176#define NOARGS 0
177#define REMOTE 1
178#define LOCAL 2
179
Damien Miller20e1fab2004-02-18 14:30:55 +1100180static const struct CMD cmds[] = {
Darren Tucker909d8582010-01-08 19:02:40 +1100181 { "bye", I_QUIT, NOARGS },
182 { "cd", I_CHDIR, REMOTE },
183 { "chdir", I_CHDIR, REMOTE },
184 { "chgrp", I_CHGRP, REMOTE },
185 { "chmod", I_CHMOD, REMOTE },
186 { "chown", I_CHOWN, REMOTE },
187 { "df", I_DF, REMOTE },
188 { "dir", I_LS, REMOTE },
189 { "exit", I_QUIT, NOARGS },
190 { "get", I_GET, REMOTE },
191 { "help", I_HELP, NOARGS },
192 { "lcd", I_LCHDIR, LOCAL },
193 { "lchdir", I_LCHDIR, LOCAL },
194 { "lls", I_LLS, LOCAL },
195 { "lmkdir", I_LMKDIR, LOCAL },
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100196 { "ln", I_LINK, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100197 { "lpwd", I_LPWD, LOCAL },
198 { "ls", I_LS, REMOTE },
199 { "lumask", I_LUMASK, NOARGS },
200 { "mkdir", I_MKDIR, REMOTE },
Damien Miller099fc162010-05-10 11:56:50 +1000201 { "mget", I_GET, REMOTE },
202 { "mput", I_PUT, LOCAL },
Darren Tucker909d8582010-01-08 19:02:40 +1100203 { "progress", I_PROGRESS, NOARGS },
204 { "put", I_PUT, LOCAL },
205 { "pwd", I_PWD, REMOTE },
206 { "quit", I_QUIT, NOARGS },
Damien Miller0d032412013-07-25 11:56:52 +1000207 { "reget", I_REGET, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100208 { "rename", I_RENAME, REMOTE },
djm@openbsd.org4a459222014-10-06 00:47:15 +0000209 { "reput", I_REPUT, LOCAL },
Darren Tucker909d8582010-01-08 19:02:40 +1100210 { "rm", I_RM, REMOTE },
211 { "rmdir", I_RMDIR, REMOTE },
212 { "symlink", I_SYMLINK, REMOTE },
213 { "version", I_VERSION, NOARGS },
214 { "!", I_SHELL, NOARGS },
215 { "?", I_HELP, NOARGS },
216 { NULL, -1, -1 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100217};
218
Darren Tucker21063192010-01-08 17:10:36 +1100219int interactive_loop(struct sftp_conn *, char *file1, char *file2);
Damien Miller20e1fab2004-02-18 14:30:55 +1100220
Damien Millerb6c85fc2007-01-05 16:30:41 +1100221/* ARGSUSED */
Damien Miller20e1fab2004-02-18 14:30:55 +1100222static void
Darren Tuckercdf547a2004-05-24 10:12:19 +1000223killchild(int signo)
224{
Darren Tuckerba66df82005-01-24 21:57:40 +1100225 if (sshpid > 1) {
Darren Tuckercdf547a2004-05-24 10:12:19 +1000226 kill(sshpid, SIGTERM);
Darren Tuckerba66df82005-01-24 21:57:40 +1100227 waitpid(sshpid, NULL, 0);
228 }
Darren Tuckercdf547a2004-05-24 10:12:19 +1000229
230 _exit(1);
231}
232
Damien Millerb6c85fc2007-01-05 16:30:41 +1100233/* ARGSUSED */
Darren Tuckercdf547a2004-05-24 10:12:19 +1000234static void
millert@openbsd.org2c6697c2016-10-18 12:41:22 +0000235suspchild(int signo)
236{
237 if (sshpid > 1) {
238 kill(sshpid, signo);
239 while (waitpid(sshpid, NULL, WUNTRACED) == -1 && errno == EINTR)
240 continue;
241 }
242 kill(getpid(), SIGSTOP);
243}
244
245/* ARGSUSED */
246static void
Darren Tuckercdf547a2004-05-24 10:12:19 +1000247cmd_interrupt(int signo)
248{
249 const char msg[] = "\rInterrupt \n";
Darren Tuckere2f189a2004-12-06 22:45:53 +1100250 int olderrno = errno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000251
Darren Tuckerdbee3082013-05-16 20:32:29 +1000252 (void)write(STDERR_FILENO, msg, sizeof(msg) - 1);
Darren Tuckercdf547a2004-05-24 10:12:19 +1000253 interrupted = 1;
Darren Tuckere2f189a2004-12-06 22:45:53 +1100254 errno = olderrno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000255}
256
257static void
Damien Miller20e1fab2004-02-18 14:30:55 +1100258help(void)
259{
Damien Miller62fd18a2009-01-28 16:14:09 +1100260 printf("Available commands:\n"
261 "bye Quit sftp\n"
262 "cd path Change remote directory to 'path'\n"
263 "chgrp grp path Change group of file 'path' to 'grp'\n"
264 "chmod mode path Change permissions of file 'path' to 'mode'\n"
265 "chown own path Change owner of file 'path' to 'own'\n"
266 "df [-hi] [path] Display statistics for current directory or\n"
267 " filesystem containing 'path'\n"
268 "exit Quit sftp\n"
djm@openbsd.org4a459222014-10-06 00:47:15 +0000269 "get [-afPpRr] remote [local] Download file\n"
270 "reget [-fPpRr] remote [local] Resume download file\n"
271 "reput [-fPpRr] [local] remote Resume upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100272 "help Display this help text\n"
273 "lcd path Change local directory to 'path'\n"
274 "lls [ls-options [path]] Display local directory listing\n"
275 "lmkdir path Create local directory\n"
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100276 "ln [-s] oldpath newpath Link remote file (-s for symlink)\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100277 "lpwd Print local working directory\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100278 "ls [-1afhlnrSt] [path] Display remote directory listing\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100279 "lumask umask Set local umask to 'umask'\n"
280 "mkdir path Create remote directory\n"
281 "progress Toggle display of progress meter\n"
djm@openbsd.org4a459222014-10-06 00:47:15 +0000282 "put [-afPpRr] local [remote] Upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100283 "pwd Display remote working directory\n"
284 "quit Quit sftp\n"
285 "rename oldpath newpath Rename remote file\n"
286 "rm path Delete remote file\n"
287 "rmdir path Remove remote directory\n"
288 "symlink oldpath newpath Symlink remote file\n"
289 "version Show SFTP version\n"
290 "!command Execute 'command' in local shell\n"
291 "! Escape to local shell\n"
292 "? Synonym for help\n");
Damien Miller20e1fab2004-02-18 14:30:55 +1100293}
294
295static void
296local_do_shell(const char *args)
297{
298 int status;
299 char *shell;
300 pid_t pid;
301
302 if (!*args)
303 args = NULL;
304
Damien Miller38d9a962010-10-07 22:07:11 +1100305 if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
Damien Miller20e1fab2004-02-18 14:30:55 +1100306 shell = _PATH_BSHELL;
307
308 if ((pid = fork()) == -1)
309 fatal("Couldn't fork: %s", strerror(errno));
310
311 if (pid == 0) {
312 /* XXX: child has pipe fds to ssh subproc open - issue? */
313 if (args) {
314 debug3("Executing %s -c \"%s\"", shell, args);
315 execl(shell, shell, "-c", args, (char *)NULL);
316 } else {
317 debug3("Executing %s", shell);
318 execl(shell, shell, (char *)NULL);
319 }
320 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
321 strerror(errno));
322 _exit(1);
323 }
324 while (waitpid(pid, &status, 0) == -1)
325 if (errno != EINTR)
326 fatal("Couldn't wait for child: %s", strerror(errno));
327 if (!WIFEXITED(status))
Damien Miller55b04f12006-03-26 14:23:17 +1100328 error("Shell exited abnormally");
Damien Miller20e1fab2004-02-18 14:30:55 +1100329 else if (WEXITSTATUS(status))
330 error("Shell exited with status %d", WEXITSTATUS(status));
331}
332
333static void
334local_do_ls(const char *args)
335{
336 if (!args || !*args)
337 local_do_shell(_PATH_LS);
338 else {
339 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
340 char *buf = xmalloc(len);
341
342 /* XXX: quoting - rip quoting code from ftp? */
343 snprintf(buf, len, _PATH_LS " %s", args);
344 local_do_shell(buf);
Darren Tuckera627d422013-06-02 07:31:17 +1000345 free(buf);
Damien Miller20e1fab2004-02-18 14:30:55 +1100346 }
347}
348
349/* Strip one path (usually the pwd) from the start of another */
350static char *
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000351path_strip(const char *path, const char *strip)
Damien Miller20e1fab2004-02-18 14:30:55 +1100352{
353 size_t len;
354
355 if (strip == NULL)
356 return (xstrdup(path));
357
358 len = strlen(strip);
Darren Tuckere2f189a2004-12-06 22:45:53 +1100359 if (strncmp(path, strip, len) == 0) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100360 if (strip[len - 1] != '/' && path[len] == '/')
361 len++;
362 return (xstrdup(path + len));
363 }
364
365 return (xstrdup(path));
366}
367
368static char *
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000369make_absolute(char *p, const char *pwd)
Damien Miller20e1fab2004-02-18 14:30:55 +1100370{
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000371 char *abs_str;
Damien Miller20e1fab2004-02-18 14:30:55 +1100372
373 /* Derelativise */
374 if (p && p[0] != '/') {
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000375 abs_str = path_append(pwd, p);
Darren Tuckera627d422013-06-02 07:31:17 +1000376 free(p);
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000377 return(abs_str);
Damien Miller20e1fab2004-02-18 14:30:55 +1100378 } else
379 return(p);
380}
381
382static int
Damien Miller0d032412013-07-25 11:56:52 +1000383parse_getput_flags(const char *cmd, char **argv, int argc,
Damien Millerf29238e2013-10-17 11:48:52 +1100384 int *aflag, int *fflag, int *pflag, int *rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100385{
Damien Millerf184bcf2008-06-29 22:45:13 +1000386 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000387 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100388
Damien Miller1cbc2922007-10-26 14:27:45 +1000389 optind = optreset = 1;
390 opterr = 0;
391
Damien Millerf29238e2013-10-17 11:48:52 +1100392 *aflag = *fflag = *rflag = *pflag = 0;
393 while ((ch = getopt(argc, argv, "afPpRr")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000394 switch (ch) {
Damien Miller0d032412013-07-25 11:56:52 +1000395 case 'a':
396 *aflag = 1;
397 break;
Damien Millerf29238e2013-10-17 11:48:52 +1100398 case 'f':
399 *fflag = 1;
400 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100401 case 'p':
402 case 'P':
403 *pflag = 1;
404 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100405 case 'r':
406 case 'R':
407 *rflag = 1;
408 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100409 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000410 error("%s: Invalid flag -%c", cmd, optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000411 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100412 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100413 }
414
Damien Miller1cbc2922007-10-26 14:27:45 +1000415 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100416}
417
418static int
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100419parse_link_flags(const char *cmd, char **argv, int argc, int *sflag)
420{
421 extern int opterr, optind, optopt, optreset;
422 int ch;
423
424 optind = optreset = 1;
425 opterr = 0;
426
427 *sflag = 0;
428 while ((ch = getopt(argc, argv, "s")) != -1) {
429 switch (ch) {
430 case 's':
431 *sflag = 1;
432 break;
433 default:
434 error("%s: Invalid flag -%c", cmd, optopt);
435 return -1;
436 }
437 }
438
439 return optind;
440}
441
442static int
Damien Millerc7dba122013-08-21 02:41:15 +1000443parse_rename_flags(const char *cmd, char **argv, int argc, int *lflag)
444{
445 extern int opterr, optind, optopt, optreset;
446 int ch;
447
448 optind = optreset = 1;
449 opterr = 0;
450
451 *lflag = 0;
452 while ((ch = getopt(argc, argv, "l")) != -1) {
453 switch (ch) {
454 case 'l':
455 *lflag = 1;
456 break;
457 default:
458 error("%s: Invalid flag -%c", cmd, optopt);
459 return -1;
460 }
461 }
462
463 return optind;
464}
465
466static int
Damien Miller1cbc2922007-10-26 14:27:45 +1000467parse_ls_flags(char **argv, int argc, int *lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100468{
Damien Millerf184bcf2008-06-29 22:45:13 +1000469 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000470 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100471
Damien Miller1cbc2922007-10-26 14:27:45 +1000472 optind = optreset = 1;
473 opterr = 0;
474
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000475 *lflag = LS_NAME_SORT;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100476 while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000477 switch (ch) {
478 case '1':
479 *lflag &= ~VIEW_FLAGS;
480 *lflag |= LS_SHORT_VIEW;
481 break;
482 case 'S':
483 *lflag &= ~SORT_FLAGS;
484 *lflag |= LS_SIZE_SORT;
485 break;
486 case 'a':
487 *lflag |= LS_SHOW_ALL;
488 break;
489 case 'f':
490 *lflag &= ~SORT_FLAGS;
491 break;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100492 case 'h':
493 *lflag |= LS_SI_UNITS;
494 break;
Damien Miller1cbc2922007-10-26 14:27:45 +1000495 case 'l':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100496 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000497 *lflag |= LS_LONG_VIEW;
498 break;
499 case 'n':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100500 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000501 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
502 break;
503 case 'r':
504 *lflag |= LS_REVERSE_SORT;
505 break;
506 case 't':
507 *lflag &= ~SORT_FLAGS;
508 *lflag |= LS_TIME_SORT;
509 break;
510 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000511 error("ls: Invalid flag -%c", optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000512 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100513 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100514 }
515
Damien Miller1cbc2922007-10-26 14:27:45 +1000516 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100517}
518
519static int
Damien Millerd671e5a2008-05-19 14:53:33 +1000520parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
521{
Damien Millerf184bcf2008-06-29 22:45:13 +1000522 extern int opterr, optind, optopt, optreset;
Damien Millerd671e5a2008-05-19 14:53:33 +1000523 int ch;
524
525 optind = optreset = 1;
526 opterr = 0;
527
528 *hflag = *iflag = 0;
529 while ((ch = getopt(argc, argv, "hi")) != -1) {
530 switch (ch) {
531 case 'h':
532 *hflag = 1;
533 break;
534 case 'i':
535 *iflag = 1;
536 break;
537 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000538 error("%s: Invalid flag -%c", cmd, optopt);
Damien Millerd671e5a2008-05-19 14:53:33 +1000539 return -1;
540 }
541 }
542
543 return optind;
544}
545
546static int
Damien Miller036d3072013-08-21 02:41:46 +1000547parse_no_flags(const char *cmd, char **argv, int argc)
548{
549 extern int opterr, optind, optopt, optreset;
550 int ch;
551
552 optind = optreset = 1;
553 opterr = 0;
554
555 while ((ch = getopt(argc, argv, "")) != -1) {
556 switch (ch) {
557 default:
558 error("%s: Invalid flag -%c", cmd, optopt);
559 return -1;
560 }
561 }
562
563 return optind;
564}
565
566static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000567is_dir(const char *path)
Damien Miller20e1fab2004-02-18 14:30:55 +1100568{
569 struct stat sb;
570
571 /* XXX: report errors? */
572 if (stat(path, &sb) == -1)
573 return(0);
574
Darren Tucker1e80e402006-09-21 12:59:33 +1000575 return(S_ISDIR(sb.st_mode));
Damien Miller20e1fab2004-02-18 14:30:55 +1100576}
577
578static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000579remote_is_dir(struct sftp_conn *conn, const char *path)
Damien Miller20e1fab2004-02-18 14:30:55 +1100580{
581 Attrib *a;
582
583 /* XXX: report errors? */
584 if ((a = do_stat(conn, path, 1)) == NULL)
585 return(0);
586 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
587 return(0);
Darren Tucker1e80e402006-09-21 12:59:33 +1000588 return(S_ISDIR(a->perm));
Damien Miller20e1fab2004-02-18 14:30:55 +1100589}
590
Darren Tucker1b0dd172009-10-07 08:37:48 +1100591/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
Damien Miller20e1fab2004-02-18 14:30:55 +1100592static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000593pathname_is_dir(const char *pathname)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100594{
595 size_t l = strlen(pathname);
596
597 return l > 0 && pathname[l - 1] == '/';
598}
599
600static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000601process_get(struct sftp_conn *conn, const char *src, const char *dst,
602 const char *pwd, int pflag, int rflag, int resume, int fflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100603{
604 char *abs_src = NULL;
605 char *abs_dst = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100606 glob_t g;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100607 char *filename, *tmp=NULL;
Damien Miller00707762014-07-09 13:07:06 +1000608 int i, r, err = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100609
610 abs_src = xstrdup(src);
611 abs_src = make_absolute(abs_src, pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +1100612 memset(&g, 0, sizeof(g));
Darren Tucker1b0dd172009-10-07 08:37:48 +1100613
Damien Miller20e1fab2004-02-18 14:30:55 +1100614 debug3("Looking up %s", abs_src);
Damien Miller00707762014-07-09 13:07:06 +1000615 if ((r = remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) != 0) {
616 if (r == GLOB_NOSPACE) {
617 error("Too many matches for \"%s\".", abs_src);
618 } else {
619 error("File \"%s\" not found.", abs_src);
620 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100621 err = -1;
622 goto out;
623 }
624
Darren Tucker1b0dd172009-10-07 08:37:48 +1100625 /*
626 * If multiple matches then dst must be a directory or
627 * unspecified.
628 */
629 if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
630 error("Multiple source paths, but destination "
631 "\"%s\" is not a directory", dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100632 err = -1;
633 goto out;
634 }
635
Darren Tuckercdf547a2004-05-24 10:12:19 +1000636 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100637 tmp = xstrdup(g.gl_pathv[i]);
638 if ((filename = basename(tmp)) == NULL) {
639 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000640 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100641 err = -1;
642 goto out;
643 }
644
645 if (g.gl_matchc == 1 && dst) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100646 if (is_dir(dst)) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100647 abs_dst = path_append(dst, filename);
648 } else {
Damien Miller20e1fab2004-02-18 14:30:55 +1100649 abs_dst = xstrdup(dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100650 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100651 } else if (dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100652 abs_dst = path_append(dst, filename);
653 } else {
654 abs_dst = xstrdup(filename);
655 }
Darren Tuckera627d422013-06-02 07:31:17 +1000656 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100657
Damien Miller0d032412013-07-25 11:56:52 +1000658 resume |= global_aflag;
659 if (!quiet && resume)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000660 mprintf("Resuming %s to %s\n",
661 g.gl_pathv[i], abs_dst);
Damien Miller0d032412013-07-25 11:56:52 +1000662 else if (!quiet && !resume)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000663 mprintf("Fetching %s to %s\n",
664 g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100665 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
Damien Miller0d032412013-07-25 11:56:52 +1000666 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
Damien Millerf29238e2013-10-17 11:48:52 +1100667 pflag || global_pflag, 1, resume,
668 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100669 err = -1;
670 } else {
671 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
Damien Millerf29238e2013-10-17 11:48:52 +1100672 pflag || global_pflag, resume,
673 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100674 err = -1;
675 }
Darren Tuckera627d422013-06-02 07:31:17 +1000676 free(abs_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100677 abs_dst = NULL;
678 }
679
680out:
Darren Tuckera627d422013-06-02 07:31:17 +1000681 free(abs_src);
Damien Miller20e1fab2004-02-18 14:30:55 +1100682 globfree(&g);
683 return(err);
684}
685
686static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000687process_put(struct sftp_conn *conn, const char *src, const char *dst,
688 const char *pwd, int pflag, int rflag, int resume, int fflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100689{
690 char *tmp_dst = NULL;
691 char *abs_dst = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100692 char *tmp = NULL, *filename = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100693 glob_t g;
694 int err = 0;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100695 int i, dst_is_dir = 1;
Damien Milleraec5cf82008-02-10 22:26:24 +1100696 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100697
698 if (dst) {
699 tmp_dst = xstrdup(dst);
700 tmp_dst = make_absolute(tmp_dst, pwd);
701 }
702
703 memset(&g, 0, sizeof(g));
704 debug3("Looking up %s", src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100705 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100706 error("File \"%s\" not found.", src);
707 err = -1;
708 goto out;
709 }
710
Darren Tucker1b0dd172009-10-07 08:37:48 +1100711 /* If we aren't fetching to pwd then stash this status for later */
712 if (tmp_dst != NULL)
713 dst_is_dir = remote_is_dir(conn, tmp_dst);
714
Damien Miller20e1fab2004-02-18 14:30:55 +1100715 /* If multiple matches, dst may be directory or unspecified */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100716 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
717 error("Multiple paths match, but destination "
718 "\"%s\" is not a directory", tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100719 err = -1;
720 goto out;
721 }
722
Darren Tuckercdf547a2004-05-24 10:12:19 +1000723 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Milleraec5cf82008-02-10 22:26:24 +1100724 if (stat(g.gl_pathv[i], &sb) == -1) {
725 err = -1;
726 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
727 continue;
728 }
Damien Miller02e87802013-08-21 02:38:51 +1000729
Darren Tucker1b0dd172009-10-07 08:37:48 +1100730 tmp = xstrdup(g.gl_pathv[i]);
731 if ((filename = basename(tmp)) == NULL) {
732 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000733 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100734 err = -1;
735 goto out;
736 }
737
738 if (g.gl_matchc == 1 && tmp_dst) {
739 /* If directory specified, append filename */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100740 if (dst_is_dir)
741 abs_dst = path_append(tmp_dst, filename);
742 else
Damien Miller20e1fab2004-02-18 14:30:55 +1100743 abs_dst = xstrdup(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100744 } else if (tmp_dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100745 abs_dst = path_append(tmp_dst, filename);
746 } else {
747 abs_dst = make_absolute(xstrdup(filename), pwd);
748 }
Darren Tuckera627d422013-06-02 07:31:17 +1000749 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100750
Damien Millerd8accc02014-05-15 13:46:25 +1000751 resume |= global_aflag;
752 if (!quiet && resume)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000753 mprintf("Resuming upload of %s to %s\n",
754 g.gl_pathv[i], abs_dst);
Damien Millerd8accc02014-05-15 13:46:25 +1000755 else if (!quiet && !resume)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000756 mprintf("Uploading %s to %s\n",
757 g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100758 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
759 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
Damien Millerd8accc02014-05-15 13:46:25 +1000760 pflag || global_pflag, 1, resume,
Damien Millerf29238e2013-10-17 11:48:52 +1100761 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100762 err = -1;
763 } else {
764 if (do_upload(conn, g.gl_pathv[i], abs_dst,
Damien Millerd8accc02014-05-15 13:46:25 +1000765 pflag || global_pflag, resume,
Damien Millerf29238e2013-10-17 11:48:52 +1100766 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100767 err = -1;
768 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100769 }
770
771out:
Darren Tuckera627d422013-06-02 07:31:17 +1000772 free(abs_dst);
773 free(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100774 globfree(&g);
775 return(err);
776}
777
778static int
779sdirent_comp(const void *aa, const void *bb)
780{
781 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
782 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000783 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100784
Darren Tuckerb9123452004-06-22 13:06:45 +1000785#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000786 if (sort_flag & LS_NAME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000787 return (rmul * strcmp(a->filename, b->filename));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000788 else if (sort_flag & LS_TIME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000789 return (rmul * NCMP(a->a.mtime, b->a.mtime));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000790 else if (sort_flag & LS_SIZE_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000791 return (rmul * NCMP(a->a.size, b->a.size));
792
793 fatal("Unknown ls sort type");
Damien Miller20e1fab2004-02-18 14:30:55 +1100794}
795
796/* sftp ls.1 replacement for directories */
797static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000798do_ls_dir(struct sftp_conn *conn, const char *path,
799 const char *strip_path, int lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100800{
Damien Millereccb9de2005-06-17 12:59:34 +1000801 int n;
802 u_int c = 1, colspace = 0, columns = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100803 SFTP_DIRENT **d;
804
805 if ((n = do_readdir(conn, path, &d)) != 0)
806 return (n);
807
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000808 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000809 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100810 struct winsize ws;
811 char *tmp;
812
813 /* Count entries for sort and find longest filename */
Darren Tucker9a526452004-06-22 13:09:55 +1000814 for (n = 0; d[n] != NULL; n++) {
815 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +0000816 m = MAXIMUM(m, strlen(d[n]->filename));
Darren Tucker9a526452004-06-22 13:09:55 +1000817 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100818
819 /* Add any subpath that also needs to be counted */
820 tmp = path_strip(path, strip_path);
821 m += strlen(tmp);
Darren Tuckera627d422013-06-02 07:31:17 +1000822 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100823
824 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
825 width = ws.ws_col;
826
827 columns = width / (m + 2);
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +0000828 columns = MAXIMUM(columns, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +1100829 colspace = width / columns;
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +0000830 colspace = MINIMUM(colspace, width);
Damien Miller20e1fab2004-02-18 14:30:55 +1100831 }
832
Darren Tuckerb9123452004-06-22 13:06:45 +1000833 if (lflag & SORT_FLAGS) {
Damien Miller653b93b2005-11-05 15:15:23 +1100834 for (n = 0; d[n] != NULL; n++)
835 ; /* count entries */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000836 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
Darren Tuckerb9123452004-06-22 13:06:45 +1000837 qsort(d, n, sizeof(*d), sdirent_comp);
838 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100839
Darren Tuckercdf547a2004-05-24 10:12:19 +1000840 for (n = 0; d[n] != NULL && !interrupted; n++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100841 char *tmp, *fname;
842
Darren Tucker9a526452004-06-22 13:09:55 +1000843 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
844 continue;
845
Damien Miller20e1fab2004-02-18 14:30:55 +1100846 tmp = path_append(path, d[n]->filename);
847 fname = path_strip(tmp, strip_path);
Darren Tuckera627d422013-06-02 07:31:17 +1000848 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100849
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000850 if (lflag & LS_LONG_VIEW) {
Darren Tucker2901e2d2010-01-13 22:44:06 +1100851 if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000852 char *lname;
853 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100854
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000855 memset(&sb, 0, sizeof(sb));
856 attrib_to_stat(&d[n]->a, &sb);
Darren Tucker2901e2d2010-01-13 22:44:06 +1100857 lname = ls_file(fname, &sb, 1,
858 (lflag & LS_SI_UNITS));
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000859 mprintf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000860 free(lname);
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000861 } else
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000862 mprintf("%s\n", d[n]->longname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100863 } else {
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000864 mprintf("%-*s", colspace, fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100865 if (c >= columns) {
866 printf("\n");
867 c = 1;
868 } else
869 c++;
870 }
871
Darren Tuckera627d422013-06-02 07:31:17 +1000872 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100873 }
874
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000875 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100876 printf("\n");
877
878 free_sftp_dirents(d);
879 return (0);
880}
881
882/* sftp ls.1 replacement which handles path globs */
883static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000884do_globbed_ls(struct sftp_conn *conn, const char *path,
885 const char *strip_path, int lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100886{
Damien Millera6e121a2010-10-07 21:39:17 +1100887 char *fname, *lname;
Damien Miller68e2e562010-10-07 21:39:55 +1100888 glob_t g;
Damien Miller00707762014-07-09 13:07:06 +1000889 int err, r;
Damien Miller68e2e562010-10-07 21:39:55 +1100890 struct winsize ws;
891 u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100892
893 memset(&g, 0, sizeof(g));
894
Damien Miller00707762014-07-09 13:07:06 +1000895 if ((r = remote_glob(conn, path,
Damien Millerd7be70d2011-09-22 21:43:06 +1000896 GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
Damien Miller00707762014-07-09 13:07:06 +1000897 NULL, &g)) != 0 ||
Damien Millera6e121a2010-10-07 21:39:17 +1100898 (g.gl_pathc && !g.gl_matchc)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100899 if (g.gl_pathc)
900 globfree(&g);
Damien Miller00707762014-07-09 13:07:06 +1000901 if (r == GLOB_NOSPACE) {
902 error("Can't ls: Too many matches for \"%s\"", path);
903 } else {
904 error("Can't ls: \"%s\" not found", path);
905 }
Damien Millera6e121a2010-10-07 21:39:17 +1100906 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100907 }
908
Darren Tuckercdf547a2004-05-24 10:12:19 +1000909 if (interrupted)
910 goto out;
911
Damien Miller20e1fab2004-02-18 14:30:55 +1100912 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100913 * If the glob returns a single match and it is a directory,
914 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100915 */
Damien Millera6e121a2010-10-07 21:39:17 +1100916 if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
917 S_ISDIR(g.gl_statv[0]->st_mode)) {
918 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
919 globfree(&g);
920 return err;
Damien Miller20e1fab2004-02-18 14:30:55 +1100921 }
922
Damien Miller68e2e562010-10-07 21:39:55 +1100923 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
924 width = ws.ws_col;
Damien Miller20e1fab2004-02-18 14:30:55 +1100925
Damien Miller68e2e562010-10-07 21:39:55 +1100926 if (!(lflag & LS_SHORT_VIEW)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100927 /* Count entries for sort and find longest filename */
928 for (i = 0; g.gl_pathv[i]; i++)
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +0000929 m = MAXIMUM(m, strlen(g.gl_pathv[i]));
Damien Miller20e1fab2004-02-18 14:30:55 +1100930
Damien Miller20e1fab2004-02-18 14:30:55 +1100931 columns = width / (m + 2);
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +0000932 columns = MAXIMUM(columns, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +1100933 colspace = width / columns;
934 }
935
Damien Millerea858292012-06-30 08:33:32 +1000936 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100937 fname = path_strip(g.gl_pathv[i], strip_path);
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000938 if (lflag & LS_LONG_VIEW) {
Damien Millera6e121a2010-10-07 21:39:17 +1100939 if (g.gl_statv[i] == NULL) {
940 error("no stat information for %s", fname);
941 continue;
942 }
943 lname = ls_file(fname, g.gl_statv[i], 1,
944 (lflag & LS_SI_UNITS));
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000945 mprintf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000946 free(lname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100947 } else {
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000948 mprintf("%-*s", colspace, fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100949 if (c >= columns) {
950 printf("\n");
951 c = 1;
952 } else
953 c++;
954 }
Darren Tuckera627d422013-06-02 07:31:17 +1000955 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100956 }
957
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000958 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100959 printf("\n");
960
Darren Tuckercdf547a2004-05-24 10:12:19 +1000961 out:
Damien Miller20e1fab2004-02-18 14:30:55 +1100962 if (g.gl_pathc)
963 globfree(&g);
964
Damien Millera6e121a2010-10-07 21:39:17 +1100965 return 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100966}
967
Damien Millerd671e5a2008-05-19 14:53:33 +1000968static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000969do_df(struct sftp_conn *conn, const char *path, int hflag, int iflag)
Damien Millerd671e5a2008-05-19 14:53:33 +1000970{
Darren Tucker7b598892008-06-09 22:49:36 +1000971 struct sftp_statvfs st;
Damien Millerd671e5a2008-05-19 14:53:33 +1000972 char s_used[FMT_SCALED_STRSIZE];
973 char s_avail[FMT_SCALED_STRSIZE];
974 char s_root[FMT_SCALED_STRSIZE];
975 char s_total[FMT_SCALED_STRSIZE];
Darren Tuckerb5082e92010-01-08 18:51:47 +1100976 unsigned long long ffree;
Damien Millerd671e5a2008-05-19 14:53:33 +1000977
978 if (do_statvfs(conn, path, &st, 1) == -1)
979 return -1;
980 if (iflag) {
Darren Tuckerb5082e92010-01-08 18:51:47 +1100981 ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
Damien Millerd671e5a2008-05-19 14:53:33 +1000982 printf(" Inodes Used Avail "
983 "(root) %%Capacity\n");
984 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
985 (unsigned long long)st.f_files,
986 (unsigned long long)(st.f_files - st.f_ffree),
987 (unsigned long long)st.f_favail,
Darren Tuckerb5082e92010-01-08 18:51:47 +1100988 (unsigned long long)st.f_ffree, ffree);
Damien Millerd671e5a2008-05-19 14:53:33 +1000989 } else if (hflag) {
990 strlcpy(s_used, "error", sizeof(s_used));
991 strlcpy(s_avail, "error", sizeof(s_avail));
992 strlcpy(s_root, "error", sizeof(s_root));
993 strlcpy(s_total, "error", sizeof(s_total));
994 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
995 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
996 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
997 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
998 printf(" Size Used Avail (root) %%Capacity\n");
999 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
1000 s_total, s_used, s_avail, s_root,
1001 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
1002 st.f_blocks));
1003 } else {
1004 printf(" Size Used Avail "
1005 "(root) %%Capacity\n");
1006 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
1007 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
1008 (unsigned long long)(st.f_frsize *
1009 (st.f_blocks - st.f_bfree) / 1024),
1010 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
1011 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
1012 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
1013 st.f_blocks));
1014 }
1015 return 0;
1016}
1017
Damien Miller1cbc2922007-10-26 14:27:45 +10001018/*
1019 * Undo escaping of glob sequences in place. Used to undo extra escaping
1020 * applied in makeargv() when the string is destined for a function that
1021 * does not glob it.
1022 */
1023static void
1024undo_glob_escape(char *s)
1025{
1026 size_t i, j;
1027
1028 for (i = j = 0;;) {
1029 if (s[i] == '\0') {
1030 s[j] = '\0';
1031 return;
1032 }
1033 if (s[i] != '\\') {
1034 s[j++] = s[i++];
1035 continue;
1036 }
1037 /* s[i] == '\\' */
1038 ++i;
1039 switch (s[i]) {
1040 case '?':
1041 case '[':
1042 case '*':
1043 case '\\':
1044 s[j++] = s[i++];
1045 break;
1046 case '\0':
1047 s[j++] = '\\';
1048 s[j] = '\0';
1049 return;
1050 default:
1051 s[j++] = '\\';
1052 s[j++] = s[i++];
1053 break;
1054 }
1055 }
1056}
1057
1058/*
1059 * Split a string into an argument vector using sh(1)-style quoting,
1060 * comment and escaping rules, but with some tweaks to handle glob(3)
1061 * wildcards.
Darren Tucker909d8582010-01-08 19:02:40 +11001062 * The "sloppy" flag allows for recovery from missing terminating quote, for
1063 * use in parsing incomplete commandlines during tab autocompletion.
1064 *
Damien Miller1cbc2922007-10-26 14:27:45 +10001065 * Returns NULL on error or a NULL-terminated array of arguments.
Darren Tucker909d8582010-01-08 19:02:40 +11001066 *
1067 * If "lastquote" is not NULL, the quoting character used for the last
1068 * argument is placed in *lastquote ("\0", "'" or "\"").
Damien Miller02e87802013-08-21 02:38:51 +10001069 *
Darren Tucker909d8582010-01-08 19:02:40 +11001070 * If "terminated" is not NULL, *terminated will be set to 1 when the
1071 * last argument's quote has been properly terminated or 0 otherwise.
1072 * This parameter is only of use if "sloppy" is set.
Damien Miller1cbc2922007-10-26 14:27:45 +10001073 */
1074#define MAXARGS 128
1075#define MAXARGLEN 8192
1076static char **
Darren Tucker909d8582010-01-08 19:02:40 +11001077makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
1078 u_int *terminated)
Damien Miller1cbc2922007-10-26 14:27:45 +10001079{
1080 int argc, quot;
1081 size_t i, j;
1082 static char argvs[MAXARGLEN];
1083 static char *argv[MAXARGS + 1];
1084 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
1085
1086 *argcp = argc = 0;
1087 if (strlen(arg) > sizeof(argvs) - 1) {
1088 args_too_longs:
1089 error("string too long");
1090 return NULL;
1091 }
Darren Tucker909d8582010-01-08 19:02:40 +11001092 if (terminated != NULL)
1093 *terminated = 1;
1094 if (lastquote != NULL)
1095 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001096 state = MA_START;
1097 i = j = 0;
1098 for (;;) {
Damien Miller07daed52012-10-31 08:57:55 +11001099 if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){
Darren Tucker063018d2012-10-05 10:43:58 +10001100 error("Too many arguments.");
1101 return NULL;
1102 }
Damien Millerfdb23062013-11-21 13:57:15 +11001103 if (isspace((unsigned char)arg[i])) {
Damien Miller1cbc2922007-10-26 14:27:45 +10001104 if (state == MA_UNQUOTED) {
1105 /* Terminate current argument */
1106 argvs[j++] = '\0';
1107 argc++;
1108 state = MA_START;
1109 } else if (state != MA_START)
1110 argvs[j++] = arg[i];
1111 } else if (arg[i] == '"' || arg[i] == '\'') {
1112 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1113 if (state == MA_START) {
1114 argv[argc] = argvs + j;
1115 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +11001116 if (lastquote != NULL)
1117 *lastquote = arg[i];
Damien Miller02e87802013-08-21 02:38:51 +10001118 } else if (state == MA_UNQUOTED)
Damien Miller1cbc2922007-10-26 14:27:45 +10001119 state = q;
1120 else if (state == q)
1121 state = MA_UNQUOTED;
1122 else
1123 argvs[j++] = arg[i];
1124 } else if (arg[i] == '\\') {
1125 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1126 quot = state == MA_SQUOTE ? '\'' : '"';
1127 /* Unescape quote we are in */
1128 /* XXX support \n and friends? */
1129 if (arg[i + 1] == quot) {
1130 i++;
1131 argvs[j++] = arg[i];
1132 } else if (arg[i + 1] == '?' ||
1133 arg[i + 1] == '[' || arg[i + 1] == '*') {
1134 /*
1135 * Special case for sftp: append
1136 * double-escaped glob sequence -
1137 * glob will undo one level of
1138 * escaping. NB. string can grow here.
1139 */
1140 if (j >= sizeof(argvs) - 5)
1141 goto args_too_longs;
1142 argvs[j++] = '\\';
1143 argvs[j++] = arg[i++];
1144 argvs[j++] = '\\';
1145 argvs[j++] = arg[i];
1146 } else {
1147 argvs[j++] = arg[i++];
1148 argvs[j++] = arg[i];
1149 }
1150 } else {
1151 if (state == MA_START) {
1152 argv[argc] = argvs + j;
1153 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001154 if (lastquote != NULL)
1155 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001156 }
1157 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1158 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1159 /*
1160 * Special case for sftp: append
1161 * escaped glob sequence -
1162 * glob will undo one level of
1163 * escaping.
1164 */
1165 argvs[j++] = arg[i++];
1166 argvs[j++] = arg[i];
1167 } else {
1168 /* Unescape everything */
1169 /* XXX support \n and friends? */
1170 i++;
1171 argvs[j++] = arg[i];
1172 }
1173 }
1174 } else if (arg[i] == '#') {
1175 if (state == MA_SQUOTE || state == MA_DQUOTE)
1176 argvs[j++] = arg[i];
1177 else
1178 goto string_done;
1179 } else if (arg[i] == '\0') {
1180 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001181 if (sloppy) {
1182 state = MA_UNQUOTED;
1183 if (terminated != NULL)
1184 *terminated = 0;
1185 goto string_done;
1186 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001187 error("Unterminated quoted argument");
1188 return NULL;
1189 }
1190 string_done:
1191 if (state == MA_UNQUOTED) {
1192 argvs[j++] = '\0';
1193 argc++;
1194 }
1195 break;
1196 } else {
1197 if (state == MA_START) {
1198 argv[argc] = argvs + j;
1199 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001200 if (lastquote != NULL)
1201 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001202 }
1203 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1204 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1205 /*
1206 * Special case for sftp: escape quoted
1207 * glob(3) wildcards. NB. string can grow
1208 * here.
1209 */
1210 if (j >= sizeof(argvs) - 3)
1211 goto args_too_longs;
1212 argvs[j++] = '\\';
1213 argvs[j++] = arg[i];
1214 } else
1215 argvs[j++] = arg[i];
1216 }
1217 i++;
1218 }
1219 *argcp = argc;
1220 return argv;
1221}
1222
Damien Miller20e1fab2004-02-18 14:30:55 +11001223static int
Damien Millerd8accc02014-05-15 13:46:25 +10001224parse_args(const char **cpp, int *ignore_errors, int *aflag,
djm@openbsd.org34a01b22016-04-08 08:19:17 +00001225 int *fflag, int *hflag, int *iflag, int *lflag, int *pflag,
Damien Millerd8accc02014-05-15 13:46:25 +10001226 int *rflag, int *sflag,
Damien Millerf29238e2013-10-17 11:48:52 +11001227 unsigned long *n_arg, char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001228{
1229 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001230 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001231 int base = 0;
1232 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001233 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001234
1235 /* Skip leading whitespace */
1236 cp = cp + strspn(cp, WHITESPACE);
1237
Damien Miller20e1fab2004-02-18 14:30:55 +11001238 /* Check for leading '-' (disable error processing) */
Damien Millerf29238e2013-10-17 11:48:52 +11001239 *ignore_errors = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001240 if (*cp == '-') {
Damien Millerf29238e2013-10-17 11:48:52 +11001241 *ignore_errors = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001242 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001243 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001244 }
1245
Darren Tucker70cc0922010-01-09 22:28:03 +11001246 /* Ignore blank lines and lines which begin with comment '#' char */
1247 if (*cp == '\0' || *cp == '#')
1248 return (0);
1249
Darren Tucker909d8582010-01-08 19:02:40 +11001250 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001251 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001252
Damien Miller1cbc2922007-10-26 14:27:45 +10001253 /* Figure out which command we have */
1254 for (i = 0; cmds[i].c != NULL; i++) {
Damien Millerd6d9fa02013-02-12 11:02:46 +11001255 if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001256 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001257 }
1258 cmdnum = cmds[i].n;
1259 cmd = cmds[i].c;
1260
1261 /* Special case */
1262 if (*cp == '!') {
1263 cp++;
1264 cmdnum = I_SHELL;
1265 } else if (cmdnum == -1) {
1266 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001267 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001268 }
1269
1270 /* Get arguments and parse flags */
Damien Millerf29238e2013-10-17 11:48:52 +11001271 *aflag = *fflag = *hflag = *iflag = *lflag = *pflag = 0;
1272 *rflag = *sflag = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001273 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001274 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001275 switch (cmdnum) {
1276 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001277 case I_REGET:
Damien Millerd8accc02014-05-15 13:46:25 +10001278 case I_REPUT:
Damien Miller20e1fab2004-02-18 14:30:55 +11001279 case I_PUT:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001280 if ((optidx = parse_getput_flags(cmd, argv, argc,
Damien Millerf29238e2013-10-17 11:48:52 +11001281 aflag, fflag, pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001282 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001283 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001284 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001285 error("You must specify at least one path after a "
1286 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001287 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001288 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001289 *path1 = xstrdup(argv[optidx]);
1290 /* Get second pathname (optional) */
1291 if (argc - optidx > 1) {
1292 *path2 = xstrdup(argv[optidx + 1]);
1293 /* Destination is not globbed */
1294 undo_glob_escape(*path2);
1295 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001296 break;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001297 case I_LINK:
1298 if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1299 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001300 goto parse_two_paths;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001301 case I_RENAME:
Damien Millerc7dba122013-08-21 02:41:15 +10001302 if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1)
1303 return -1;
1304 goto parse_two_paths;
1305 case I_SYMLINK:
Damien Miller036d3072013-08-21 02:41:46 +10001306 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1307 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001308 parse_two_paths:
Damien Miller1cbc2922007-10-26 14:27:45 +10001309 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001310 error("You must specify two paths after a %s "
1311 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001312 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001313 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001314 *path1 = xstrdup(argv[optidx]);
1315 *path2 = xstrdup(argv[optidx + 1]);
1316 /* Paths are not globbed */
1317 undo_glob_escape(*path1);
1318 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001319 break;
1320 case I_RM:
1321 case I_MKDIR:
1322 case I_RMDIR:
1323 case I_CHDIR:
1324 case I_LCHDIR:
1325 case I_LMKDIR:
Damien Miller036d3072013-08-21 02:41:46 +10001326 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1327 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001328 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001329 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001330 error("You must specify a path after a %s command.",
1331 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001332 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001333 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001334 *path1 = xstrdup(argv[optidx]);
1335 /* Only "rm" globs */
1336 if (cmdnum != I_RM)
1337 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001338 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001339 case I_DF:
1340 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1341 iflag)) == -1)
1342 return -1;
1343 /* Default to current directory if no path specified */
1344 if (argc - optidx < 1)
1345 *path1 = NULL;
1346 else {
1347 *path1 = xstrdup(argv[optidx]);
1348 undo_glob_escape(*path1);
1349 }
1350 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001351 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001352 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001353 return(-1);
1354 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001355 if (argc - optidx > 0)
1356 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001357 break;
1358 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001359 /* Skip ls command and following whitespace */
1360 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001361 case I_SHELL:
1362 /* Uses the rest of the line */
1363 break;
1364 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001365 case I_CHMOD:
1366 base = 8;
1367 case I_CHOWN:
1368 case I_CHGRP:
Damien Miller036d3072013-08-21 02:41:46 +10001369 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1370 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001371 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001372 if (argc - optidx < 1)
1373 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001374 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001375 l = strtol(argv[optidx], &cp2, base);
1376 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1377 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1378 l < 0) {
1379 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001380 error("You must supply a numeric argument "
1381 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001382 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001383 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001384 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001385 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001386 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001387 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001388 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001389 error("You must specify a path after a %s command.",
1390 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001391 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001392 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001393 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001394 break;
1395 case I_QUIT:
1396 case I_PWD:
1397 case I_LPWD:
1398 case I_HELP:
1399 case I_VERSION:
1400 case I_PROGRESS:
Damien Miller036d3072013-08-21 02:41:46 +10001401 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1402 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001403 break;
1404 default:
1405 fatal("Command not implemented");
1406 }
1407
1408 *cpp = cp;
1409 return(cmdnum);
1410}
1411
1412static int
1413parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1414 int err_abort)
1415{
1416 char *path1, *path2, *tmp;
djm@openbsd.org34a01b22016-04-08 08:19:17 +00001417 int ignore_errors = 0, aflag = 0, fflag = 0, hflag = 0,
Damien Millerd8accc02014-05-15 13:46:25 +10001418 iflag = 0;
Damien Millerf29238e2013-10-17 11:48:52 +11001419 int lflag = 0, pflag = 0, rflag = 0, sflag = 0;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001420 int cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001421 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001422 Attrib a, *aa;
deraadt@openbsd.org087266e2015-01-20 23:14:00 +00001423 char path_buf[PATH_MAX];
Damien Miller20e1fab2004-02-18 14:30:55 +11001424 int err = 0;
1425 glob_t g;
1426
1427 path1 = path2 = NULL;
Damien Millerf29238e2013-10-17 11:48:52 +11001428 cmdnum = parse_args(&cmd, &ignore_errors, &aflag, &fflag, &hflag,
1429 &iflag, &lflag, &pflag, &rflag, &sflag, &n_arg, &path1, &path2);
1430 if (ignore_errors != 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001431 err_abort = 0;
1432
1433 memset(&g, 0, sizeof(g));
1434
1435 /* Perform command */
1436 switch (cmdnum) {
1437 case 0:
1438 /* Blank line */
1439 break;
1440 case -1:
1441 /* Unrecognized command */
1442 err = -1;
1443 break;
Damien Miller0d032412013-07-25 11:56:52 +10001444 case I_REGET:
1445 aflag = 1;
1446 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001447 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001448 err = process_get(conn, path1, path2, *pwd, pflag,
Damien Millerf29238e2013-10-17 11:48:52 +11001449 rflag, aflag, fflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001450 break;
Damien Millerd8accc02014-05-15 13:46:25 +10001451 case I_REPUT:
1452 aflag = 1;
1453 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001454 case I_PUT:
Damien Millerf29238e2013-10-17 11:48:52 +11001455 err = process_put(conn, path1, path2, *pwd, pflag,
Damien Millerd8accc02014-05-15 13:46:25 +10001456 rflag, aflag, fflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001457 break;
1458 case I_RENAME:
1459 path1 = make_absolute(path1, *pwd);
1460 path2 = make_absolute(path2, *pwd);
Damien Millerc7dba122013-08-21 02:41:15 +10001461 err = do_rename(conn, path1, path2, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001462 break;
1463 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001464 sflag = 1;
1465 case I_LINK:
Damien Miller034f27a2013-08-21 02:40:44 +10001466 if (!sflag)
1467 path1 = make_absolute(path1, *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001468 path2 = make_absolute(path2, *pwd);
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001469 err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001470 break;
1471 case I_RM:
1472 path1 = make_absolute(path1, *pwd);
1473 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001474 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001475 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001476 mprintf("Removing %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001477 err = do_rm(conn, g.gl_pathv[i]);
1478 if (err != 0 && err_abort)
1479 break;
1480 }
1481 break;
1482 case I_MKDIR:
1483 path1 = make_absolute(path1, *pwd);
1484 attrib_clear(&a);
1485 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1486 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001487 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001488 break;
1489 case I_RMDIR:
1490 path1 = make_absolute(path1, *pwd);
1491 err = do_rmdir(conn, path1);
1492 break;
1493 case I_CHDIR:
1494 path1 = make_absolute(path1, *pwd);
1495 if ((tmp = do_realpath(conn, path1)) == NULL) {
1496 err = 1;
1497 break;
1498 }
1499 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
Darren Tuckera627d422013-06-02 07:31:17 +10001500 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001501 err = 1;
1502 break;
1503 }
1504 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1505 error("Can't change directory: Can't check target");
Darren Tuckera627d422013-06-02 07:31:17 +10001506 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001507 err = 1;
1508 break;
1509 }
1510 if (!S_ISDIR(aa->perm)) {
1511 error("Can't change directory: \"%s\" is not "
1512 "a directory", tmp);
Darren Tuckera627d422013-06-02 07:31:17 +10001513 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001514 err = 1;
1515 break;
1516 }
Darren Tuckera627d422013-06-02 07:31:17 +10001517 free(*pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001518 *pwd = tmp;
1519 break;
1520 case I_LS:
1521 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001522 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001523 break;
1524 }
1525
1526 /* Strip pwd off beginning of non-absolute paths */
1527 tmp = NULL;
1528 if (*path1 != '/')
1529 tmp = *pwd;
1530
1531 path1 = make_absolute(path1, *pwd);
1532 err = do_globbed_ls(conn, path1, tmp, lflag);
1533 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001534 case I_DF:
1535 /* Default to current directory if no path specified */
1536 if (path1 == NULL)
1537 path1 = xstrdup(*pwd);
1538 path1 = make_absolute(path1, *pwd);
1539 err = do_df(conn, path1, hflag, iflag);
1540 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001541 case I_LCHDIR:
deraadt@openbsd.org40ba4c92014-08-20 01:28:55 +00001542 tmp = tilde_expand_filename(path1, getuid());
djm@openbsd.org7ff880e2014-08-19 23:57:18 +00001543 free(path1);
1544 path1 = tmp;
Damien Miller20e1fab2004-02-18 14:30:55 +11001545 if (chdir(path1) == -1) {
1546 error("Couldn't change local directory to "
1547 "\"%s\": %s", path1, strerror(errno));
1548 err = 1;
1549 }
1550 break;
1551 case I_LMKDIR:
1552 if (mkdir(path1, 0777) == -1) {
1553 error("Couldn't create local directory "
1554 "\"%s\": %s", path1, strerror(errno));
1555 err = 1;
1556 }
1557 break;
1558 case I_LLS:
1559 local_do_ls(cmd);
1560 break;
1561 case I_SHELL:
1562 local_do_shell(cmd);
1563 break;
1564 case I_LUMASK:
1565 umask(n_arg);
1566 printf("Local umask: %03lo\n", n_arg);
1567 break;
1568 case I_CHMOD:
1569 path1 = make_absolute(path1, *pwd);
1570 attrib_clear(&a);
1571 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1572 a.perm = n_arg;
1573 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001574 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001575 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001576 mprintf("Changing mode on %s\n",
1577 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001578 err = do_setstat(conn, g.gl_pathv[i], &a);
1579 if (err != 0 && err_abort)
1580 break;
1581 }
1582 break;
1583 case I_CHOWN:
1584 case I_CHGRP:
1585 path1 = make_absolute(path1, *pwd);
1586 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001587 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001588 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001589 if (err_abort) {
1590 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001591 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001592 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001593 continue;
1594 }
1595 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1596 error("Can't get current ownership of "
1597 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001598 if (err_abort) {
1599 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001600 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001601 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001602 continue;
1603 }
1604 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1605 if (cmdnum == I_CHOWN) {
Damien Miller9303e652013-04-23 15:22:40 +10001606 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001607 mprintf("Changing owner on %s\n",
Damien Miller9303e652013-04-23 15:22:40 +10001608 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001609 aa->uid = n_arg;
1610 } else {
Damien Miller9303e652013-04-23 15:22:40 +10001611 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001612 mprintf("Changing group on %s\n",
Damien Miller9303e652013-04-23 15:22:40 +10001613 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001614 aa->gid = n_arg;
1615 }
1616 err = do_setstat(conn, g.gl_pathv[i], aa);
1617 if (err != 0 && err_abort)
1618 break;
1619 }
1620 break;
1621 case I_PWD:
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001622 mprintf("Remote working directory: %s\n", *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001623 break;
1624 case I_LPWD:
1625 if (!getcwd(path_buf, sizeof(path_buf))) {
1626 error("Couldn't get local cwd: %s", strerror(errno));
1627 err = -1;
1628 break;
1629 }
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001630 mprintf("Local working directory: %s\n", path_buf);
Damien Miller20e1fab2004-02-18 14:30:55 +11001631 break;
1632 case I_QUIT:
1633 /* Processed below */
1634 break;
1635 case I_HELP:
1636 help();
1637 break;
1638 case I_VERSION:
1639 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1640 break;
1641 case I_PROGRESS:
1642 showprogress = !showprogress;
1643 if (showprogress)
1644 printf("Progress meter enabled\n");
1645 else
1646 printf("Progress meter disabled\n");
1647 break;
1648 default:
1649 fatal("%d is not implemented", cmdnum);
1650 }
1651
1652 if (g.gl_pathc)
1653 globfree(&g);
Darren Tuckera627d422013-06-02 07:31:17 +10001654 free(path1);
1655 free(path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001656
1657 /* If an unignored error occurs in batch mode we should abort. */
1658 if (err_abort && err != 0)
1659 return (-1);
1660 else if (cmdnum == I_QUIT)
1661 return (1);
1662
1663 return (0);
1664}
1665
Darren Tucker2d963d82004-11-07 20:04:10 +11001666#ifdef USE_LIBEDIT
1667static char *
1668prompt(EditLine *el)
1669{
1670 return ("sftp> ");
1671}
Darren Tucker2d963d82004-11-07 20:04:10 +11001672
Darren Tucker909d8582010-01-08 19:02:40 +11001673/* Display entries in 'list' after skipping the first 'len' chars */
1674static void
1675complete_display(char **list, u_int len)
1676{
1677 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1678 struct winsize ws;
1679 char *tmp;
1680
1681 /* Count entries for sort and find longest */
Damien Miller02e87802013-08-21 02:38:51 +10001682 for (y = 0; list[y]; y++)
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +00001683 m = MAXIMUM(m, strlen(list[y]));
Darren Tucker909d8582010-01-08 19:02:40 +11001684
1685 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1686 width = ws.ws_col;
1687
1688 m = m > len ? m - len : 0;
1689 columns = width / (m + 2);
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +00001690 columns = MAXIMUM(columns, 1);
Darren Tucker909d8582010-01-08 19:02:40 +11001691 colspace = width / columns;
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +00001692 colspace = MINIMUM(colspace, width);
Darren Tucker909d8582010-01-08 19:02:40 +11001693
1694 printf("\n");
1695 m = 1;
1696 for (y = 0; list[y]; y++) {
1697 llen = strlen(list[y]);
1698 tmp = llen > len ? list[y] + len : "";
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001699 mprintf("%-*s", colspace, tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001700 if (m >= columns) {
1701 printf("\n");
1702 m = 1;
1703 } else
1704 m++;
1705 }
1706 printf("\n");
1707}
1708
1709/*
1710 * Given a "list" of words that begin with a common prefix of "word",
1711 * attempt to find an autocompletion to extends "word" by the next
1712 * characters common to all entries in "list".
1713 */
1714static char *
1715complete_ambiguous(const char *word, char **list, size_t count)
1716{
1717 if (word == NULL)
1718 return NULL;
1719
1720 if (count > 0) {
1721 u_int y, matchlen = strlen(list[0]);
1722
1723 /* Find length of common stem */
1724 for (y = 1; list[y]; y++) {
1725 u_int x;
1726
Damien Miller02e87802013-08-21 02:38:51 +10001727 for (x = 0; x < matchlen; x++)
1728 if (list[0][x] != list[y][x])
Darren Tucker909d8582010-01-08 19:02:40 +11001729 break;
1730
1731 matchlen = x;
1732 }
1733
1734 if (matchlen > strlen(word)) {
1735 char *tmp = xstrdup(list[0]);
1736
Darren Tucker340d1682010-01-09 08:54:31 +11001737 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001738 return tmp;
1739 }
Damien Miller02e87802013-08-21 02:38:51 +10001740 }
Darren Tucker909d8582010-01-08 19:02:40 +11001741
1742 return xstrdup(word);
1743}
1744
1745/* Autocomplete a sftp command */
1746static int
1747complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1748 int terminated)
1749{
1750 u_int y, count = 0, cmdlen, tmplen;
1751 char *tmp, **list, argterm[3];
1752 const LineInfo *lf;
1753
1754 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1755
1756 /* No command specified: display all available commands */
1757 if (cmd == NULL) {
1758 for (y = 0; cmds[y].c; y++)
1759 list[count++] = xstrdup(cmds[y].c);
Damien Miller02e87802013-08-21 02:38:51 +10001760
Darren Tucker909d8582010-01-08 19:02:40 +11001761 list[count] = NULL;
1762 complete_display(list, 0);
1763
Damien Miller02e87802013-08-21 02:38:51 +10001764 for (y = 0; list[y] != NULL; y++)
1765 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001766 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001767 return count;
1768 }
1769
1770 /* Prepare subset of commands that start with "cmd" */
1771 cmdlen = strlen(cmd);
1772 for (y = 0; cmds[y].c; y++) {
Damien Miller02e87802013-08-21 02:38:51 +10001773 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
Darren Tucker909d8582010-01-08 19:02:40 +11001774 list[count++] = xstrdup(cmds[y].c);
1775 }
1776 list[count] = NULL;
1777
Damien Miller47d81152011-11-25 13:53:48 +11001778 if (count == 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10001779 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001780 return 0;
Damien Miller47d81152011-11-25 13:53:48 +11001781 }
Darren Tucker909d8582010-01-08 19:02:40 +11001782
1783 /* Complete ambigious command */
1784 tmp = complete_ambiguous(cmd, list, count);
1785 if (count > 1)
1786 complete_display(list, 0);
1787
Damien Miller02e87802013-08-21 02:38:51 +10001788 for (y = 0; list[y]; y++)
1789 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001790 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001791
1792 if (tmp != NULL) {
1793 tmplen = strlen(tmp);
1794 cmdlen = strlen(cmd);
1795 /* If cmd may be extended then do so */
1796 if (tmplen > cmdlen)
1797 if (el_insertstr(el, tmp + cmdlen) == -1)
1798 fatal("el_insertstr failed.");
1799 lf = el_line(el);
1800 /* Terminate argument cleanly */
1801 if (count == 1) {
1802 y = 0;
1803 if (!terminated)
1804 argterm[y++] = quote;
1805 if (lastarg || *(lf->cursor) != ' ')
1806 argterm[y++] = ' ';
1807 argterm[y] = '\0';
1808 if (y > 0 && el_insertstr(el, argterm) == -1)
1809 fatal("el_insertstr failed.");
1810 }
Darren Tuckera627d422013-06-02 07:31:17 +10001811 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001812 }
1813
1814 return count;
1815}
1816
1817/*
1818 * Determine whether a particular sftp command's arguments (if any)
1819 * represent local or remote files.
1820 */
1821static int
1822complete_is_remote(char *cmd) {
1823 int i;
1824
1825 if (cmd == NULL)
1826 return -1;
1827
1828 for (i = 0; cmds[i].c; i++) {
Damien Miller02e87802013-08-21 02:38:51 +10001829 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
Darren Tucker909d8582010-01-08 19:02:40 +11001830 return cmds[i].t;
1831 }
1832
1833 return -1;
1834}
1835
1836/* Autocomplete a filename "file" */
1837static int
1838complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1839 char *file, int remote, int lastarg, char quote, int terminated)
1840{
1841 glob_t g;
Darren Tuckerea647212013-06-06 08:19:09 +10001842 char *tmp, *tmp2, ins[8];
Darren Tucker17146d32012-10-05 10:46:16 +10001843 u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
Darren Tuckerea647212013-06-06 08:19:09 +10001844 int clen;
Darren Tucker909d8582010-01-08 19:02:40 +11001845 const LineInfo *lf;
Damien Miller02e87802013-08-21 02:38:51 +10001846
Darren Tucker909d8582010-01-08 19:02:40 +11001847 /* Glob from "file" location */
1848 if (file == NULL)
1849 tmp = xstrdup("*");
1850 else
1851 xasprintf(&tmp, "%s*", file);
1852
Darren Tucker191fcc62012-10-05 10:45:01 +10001853 /* Check if the path is absolute. */
1854 isabs = tmp[0] == '/';
1855
Darren Tucker909d8582010-01-08 19:02:40 +11001856 memset(&g, 0, sizeof(g));
1857 if (remote != LOCAL) {
1858 tmp = make_absolute(tmp, remote_path);
1859 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001860 } else
Darren Tucker909d8582010-01-08 19:02:40 +11001861 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001862
Darren Tucker909d8582010-01-08 19:02:40 +11001863 /* Determine length of pwd so we can trim completion display */
1864 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1865 /* Terminate counting on first unescaped glob metacharacter */
1866 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1867 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1868 hadglob = 1;
1869 break;
1870 }
1871 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1872 tmplen++;
1873 if (tmp[tmplen] == '/')
1874 pwdlen = tmplen + 1; /* track last seen '/' */
1875 }
Darren Tuckera627d422013-06-02 07:31:17 +10001876 free(tmp);
Damien Millerd7fd8be2014-05-15 14:24:59 +10001877 tmp = NULL;
Darren Tucker909d8582010-01-08 19:02:40 +11001878
Damien Miller02e87802013-08-21 02:38:51 +10001879 if (g.gl_matchc == 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001880 goto out;
1881
1882 if (g.gl_matchc > 1)
1883 complete_display(g.gl_pathv, pwdlen);
1884
Darren Tucker909d8582010-01-08 19:02:40 +11001885 /* Don't try to extend globs */
1886 if (file == NULL || hadglob)
1887 goto out;
1888
1889 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
Darren Tucker191fcc62012-10-05 10:45:01 +10001890 tmp = path_strip(tmp2, isabs ? NULL : remote_path);
Darren Tuckera627d422013-06-02 07:31:17 +10001891 free(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001892
1893 if (tmp == NULL)
1894 goto out;
1895
1896 tmplen = strlen(tmp);
1897 filelen = strlen(file);
1898
Darren Tucker17146d32012-10-05 10:46:16 +10001899 /* Count the number of escaped characters in the input string. */
1900 cesc = isesc = 0;
1901 for (i = 0; i < filelen; i++) {
1902 if (!isesc && file[i] == '\\' && i + 1 < filelen){
1903 isesc = 1;
1904 cesc++;
1905 } else
1906 isesc = 0;
1907 }
1908
1909 if (tmplen > (filelen - cesc)) {
1910 tmp2 = tmp + filelen - cesc;
Damien Miller02e87802013-08-21 02:38:51 +10001911 len = strlen(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001912 /* quote argument on way out */
Darren Tuckerea647212013-06-06 08:19:09 +10001913 for (i = 0; i < len; i += clen) {
1914 if ((clen = mblen(tmp2 + i, len - i)) < 0 ||
1915 (size_t)clen > sizeof(ins) - 2)
1916 fatal("invalid multibyte character");
Darren Tucker909d8582010-01-08 19:02:40 +11001917 ins[0] = '\\';
Darren Tuckerea647212013-06-06 08:19:09 +10001918 memcpy(ins + 1, tmp2 + i, clen);
1919 ins[clen + 1] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001920 switch (tmp2[i]) {
1921 case '\'':
1922 case '"':
1923 case '\\':
1924 case '\t':
Darren Tuckerd78739a2010-10-24 10:56:32 +11001925 case '[':
Darren Tucker909d8582010-01-08 19:02:40 +11001926 case ' ':
Darren Tucker17146d32012-10-05 10:46:16 +10001927 case '#':
1928 case '*':
Darren Tucker909d8582010-01-08 19:02:40 +11001929 if (quote == '\0' || tmp2[i] == quote) {
1930 if (el_insertstr(el, ins) == -1)
1931 fatal("el_insertstr "
1932 "failed.");
1933 break;
1934 }
1935 /* FALLTHROUGH */
1936 default:
1937 if (el_insertstr(el, ins + 1) == -1)
1938 fatal("el_insertstr failed.");
1939 break;
1940 }
1941 }
1942 }
1943
1944 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11001945 if (g.gl_matchc == 1) {
1946 i = 0;
Damien Miller38094812014-05-15 14:25:18 +10001947 if (!terminated && quote != '\0')
Darren Tucker909d8582010-01-08 19:02:40 +11001948 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11001949 if (*(lf->cursor - 1) != '/' &&
1950 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11001951 ins[i++] = ' ';
1952 ins[i] = '\0';
1953 if (i > 0 && el_insertstr(el, ins) == -1)
1954 fatal("el_insertstr failed.");
1955 }
Darren Tuckera627d422013-06-02 07:31:17 +10001956 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001957
1958 out:
1959 globfree(&g);
1960 return g.gl_matchc;
1961}
1962
1963/* tab-completion hook function, called via libedit */
1964static unsigned char
1965complete(EditLine *el, int ch)
1966{
Damien Miller02e87802013-08-21 02:38:51 +10001967 char **argv, *line, quote;
Damien Miller746d1a62013-07-18 16:13:02 +10001968 int argc, carg;
1969 u_int cursor, len, terminated, ret = CC_ERROR;
Darren Tucker909d8582010-01-08 19:02:40 +11001970 const LineInfo *lf;
1971 struct complete_ctx *complete_ctx;
1972
1973 lf = el_line(el);
1974 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1975 fatal("%s: el_get failed", __func__);
1976
1977 /* Figure out which argument the cursor points to */
1978 cursor = lf->cursor - lf->buffer;
deraadt@openbsd.orgce445b02015-08-20 22:32:42 +00001979 line = xmalloc(cursor + 1);
Darren Tucker909d8582010-01-08 19:02:40 +11001980 memcpy(line, lf->buffer, cursor);
1981 line[cursor] = '\0';
1982 argv = makeargv(line, &carg, 1, &quote, &terminated);
Darren Tuckera627d422013-06-02 07:31:17 +10001983 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001984
1985 /* Get all the arguments on the line */
1986 len = lf->lastchar - lf->buffer;
deraadt@openbsd.orgce445b02015-08-20 22:32:42 +00001987 line = xmalloc(len + 1);
Darren Tucker909d8582010-01-08 19:02:40 +11001988 memcpy(line, lf->buffer, len);
1989 line[len] = '\0';
1990 argv = makeargv(line, &argc, 1, NULL, NULL);
1991
1992 /* Ensure cursor is at EOL or a argument boundary */
1993 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1994 line[cursor] != '\n') {
Darren Tuckera627d422013-06-02 07:31:17 +10001995 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001996 return ret;
1997 }
1998
1999 if (carg == 0) {
2000 /* Show all available commands */
2001 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
2002 ret = CC_REDISPLAY;
2003 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
2004 /* Handle the command parsing */
2005 if (complete_cmd_parse(el, argv[0], argc == carg,
Damien Miller02e87802013-08-21 02:38:51 +10002006 quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11002007 ret = CC_REDISPLAY;
2008 } else if (carg >= 1) {
2009 /* Handle file parsing */
2010 int remote = complete_is_remote(argv[0]);
2011 char *filematch = NULL;
2012
2013 if (carg > 1 && line[cursor-1] != ' ')
2014 filematch = argv[carg - 1];
2015
2016 if (remote != 0 &&
2017 complete_match(el, complete_ctx->conn,
2018 *complete_ctx->remote_pathp, filematch,
Damien Miller02e87802013-08-21 02:38:51 +10002019 remote, carg == argc, quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11002020 ret = CC_REDISPLAY;
2021 }
2022
Damien Miller02e87802013-08-21 02:38:51 +10002023 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11002024 return ret;
2025}
Darren Tuckere67f7db2010-01-08 19:50:02 +11002026#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11002027
Damien Miller20e1fab2004-02-18 14:30:55 +11002028int
Darren Tucker21063192010-01-08 17:10:36 +11002029interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11002030{
Darren Tucker909d8582010-01-08 19:02:40 +11002031 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11002032 char *dir = NULL;
2033 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10002034 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11002035 EditLine *el = NULL;
2036#ifdef USE_LIBEDIT
2037 History *hl = NULL;
2038 HistEvent hev;
2039 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11002040 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11002041
2042 if (!batchmode && isatty(STDIN_FILENO)) {
2043 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
2044 fatal("Couldn't initialise editline");
2045 if ((hl = history_init()) == NULL)
2046 fatal("Couldn't initialise editline history");
2047 history(hl, &hev, H_SETSIZE, 100);
2048 el_set(el, EL_HIST, history, hl);
2049
2050 el_set(el, EL_PROMPT, prompt);
2051 el_set(el, EL_EDITOR, "emacs");
2052 el_set(el, EL_TERMINAL, NULL);
2053 el_set(el, EL_SIGNAL, 1);
2054 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11002055
2056 /* Tab Completion */
Damien Miller02e87802013-08-21 02:38:51 +10002057 el_set(el, EL_ADDFN, "ftp-complete",
Darren Tuckerd78739a2010-10-24 10:56:32 +11002058 "Context sensitive argument completion", complete);
Darren Tucker909d8582010-01-08 19:02:40 +11002059 complete_ctx.conn = conn;
2060 complete_ctx.remote_pathp = &remote_path;
2061 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
2062 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Damien Millere0ee7272013-08-21 02:42:35 +10002063 /* enable ctrl-left-arrow and ctrl-right-arrow */
2064 el_set(el, EL_BIND, "\\e[1;5C", "em-next-word", NULL);
2065 el_set(el, EL_BIND, "\\e[5C", "em-next-word", NULL);
2066 el_set(el, EL_BIND, "\\e[1;5D", "ed-prev-word", NULL);
2067 el_set(el, EL_BIND, "\\e\\e[D", "ed-prev-word", NULL);
Damien Miller61353b32013-09-14 09:45:32 +10002068 /* make ^w match ksh behaviour */
2069 el_set(el, EL_BIND, "^w", "ed-delete-prev-word", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11002070 }
2071#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002072
Darren Tucker909d8582010-01-08 19:02:40 +11002073 remote_path = do_realpath(conn, ".");
2074 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11002075 fatal("Need cwd");
2076
2077 if (file1 != NULL) {
2078 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11002079 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11002080
2081 if (remote_is_dir(conn, dir) && file2 == NULL) {
Damien Miller9303e652013-04-23 15:22:40 +10002082 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00002083 mprintf("Changing to: %s\n", dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002084 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11002085 if (parse_dispatch_command(conn, cmd,
2086 &remote_path, 1) != 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10002087 free(dir);
2088 free(remote_path);
2089 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002090 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11002091 }
Damien Miller20e1fab2004-02-18 14:30:55 +11002092 } else {
Darren Tucker0af24052012-10-05 10:41:25 +10002093 /* XXX this is wrong wrt quoting */
Damien Miller0d032412013-07-25 11:56:52 +10002094 snprintf(cmd, sizeof cmd, "get%s %s%s%s",
2095 global_aflag ? " -a" : "", dir,
2096 file2 == NULL ? "" : " ",
2097 file2 == NULL ? "" : file2);
Darren Tucker909d8582010-01-08 19:02:40 +11002098 err = parse_dispatch_command(conn, cmd,
2099 &remote_path, 1);
Darren Tuckera627d422013-06-02 07:31:17 +10002100 free(dir);
2101 free(remote_path);
2102 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002103 return (err);
2104 }
Darren Tuckera627d422013-06-02 07:31:17 +10002105 free(dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002106 }
2107
millert@openbsd.orgdb995f22014-11-26 18:34:51 +00002108 setvbuf(stdout, NULL, _IOLBF, 0);
2109 setvbuf(infile, NULL, _IOLBF, 0);
Damien Miller20e1fab2004-02-18 14:30:55 +11002110
Damien Miller0e2c1022005-08-12 22:16:22 +10002111 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11002112 err = 0;
2113 for (;;) {
2114 char *cp;
2115
Darren Tuckercdf547a2004-05-24 10:12:19 +10002116 signal(SIGINT, SIG_IGN);
2117
Darren Tucker2d963d82004-11-07 20:04:10 +11002118 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002119 if (interactive)
2120 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11002121 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002122 if (interactive)
2123 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11002124 break;
2125 }
Damien Miller0e2c1022005-08-12 22:16:22 +10002126 if (!interactive) { /* Echo command */
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00002127 mprintf("sftp> %s", cmd);
Damien Miller0e2c1022005-08-12 22:16:22 +10002128 if (strlen(cmd) > 0 &&
2129 cmd[strlen(cmd) - 1] != '\n')
2130 printf("\n");
2131 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002132 } else {
2133#ifdef USE_LIBEDIT
2134 const char *line;
2135 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11002136
Darren Tucker909d8582010-01-08 19:02:40 +11002137 if ((line = el_gets(el, &count)) == NULL ||
2138 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002139 printf("\n");
2140 break;
2141 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002142 history(hl, &hev, H_ENTER, line);
2143 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
2144 fprintf(stderr, "Error: input line too long\n");
2145 continue;
2146 }
2147#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002148 }
2149
Damien Miller20e1fab2004-02-18 14:30:55 +11002150 cp = strrchr(cmd, '\n');
2151 if (cp)
2152 *cp = '\0';
2153
Darren Tuckercdf547a2004-05-24 10:12:19 +10002154 /* Handle user interrupts gracefully during commands */
2155 interrupted = 0;
2156 signal(SIGINT, cmd_interrupt);
2157
Darren Tucker909d8582010-01-08 19:02:40 +11002158 err = parse_dispatch_command(conn, cmd, &remote_path,
2159 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11002160 if (err != 0)
2161 break;
2162 }
Darren Tuckera627d422013-06-02 07:31:17 +10002163 free(remote_path);
2164 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002165
Tim Rice027e8b12005-08-15 14:52:50 -07002166#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10002167 if (el != NULL)
2168 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07002169#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10002170
Damien Miller20e1fab2004-02-18 14:30:55 +11002171 /* err == 1 signifies normal "quit" exit */
2172 return (err >= 0 ? 0 : -1);
2173}
Damien Miller62d57f62003-01-10 21:43:24 +11002174
Ben Lindstrombba81212001-06-25 05:01:22 +00002175static void
Damien Millercc685c12003-06-04 22:51:38 +10002176connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11002177{
2178 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002179
Damien Miller33804262001-02-04 23:20:18 +11002180#ifdef USE_PIPES
2181 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002182
Damien Miller33804262001-02-04 23:20:18 +11002183 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2184 fatal("pipe: %s", strerror(errno));
2185 *in = pin[0];
2186 *out = pout[1];
2187 c_in = pout[0];
2188 c_out = pin[1];
2189#else /* USE_PIPES */
2190 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002191
Damien Miller33804262001-02-04 23:20:18 +11002192 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2193 fatal("socketpair: %s", strerror(errno));
2194 *in = *out = inout[0];
2195 c_in = c_out = inout[1];
2196#endif /* USE_PIPES */
2197
Damien Millercc685c12003-06-04 22:51:38 +10002198 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002199 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002200 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002201 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2202 (dup2(c_out, STDOUT_FILENO) == -1)) {
2203 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002204 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002205 }
2206 close(*in);
2207 close(*out);
2208 close(c_in);
2209 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002210
2211 /*
2212 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002213 * ignore SIGINT if we want to gracefully abort commands,
2214 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002215 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2216 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002217 */
2218 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002219 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002220 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002221 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002222 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002223 }
2224
Damien Millercc685c12003-06-04 22:51:38 +10002225 signal(SIGTERM, killchild);
2226 signal(SIGINT, killchild);
2227 signal(SIGHUP, killchild);
millert@openbsd.org2c6697c2016-10-18 12:41:22 +00002228 signal(SIGTSTP, suspchild);
2229 signal(SIGTTIN, suspchild);
2230 signal(SIGTTOU, suspchild);
Damien Miller33804262001-02-04 23:20:18 +11002231 close(c_in);
2232 close(c_out);
2233}
2234
Ben Lindstrombba81212001-06-25 05:01:22 +00002235static void
Damien Miller33804262001-02-04 23:20:18 +11002236usage(void)
2237{
Damien Miller025e01c2002-02-08 22:06:29 +11002238 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002239
Ben Lindstrom1e243242001-09-18 05:38:44 +00002240 fprintf(stderr,
Damien Miller1edcbf62013-10-18 10:17:17 +11002241 "usage: %s [-1246aCfpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002242 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002243 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002244 " [-o ssh_option] [-P port] [-R num_requests] "
2245 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002246 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002247 " %s [user@]host[:file ...]\n"
2248 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002249 " %s -b batchfile [user@]host\n",
2250 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002251 exit(1);
2252}
2253
Kevin Stevesef4eea92001-02-05 12:42:17 +00002254int
Damien Miller33804262001-02-04 23:20:18 +11002255main(int argc, char **argv)
2256{
Damien Miller956f3fb2003-01-10 21:40:00 +11002257 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002258 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002259 int debug_level = 0, sshver = 2;
2260 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002261 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002262 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002263 LogLevel ll = SYSLOG_LEVEL_INFO;
2264 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002265 extern int optind;
2266 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002267 struct sftp_conn *conn;
2268 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2269 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002270 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002271
dtucker@openbsd.orgffb1e7e2016-02-15 09:47:49 +00002272 ssh_malloc_init(); /* must be called before any mallocs */
Darren Tuckerce321d82005-10-03 18:11:24 +10002273 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2274 sanitise_stdfd();
Darren Tuckerea647212013-06-06 08:19:09 +10002275 setlocale(LC_CTYPE, "");
Darren Tuckerce321d82005-10-03 18:11:24 +10002276
Damien Miller59d3d5b2003-08-22 09:34:41 +10002277 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002278 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002279 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002280 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002281 addargs(&args, "-oForwardX11 no");
2282 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002283 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002284 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002285
Ben Lindstrom387c4722001-05-08 20:27:25 +00002286 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002287 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002288
Darren Tucker282b4022009-10-07 08:23:06 +11002289 while ((ch = getopt(argc, argv,
Damien Millerf29238e2013-10-17 11:48:52 +11002290 "1246afhpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002291 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002292 /* Passed through to ssh(1) */
2293 case '4':
2294 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002295 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002296 addargs(&args, "-%c", ch);
2297 break;
2298 /* Passed through to ssh(1) with argument */
2299 case 'F':
2300 case 'c':
2301 case 'i':
2302 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002303 addargs(&args, "-%c", ch);
2304 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002305 break;
2306 case 'q':
Damien Miller9303e652013-04-23 15:22:40 +10002307 ll = SYSLOG_LEVEL_ERROR;
2308 quiet = 1;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002309 showprogress = 0;
2310 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002311 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002312 case 'P':
2313 addargs(&args, "-oPort %s", optarg);
2314 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002315 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002316 if (debug_level < 3) {
2317 addargs(&args, "-v");
2318 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2319 }
2320 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002321 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002322 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002323 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002324 if (sftp_server == NULL)
2325 sftp_server = _PATH_SFTP_SERVER;
2326 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002327 case '2':
2328 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002329 break;
Damien Miller0d032412013-07-25 11:56:52 +10002330 case 'a':
2331 global_aflag = 1;
2332 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002333 case 'B':
2334 copy_buffer_len = strtol(optarg, &cp, 10);
2335 if (copy_buffer_len == 0 || *cp != '\0')
2336 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002337 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002338 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002339 if (batchmode)
2340 fatal("Batch file already specified.");
2341
2342 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002343 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002344 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002345 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002346 showprogress = 0;
Damien Miller9303e652013-04-23 15:22:40 +10002347 quiet = batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002348 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002349 break;
Damien Millerf29238e2013-10-17 11:48:52 +11002350 case 'f':
2351 global_fflag = 1;
2352 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002353 case 'p':
2354 global_pflag = 1;
2355 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002356 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002357 sftp_direct = optarg;
2358 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002359 case 'l':
2360 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2361 &errstr);
2362 if (errstr != NULL)
2363 usage();
2364 limit_kbps *= 1024; /* kbps */
2365 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002366 case 'r':
2367 global_rflag = 1;
2368 break;
Damien Miller16a13332002-02-13 14:03:56 +11002369 case 'R':
2370 num_requests = strtol(optarg, &cp, 10);
2371 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002372 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002373 optarg);
2374 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002375 case 's':
2376 sftp_server = optarg;
2377 break;
2378 case 'S':
2379 ssh_program = optarg;
2380 replacearg(&args, 0, "%s", ssh_program);
2381 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002382 case 'h':
2383 default:
Damien Miller33804262001-02-04 23:20:18 +11002384 usage();
2385 }
2386 }
2387
Damien Millerc0f27d82004-03-08 23:12:19 +11002388 if (!isatty(STDERR_FILENO))
2389 showprogress = 0;
2390
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002391 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2392
Damien Millerd14ee1e2002-02-05 12:27:31 +11002393 if (sftp_direct == NULL) {
2394 if (optind == argc || argc > (optind + 2))
2395 usage();
Damien Miller33804262001-02-04 23:20:18 +11002396
Damien Millerd14ee1e2002-02-05 12:27:31 +11002397 userhost = xstrdup(argv[optind]);
2398 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002399
Ben Lindstromc276c122002-12-23 02:14:51 +00002400 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002401 host = userhost;
2402 else {
2403 *host++ = '\0';
2404 if (!userhost[0]) {
2405 fprintf(stderr, "Missing username\n");
2406 usage();
2407 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002408 addargs(&args, "-l");
2409 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002410 }
2411
Damien Millerec692032004-01-27 21:22:00 +11002412 if ((cp = colon(host)) != NULL) {
2413 *cp++ = '\0';
2414 file1 = cp;
2415 }
2416
Damien Millerd14ee1e2002-02-05 12:27:31 +11002417 host = cleanhostname(host);
2418 if (!*host) {
2419 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002420 usage();
2421 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002422
Damien Millerd14ee1e2002-02-05 12:27:31 +11002423 addargs(&args, "-oProtocol %d", sshver);
2424
2425 /* no subsystem if the server-spec contains a '/' */
2426 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2427 addargs(&args, "-s");
2428
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002429 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002430 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002431 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002432 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002433
Damien Millercc685c12003-06-04 22:51:38 +10002434 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002435 } else {
2436 args.list = NULL;
2437 addargs(&args, "sftp-server");
2438
Damien Millercc685c12003-06-04 22:51:38 +10002439 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002440 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002441 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002442
Damien Miller65e42f82010-09-24 22:15:11 +10002443 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002444 if (conn == NULL)
2445 fatal("Couldn't initialise connection to server");
2446
Damien Miller9303e652013-04-23 15:22:40 +10002447 if (!quiet) {
Darren Tucker21063192010-01-08 17:10:36 +11002448 if (sftp_direct == NULL)
2449 fprintf(stderr, "Connected to %s.\n", host);
2450 else
2451 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2452 }
2453
2454 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002455
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002456#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002457 shutdown(in, SHUT_RDWR);
2458 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002459#endif
2460
Damien Miller33804262001-02-04 23:20:18 +11002461 close(in);
2462 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002463 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002464 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002465
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002466 while (waitpid(sshpid, NULL, 0) == -1)
2467 if (errno != EINTR)
2468 fatal("Couldn't wait for ssh process: %s",
2469 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002470
Damien Miller956f3fb2003-01-10 21:40:00 +11002471 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002472}