blob: 9737bf50c60bdc551c1c92d899bcabf21a620053 [file] [log] [blame]
djm@openbsd.org3575f0b2017-05-02 08:54:19 +00001/* $OpenBSD: sftp.c,v 1.179 2017/05/02 08:54:19 djm Exp $ */
Damien Miller33804262001-02-04 23:20:18 +11002/*
Damien Miller4e60ed72004-02-17 17:07:59 +11003 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
Damien Miller33804262001-02-04 23:20:18 +11004 *
Damien Miller4e60ed72004-02-17 17:07:59 +11005 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
Damien Miller33804262001-02-04 23:20:18 +11008 *
Damien Miller4e60ed72004-02-17 17:07:59 +11009 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Damien Miller33804262001-02-04 23:20:18 +110016 */
17
18#include "includes.h"
19
Damien Miller9cf6d072006-03-15 11:29:24 +110020#include <sys/types.h>
Damien Millerd7834352006-08-05 12:39:39 +100021#include <sys/ioctl.h>
Damien Millerf17883e2006-03-15 11:45:54 +110022#ifdef HAVE_SYS_STAT_H
23# include <sys/stat.h>
24#endif
Damien Miller8dbffe72006-08-05 11:02:17 +100025#include <sys/param.h>
Damien Millere3b60b52006-07-10 21:08:03 +100026#include <sys/socket.h>
Damien Miller9cf6d072006-03-15 11:29:24 +110027#include <sys/wait.h>
Darren Tucker5b2e2ba2008-06-08 09:25:28 +100028#ifdef HAVE_SYS_STATVFS_H
Damien Millerd671e5a2008-05-19 14:53:33 +100029#include <sys/statvfs.h>
Darren Tucker5b2e2ba2008-06-08 09:25:28 +100030#endif
Darren Tucker2d963d82004-11-07 20:04:10 +110031
Damien Miller1cbc2922007-10-26 14:27:45 +100032#include <ctype.h>
Darren Tucker39972492006-07-12 22:22:46 +100033#include <errno.h>
34
Damien Miller03e20032006-03-15 11:16:59 +110035#ifdef HAVE_PATHS_H
Damien Millera9263d02006-03-15 11:18:26 +110036# include <paths.h>
Damien Miller03e20032006-03-15 11:16:59 +110037#endif
Darren Tucker1b0dd172009-10-07 08:37:48 +110038#ifdef HAVE_LIBGEN_H
39#include <libgen.h>
40#endif
Darren Tuckerae133d42013-06-06 08:30:20 +100041#ifdef HAVE_LOCALE_H
42# include <locale.h>
43#endif
Darren Tucker2d963d82004-11-07 20:04:10 +110044#ifdef USE_LIBEDIT
45#include <histedit.h>
46#else
47typedef void EditLine;
48#endif
deraadt@openbsd.org087266e2015-01-20 23:14:00 +000049#include <limits.h>
Damien Miller6ff3cad2006-03-15 11:52:09 +110050#include <signal.h>
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +000051#include <stdarg.h>
Damien Millere7a1e5c2006-08-05 11:34:19 +100052#include <stdlib.h>
Damien Millera7a73ee2006-08-05 11:37:59 +100053#include <stdio.h>
Damien Millere3476ed2006-07-24 14:13:33 +100054#include <string.h>
Damien Millere6b3b612006-07-24 14:01:23 +100055#include <unistd.h>
Damien Millerd7834352006-08-05 12:39:39 +100056#include <stdarg.h>
Damien Miller33804262001-02-04 23:20:18 +110057
Damien Millera7058ec2008-05-20 08:57:06 +100058#ifdef HAVE_UTIL_H
59# include <util.h>
60#endif
61
Damien Miller33804262001-02-04 23:20:18 +110062#include "xmalloc.h"
63#include "log.h"
64#include "pathnames.h"
Ben Lindstrom4529b702001-05-03 23:39:53 +000065#include "misc.h"
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +000066#include "utf8.h"
Damien Miller33804262001-02-04 23:20:18 +110067
68#include "sftp.h"
djm@openbsd.org7d845f42015-01-14 13:54:13 +000069#include "ssherr.h"
70#include "sshbuf.h"
Damien Miller33804262001-02-04 23:20:18 +110071#include "sftp-common.h"
72#include "sftp-client.h"
Damien Millerd7d46bb2004-02-18 14:11:13 +110073
Darren Tucker21063192010-01-08 17:10:36 +110074#define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */
75#define DEFAULT_NUM_REQUESTS 64 /* # concurrent outstanding requests */
76
Damien Miller20e1fab2004-02-18 14:30:55 +110077/* File to read commands from */
78FILE* infile;
79
80/* Are we in batchfile mode? */
81int batchmode = 0;
82
Damien Miller20e1fab2004-02-18 14:30:55 +110083/* PID of ssh transport process */
84static pid_t sshpid = -1;
85
Damien Miller9303e652013-04-23 15:22:40 +100086/* Suppress diagnositic messages */
87int quiet = 0;
88
Damien Miller20e1fab2004-02-18 14:30:55 +110089/* This is set to 0 if the progressmeter is not desired. */
Damien Millerc0f27d82004-03-08 23:12:19 +110090int showprogress = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +110091
Darren Tucker1b0dd172009-10-07 08:37:48 +110092/* When this option is set, we always recursively download/upload directories */
93int global_rflag = 0;
94
Damien Millerd8accc02014-05-15 13:46:25 +100095/* When this option is set, we resume download or upload if possible */
Damien Miller0d032412013-07-25 11:56:52 +100096int global_aflag = 0;
97
Darren Tucker1b0dd172009-10-07 08:37:48 +110098/* When this option is set, the file transfers will always preserve times */
99int global_pflag = 0;
100
Damien Millerf29238e2013-10-17 11:48:52 +1100101/* When this option is set, transfers will have fsync() called on each file */
102int global_fflag = 0;
103
Darren Tuckercdf547a2004-05-24 10:12:19 +1000104/* SIGINT received during command processing */
105volatile sig_atomic_t interrupted = 0;
106
Darren Tuckerb9123452004-06-22 13:06:45 +1000107/* I wish qsort() took a separate ctx for the comparison function...*/
108int sort_flag;
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;
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +0000972 char s_used[FMT_SCALED_STRSIZE], s_avail[FMT_SCALED_STRSIZE];
973 char s_root[FMT_SCALED_STRSIZE], s_total[FMT_SCALED_STRSIZE];
974 char s_icapacity[16], s_dcapacity[16];
Damien Millerd671e5a2008-05-19 14:53:33 +1000975
976 if (do_statvfs(conn, path, &st, 1) == -1)
977 return -1;
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +0000978 if (st.f_files == 0)
979 strlcpy(s_icapacity, "ERR", sizeof(s_icapacity));
980 else {
981 snprintf(s_icapacity, sizeof(s_icapacity), "%3llu%%",
982 (unsigned long long)(100 * (st.f_files - st.f_ffree) /
983 st.f_files));
984 }
985 if (st.f_blocks == 0)
986 strlcpy(s_dcapacity, "ERR", sizeof(s_dcapacity));
987 else {
988 snprintf(s_dcapacity, sizeof(s_dcapacity), "%3llu%%",
989 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
990 st.f_blocks));
991 }
Damien Millerd671e5a2008-05-19 14:53:33 +1000992 if (iflag) {
993 printf(" Inodes Used Avail "
994 "(root) %%Capacity\n");
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +0000995 printf("%11llu %11llu %11llu %11llu %s\n",
Damien Millerd671e5a2008-05-19 14:53:33 +1000996 (unsigned long long)st.f_files,
997 (unsigned long long)(st.f_files - st.f_ffree),
998 (unsigned long long)st.f_favail,
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +0000999 (unsigned long long)st.f_ffree, s_icapacity);
Damien Millerd671e5a2008-05-19 14:53:33 +10001000 } else if (hflag) {
1001 strlcpy(s_used, "error", sizeof(s_used));
1002 strlcpy(s_avail, "error", sizeof(s_avail));
1003 strlcpy(s_root, "error", sizeof(s_root));
1004 strlcpy(s_total, "error", sizeof(s_total));
1005 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
1006 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
1007 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
1008 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
1009 printf(" Size Used Avail (root) %%Capacity\n");
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +00001010 printf("%7sB %7sB %7sB %7sB %s\n",
1011 s_total, s_used, s_avail, s_root, s_dcapacity);
Damien Millerd671e5a2008-05-19 14:53:33 +10001012 } else {
1013 printf(" Size Used Avail "
1014 "(root) %%Capacity\n");
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +00001015 printf("%12llu %12llu %12llu %12llu %s\n",
Damien Millerd671e5a2008-05-19 14:53:33 +10001016 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
1017 (unsigned long long)(st.f_frsize *
1018 (st.f_blocks - st.f_bfree) / 1024),
1019 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
1020 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +00001021 s_dcapacity);
Damien Millerd671e5a2008-05-19 14:53:33 +10001022 }
1023 return 0;
1024}
1025
Damien Miller1cbc2922007-10-26 14:27:45 +10001026/*
1027 * Undo escaping of glob sequences in place. Used to undo extra escaping
1028 * applied in makeargv() when the string is destined for a function that
1029 * does not glob it.
1030 */
1031static void
1032undo_glob_escape(char *s)
1033{
1034 size_t i, j;
1035
1036 for (i = j = 0;;) {
1037 if (s[i] == '\0') {
1038 s[j] = '\0';
1039 return;
1040 }
1041 if (s[i] != '\\') {
1042 s[j++] = s[i++];
1043 continue;
1044 }
1045 /* s[i] == '\\' */
1046 ++i;
1047 switch (s[i]) {
1048 case '?':
1049 case '[':
1050 case '*':
1051 case '\\':
1052 s[j++] = s[i++];
1053 break;
1054 case '\0':
1055 s[j++] = '\\';
1056 s[j] = '\0';
1057 return;
1058 default:
1059 s[j++] = '\\';
1060 s[j++] = s[i++];
1061 break;
1062 }
1063 }
1064}
1065
1066/*
1067 * Split a string into an argument vector using sh(1)-style quoting,
1068 * comment and escaping rules, but with some tweaks to handle glob(3)
1069 * wildcards.
Darren Tucker909d8582010-01-08 19:02:40 +11001070 * The "sloppy" flag allows for recovery from missing terminating quote, for
1071 * use in parsing incomplete commandlines during tab autocompletion.
1072 *
Damien Miller1cbc2922007-10-26 14:27:45 +10001073 * Returns NULL on error or a NULL-terminated array of arguments.
Darren Tucker909d8582010-01-08 19:02:40 +11001074 *
1075 * If "lastquote" is not NULL, the quoting character used for the last
1076 * argument is placed in *lastquote ("\0", "'" or "\"").
Damien Miller02e87802013-08-21 02:38:51 +10001077 *
Darren Tucker909d8582010-01-08 19:02:40 +11001078 * If "terminated" is not NULL, *terminated will be set to 1 when the
1079 * last argument's quote has been properly terminated or 0 otherwise.
1080 * This parameter is only of use if "sloppy" is set.
Damien Miller1cbc2922007-10-26 14:27:45 +10001081 */
1082#define MAXARGS 128
1083#define MAXARGLEN 8192
1084static char **
Darren Tucker909d8582010-01-08 19:02:40 +11001085makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
1086 u_int *terminated)
Damien Miller1cbc2922007-10-26 14:27:45 +10001087{
1088 int argc, quot;
1089 size_t i, j;
1090 static char argvs[MAXARGLEN];
1091 static char *argv[MAXARGS + 1];
1092 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
1093
1094 *argcp = argc = 0;
1095 if (strlen(arg) > sizeof(argvs) - 1) {
1096 args_too_longs:
1097 error("string too long");
1098 return NULL;
1099 }
Darren Tucker909d8582010-01-08 19:02:40 +11001100 if (terminated != NULL)
1101 *terminated = 1;
1102 if (lastquote != NULL)
1103 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001104 state = MA_START;
1105 i = j = 0;
1106 for (;;) {
Damien Miller07daed52012-10-31 08:57:55 +11001107 if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){
Darren Tucker063018d2012-10-05 10:43:58 +10001108 error("Too many arguments.");
1109 return NULL;
1110 }
Damien Millerfdb23062013-11-21 13:57:15 +11001111 if (isspace((unsigned char)arg[i])) {
Damien Miller1cbc2922007-10-26 14:27:45 +10001112 if (state == MA_UNQUOTED) {
1113 /* Terminate current argument */
1114 argvs[j++] = '\0';
1115 argc++;
1116 state = MA_START;
1117 } else if (state != MA_START)
1118 argvs[j++] = arg[i];
1119 } else if (arg[i] == '"' || arg[i] == '\'') {
1120 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1121 if (state == MA_START) {
1122 argv[argc] = argvs + j;
1123 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +11001124 if (lastquote != NULL)
1125 *lastquote = arg[i];
Damien Miller02e87802013-08-21 02:38:51 +10001126 } else if (state == MA_UNQUOTED)
Damien Miller1cbc2922007-10-26 14:27:45 +10001127 state = q;
1128 else if (state == q)
1129 state = MA_UNQUOTED;
1130 else
1131 argvs[j++] = arg[i];
1132 } else if (arg[i] == '\\') {
1133 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1134 quot = state == MA_SQUOTE ? '\'' : '"';
1135 /* Unescape quote we are in */
1136 /* XXX support \n and friends? */
1137 if (arg[i + 1] == quot) {
1138 i++;
1139 argvs[j++] = arg[i];
1140 } else if (arg[i + 1] == '?' ||
1141 arg[i + 1] == '[' || arg[i + 1] == '*') {
1142 /*
1143 * Special case for sftp: append
1144 * double-escaped glob sequence -
1145 * glob will undo one level of
1146 * escaping. NB. string can grow here.
1147 */
1148 if (j >= sizeof(argvs) - 5)
1149 goto args_too_longs;
1150 argvs[j++] = '\\';
1151 argvs[j++] = arg[i++];
1152 argvs[j++] = '\\';
1153 argvs[j++] = arg[i];
1154 } else {
1155 argvs[j++] = arg[i++];
1156 argvs[j++] = arg[i];
1157 }
1158 } else {
1159 if (state == MA_START) {
1160 argv[argc] = argvs + j;
1161 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001162 if (lastquote != NULL)
1163 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001164 }
1165 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1166 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1167 /*
1168 * Special case for sftp: append
1169 * escaped glob sequence -
1170 * glob will undo one level of
1171 * escaping.
1172 */
1173 argvs[j++] = arg[i++];
1174 argvs[j++] = arg[i];
1175 } else {
1176 /* Unescape everything */
1177 /* XXX support \n and friends? */
1178 i++;
1179 argvs[j++] = arg[i];
1180 }
1181 }
1182 } else if (arg[i] == '#') {
1183 if (state == MA_SQUOTE || state == MA_DQUOTE)
1184 argvs[j++] = arg[i];
1185 else
1186 goto string_done;
1187 } else if (arg[i] == '\0') {
1188 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001189 if (sloppy) {
1190 state = MA_UNQUOTED;
1191 if (terminated != NULL)
1192 *terminated = 0;
1193 goto string_done;
1194 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001195 error("Unterminated quoted argument");
1196 return NULL;
1197 }
1198 string_done:
1199 if (state == MA_UNQUOTED) {
1200 argvs[j++] = '\0';
1201 argc++;
1202 }
1203 break;
1204 } else {
1205 if (state == MA_START) {
1206 argv[argc] = argvs + j;
1207 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001208 if (lastquote != NULL)
1209 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001210 }
1211 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1212 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1213 /*
1214 * Special case for sftp: escape quoted
1215 * glob(3) wildcards. NB. string can grow
1216 * here.
1217 */
1218 if (j >= sizeof(argvs) - 3)
1219 goto args_too_longs;
1220 argvs[j++] = '\\';
1221 argvs[j++] = arg[i];
1222 } else
1223 argvs[j++] = arg[i];
1224 }
1225 i++;
1226 }
1227 *argcp = argc;
1228 return argv;
1229}
1230
Damien Miller20e1fab2004-02-18 14:30:55 +11001231static int
Damien Millerd8accc02014-05-15 13:46:25 +10001232parse_args(const char **cpp, int *ignore_errors, int *aflag,
djm@openbsd.org34a01b22016-04-08 08:19:17 +00001233 int *fflag, int *hflag, int *iflag, int *lflag, int *pflag,
Damien Millerd8accc02014-05-15 13:46:25 +10001234 int *rflag, int *sflag,
Damien Millerf29238e2013-10-17 11:48:52 +11001235 unsigned long *n_arg, char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001236{
1237 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001238 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001239 int base = 0;
1240 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001241 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001242
1243 /* Skip leading whitespace */
1244 cp = cp + strspn(cp, WHITESPACE);
1245
Damien Miller20e1fab2004-02-18 14:30:55 +11001246 /* Check for leading '-' (disable error processing) */
Damien Millerf29238e2013-10-17 11:48:52 +11001247 *ignore_errors = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001248 if (*cp == '-') {
Damien Millerf29238e2013-10-17 11:48:52 +11001249 *ignore_errors = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001250 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001251 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001252 }
1253
Darren Tucker70cc0922010-01-09 22:28:03 +11001254 /* Ignore blank lines and lines which begin with comment '#' char */
1255 if (*cp == '\0' || *cp == '#')
1256 return (0);
1257
Darren Tucker909d8582010-01-08 19:02:40 +11001258 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001259 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001260
Damien Miller1cbc2922007-10-26 14:27:45 +10001261 /* Figure out which command we have */
1262 for (i = 0; cmds[i].c != NULL; i++) {
Damien Millerd6d9fa02013-02-12 11:02:46 +11001263 if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001264 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001265 }
1266 cmdnum = cmds[i].n;
1267 cmd = cmds[i].c;
1268
1269 /* Special case */
1270 if (*cp == '!') {
1271 cp++;
1272 cmdnum = I_SHELL;
1273 } else if (cmdnum == -1) {
1274 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001275 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001276 }
1277
1278 /* Get arguments and parse flags */
Damien Millerf29238e2013-10-17 11:48:52 +11001279 *aflag = *fflag = *hflag = *iflag = *lflag = *pflag = 0;
1280 *rflag = *sflag = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001281 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001282 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001283 switch (cmdnum) {
1284 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001285 case I_REGET:
Damien Millerd8accc02014-05-15 13:46:25 +10001286 case I_REPUT:
Damien Miller20e1fab2004-02-18 14:30:55 +11001287 case I_PUT:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001288 if ((optidx = parse_getput_flags(cmd, argv, argc,
Damien Millerf29238e2013-10-17 11:48:52 +11001289 aflag, fflag, pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001290 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001291 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001292 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001293 error("You must specify at least one path after a "
1294 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001295 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001296 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001297 *path1 = xstrdup(argv[optidx]);
1298 /* Get second pathname (optional) */
1299 if (argc - optidx > 1) {
1300 *path2 = xstrdup(argv[optidx + 1]);
1301 /* Destination is not globbed */
1302 undo_glob_escape(*path2);
1303 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001304 break;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001305 case I_LINK:
1306 if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1307 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001308 goto parse_two_paths;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001309 case I_RENAME:
Damien Millerc7dba122013-08-21 02:41:15 +10001310 if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1)
1311 return -1;
1312 goto parse_two_paths;
1313 case I_SYMLINK:
Damien Miller036d3072013-08-21 02:41:46 +10001314 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1315 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001316 parse_two_paths:
Damien Miller1cbc2922007-10-26 14:27:45 +10001317 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001318 error("You must specify two paths after a %s "
1319 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001320 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001321 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001322 *path1 = xstrdup(argv[optidx]);
1323 *path2 = xstrdup(argv[optidx + 1]);
1324 /* Paths are not globbed */
1325 undo_glob_escape(*path1);
1326 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001327 break;
1328 case I_RM:
1329 case I_MKDIR:
1330 case I_RMDIR:
1331 case I_CHDIR:
1332 case I_LCHDIR:
1333 case I_LMKDIR:
Damien Miller036d3072013-08-21 02:41:46 +10001334 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1335 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001336 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001337 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001338 error("You must specify a path after a %s command.",
1339 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001340 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001341 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001342 *path1 = xstrdup(argv[optidx]);
1343 /* Only "rm" globs */
1344 if (cmdnum != I_RM)
1345 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001346 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001347 case I_DF:
1348 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1349 iflag)) == -1)
1350 return -1;
1351 /* Default to current directory if no path specified */
1352 if (argc - optidx < 1)
1353 *path1 = NULL;
1354 else {
1355 *path1 = xstrdup(argv[optidx]);
1356 undo_glob_escape(*path1);
1357 }
1358 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001359 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001360 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001361 return(-1);
1362 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001363 if (argc - optidx > 0)
1364 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001365 break;
1366 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001367 /* Skip ls command and following whitespace */
1368 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001369 case I_SHELL:
1370 /* Uses the rest of the line */
1371 break;
1372 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001373 case I_CHMOD:
1374 base = 8;
1375 case I_CHOWN:
1376 case I_CHGRP:
Damien Miller036d3072013-08-21 02:41:46 +10001377 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1378 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001379 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001380 if (argc - optidx < 1)
1381 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001382 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001383 l = strtol(argv[optidx], &cp2, base);
1384 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1385 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1386 l < 0) {
1387 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001388 error("You must supply a numeric argument "
1389 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001390 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001391 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001392 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001393 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001394 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001395 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001396 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001397 error("You must specify a path after a %s command.",
1398 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001399 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001400 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001401 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001402 break;
1403 case I_QUIT:
1404 case I_PWD:
1405 case I_LPWD:
1406 case I_HELP:
1407 case I_VERSION:
1408 case I_PROGRESS:
Damien Miller036d3072013-08-21 02:41:46 +10001409 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1410 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001411 break;
1412 default:
1413 fatal("Command not implemented");
1414 }
1415
1416 *cpp = cp;
1417 return(cmdnum);
1418}
1419
1420static int
1421parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1422 int err_abort)
1423{
1424 char *path1, *path2, *tmp;
djm@openbsd.org34a01b22016-04-08 08:19:17 +00001425 int ignore_errors = 0, aflag = 0, fflag = 0, hflag = 0,
Damien Millerd8accc02014-05-15 13:46:25 +10001426 iflag = 0;
Damien Millerf29238e2013-10-17 11:48:52 +11001427 int lflag = 0, pflag = 0, rflag = 0, sflag = 0;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001428 int cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001429 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001430 Attrib a, *aa;
deraadt@openbsd.org087266e2015-01-20 23:14:00 +00001431 char path_buf[PATH_MAX];
Damien Miller20e1fab2004-02-18 14:30:55 +11001432 int err = 0;
1433 glob_t g;
1434
1435 path1 = path2 = NULL;
Damien Millerf29238e2013-10-17 11:48:52 +11001436 cmdnum = parse_args(&cmd, &ignore_errors, &aflag, &fflag, &hflag,
1437 &iflag, &lflag, &pflag, &rflag, &sflag, &n_arg, &path1, &path2);
1438 if (ignore_errors != 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001439 err_abort = 0;
1440
1441 memset(&g, 0, sizeof(g));
1442
1443 /* Perform command */
1444 switch (cmdnum) {
1445 case 0:
1446 /* Blank line */
1447 break;
1448 case -1:
1449 /* Unrecognized command */
1450 err = -1;
1451 break;
Damien Miller0d032412013-07-25 11:56:52 +10001452 case I_REGET:
1453 aflag = 1;
1454 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001455 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001456 err = process_get(conn, path1, path2, *pwd, pflag,
Damien Millerf29238e2013-10-17 11:48:52 +11001457 rflag, aflag, fflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001458 break;
Damien Millerd8accc02014-05-15 13:46:25 +10001459 case I_REPUT:
1460 aflag = 1;
1461 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001462 case I_PUT:
Damien Millerf29238e2013-10-17 11:48:52 +11001463 err = process_put(conn, path1, path2, *pwd, pflag,
Damien Millerd8accc02014-05-15 13:46:25 +10001464 rflag, aflag, fflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001465 break;
1466 case I_RENAME:
1467 path1 = make_absolute(path1, *pwd);
1468 path2 = make_absolute(path2, *pwd);
Damien Millerc7dba122013-08-21 02:41:15 +10001469 err = do_rename(conn, path1, path2, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001470 break;
1471 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001472 sflag = 1;
1473 case I_LINK:
Damien Miller034f27a2013-08-21 02:40:44 +10001474 if (!sflag)
1475 path1 = make_absolute(path1, *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001476 path2 = make_absolute(path2, *pwd);
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001477 err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001478 break;
1479 case I_RM:
1480 path1 = make_absolute(path1, *pwd);
1481 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001482 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001483 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001484 mprintf("Removing %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001485 err = do_rm(conn, g.gl_pathv[i]);
1486 if (err != 0 && err_abort)
1487 break;
1488 }
1489 break;
1490 case I_MKDIR:
1491 path1 = make_absolute(path1, *pwd);
1492 attrib_clear(&a);
1493 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1494 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001495 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001496 break;
1497 case I_RMDIR:
1498 path1 = make_absolute(path1, *pwd);
1499 err = do_rmdir(conn, path1);
1500 break;
1501 case I_CHDIR:
1502 path1 = make_absolute(path1, *pwd);
1503 if ((tmp = do_realpath(conn, path1)) == NULL) {
1504 err = 1;
1505 break;
1506 }
1507 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
Darren Tuckera627d422013-06-02 07:31:17 +10001508 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001509 err = 1;
1510 break;
1511 }
1512 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1513 error("Can't change directory: Can't check target");
Darren Tuckera627d422013-06-02 07:31:17 +10001514 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001515 err = 1;
1516 break;
1517 }
1518 if (!S_ISDIR(aa->perm)) {
1519 error("Can't change directory: \"%s\" is not "
1520 "a directory", tmp);
Darren Tuckera627d422013-06-02 07:31:17 +10001521 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001522 err = 1;
1523 break;
1524 }
Darren Tuckera627d422013-06-02 07:31:17 +10001525 free(*pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001526 *pwd = tmp;
1527 break;
1528 case I_LS:
1529 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001530 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001531 break;
1532 }
1533
1534 /* Strip pwd off beginning of non-absolute paths */
1535 tmp = NULL;
1536 if (*path1 != '/')
1537 tmp = *pwd;
1538
1539 path1 = make_absolute(path1, *pwd);
1540 err = do_globbed_ls(conn, path1, tmp, lflag);
1541 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001542 case I_DF:
1543 /* Default to current directory if no path specified */
1544 if (path1 == NULL)
1545 path1 = xstrdup(*pwd);
1546 path1 = make_absolute(path1, *pwd);
1547 err = do_df(conn, path1, hflag, iflag);
1548 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001549 case I_LCHDIR:
deraadt@openbsd.org40ba4c92014-08-20 01:28:55 +00001550 tmp = tilde_expand_filename(path1, getuid());
djm@openbsd.org7ff880e2014-08-19 23:57:18 +00001551 free(path1);
1552 path1 = tmp;
Damien Miller20e1fab2004-02-18 14:30:55 +11001553 if (chdir(path1) == -1) {
1554 error("Couldn't change local directory to "
1555 "\"%s\": %s", path1, strerror(errno));
1556 err = 1;
1557 }
1558 break;
1559 case I_LMKDIR:
1560 if (mkdir(path1, 0777) == -1) {
1561 error("Couldn't create local directory "
1562 "\"%s\": %s", path1, strerror(errno));
1563 err = 1;
1564 }
1565 break;
1566 case I_LLS:
1567 local_do_ls(cmd);
1568 break;
1569 case I_SHELL:
1570 local_do_shell(cmd);
1571 break;
1572 case I_LUMASK:
1573 umask(n_arg);
1574 printf("Local umask: %03lo\n", n_arg);
1575 break;
1576 case I_CHMOD:
1577 path1 = make_absolute(path1, *pwd);
1578 attrib_clear(&a);
1579 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1580 a.perm = n_arg;
1581 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001582 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001583 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001584 mprintf("Changing mode on %s\n",
1585 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001586 err = do_setstat(conn, g.gl_pathv[i], &a);
1587 if (err != 0 && err_abort)
1588 break;
1589 }
1590 break;
1591 case I_CHOWN:
1592 case I_CHGRP:
1593 path1 = make_absolute(path1, *pwd);
1594 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001595 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001596 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001597 if (err_abort) {
1598 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001599 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001600 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001601 continue;
1602 }
1603 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1604 error("Can't get current ownership of "
1605 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001606 if (err_abort) {
1607 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001608 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001609 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001610 continue;
1611 }
1612 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1613 if (cmdnum == I_CHOWN) {
Damien Miller9303e652013-04-23 15:22:40 +10001614 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001615 mprintf("Changing owner on %s\n",
Damien Miller9303e652013-04-23 15:22:40 +10001616 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001617 aa->uid = n_arg;
1618 } else {
Damien Miller9303e652013-04-23 15:22:40 +10001619 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001620 mprintf("Changing group on %s\n",
Damien Miller9303e652013-04-23 15:22:40 +10001621 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001622 aa->gid = n_arg;
1623 }
1624 err = do_setstat(conn, g.gl_pathv[i], aa);
1625 if (err != 0 && err_abort)
1626 break;
1627 }
1628 break;
1629 case I_PWD:
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001630 mprintf("Remote working directory: %s\n", *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001631 break;
1632 case I_LPWD:
1633 if (!getcwd(path_buf, sizeof(path_buf))) {
1634 error("Couldn't get local cwd: %s", strerror(errno));
1635 err = -1;
1636 break;
1637 }
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001638 mprintf("Local working directory: %s\n", path_buf);
Damien Miller20e1fab2004-02-18 14:30:55 +11001639 break;
1640 case I_QUIT:
1641 /* Processed below */
1642 break;
1643 case I_HELP:
1644 help();
1645 break;
1646 case I_VERSION:
1647 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1648 break;
1649 case I_PROGRESS:
1650 showprogress = !showprogress;
1651 if (showprogress)
1652 printf("Progress meter enabled\n");
1653 else
1654 printf("Progress meter disabled\n");
1655 break;
1656 default:
1657 fatal("%d is not implemented", cmdnum);
1658 }
1659
1660 if (g.gl_pathc)
1661 globfree(&g);
Darren Tuckera627d422013-06-02 07:31:17 +10001662 free(path1);
1663 free(path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001664
1665 /* If an unignored error occurs in batch mode we should abort. */
1666 if (err_abort && err != 0)
1667 return (-1);
1668 else if (cmdnum == I_QUIT)
1669 return (1);
1670
1671 return (0);
1672}
1673
Darren Tucker2d963d82004-11-07 20:04:10 +11001674#ifdef USE_LIBEDIT
1675static char *
1676prompt(EditLine *el)
1677{
1678 return ("sftp> ");
1679}
Darren Tucker2d963d82004-11-07 20:04:10 +11001680
Darren Tucker909d8582010-01-08 19:02:40 +11001681/* Display entries in 'list' after skipping the first 'len' chars */
1682static void
1683complete_display(char **list, u_int len)
1684{
1685 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1686 struct winsize ws;
1687 char *tmp;
1688
1689 /* Count entries for sort and find longest */
Damien Miller02e87802013-08-21 02:38:51 +10001690 for (y = 0; list[y]; y++)
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +00001691 m = MAXIMUM(m, strlen(list[y]));
Darren Tucker909d8582010-01-08 19:02:40 +11001692
1693 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1694 width = ws.ws_col;
1695
1696 m = m > len ? m - len : 0;
1697 columns = width / (m + 2);
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +00001698 columns = MAXIMUM(columns, 1);
Darren Tucker909d8582010-01-08 19:02:40 +11001699 colspace = width / columns;
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +00001700 colspace = MINIMUM(colspace, width);
Darren Tucker909d8582010-01-08 19:02:40 +11001701
1702 printf("\n");
1703 m = 1;
1704 for (y = 0; list[y]; y++) {
1705 llen = strlen(list[y]);
1706 tmp = llen > len ? list[y] + len : "";
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001707 mprintf("%-*s", colspace, tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001708 if (m >= columns) {
1709 printf("\n");
1710 m = 1;
1711 } else
1712 m++;
1713 }
1714 printf("\n");
1715}
1716
1717/*
1718 * Given a "list" of words that begin with a common prefix of "word",
1719 * attempt to find an autocompletion to extends "word" by the next
1720 * characters common to all entries in "list".
1721 */
1722static char *
1723complete_ambiguous(const char *word, char **list, size_t count)
1724{
1725 if (word == NULL)
1726 return NULL;
1727
1728 if (count > 0) {
1729 u_int y, matchlen = strlen(list[0]);
1730
1731 /* Find length of common stem */
1732 for (y = 1; list[y]; y++) {
1733 u_int x;
1734
Damien Miller02e87802013-08-21 02:38:51 +10001735 for (x = 0; x < matchlen; x++)
1736 if (list[0][x] != list[y][x])
Darren Tucker909d8582010-01-08 19:02:40 +11001737 break;
1738
1739 matchlen = x;
1740 }
1741
1742 if (matchlen > strlen(word)) {
1743 char *tmp = xstrdup(list[0]);
1744
Darren Tucker340d1682010-01-09 08:54:31 +11001745 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001746 return tmp;
1747 }
Damien Miller02e87802013-08-21 02:38:51 +10001748 }
Darren Tucker909d8582010-01-08 19:02:40 +11001749
1750 return xstrdup(word);
1751}
1752
1753/* Autocomplete a sftp command */
1754static int
1755complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1756 int terminated)
1757{
1758 u_int y, count = 0, cmdlen, tmplen;
1759 char *tmp, **list, argterm[3];
1760 const LineInfo *lf;
1761
1762 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1763
1764 /* No command specified: display all available commands */
1765 if (cmd == NULL) {
1766 for (y = 0; cmds[y].c; y++)
1767 list[count++] = xstrdup(cmds[y].c);
Damien Miller02e87802013-08-21 02:38:51 +10001768
Darren Tucker909d8582010-01-08 19:02:40 +11001769 list[count] = NULL;
1770 complete_display(list, 0);
1771
Damien Miller02e87802013-08-21 02:38:51 +10001772 for (y = 0; list[y] != NULL; y++)
1773 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001774 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001775 return count;
1776 }
1777
1778 /* Prepare subset of commands that start with "cmd" */
1779 cmdlen = strlen(cmd);
1780 for (y = 0; cmds[y].c; y++) {
Damien Miller02e87802013-08-21 02:38:51 +10001781 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
Darren Tucker909d8582010-01-08 19:02:40 +11001782 list[count++] = xstrdup(cmds[y].c);
1783 }
1784 list[count] = NULL;
1785
Damien Miller47d81152011-11-25 13:53:48 +11001786 if (count == 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10001787 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001788 return 0;
Damien Miller47d81152011-11-25 13:53:48 +11001789 }
Darren Tucker909d8582010-01-08 19:02:40 +11001790
1791 /* Complete ambigious command */
1792 tmp = complete_ambiguous(cmd, list, count);
1793 if (count > 1)
1794 complete_display(list, 0);
1795
Damien Miller02e87802013-08-21 02:38:51 +10001796 for (y = 0; list[y]; y++)
1797 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001798 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001799
1800 if (tmp != NULL) {
1801 tmplen = strlen(tmp);
1802 cmdlen = strlen(cmd);
1803 /* If cmd may be extended then do so */
1804 if (tmplen > cmdlen)
1805 if (el_insertstr(el, tmp + cmdlen) == -1)
1806 fatal("el_insertstr failed.");
1807 lf = el_line(el);
1808 /* Terminate argument cleanly */
1809 if (count == 1) {
1810 y = 0;
1811 if (!terminated)
1812 argterm[y++] = quote;
1813 if (lastarg || *(lf->cursor) != ' ')
1814 argterm[y++] = ' ';
1815 argterm[y] = '\0';
1816 if (y > 0 && el_insertstr(el, argterm) == -1)
1817 fatal("el_insertstr failed.");
1818 }
Darren Tuckera627d422013-06-02 07:31:17 +10001819 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001820 }
1821
1822 return count;
1823}
1824
1825/*
1826 * Determine whether a particular sftp command's arguments (if any)
1827 * represent local or remote files.
1828 */
1829static int
1830complete_is_remote(char *cmd) {
1831 int i;
1832
1833 if (cmd == NULL)
1834 return -1;
1835
1836 for (i = 0; cmds[i].c; i++) {
Damien Miller02e87802013-08-21 02:38:51 +10001837 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
Darren Tucker909d8582010-01-08 19:02:40 +11001838 return cmds[i].t;
1839 }
1840
1841 return -1;
1842}
1843
1844/* Autocomplete a filename "file" */
1845static int
1846complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1847 char *file, int remote, int lastarg, char quote, int terminated)
1848{
1849 glob_t g;
Darren Tuckerea647212013-06-06 08:19:09 +10001850 char *tmp, *tmp2, ins[8];
Darren Tucker17146d32012-10-05 10:46:16 +10001851 u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
Darren Tuckerea647212013-06-06 08:19:09 +10001852 int clen;
Darren Tucker909d8582010-01-08 19:02:40 +11001853 const LineInfo *lf;
Damien Miller02e87802013-08-21 02:38:51 +10001854
Darren Tucker909d8582010-01-08 19:02:40 +11001855 /* Glob from "file" location */
1856 if (file == NULL)
1857 tmp = xstrdup("*");
1858 else
1859 xasprintf(&tmp, "%s*", file);
1860
Darren Tucker191fcc62012-10-05 10:45:01 +10001861 /* Check if the path is absolute. */
1862 isabs = tmp[0] == '/';
1863
Darren Tucker909d8582010-01-08 19:02:40 +11001864 memset(&g, 0, sizeof(g));
1865 if (remote != LOCAL) {
1866 tmp = make_absolute(tmp, remote_path);
1867 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001868 } else
Darren Tucker909d8582010-01-08 19:02:40 +11001869 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001870
Darren Tucker909d8582010-01-08 19:02:40 +11001871 /* Determine length of pwd so we can trim completion display */
1872 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1873 /* Terminate counting on first unescaped glob metacharacter */
1874 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1875 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1876 hadglob = 1;
1877 break;
1878 }
1879 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1880 tmplen++;
1881 if (tmp[tmplen] == '/')
1882 pwdlen = tmplen + 1; /* track last seen '/' */
1883 }
Darren Tuckera627d422013-06-02 07:31:17 +10001884 free(tmp);
Damien Millerd7fd8be2014-05-15 14:24:59 +10001885 tmp = NULL;
Darren Tucker909d8582010-01-08 19:02:40 +11001886
Damien Miller02e87802013-08-21 02:38:51 +10001887 if (g.gl_matchc == 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001888 goto out;
1889
1890 if (g.gl_matchc > 1)
1891 complete_display(g.gl_pathv, pwdlen);
1892
Darren Tucker909d8582010-01-08 19:02:40 +11001893 /* Don't try to extend globs */
1894 if (file == NULL || hadglob)
1895 goto out;
1896
1897 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
Darren Tucker191fcc62012-10-05 10:45:01 +10001898 tmp = path_strip(tmp2, isabs ? NULL : remote_path);
Darren Tuckera627d422013-06-02 07:31:17 +10001899 free(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001900
1901 if (tmp == NULL)
1902 goto out;
1903
1904 tmplen = strlen(tmp);
1905 filelen = strlen(file);
1906
Darren Tucker17146d32012-10-05 10:46:16 +10001907 /* Count the number of escaped characters in the input string. */
1908 cesc = isesc = 0;
1909 for (i = 0; i < filelen; i++) {
1910 if (!isesc && file[i] == '\\' && i + 1 < filelen){
1911 isesc = 1;
1912 cesc++;
1913 } else
1914 isesc = 0;
1915 }
1916
1917 if (tmplen > (filelen - cesc)) {
1918 tmp2 = tmp + filelen - cesc;
Damien Miller02e87802013-08-21 02:38:51 +10001919 len = strlen(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001920 /* quote argument on way out */
Darren Tuckerea647212013-06-06 08:19:09 +10001921 for (i = 0; i < len; i += clen) {
1922 if ((clen = mblen(tmp2 + i, len - i)) < 0 ||
1923 (size_t)clen > sizeof(ins) - 2)
1924 fatal("invalid multibyte character");
Darren Tucker909d8582010-01-08 19:02:40 +11001925 ins[0] = '\\';
Darren Tuckerea647212013-06-06 08:19:09 +10001926 memcpy(ins + 1, tmp2 + i, clen);
1927 ins[clen + 1] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001928 switch (tmp2[i]) {
1929 case '\'':
1930 case '"':
1931 case '\\':
1932 case '\t':
Darren Tuckerd78739a2010-10-24 10:56:32 +11001933 case '[':
Darren Tucker909d8582010-01-08 19:02:40 +11001934 case ' ':
Darren Tucker17146d32012-10-05 10:46:16 +10001935 case '#':
1936 case '*':
Darren Tucker909d8582010-01-08 19:02:40 +11001937 if (quote == '\0' || tmp2[i] == quote) {
1938 if (el_insertstr(el, ins) == -1)
1939 fatal("el_insertstr "
1940 "failed.");
1941 break;
1942 }
1943 /* FALLTHROUGH */
1944 default:
1945 if (el_insertstr(el, ins + 1) == -1)
1946 fatal("el_insertstr failed.");
1947 break;
1948 }
1949 }
1950 }
1951
1952 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11001953 if (g.gl_matchc == 1) {
1954 i = 0;
Damien Miller38094812014-05-15 14:25:18 +10001955 if (!terminated && quote != '\0')
Darren Tucker909d8582010-01-08 19:02:40 +11001956 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11001957 if (*(lf->cursor - 1) != '/' &&
1958 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11001959 ins[i++] = ' ';
1960 ins[i] = '\0';
1961 if (i > 0 && el_insertstr(el, ins) == -1)
1962 fatal("el_insertstr failed.");
1963 }
Darren Tuckera627d422013-06-02 07:31:17 +10001964 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001965
1966 out:
1967 globfree(&g);
1968 return g.gl_matchc;
1969}
1970
1971/* tab-completion hook function, called via libedit */
1972static unsigned char
1973complete(EditLine *el, int ch)
1974{
Damien Miller02e87802013-08-21 02:38:51 +10001975 char **argv, *line, quote;
Damien Miller746d1a62013-07-18 16:13:02 +10001976 int argc, carg;
1977 u_int cursor, len, terminated, ret = CC_ERROR;
Darren Tucker909d8582010-01-08 19:02:40 +11001978 const LineInfo *lf;
1979 struct complete_ctx *complete_ctx;
1980
1981 lf = el_line(el);
1982 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1983 fatal("%s: el_get failed", __func__);
1984
1985 /* Figure out which argument the cursor points to */
1986 cursor = lf->cursor - lf->buffer;
deraadt@openbsd.orgce445b02015-08-20 22:32:42 +00001987 line = xmalloc(cursor + 1);
Darren Tucker909d8582010-01-08 19:02:40 +11001988 memcpy(line, lf->buffer, cursor);
1989 line[cursor] = '\0';
1990 argv = makeargv(line, &carg, 1, &quote, &terminated);
Darren Tuckera627d422013-06-02 07:31:17 +10001991 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001992
1993 /* Get all the arguments on the line */
1994 len = lf->lastchar - lf->buffer;
deraadt@openbsd.orgce445b02015-08-20 22:32:42 +00001995 line = xmalloc(len + 1);
Darren Tucker909d8582010-01-08 19:02:40 +11001996 memcpy(line, lf->buffer, len);
1997 line[len] = '\0';
1998 argv = makeargv(line, &argc, 1, NULL, NULL);
1999
2000 /* Ensure cursor is at EOL or a argument boundary */
2001 if (line[cursor] != ' ' && line[cursor] != '\0' &&
2002 line[cursor] != '\n') {
Darren Tuckera627d422013-06-02 07:31:17 +10002003 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11002004 return ret;
2005 }
2006
2007 if (carg == 0) {
2008 /* Show all available commands */
2009 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
2010 ret = CC_REDISPLAY;
2011 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
2012 /* Handle the command parsing */
2013 if (complete_cmd_parse(el, argv[0], argc == carg,
Damien Miller02e87802013-08-21 02:38:51 +10002014 quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11002015 ret = CC_REDISPLAY;
2016 } else if (carg >= 1) {
2017 /* Handle file parsing */
2018 int remote = complete_is_remote(argv[0]);
2019 char *filematch = NULL;
2020
2021 if (carg > 1 && line[cursor-1] != ' ')
2022 filematch = argv[carg - 1];
2023
2024 if (remote != 0 &&
2025 complete_match(el, complete_ctx->conn,
2026 *complete_ctx->remote_pathp, filematch,
Damien Miller02e87802013-08-21 02:38:51 +10002027 remote, carg == argc, quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11002028 ret = CC_REDISPLAY;
2029 }
2030
Damien Miller02e87802013-08-21 02:38:51 +10002031 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11002032 return ret;
2033}
Darren Tuckere67f7db2010-01-08 19:50:02 +11002034#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11002035
Damien Miller20e1fab2004-02-18 14:30:55 +11002036int
Darren Tucker21063192010-01-08 17:10:36 +11002037interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11002038{
Darren Tucker909d8582010-01-08 19:02:40 +11002039 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11002040 char *dir = NULL;
2041 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10002042 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11002043 EditLine *el = NULL;
2044#ifdef USE_LIBEDIT
2045 History *hl = NULL;
2046 HistEvent hev;
2047 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11002048 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11002049
2050 if (!batchmode && isatty(STDIN_FILENO)) {
2051 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
2052 fatal("Couldn't initialise editline");
2053 if ((hl = history_init()) == NULL)
2054 fatal("Couldn't initialise editline history");
2055 history(hl, &hev, H_SETSIZE, 100);
2056 el_set(el, EL_HIST, history, hl);
2057
2058 el_set(el, EL_PROMPT, prompt);
2059 el_set(el, EL_EDITOR, "emacs");
2060 el_set(el, EL_TERMINAL, NULL);
2061 el_set(el, EL_SIGNAL, 1);
2062 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11002063
2064 /* Tab Completion */
Damien Miller02e87802013-08-21 02:38:51 +10002065 el_set(el, EL_ADDFN, "ftp-complete",
Darren Tuckerd78739a2010-10-24 10:56:32 +11002066 "Context sensitive argument completion", complete);
Darren Tucker909d8582010-01-08 19:02:40 +11002067 complete_ctx.conn = conn;
2068 complete_ctx.remote_pathp = &remote_path;
2069 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
2070 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Damien Millere0ee7272013-08-21 02:42:35 +10002071 /* enable ctrl-left-arrow and ctrl-right-arrow */
2072 el_set(el, EL_BIND, "\\e[1;5C", "em-next-word", NULL);
2073 el_set(el, EL_BIND, "\\e[5C", "em-next-word", NULL);
2074 el_set(el, EL_BIND, "\\e[1;5D", "ed-prev-word", NULL);
2075 el_set(el, EL_BIND, "\\e\\e[D", "ed-prev-word", NULL);
Damien Miller61353b32013-09-14 09:45:32 +10002076 /* make ^w match ksh behaviour */
2077 el_set(el, EL_BIND, "^w", "ed-delete-prev-word", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11002078 }
2079#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002080
Darren Tucker909d8582010-01-08 19:02:40 +11002081 remote_path = do_realpath(conn, ".");
2082 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11002083 fatal("Need cwd");
2084
2085 if (file1 != NULL) {
2086 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11002087 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11002088
2089 if (remote_is_dir(conn, dir) && file2 == NULL) {
Damien Miller9303e652013-04-23 15:22:40 +10002090 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00002091 mprintf("Changing to: %s\n", dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002092 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11002093 if (parse_dispatch_command(conn, cmd,
2094 &remote_path, 1) != 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10002095 free(dir);
2096 free(remote_path);
2097 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002098 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11002099 }
Damien Miller20e1fab2004-02-18 14:30:55 +11002100 } else {
Darren Tucker0af24052012-10-05 10:41:25 +10002101 /* XXX this is wrong wrt quoting */
Damien Miller0d032412013-07-25 11:56:52 +10002102 snprintf(cmd, sizeof cmd, "get%s %s%s%s",
2103 global_aflag ? " -a" : "", dir,
2104 file2 == NULL ? "" : " ",
2105 file2 == NULL ? "" : file2);
Darren Tucker909d8582010-01-08 19:02:40 +11002106 err = parse_dispatch_command(conn, cmd,
2107 &remote_path, 1);
Darren Tuckera627d422013-06-02 07:31:17 +10002108 free(dir);
2109 free(remote_path);
2110 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002111 return (err);
2112 }
Darren Tuckera627d422013-06-02 07:31:17 +10002113 free(dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002114 }
2115
millert@openbsd.orgdb995f22014-11-26 18:34:51 +00002116 setvbuf(stdout, NULL, _IOLBF, 0);
2117 setvbuf(infile, NULL, _IOLBF, 0);
Damien Miller20e1fab2004-02-18 14:30:55 +11002118
Damien Miller0e2c1022005-08-12 22:16:22 +10002119 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11002120 err = 0;
2121 for (;;) {
2122 char *cp;
2123
Darren Tuckercdf547a2004-05-24 10:12:19 +10002124 signal(SIGINT, SIG_IGN);
2125
Darren Tucker2d963d82004-11-07 20:04:10 +11002126 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002127 if (interactive)
2128 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11002129 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002130 if (interactive)
2131 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11002132 break;
2133 }
Damien Miller0e2c1022005-08-12 22:16:22 +10002134 if (!interactive) { /* Echo command */
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00002135 mprintf("sftp> %s", cmd);
Damien Miller0e2c1022005-08-12 22:16:22 +10002136 if (strlen(cmd) > 0 &&
2137 cmd[strlen(cmd) - 1] != '\n')
2138 printf("\n");
2139 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002140 } else {
2141#ifdef USE_LIBEDIT
2142 const char *line;
2143 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11002144
Darren Tucker909d8582010-01-08 19:02:40 +11002145 if ((line = el_gets(el, &count)) == NULL ||
2146 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002147 printf("\n");
2148 break;
2149 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002150 history(hl, &hev, H_ENTER, line);
2151 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
2152 fprintf(stderr, "Error: input line too long\n");
2153 continue;
2154 }
2155#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002156 }
2157
Damien Miller20e1fab2004-02-18 14:30:55 +11002158 cp = strrchr(cmd, '\n');
2159 if (cp)
2160 *cp = '\0';
2161
Darren Tuckercdf547a2004-05-24 10:12:19 +10002162 /* Handle user interrupts gracefully during commands */
2163 interrupted = 0;
2164 signal(SIGINT, cmd_interrupt);
2165
Darren Tucker909d8582010-01-08 19:02:40 +11002166 err = parse_dispatch_command(conn, cmd, &remote_path,
2167 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11002168 if (err != 0)
2169 break;
2170 }
Darren Tuckera627d422013-06-02 07:31:17 +10002171 free(remote_path);
2172 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002173
Tim Rice027e8b12005-08-15 14:52:50 -07002174#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10002175 if (el != NULL)
2176 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07002177#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10002178
Damien Miller20e1fab2004-02-18 14:30:55 +11002179 /* err == 1 signifies normal "quit" exit */
2180 return (err >= 0 ? 0 : -1);
2181}
Damien Miller62d57f62003-01-10 21:43:24 +11002182
Ben Lindstrombba81212001-06-25 05:01:22 +00002183static void
Damien Millercc685c12003-06-04 22:51:38 +10002184connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11002185{
2186 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002187
Damien Miller33804262001-02-04 23:20:18 +11002188#ifdef USE_PIPES
2189 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002190
Damien Miller33804262001-02-04 23:20:18 +11002191 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2192 fatal("pipe: %s", strerror(errno));
2193 *in = pin[0];
2194 *out = pout[1];
2195 c_in = pout[0];
2196 c_out = pin[1];
2197#else /* USE_PIPES */
2198 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002199
Damien Miller33804262001-02-04 23:20:18 +11002200 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2201 fatal("socketpair: %s", strerror(errno));
2202 *in = *out = inout[0];
2203 c_in = c_out = inout[1];
2204#endif /* USE_PIPES */
2205
Damien Millercc685c12003-06-04 22:51:38 +10002206 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002207 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002208 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002209 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2210 (dup2(c_out, STDOUT_FILENO) == -1)) {
2211 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002212 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002213 }
2214 close(*in);
2215 close(*out);
2216 close(c_in);
2217 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002218
2219 /*
2220 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002221 * ignore SIGINT if we want to gracefully abort commands,
2222 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002223 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2224 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002225 */
2226 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002227 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002228 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002229 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002230 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002231 }
2232
Damien Millercc685c12003-06-04 22:51:38 +10002233 signal(SIGTERM, killchild);
2234 signal(SIGINT, killchild);
2235 signal(SIGHUP, killchild);
millert@openbsd.org2c6697c2016-10-18 12:41:22 +00002236 signal(SIGTSTP, suspchild);
2237 signal(SIGTTIN, suspchild);
2238 signal(SIGTTOU, suspchild);
Damien Miller33804262001-02-04 23:20:18 +11002239 close(c_in);
2240 close(c_out);
2241}
2242
Ben Lindstrombba81212001-06-25 05:01:22 +00002243static void
Damien Miller33804262001-02-04 23:20:18 +11002244usage(void)
2245{
Damien Miller025e01c2002-02-08 22:06:29 +11002246 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002247
Ben Lindstrom1e243242001-09-18 05:38:44 +00002248 fprintf(stderr,
djm@openbsd.org3575f0b2017-05-02 08:54:19 +00002249 "usage: %s [-46aCfpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002250 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002251 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002252 " [-o ssh_option] [-P port] [-R num_requests] "
2253 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002254 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002255 " %s [user@]host[:file ...]\n"
2256 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002257 " %s -b batchfile [user@]host\n",
2258 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002259 exit(1);
2260}
2261
Kevin Stevesef4eea92001-02-05 12:42:17 +00002262int
Damien Miller33804262001-02-04 23:20:18 +11002263main(int argc, char **argv)
2264{
Damien Miller956f3fb2003-01-10 21:40:00 +11002265 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002266 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002267 int debug_level = 0, sshver = 2;
2268 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002269 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002270 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002271 LogLevel ll = SYSLOG_LEVEL_INFO;
2272 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002273 extern int optind;
2274 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002275 struct sftp_conn *conn;
2276 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2277 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002278 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002279
dtucker@openbsd.orgffb1e7e2016-02-15 09:47:49 +00002280 ssh_malloc_init(); /* must be called before any mallocs */
Darren Tuckerce321d82005-10-03 18:11:24 +10002281 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2282 sanitise_stdfd();
Damien Millerdda78a02016-12-12 13:57:10 +11002283 msetlocale();
Darren Tuckerce321d82005-10-03 18:11:24 +10002284
Damien Miller59d3d5b2003-08-22 09:34:41 +10002285 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002286 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002287 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002288 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002289 addargs(&args, "-oForwardX11 no");
2290 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002291 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002292 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002293
Ben Lindstrom387c4722001-05-08 20:27:25 +00002294 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002295 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002296
Darren Tucker282b4022009-10-07 08:23:06 +11002297 while ((ch = getopt(argc, argv,
Damien Millerf29238e2013-10-17 11:48:52 +11002298 "1246afhpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002299 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002300 /* Passed through to ssh(1) */
2301 case '4':
2302 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002303 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002304 addargs(&args, "-%c", ch);
2305 break;
2306 /* Passed through to ssh(1) with argument */
2307 case 'F':
2308 case 'c':
2309 case 'i':
2310 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002311 addargs(&args, "-%c", ch);
2312 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002313 break;
2314 case 'q':
Damien Miller9303e652013-04-23 15:22:40 +10002315 ll = SYSLOG_LEVEL_ERROR;
2316 quiet = 1;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002317 showprogress = 0;
2318 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002319 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002320 case 'P':
2321 addargs(&args, "-oPort %s", optarg);
2322 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002323 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002324 if (debug_level < 3) {
2325 addargs(&args, "-v");
2326 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2327 }
2328 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002329 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002330 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002331 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002332 if (sftp_server == NULL)
2333 sftp_server = _PATH_SFTP_SERVER;
2334 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002335 case '2':
2336 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002337 break;
Damien Miller0d032412013-07-25 11:56:52 +10002338 case 'a':
2339 global_aflag = 1;
2340 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002341 case 'B':
2342 copy_buffer_len = strtol(optarg, &cp, 10);
2343 if (copy_buffer_len == 0 || *cp != '\0')
2344 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002345 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002346 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002347 if (batchmode)
2348 fatal("Batch file already specified.");
2349
2350 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002351 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002352 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002353 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002354 showprogress = 0;
Damien Miller9303e652013-04-23 15:22:40 +10002355 quiet = batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002356 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002357 break;
Damien Millerf29238e2013-10-17 11:48:52 +11002358 case 'f':
2359 global_fflag = 1;
2360 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002361 case 'p':
2362 global_pflag = 1;
2363 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002364 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002365 sftp_direct = optarg;
2366 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002367 case 'l':
2368 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2369 &errstr);
2370 if (errstr != NULL)
2371 usage();
2372 limit_kbps *= 1024; /* kbps */
2373 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002374 case 'r':
2375 global_rflag = 1;
2376 break;
Damien Miller16a13332002-02-13 14:03:56 +11002377 case 'R':
2378 num_requests = strtol(optarg, &cp, 10);
2379 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002380 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002381 optarg);
2382 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002383 case 's':
2384 sftp_server = optarg;
2385 break;
2386 case 'S':
2387 ssh_program = optarg;
2388 replacearg(&args, 0, "%s", ssh_program);
2389 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002390 case 'h':
2391 default:
Damien Miller33804262001-02-04 23:20:18 +11002392 usage();
2393 }
2394 }
2395
Damien Millerc0f27d82004-03-08 23:12:19 +11002396 if (!isatty(STDERR_FILENO))
2397 showprogress = 0;
2398
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002399 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2400
Damien Millerd14ee1e2002-02-05 12:27:31 +11002401 if (sftp_direct == NULL) {
2402 if (optind == argc || argc > (optind + 2))
2403 usage();
Damien Miller33804262001-02-04 23:20:18 +11002404
Damien Millerd14ee1e2002-02-05 12:27:31 +11002405 userhost = xstrdup(argv[optind]);
2406 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002407
Ben Lindstromc276c122002-12-23 02:14:51 +00002408 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002409 host = userhost;
2410 else {
2411 *host++ = '\0';
2412 if (!userhost[0]) {
2413 fprintf(stderr, "Missing username\n");
2414 usage();
2415 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002416 addargs(&args, "-l");
2417 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002418 }
2419
Damien Millerec692032004-01-27 21:22:00 +11002420 if ((cp = colon(host)) != NULL) {
2421 *cp++ = '\0';
2422 file1 = cp;
2423 }
2424
Damien Millerd14ee1e2002-02-05 12:27:31 +11002425 host = cleanhostname(host);
2426 if (!*host) {
2427 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002428 usage();
2429 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002430
Damien Millerd14ee1e2002-02-05 12:27:31 +11002431 addargs(&args, "-oProtocol %d", sshver);
2432
2433 /* no subsystem if the server-spec contains a '/' */
2434 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2435 addargs(&args, "-s");
2436
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002437 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002438 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002439 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002440 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002441
Damien Millercc685c12003-06-04 22:51:38 +10002442 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002443 } else {
2444 args.list = NULL;
2445 addargs(&args, "sftp-server");
2446
Damien Millercc685c12003-06-04 22:51:38 +10002447 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002448 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002449 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002450
Damien Miller65e42f82010-09-24 22:15:11 +10002451 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002452 if (conn == NULL)
2453 fatal("Couldn't initialise connection to server");
2454
Damien Miller9303e652013-04-23 15:22:40 +10002455 if (!quiet) {
Darren Tucker21063192010-01-08 17:10:36 +11002456 if (sftp_direct == NULL)
2457 fprintf(stderr, "Connected to %s.\n", host);
2458 else
2459 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2460 }
2461
2462 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002463
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002464#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002465 shutdown(in, SHUT_RDWR);
2466 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002467#endif
2468
Damien Miller33804262001-02-04 23:20:18 +11002469 close(in);
2470 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002471 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002472 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002473
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002474 while (waitpid(sshpid, NULL, 0) == -1)
2475 if (errno != EINTR)
2476 fatal("Couldn't wait for ssh process: %s",
2477 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002478
Damien Miller956f3fb2003-01-10 21:40:00 +11002479 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002480}