blob: f886b330bb263104697701b9c9cabfa1cc4813c7 [file] [log] [blame]
djm@openbsd.org2a358622018-11-16 03:26:01 +00001/* $OpenBSD: sftp.c,v 1.188 2018/11/16 03:26:01 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 */
bluhm@openbsd.orge7751aa2018-04-26 14:47:03 +000084static volatile pid_t sshpid = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +110085
Damien Miller9303e652013-04-23 15:22:40 +100086/* Suppress diagnositic messages */
87int quiet = 0;
88
Damien Miller20e1fab2004-02-18 14:30:55 +110089/* This is set to 0 if the progressmeter is not desired. */
Damien Millerc0f27d82004-03-08 23:12:19 +110090int showprogress = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +110091
Darren Tucker1b0dd172009-10-07 08:37:48 +110092/* When this option is set, we always recursively download/upload directories */
93int global_rflag = 0;
94
Damien Millerd8accc02014-05-15 13:46:25 +100095/* When this option is set, we resume download or upload if possible */
Damien Miller0d032412013-07-25 11:56:52 +100096int global_aflag = 0;
97
Darren Tucker1b0dd172009-10-07 08:37:48 +110098/* When this option is set, the file transfers will always preserve times */
99int global_pflag = 0;
100
Damien Millerf29238e2013-10-17 11:48:52 +1100101/* When this option is set, transfers will have fsync() called on each file */
102int global_fflag = 0;
103
Darren Tuckercdf547a2004-05-24 10:12:19 +1000104/* SIGINT received during command processing */
105volatile sig_atomic_t interrupted = 0;
106
Darren Tuckerb9123452004-06-22 13:06:45 +1000107/* I wish qsort() took a separate ctx for the comparison function...*/
108int sort_flag;
djm@openbsd.org72be5b22017-06-10 06:33:34 +0000109glob_t *sort_glob;
Darren Tuckerb9123452004-06-22 13:06:45 +1000110
Darren Tucker909d8582010-01-08 19:02:40 +1100111/* Context used for commandline completion */
112struct complete_ctx {
113 struct sftp_conn *conn;
114 char **remote_pathp;
115};
116
Damien Miller20e1fab2004-02-18 14:30:55 +1100117int remote_glob(struct sftp_conn *, const char *, int,
118 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
Damien Miller33804262001-02-04 23:20:18 +1100119
Kevin Steves12888d12001-03-05 19:50:57 +0000120extern char *__progname;
Kevin Steves12888d12001-03-05 19:50:57 +0000121
Damien Miller20e1fab2004-02-18 14:30:55 +1100122/* Separators for interactive commands */
123#define WHITESPACE " \t\r\n"
Damien Millerd7686fd2001-02-10 00:40:03 +1100124
Darren Tuckerb9123452004-06-22 13:06:45 +1000125/* ls flags */
Darren Tucker2901e2d2010-01-13 22:44:06 +1100126#define LS_LONG_VIEW 0x0001 /* Full view ala ls -l */
127#define LS_SHORT_VIEW 0x0002 /* Single row view ala ls -1 */
128#define LS_NUMERIC_VIEW 0x0004 /* Long view with numeric uid/gid */
129#define LS_NAME_SORT 0x0008 /* Sort by name (default) */
130#define LS_TIME_SORT 0x0010 /* Sort by mtime */
131#define LS_SIZE_SORT 0x0020 /* Sort by file size */
132#define LS_REVERSE_SORT 0x0040 /* Reverse sort order */
133#define LS_SHOW_ALL 0x0080 /* Don't skip filenames starting with '.' */
134#define LS_SI_UNITS 0x0100 /* Display sizes as K, M, G, etc. */
Darren Tuckerb9123452004-06-22 13:06:45 +1000135
Darren Tucker2901e2d2010-01-13 22:44:06 +1100136#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS)
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000137#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
Damien Miller20e1fab2004-02-18 14:30:55 +1100138
139/* Commands for interactive mode */
Damien Miller02e87802013-08-21 02:38:51 +1000140enum sftp_command {
141 I_CHDIR = 1,
142 I_CHGRP,
143 I_CHMOD,
144 I_CHOWN,
145 I_DF,
146 I_GET,
147 I_HELP,
148 I_LCHDIR,
149 I_LINK,
150 I_LLS,
151 I_LMKDIR,
152 I_LPWD,
153 I_LS,
154 I_LUMASK,
155 I_MKDIR,
156 I_PUT,
157 I_PWD,
158 I_QUIT,
Damien Millerb15cd7b2014-05-15 13:46:52 +1000159 I_REGET,
Damien Miller02e87802013-08-21 02:38:51 +1000160 I_RENAME,
Damien Millerb15cd7b2014-05-15 13:46:52 +1000161 I_REPUT,
Damien Miller02e87802013-08-21 02:38:51 +1000162 I_RM,
163 I_RMDIR,
164 I_SHELL,
165 I_SYMLINK,
166 I_VERSION,
167 I_PROGRESS,
Damien Miller02e87802013-08-21 02:38:51 +1000168};
Damien Miller20e1fab2004-02-18 14:30:55 +1100169
170struct CMD {
171 const char *c;
172 const int n;
Darren Tucker909d8582010-01-08 19:02:40 +1100173 const int t;
Damien Miller20e1fab2004-02-18 14:30:55 +1100174};
175
Darren Tucker909d8582010-01-08 19:02:40 +1100176/* Type of completion */
177#define NOARGS 0
178#define REMOTE 1
179#define LOCAL 2
180
Damien Miller20e1fab2004-02-18 14:30:55 +1100181static const struct CMD cmds[] = {
Darren Tucker909d8582010-01-08 19:02:40 +1100182 { "bye", I_QUIT, NOARGS },
183 { "cd", I_CHDIR, REMOTE },
184 { "chdir", I_CHDIR, REMOTE },
185 { "chgrp", I_CHGRP, REMOTE },
186 { "chmod", I_CHMOD, REMOTE },
187 { "chown", I_CHOWN, REMOTE },
188 { "df", I_DF, REMOTE },
189 { "dir", I_LS, REMOTE },
190 { "exit", I_QUIT, NOARGS },
191 { "get", I_GET, REMOTE },
192 { "help", I_HELP, NOARGS },
193 { "lcd", I_LCHDIR, LOCAL },
194 { "lchdir", I_LCHDIR, LOCAL },
195 { "lls", I_LLS, LOCAL },
196 { "lmkdir", I_LMKDIR, LOCAL },
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100197 { "ln", I_LINK, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100198 { "lpwd", I_LPWD, LOCAL },
199 { "ls", I_LS, REMOTE },
200 { "lumask", I_LUMASK, NOARGS },
201 { "mkdir", I_MKDIR, REMOTE },
Damien Miller099fc162010-05-10 11:56:50 +1000202 { "mget", I_GET, REMOTE },
203 { "mput", I_PUT, LOCAL },
Darren Tucker909d8582010-01-08 19:02:40 +1100204 { "progress", I_PROGRESS, NOARGS },
205 { "put", I_PUT, LOCAL },
206 { "pwd", I_PWD, REMOTE },
207 { "quit", I_QUIT, NOARGS },
Damien Miller0d032412013-07-25 11:56:52 +1000208 { "reget", I_REGET, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100209 { "rename", I_RENAME, REMOTE },
djm@openbsd.org4a459222014-10-06 00:47:15 +0000210 { "reput", I_REPUT, LOCAL },
Darren Tucker909d8582010-01-08 19:02:40 +1100211 { "rm", I_RM, REMOTE },
212 { "rmdir", I_RMDIR, REMOTE },
213 { "symlink", I_SYMLINK, REMOTE },
214 { "version", I_VERSION, NOARGS },
215 { "!", I_SHELL, NOARGS },
216 { "?", I_HELP, NOARGS },
217 { NULL, -1, -1 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100218};
219
Damien Millerb6c85fc2007-01-05 16:30:41 +1100220/* ARGSUSED */
Damien Miller20e1fab2004-02-18 14:30:55 +1100221static void
Darren Tuckercdf547a2004-05-24 10:12:19 +1000222killchild(int signo)
223{
Darren Tuckerba66df82005-01-24 21:57:40 +1100224 if (sshpid > 1) {
Darren Tuckercdf547a2004-05-24 10:12:19 +1000225 kill(sshpid, SIGTERM);
Darren Tuckerba66df82005-01-24 21:57:40 +1100226 waitpid(sshpid, NULL, 0);
227 }
Darren Tuckercdf547a2004-05-24 10:12:19 +1000228
229 _exit(1);
230}
231
Damien Millerb6c85fc2007-01-05 16:30:41 +1100232/* ARGSUSED */
Darren Tuckercdf547a2004-05-24 10:12:19 +1000233static void
millert@openbsd.org2c6697c2016-10-18 12:41:22 +0000234suspchild(int signo)
235{
236 if (sshpid > 1) {
237 kill(sshpid, signo);
238 while (waitpid(sshpid, NULL, WUNTRACED) == -1 && errno == EINTR)
239 continue;
240 }
241 kill(getpid(), SIGSTOP);
242}
243
244/* ARGSUSED */
245static void
Darren Tuckercdf547a2004-05-24 10:12:19 +1000246cmd_interrupt(int signo)
247{
248 const char msg[] = "\rInterrupt \n";
Darren Tuckere2f189a2004-12-06 22:45:53 +1100249 int olderrno = errno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000250
Darren Tuckerdbee3082013-05-16 20:32:29 +1000251 (void)write(STDERR_FILENO, msg, sizeof(msg) - 1);
Darren Tuckercdf547a2004-05-24 10:12:19 +1000252 interrupted = 1;
Darren Tuckere2f189a2004-12-06 22:45:53 +1100253 errno = olderrno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000254}
255
djm@openbsd.org3455f1e2018-04-13 05:04:12 +0000256/*ARGSUSED*/
257static void
258sigchld_handler(int sig)
259{
260 int save_errno = errno;
261 pid_t pid;
262 const char msg[] = "\rConnection closed. \n";
263
264 /* Report if ssh transport process dies. */
265 while ((pid = waitpid(sshpid, NULL, WNOHANG)) == -1 && errno == EINTR)
266 continue;
bluhm@openbsd.orge7751aa2018-04-26 14:47:03 +0000267 if (pid == sshpid) {
djm@openbsd.org3455f1e2018-04-13 05:04:12 +0000268 (void)write(STDERR_FILENO, msg, sizeof(msg) - 1);
bluhm@openbsd.orge7751aa2018-04-26 14:47:03 +0000269 sshpid = -1;
270 }
djm@openbsd.org3455f1e2018-04-13 05:04:12 +0000271
272 errno = save_errno;
273}
274
Darren Tuckercdf547a2004-05-24 10:12:19 +1000275static void
Damien Miller20e1fab2004-02-18 14:30:55 +1100276help(void)
277{
Damien Miller62fd18a2009-01-28 16:14:09 +1100278 printf("Available commands:\n"
279 "bye Quit sftp\n"
280 "cd path Change remote directory to 'path'\n"
281 "chgrp grp path Change group of file 'path' to 'grp'\n"
282 "chmod mode path Change permissions of file 'path' to 'mode'\n"
283 "chown own path Change owner of file 'path' to 'own'\n"
284 "df [-hi] [path] Display statistics for current directory or\n"
285 " filesystem containing 'path'\n"
286 "exit Quit sftp\n"
djm@openbsd.org4a459222014-10-06 00:47:15 +0000287 "get [-afPpRr] remote [local] Download file\n"
288 "reget [-fPpRr] remote [local] Resume download file\n"
289 "reput [-fPpRr] [local] remote Resume upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100290 "help Display this help text\n"
291 "lcd path Change local directory to 'path'\n"
292 "lls [ls-options [path]] Display local directory listing\n"
293 "lmkdir path Create local directory\n"
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100294 "ln [-s] oldpath newpath Link remote file (-s for symlink)\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100295 "lpwd Print local working directory\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100296 "ls [-1afhlnrSt] [path] Display remote directory listing\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100297 "lumask umask Set local umask to 'umask'\n"
298 "mkdir path Create remote directory\n"
299 "progress Toggle display of progress meter\n"
djm@openbsd.org4a459222014-10-06 00:47:15 +0000300 "put [-afPpRr] local [remote] Upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100301 "pwd Display remote working directory\n"
302 "quit Quit sftp\n"
303 "rename oldpath newpath Rename remote file\n"
304 "rm path Delete remote file\n"
305 "rmdir path Remove remote directory\n"
306 "symlink oldpath newpath Symlink remote file\n"
307 "version Show SFTP version\n"
308 "!command Execute 'command' in local shell\n"
309 "! Escape to local shell\n"
310 "? Synonym for help\n");
Damien Miller20e1fab2004-02-18 14:30:55 +1100311}
312
313static void
314local_do_shell(const char *args)
315{
316 int status;
317 char *shell;
318 pid_t pid;
319
320 if (!*args)
321 args = NULL;
322
Damien Miller38d9a962010-10-07 22:07:11 +1100323 if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
Damien Miller20e1fab2004-02-18 14:30:55 +1100324 shell = _PATH_BSHELL;
325
326 if ((pid = fork()) == -1)
327 fatal("Couldn't fork: %s", strerror(errno));
328
329 if (pid == 0) {
330 /* XXX: child has pipe fds to ssh subproc open - issue? */
331 if (args) {
332 debug3("Executing %s -c \"%s\"", shell, args);
333 execl(shell, shell, "-c", args, (char *)NULL);
334 } else {
335 debug3("Executing %s", shell);
336 execl(shell, shell, (char *)NULL);
337 }
338 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
339 strerror(errno));
340 _exit(1);
341 }
342 while (waitpid(pid, &status, 0) == -1)
343 if (errno != EINTR)
344 fatal("Couldn't wait for child: %s", strerror(errno));
345 if (!WIFEXITED(status))
Damien Miller55b04f12006-03-26 14:23:17 +1100346 error("Shell exited abnormally");
Damien Miller20e1fab2004-02-18 14:30:55 +1100347 else if (WEXITSTATUS(status))
348 error("Shell exited with status %d", WEXITSTATUS(status));
349}
350
351static void
352local_do_ls(const char *args)
353{
354 if (!args || !*args)
355 local_do_shell(_PATH_LS);
356 else {
357 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
358 char *buf = xmalloc(len);
359
360 /* XXX: quoting - rip quoting code from ftp? */
361 snprintf(buf, len, _PATH_LS " %s", args);
362 local_do_shell(buf);
Darren Tuckera627d422013-06-02 07:31:17 +1000363 free(buf);
Damien Miller20e1fab2004-02-18 14:30:55 +1100364 }
365}
366
367/* Strip one path (usually the pwd) from the start of another */
368static char *
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000369path_strip(const char *path, const char *strip)
Damien Miller20e1fab2004-02-18 14:30:55 +1100370{
371 size_t len;
372
373 if (strip == NULL)
374 return (xstrdup(path));
375
376 len = strlen(strip);
Darren Tuckere2f189a2004-12-06 22:45:53 +1100377 if (strncmp(path, strip, len) == 0) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100378 if (strip[len - 1] != '/' && path[len] == '/')
379 len++;
380 return (xstrdup(path + len));
381 }
382
383 return (xstrdup(path));
384}
385
386static char *
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000387make_absolute(char *p, const char *pwd)
Damien Miller20e1fab2004-02-18 14:30:55 +1100388{
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000389 char *abs_str;
Damien Miller20e1fab2004-02-18 14:30:55 +1100390
391 /* Derelativise */
djm@openbsd.org2a358622018-11-16 03:26:01 +0000392 if (p && !path_absolute(p)) {
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000393 abs_str = path_append(pwd, p);
Darren Tuckera627d422013-06-02 07:31:17 +1000394 free(p);
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000395 return(abs_str);
Damien Miller20e1fab2004-02-18 14:30:55 +1100396 } else
397 return(p);
398}
399
400static int
Damien Miller0d032412013-07-25 11:56:52 +1000401parse_getput_flags(const char *cmd, char **argv, int argc,
Damien Millerf29238e2013-10-17 11:48:52 +1100402 int *aflag, int *fflag, int *pflag, int *rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100403{
Damien Millerf184bcf2008-06-29 22:45:13 +1000404 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000405 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100406
Damien Miller1cbc2922007-10-26 14:27:45 +1000407 optind = optreset = 1;
408 opterr = 0;
409
Damien Millerf29238e2013-10-17 11:48:52 +1100410 *aflag = *fflag = *rflag = *pflag = 0;
411 while ((ch = getopt(argc, argv, "afPpRr")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000412 switch (ch) {
Damien Miller0d032412013-07-25 11:56:52 +1000413 case 'a':
414 *aflag = 1;
415 break;
Damien Millerf29238e2013-10-17 11:48:52 +1100416 case 'f':
417 *fflag = 1;
418 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100419 case 'p':
420 case 'P':
421 *pflag = 1;
422 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100423 case 'r':
424 case 'R':
425 *rflag = 1;
426 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100427 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000428 error("%s: Invalid flag -%c", cmd, optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000429 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100430 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100431 }
432
Damien Miller1cbc2922007-10-26 14:27:45 +1000433 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100434}
435
436static int
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100437parse_link_flags(const char *cmd, char **argv, int argc, int *sflag)
438{
439 extern int opterr, optind, optopt, optreset;
440 int ch;
441
442 optind = optreset = 1;
443 opterr = 0;
444
445 *sflag = 0;
446 while ((ch = getopt(argc, argv, "s")) != -1) {
447 switch (ch) {
448 case 's':
449 *sflag = 1;
450 break;
451 default:
452 error("%s: Invalid flag -%c", cmd, optopt);
453 return -1;
454 }
455 }
456
457 return optind;
458}
459
460static int
Damien Millerc7dba122013-08-21 02:41:15 +1000461parse_rename_flags(const char *cmd, char **argv, int argc, int *lflag)
462{
463 extern int opterr, optind, optopt, optreset;
464 int ch;
465
466 optind = optreset = 1;
467 opterr = 0;
468
469 *lflag = 0;
470 while ((ch = getopt(argc, argv, "l")) != -1) {
471 switch (ch) {
472 case 'l':
473 *lflag = 1;
474 break;
475 default:
476 error("%s: Invalid flag -%c", cmd, optopt);
477 return -1;
478 }
479 }
480
481 return optind;
482}
483
484static int
Damien Miller1cbc2922007-10-26 14:27:45 +1000485parse_ls_flags(char **argv, int argc, int *lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100486{
Damien Millerf184bcf2008-06-29 22:45:13 +1000487 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000488 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100489
Damien Miller1cbc2922007-10-26 14:27:45 +1000490 optind = optreset = 1;
491 opterr = 0;
492
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000493 *lflag = LS_NAME_SORT;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100494 while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000495 switch (ch) {
496 case '1':
497 *lflag &= ~VIEW_FLAGS;
498 *lflag |= LS_SHORT_VIEW;
499 break;
500 case 'S':
501 *lflag &= ~SORT_FLAGS;
502 *lflag |= LS_SIZE_SORT;
503 break;
504 case 'a':
505 *lflag |= LS_SHOW_ALL;
506 break;
507 case 'f':
508 *lflag &= ~SORT_FLAGS;
509 break;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100510 case 'h':
511 *lflag |= LS_SI_UNITS;
512 break;
Damien Miller1cbc2922007-10-26 14:27:45 +1000513 case 'l':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100514 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000515 *lflag |= LS_LONG_VIEW;
516 break;
517 case 'n':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100518 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000519 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
520 break;
521 case 'r':
522 *lflag |= LS_REVERSE_SORT;
523 break;
524 case 't':
525 *lflag &= ~SORT_FLAGS;
526 *lflag |= LS_TIME_SORT;
527 break;
528 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000529 error("ls: Invalid flag -%c", optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000530 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100531 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100532 }
533
Damien Miller1cbc2922007-10-26 14:27:45 +1000534 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100535}
536
537static int
Damien Millerd671e5a2008-05-19 14:53:33 +1000538parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
539{
Damien Millerf184bcf2008-06-29 22:45:13 +1000540 extern int opterr, optind, optopt, optreset;
Damien Millerd671e5a2008-05-19 14:53:33 +1000541 int ch;
542
543 optind = optreset = 1;
544 opterr = 0;
545
546 *hflag = *iflag = 0;
547 while ((ch = getopt(argc, argv, "hi")) != -1) {
548 switch (ch) {
549 case 'h':
550 *hflag = 1;
551 break;
552 case 'i':
553 *iflag = 1;
554 break;
555 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000556 error("%s: Invalid flag -%c", cmd, optopt);
Damien Millerd671e5a2008-05-19 14:53:33 +1000557 return -1;
558 }
559 }
560
561 return optind;
562}
563
564static int
Damien Miller036d3072013-08-21 02:41:46 +1000565parse_no_flags(const char *cmd, char **argv, int argc)
566{
567 extern int opterr, optind, optopt, optreset;
568 int ch;
569
570 optind = optreset = 1;
571 opterr = 0;
572
573 while ((ch = getopt(argc, argv, "")) != -1) {
574 switch (ch) {
575 default:
576 error("%s: Invalid flag -%c", cmd, optopt);
577 return -1;
578 }
579 }
580
581 return optind;
582}
583
584static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000585is_dir(const char *path)
Damien Miller20e1fab2004-02-18 14:30:55 +1100586{
587 struct stat sb;
588
589 /* XXX: report errors? */
590 if (stat(path, &sb) == -1)
591 return(0);
592
Darren Tucker1e80e402006-09-21 12:59:33 +1000593 return(S_ISDIR(sb.st_mode));
Damien Miller20e1fab2004-02-18 14:30:55 +1100594}
595
596static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000597remote_is_dir(struct sftp_conn *conn, const char *path)
Damien Miller20e1fab2004-02-18 14:30:55 +1100598{
599 Attrib *a;
600
601 /* XXX: report errors? */
602 if ((a = do_stat(conn, path, 1)) == NULL)
603 return(0);
604 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
605 return(0);
Darren Tucker1e80e402006-09-21 12:59:33 +1000606 return(S_ISDIR(a->perm));
Damien Miller20e1fab2004-02-18 14:30:55 +1100607}
608
Darren Tucker1b0dd172009-10-07 08:37:48 +1100609/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
Damien Miller20e1fab2004-02-18 14:30:55 +1100610static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000611pathname_is_dir(const char *pathname)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100612{
613 size_t l = strlen(pathname);
614
615 return l > 0 && pathname[l - 1] == '/';
616}
617
618static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000619process_get(struct sftp_conn *conn, const char *src, const char *dst,
620 const char *pwd, int pflag, int rflag, int resume, int fflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100621{
622 char *abs_src = NULL;
623 char *abs_dst = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100624 glob_t g;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100625 char *filename, *tmp=NULL;
Damien Miller00707762014-07-09 13:07:06 +1000626 int i, r, err = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100627
628 abs_src = xstrdup(src);
629 abs_src = make_absolute(abs_src, pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +1100630 memset(&g, 0, sizeof(g));
Darren Tucker1b0dd172009-10-07 08:37:48 +1100631
Damien Miller20e1fab2004-02-18 14:30:55 +1100632 debug3("Looking up %s", abs_src);
Damien Miller00707762014-07-09 13:07:06 +1000633 if ((r = remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) != 0) {
634 if (r == GLOB_NOSPACE) {
635 error("Too many matches for \"%s\".", abs_src);
636 } else {
637 error("File \"%s\" not found.", abs_src);
638 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100639 err = -1;
640 goto out;
641 }
642
Darren Tucker1b0dd172009-10-07 08:37:48 +1100643 /*
644 * If multiple matches then dst must be a directory or
645 * unspecified.
646 */
647 if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
648 error("Multiple source paths, but destination "
649 "\"%s\" is not a directory", dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100650 err = -1;
651 goto out;
652 }
653
Darren Tuckercdf547a2004-05-24 10:12:19 +1000654 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100655 tmp = xstrdup(g.gl_pathv[i]);
656 if ((filename = basename(tmp)) == NULL) {
657 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000658 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100659 err = -1;
660 goto out;
661 }
662
663 if (g.gl_matchc == 1 && dst) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100664 if (is_dir(dst)) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100665 abs_dst = path_append(dst, filename);
666 } else {
Damien Miller20e1fab2004-02-18 14:30:55 +1100667 abs_dst = xstrdup(dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100668 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100669 } else if (dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100670 abs_dst = path_append(dst, filename);
671 } else {
672 abs_dst = xstrdup(filename);
673 }
Darren Tuckera627d422013-06-02 07:31:17 +1000674 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100675
Damien Miller0d032412013-07-25 11:56:52 +1000676 resume |= global_aflag;
677 if (!quiet && resume)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000678 mprintf("Resuming %s to %s\n",
679 g.gl_pathv[i], abs_dst);
Damien Miller0d032412013-07-25 11:56:52 +1000680 else if (!quiet && !resume)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000681 mprintf("Fetching %s to %s\n",
682 g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100683 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
Damien Miller0d032412013-07-25 11:56:52 +1000684 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
Damien Millerf29238e2013-10-17 11:48:52 +1100685 pflag || global_pflag, 1, resume,
686 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100687 err = -1;
688 } else {
689 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
Damien Millerf29238e2013-10-17 11:48:52 +1100690 pflag || global_pflag, resume,
691 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100692 err = -1;
693 }
Darren Tuckera627d422013-06-02 07:31:17 +1000694 free(abs_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100695 abs_dst = NULL;
696 }
697
698out:
Darren Tuckera627d422013-06-02 07:31:17 +1000699 free(abs_src);
Damien Miller20e1fab2004-02-18 14:30:55 +1100700 globfree(&g);
701 return(err);
702}
703
704static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000705process_put(struct sftp_conn *conn, const char *src, const char *dst,
706 const char *pwd, int pflag, int rflag, int resume, int fflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100707{
708 char *tmp_dst = NULL;
709 char *abs_dst = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100710 char *tmp = NULL, *filename = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100711 glob_t g;
712 int err = 0;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100713 int i, dst_is_dir = 1;
Damien Milleraec5cf82008-02-10 22:26:24 +1100714 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100715
716 if (dst) {
717 tmp_dst = xstrdup(dst);
718 tmp_dst = make_absolute(tmp_dst, pwd);
719 }
720
721 memset(&g, 0, sizeof(g));
722 debug3("Looking up %s", src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100723 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100724 error("File \"%s\" not found.", src);
725 err = -1;
726 goto out;
727 }
728
Darren Tucker1b0dd172009-10-07 08:37:48 +1100729 /* If we aren't fetching to pwd then stash this status for later */
730 if (tmp_dst != NULL)
731 dst_is_dir = remote_is_dir(conn, tmp_dst);
732
Damien Miller20e1fab2004-02-18 14:30:55 +1100733 /* If multiple matches, dst may be directory or unspecified */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100734 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
735 error("Multiple paths match, but destination "
736 "\"%s\" is not a directory", tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100737 err = -1;
738 goto out;
739 }
740
Darren Tuckercdf547a2004-05-24 10:12:19 +1000741 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Milleraec5cf82008-02-10 22:26:24 +1100742 if (stat(g.gl_pathv[i], &sb) == -1) {
743 err = -1;
744 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
745 continue;
746 }
Damien Miller02e87802013-08-21 02:38:51 +1000747
Darren Tucker1b0dd172009-10-07 08:37:48 +1100748 tmp = xstrdup(g.gl_pathv[i]);
749 if ((filename = basename(tmp)) == NULL) {
750 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000751 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100752 err = -1;
753 goto out;
754 }
755
756 if (g.gl_matchc == 1 && tmp_dst) {
757 /* If directory specified, append filename */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100758 if (dst_is_dir)
759 abs_dst = path_append(tmp_dst, filename);
760 else
Damien Miller20e1fab2004-02-18 14:30:55 +1100761 abs_dst = xstrdup(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100762 } else if (tmp_dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100763 abs_dst = path_append(tmp_dst, filename);
764 } else {
765 abs_dst = make_absolute(xstrdup(filename), pwd);
766 }
Darren Tuckera627d422013-06-02 07:31:17 +1000767 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100768
Damien Millerd8accc02014-05-15 13:46:25 +1000769 resume |= global_aflag;
770 if (!quiet && resume)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000771 mprintf("Resuming upload of %s to %s\n",
772 g.gl_pathv[i], abs_dst);
Damien Millerd8accc02014-05-15 13:46:25 +1000773 else if (!quiet && !resume)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000774 mprintf("Uploading %s to %s\n",
775 g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100776 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
777 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
Damien Millerd8accc02014-05-15 13:46:25 +1000778 pflag || global_pflag, 1, resume,
Damien Millerf29238e2013-10-17 11:48:52 +1100779 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100780 err = -1;
781 } else {
782 if (do_upload(conn, g.gl_pathv[i], abs_dst,
Damien Millerd8accc02014-05-15 13:46:25 +1000783 pflag || global_pflag, resume,
Damien Millerf29238e2013-10-17 11:48:52 +1100784 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100785 err = -1;
786 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100787 }
788
789out:
Darren Tuckera627d422013-06-02 07:31:17 +1000790 free(abs_dst);
791 free(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100792 globfree(&g);
793 return(err);
794}
795
796static int
797sdirent_comp(const void *aa, const void *bb)
798{
799 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
800 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000801 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100802
Darren Tuckerb9123452004-06-22 13:06:45 +1000803#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000804 if (sort_flag & LS_NAME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000805 return (rmul * strcmp(a->filename, b->filename));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000806 else if (sort_flag & LS_TIME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000807 return (rmul * NCMP(a->a.mtime, b->a.mtime));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000808 else if (sort_flag & LS_SIZE_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000809 return (rmul * NCMP(a->a.size, b->a.size));
810
811 fatal("Unknown ls sort type");
Damien Miller20e1fab2004-02-18 14:30:55 +1100812}
813
814/* sftp ls.1 replacement for directories */
815static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000816do_ls_dir(struct sftp_conn *conn, const char *path,
817 const char *strip_path, int lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100818{
Damien Millereccb9de2005-06-17 12:59:34 +1000819 int n;
820 u_int c = 1, colspace = 0, columns = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100821 SFTP_DIRENT **d;
822
823 if ((n = do_readdir(conn, path, &d)) != 0)
824 return (n);
825
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000826 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000827 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100828 struct winsize ws;
829 char *tmp;
830
831 /* Count entries for sort and find longest filename */
Darren Tucker9a526452004-06-22 13:09:55 +1000832 for (n = 0; d[n] != NULL; n++) {
833 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +0000834 m = MAXIMUM(m, strlen(d[n]->filename));
Darren Tucker9a526452004-06-22 13:09:55 +1000835 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100836
837 /* Add any subpath that also needs to be counted */
838 tmp = path_strip(path, strip_path);
839 m += strlen(tmp);
Darren Tuckera627d422013-06-02 07:31:17 +1000840 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100841
842 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
843 width = ws.ws_col;
844
845 columns = width / (m + 2);
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +0000846 columns = MAXIMUM(columns, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +1100847 colspace = width / columns;
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +0000848 colspace = MINIMUM(colspace, width);
Damien Miller20e1fab2004-02-18 14:30:55 +1100849 }
850
Darren Tuckerb9123452004-06-22 13:06:45 +1000851 if (lflag & SORT_FLAGS) {
Damien Miller653b93b2005-11-05 15:15:23 +1100852 for (n = 0; d[n] != NULL; n++)
853 ; /* count entries */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000854 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
Darren Tuckerb9123452004-06-22 13:06:45 +1000855 qsort(d, n, sizeof(*d), sdirent_comp);
856 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100857
Darren Tuckercdf547a2004-05-24 10:12:19 +1000858 for (n = 0; d[n] != NULL && !interrupted; n++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100859 char *tmp, *fname;
860
Darren Tucker9a526452004-06-22 13:09:55 +1000861 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
862 continue;
863
Damien Miller20e1fab2004-02-18 14:30:55 +1100864 tmp = path_append(path, d[n]->filename);
865 fname = path_strip(tmp, strip_path);
Darren Tuckera627d422013-06-02 07:31:17 +1000866 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100867
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000868 if (lflag & LS_LONG_VIEW) {
Darren Tucker2901e2d2010-01-13 22:44:06 +1100869 if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000870 char *lname;
871 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100872
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000873 memset(&sb, 0, sizeof(sb));
874 attrib_to_stat(&d[n]->a, &sb);
Darren Tucker2901e2d2010-01-13 22:44:06 +1100875 lname = ls_file(fname, &sb, 1,
876 (lflag & LS_SI_UNITS));
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000877 mprintf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000878 free(lname);
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000879 } else
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000880 mprintf("%s\n", d[n]->longname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100881 } else {
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +0000882 mprintf("%-*s", colspace, fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100883 if (c >= columns) {
884 printf("\n");
885 c = 1;
886 } else
887 c++;
888 }
889
Darren Tuckera627d422013-06-02 07:31:17 +1000890 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100891 }
892
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000893 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100894 printf("\n");
895
896 free_sftp_dirents(d);
897 return (0);
898}
899
djm@openbsd.org72be5b22017-06-10 06:33:34 +0000900static int
901sglob_comp(const void *aa, const void *bb)
902{
903 u_int a = *(const u_int *)aa;
904 u_int b = *(const u_int *)bb;
905 const char *ap = sort_glob->gl_pathv[a];
906 const char *bp = sort_glob->gl_pathv[b];
907 const struct stat *as = sort_glob->gl_statv[a];
908 const struct stat *bs = sort_glob->gl_statv[b];
909 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
910
911#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
912 if (sort_flag & LS_NAME_SORT)
913 return (rmul * strcmp(ap, bp));
Damien Millerbcd14852017-06-10 23:41:25 +1000914 else if (sort_flag & LS_TIME_SORT) {
915#if defined(HAVE_STRUCT_STAT_ST_MTIM)
djm@openbsd.org72be5b22017-06-10 06:33:34 +0000916 return (rmul * timespeccmp(&as->st_mtim, &bs->st_mtim, <));
Damien Millerbcd14852017-06-10 23:41:25 +1000917#elif defined(HAVE_STRUCT_STAT_ST_MTIME)
918 return (rmul * NCMP(as->st_mtime, bs->st_mtime));
919#else
920 return rmul * 1;
921#endif
922 } else if (sort_flag & LS_SIZE_SORT)
djm@openbsd.org72be5b22017-06-10 06:33:34 +0000923 return (rmul * NCMP(as->st_size, bs->st_size));
924
925 fatal("Unknown ls sort type");
926}
927
Damien Miller20e1fab2004-02-18 14:30:55 +1100928/* sftp ls.1 replacement which handles path globs */
929static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +0000930do_globbed_ls(struct sftp_conn *conn, const char *path,
931 const char *strip_path, int lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100932{
Damien Millera6e121a2010-10-07 21:39:17 +1100933 char *fname, *lname;
Damien Miller68e2e562010-10-07 21:39:55 +1100934 glob_t g;
Damien Miller00707762014-07-09 13:07:06 +1000935 int err, r;
Damien Miller68e2e562010-10-07 21:39:55 +1100936 struct winsize ws;
djm@openbsd.org72be5b22017-06-10 06:33:34 +0000937 u_int i, j, nentries, *indices = NULL, c = 1;
938 u_int colspace = 0, columns = 1, m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100939
940 memset(&g, 0, sizeof(g));
941
Damien Miller00707762014-07-09 13:07:06 +1000942 if ((r = remote_glob(conn, path,
Damien Millerd7be70d2011-09-22 21:43:06 +1000943 GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
Damien Miller00707762014-07-09 13:07:06 +1000944 NULL, &g)) != 0 ||
Damien Millera6e121a2010-10-07 21:39:17 +1100945 (g.gl_pathc && !g.gl_matchc)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100946 if (g.gl_pathc)
947 globfree(&g);
Damien Miller00707762014-07-09 13:07:06 +1000948 if (r == GLOB_NOSPACE) {
949 error("Can't ls: Too many matches for \"%s\"", path);
950 } else {
951 error("Can't ls: \"%s\" not found", path);
952 }
Damien Millera6e121a2010-10-07 21:39:17 +1100953 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100954 }
955
Darren Tuckercdf547a2004-05-24 10:12:19 +1000956 if (interrupted)
957 goto out;
958
Damien Miller20e1fab2004-02-18 14:30:55 +1100959 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100960 * If the glob returns a single match and it is a directory,
961 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100962 */
Damien Millera6e121a2010-10-07 21:39:17 +1100963 if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
964 S_ISDIR(g.gl_statv[0]->st_mode)) {
965 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
966 globfree(&g);
967 return err;
Damien Miller20e1fab2004-02-18 14:30:55 +1100968 }
969
Damien Miller68e2e562010-10-07 21:39:55 +1100970 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
971 width = ws.ws_col;
Damien Miller20e1fab2004-02-18 14:30:55 +1100972
Damien Miller68e2e562010-10-07 21:39:55 +1100973 if (!(lflag & LS_SHORT_VIEW)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100974 /* Count entries for sort and find longest filename */
975 for (i = 0; g.gl_pathv[i]; i++)
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +0000976 m = MAXIMUM(m, strlen(g.gl_pathv[i]));
Damien Miller20e1fab2004-02-18 14:30:55 +1100977
Damien Miller20e1fab2004-02-18 14:30:55 +1100978 columns = width / (m + 2);
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +0000979 columns = MAXIMUM(columns, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +1100980 colspace = width / columns;
981 }
982
djm@openbsd.org72be5b22017-06-10 06:33:34 +0000983 /*
984 * Sorting: rather than mess with the contents of glob_t, prepare
985 * an array of indices into it and sort that. For the usual
986 * unsorted case, the indices are just the identity 1=1, 2=2, etc.
987 */
988 for (nentries = 0; g.gl_pathv[nentries] != NULL; nentries++)
989 ; /* count entries */
990 indices = calloc(nentries, sizeof(*indices));
991 for (i = 0; i < nentries; i++)
992 indices[i] = i;
993
994 if (lflag & SORT_FLAGS) {
995 sort_glob = &g;
996 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
997 qsort(indices, nentries, sizeof(*indices), sglob_comp);
998 sort_glob = NULL;
999 }
1000
1001 for (j = 0; j < nentries && !interrupted; j++) {
1002 i = indices[j];
Damien Miller20e1fab2004-02-18 14:30:55 +11001003 fname = path_strip(g.gl_pathv[i], strip_path);
Darren Tuckera4e9ffa2004-06-22 13:07:58 +10001004 if (lflag & LS_LONG_VIEW) {
Damien Millera6e121a2010-10-07 21:39:17 +11001005 if (g.gl_statv[i] == NULL) {
1006 error("no stat information for %s", fname);
1007 continue;
1008 }
1009 lname = ls_file(fname, g.gl_statv[i], 1,
1010 (lflag & LS_SI_UNITS));
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001011 mprintf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +10001012 free(lname);
Damien Miller20e1fab2004-02-18 14:30:55 +11001013 } else {
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001014 mprintf("%-*s", colspace, fname);
Damien Miller20e1fab2004-02-18 14:30:55 +11001015 if (c >= columns) {
1016 printf("\n");
1017 c = 1;
1018 } else
1019 c++;
1020 }
Darren Tuckera627d422013-06-02 07:31:17 +10001021 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +11001022 }
1023
Darren Tuckera4e9ffa2004-06-22 13:07:58 +10001024 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +11001025 printf("\n");
1026
Darren Tuckercdf547a2004-05-24 10:12:19 +10001027 out:
Damien Miller20e1fab2004-02-18 14:30:55 +11001028 if (g.gl_pathc)
1029 globfree(&g);
djm@openbsd.org72be5b22017-06-10 06:33:34 +00001030 free(indices);
Damien Miller20e1fab2004-02-18 14:30:55 +11001031
Damien Millera6e121a2010-10-07 21:39:17 +11001032 return 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001033}
1034
Damien Millerd671e5a2008-05-19 14:53:33 +10001035static int
djm@openbsd.orgda88a702016-07-22 03:47:36 +00001036do_df(struct sftp_conn *conn, const char *path, int hflag, int iflag)
Damien Millerd671e5a2008-05-19 14:53:33 +10001037{
Darren Tucker7b598892008-06-09 22:49:36 +10001038 struct sftp_statvfs st;
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +00001039 char s_used[FMT_SCALED_STRSIZE], s_avail[FMT_SCALED_STRSIZE];
1040 char s_root[FMT_SCALED_STRSIZE], s_total[FMT_SCALED_STRSIZE];
1041 char s_icapacity[16], s_dcapacity[16];
Damien Millerd671e5a2008-05-19 14:53:33 +10001042
1043 if (do_statvfs(conn, path, &st, 1) == -1)
1044 return -1;
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +00001045 if (st.f_files == 0)
1046 strlcpy(s_icapacity, "ERR", sizeof(s_icapacity));
1047 else {
1048 snprintf(s_icapacity, sizeof(s_icapacity), "%3llu%%",
1049 (unsigned long long)(100 * (st.f_files - st.f_ffree) /
1050 st.f_files));
1051 }
1052 if (st.f_blocks == 0)
1053 strlcpy(s_dcapacity, "ERR", sizeof(s_dcapacity));
1054 else {
1055 snprintf(s_dcapacity, sizeof(s_dcapacity), "%3llu%%",
1056 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
1057 st.f_blocks));
1058 }
Damien Millerd671e5a2008-05-19 14:53:33 +10001059 if (iflag) {
1060 printf(" Inodes Used Avail "
1061 "(root) %%Capacity\n");
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +00001062 printf("%11llu %11llu %11llu %11llu %s\n",
Damien Millerd671e5a2008-05-19 14:53:33 +10001063 (unsigned long long)st.f_files,
1064 (unsigned long long)(st.f_files - st.f_ffree),
1065 (unsigned long long)st.f_favail,
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +00001066 (unsigned long long)st.f_ffree, s_icapacity);
Damien Millerd671e5a2008-05-19 14:53:33 +10001067 } else if (hflag) {
1068 strlcpy(s_used, "error", sizeof(s_used));
1069 strlcpy(s_avail, "error", sizeof(s_avail));
1070 strlcpy(s_root, "error", sizeof(s_root));
1071 strlcpy(s_total, "error", sizeof(s_total));
1072 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
1073 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
1074 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
1075 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
1076 printf(" Size Used Avail (root) %%Capacity\n");
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +00001077 printf("%7sB %7sB %7sB %7sB %s\n",
1078 s_total, s_used, s_avail, s_root, s_dcapacity);
Damien Millerd671e5a2008-05-19 14:53:33 +10001079 } else {
1080 printf(" Size Used Avail "
1081 "(root) %%Capacity\n");
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +00001082 printf("%12llu %12llu %12llu %12llu %s\n",
Damien Millerd671e5a2008-05-19 14:53:33 +10001083 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
1084 (unsigned long long)(st.f_frsize *
1085 (st.f_blocks - st.f_bfree) / 1024),
1086 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
1087 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
djm@openbsd.org6d5a41b2017-02-15 01:46:47 +00001088 s_dcapacity);
Damien Millerd671e5a2008-05-19 14:53:33 +10001089 }
1090 return 0;
1091}
1092
Damien Miller1cbc2922007-10-26 14:27:45 +10001093/*
1094 * Undo escaping of glob sequences in place. Used to undo extra escaping
1095 * applied in makeargv() when the string is destined for a function that
1096 * does not glob it.
1097 */
1098static void
1099undo_glob_escape(char *s)
1100{
1101 size_t i, j;
1102
1103 for (i = j = 0;;) {
1104 if (s[i] == '\0') {
1105 s[j] = '\0';
1106 return;
1107 }
1108 if (s[i] != '\\') {
1109 s[j++] = s[i++];
1110 continue;
1111 }
1112 /* s[i] == '\\' */
1113 ++i;
1114 switch (s[i]) {
1115 case '?':
1116 case '[':
1117 case '*':
1118 case '\\':
1119 s[j++] = s[i++];
1120 break;
1121 case '\0':
1122 s[j++] = '\\';
1123 s[j] = '\0';
1124 return;
1125 default:
1126 s[j++] = '\\';
1127 s[j++] = s[i++];
1128 break;
1129 }
1130 }
1131}
1132
1133/*
1134 * Split a string into an argument vector using sh(1)-style quoting,
1135 * comment and escaping rules, but with some tweaks to handle glob(3)
1136 * wildcards.
Darren Tucker909d8582010-01-08 19:02:40 +11001137 * The "sloppy" flag allows for recovery from missing terminating quote, for
1138 * use in parsing incomplete commandlines during tab autocompletion.
1139 *
Damien Miller1cbc2922007-10-26 14:27:45 +10001140 * Returns NULL on error or a NULL-terminated array of arguments.
Darren Tucker909d8582010-01-08 19:02:40 +11001141 *
1142 * If "lastquote" is not NULL, the quoting character used for the last
1143 * argument is placed in *lastquote ("\0", "'" or "\"").
Damien Miller02e87802013-08-21 02:38:51 +10001144 *
Darren Tucker909d8582010-01-08 19:02:40 +11001145 * If "terminated" is not NULL, *terminated will be set to 1 when the
1146 * last argument's quote has been properly terminated or 0 otherwise.
1147 * This parameter is only of use if "sloppy" is set.
Damien Miller1cbc2922007-10-26 14:27:45 +10001148 */
1149#define MAXARGS 128
1150#define MAXARGLEN 8192
1151static char **
Darren Tucker909d8582010-01-08 19:02:40 +11001152makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
1153 u_int *terminated)
Damien Miller1cbc2922007-10-26 14:27:45 +10001154{
1155 int argc, quot;
1156 size_t i, j;
1157 static char argvs[MAXARGLEN];
1158 static char *argv[MAXARGS + 1];
1159 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
1160
1161 *argcp = argc = 0;
1162 if (strlen(arg) > sizeof(argvs) - 1) {
1163 args_too_longs:
1164 error("string too long");
1165 return NULL;
1166 }
Darren Tucker909d8582010-01-08 19:02:40 +11001167 if (terminated != NULL)
1168 *terminated = 1;
1169 if (lastquote != NULL)
1170 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001171 state = MA_START;
1172 i = j = 0;
1173 for (;;) {
Damien Miller07daed52012-10-31 08:57:55 +11001174 if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){
Darren Tucker063018d2012-10-05 10:43:58 +10001175 error("Too many arguments.");
1176 return NULL;
1177 }
Damien Millerfdb23062013-11-21 13:57:15 +11001178 if (isspace((unsigned char)arg[i])) {
Damien Miller1cbc2922007-10-26 14:27:45 +10001179 if (state == MA_UNQUOTED) {
1180 /* Terminate current argument */
1181 argvs[j++] = '\0';
1182 argc++;
1183 state = MA_START;
1184 } else if (state != MA_START)
1185 argvs[j++] = arg[i];
1186 } else if (arg[i] == '"' || arg[i] == '\'') {
1187 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1188 if (state == MA_START) {
1189 argv[argc] = argvs + j;
1190 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +11001191 if (lastquote != NULL)
1192 *lastquote = arg[i];
Damien Miller02e87802013-08-21 02:38:51 +10001193 } else if (state == MA_UNQUOTED)
Damien Miller1cbc2922007-10-26 14:27:45 +10001194 state = q;
1195 else if (state == q)
1196 state = MA_UNQUOTED;
1197 else
1198 argvs[j++] = arg[i];
1199 } else if (arg[i] == '\\') {
1200 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1201 quot = state == MA_SQUOTE ? '\'' : '"';
1202 /* Unescape quote we are in */
1203 /* XXX support \n and friends? */
1204 if (arg[i + 1] == quot) {
1205 i++;
1206 argvs[j++] = arg[i];
1207 } else if (arg[i + 1] == '?' ||
1208 arg[i + 1] == '[' || arg[i + 1] == '*') {
1209 /*
1210 * Special case for sftp: append
1211 * double-escaped glob sequence -
1212 * glob will undo one level of
1213 * escaping. NB. string can grow here.
1214 */
1215 if (j >= sizeof(argvs) - 5)
1216 goto args_too_longs;
1217 argvs[j++] = '\\';
1218 argvs[j++] = arg[i++];
1219 argvs[j++] = '\\';
1220 argvs[j++] = arg[i];
1221 } else {
1222 argvs[j++] = arg[i++];
1223 argvs[j++] = arg[i];
1224 }
1225 } else {
1226 if (state == MA_START) {
1227 argv[argc] = argvs + j;
1228 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001229 if (lastquote != NULL)
1230 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001231 }
1232 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1233 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1234 /*
1235 * Special case for sftp: append
1236 * escaped glob sequence -
1237 * glob will undo one level of
1238 * escaping.
1239 */
1240 argvs[j++] = arg[i++];
1241 argvs[j++] = arg[i];
1242 } else {
1243 /* Unescape everything */
1244 /* XXX support \n and friends? */
1245 i++;
1246 argvs[j++] = arg[i];
1247 }
1248 }
1249 } else if (arg[i] == '#') {
1250 if (state == MA_SQUOTE || state == MA_DQUOTE)
1251 argvs[j++] = arg[i];
1252 else
1253 goto string_done;
1254 } else if (arg[i] == '\0') {
1255 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001256 if (sloppy) {
1257 state = MA_UNQUOTED;
1258 if (terminated != NULL)
1259 *terminated = 0;
1260 goto string_done;
1261 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001262 error("Unterminated quoted argument");
1263 return NULL;
1264 }
1265 string_done:
1266 if (state == MA_UNQUOTED) {
1267 argvs[j++] = '\0';
1268 argc++;
1269 }
1270 break;
1271 } else {
1272 if (state == MA_START) {
1273 argv[argc] = argvs + j;
1274 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001275 if (lastquote != NULL)
1276 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001277 }
1278 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1279 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1280 /*
1281 * Special case for sftp: escape quoted
1282 * glob(3) wildcards. NB. string can grow
1283 * here.
1284 */
1285 if (j >= sizeof(argvs) - 3)
1286 goto args_too_longs;
1287 argvs[j++] = '\\';
1288 argvs[j++] = arg[i];
1289 } else
1290 argvs[j++] = arg[i];
1291 }
1292 i++;
1293 }
1294 *argcp = argc;
1295 return argv;
1296}
1297
Damien Miller20e1fab2004-02-18 14:30:55 +11001298static int
djm@openbsd.org5c1a6352018-11-16 02:30:20 +00001299parse_args(const char **cpp, int *ignore_errors, int *disable_echo, int *aflag,
djm@openbsd.org34a01b22016-04-08 08:19:17 +00001300 int *fflag, int *hflag, int *iflag, int *lflag, int *pflag,
Damien Millerd8accc02014-05-15 13:46:25 +10001301 int *rflag, int *sflag,
Damien Millerf29238e2013-10-17 11:48:52 +11001302 unsigned long *n_arg, char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001303{
1304 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001305 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001306 int base = 0;
1307 long l;
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00001308 int path1_mandatory = 0, i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001309
1310 /* Skip leading whitespace */
1311 cp = cp + strspn(cp, WHITESPACE);
1312
djm@openbsd.org5c1a6352018-11-16 02:30:20 +00001313 /*
1314 * Check for leading '-' (disable error processing) and '@' (suppress
1315 * command echo)
1316 */
Damien Millerf29238e2013-10-17 11:48:52 +11001317 *ignore_errors = 0;
djm@openbsd.org5c1a6352018-11-16 02:30:20 +00001318 *disable_echo = 0;
1319 for (;*cp != '\0'; cp++) {
1320 if (*cp == '-') {
1321 *ignore_errors = 1;
1322 } else if (*cp == '@') {
1323 *disable_echo = 1;
1324 } else {
1325 /* all other characters terminate prefix processing */
1326 break;
1327 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001328 }
djm@openbsd.org5c1a6352018-11-16 02:30:20 +00001329 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001330
Darren Tucker70cc0922010-01-09 22:28:03 +11001331 /* Ignore blank lines and lines which begin with comment '#' char */
1332 if (*cp == '\0' || *cp == '#')
1333 return (0);
1334
Darren Tucker909d8582010-01-08 19:02:40 +11001335 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001336 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001337
Damien Miller1cbc2922007-10-26 14:27:45 +10001338 /* Figure out which command we have */
1339 for (i = 0; cmds[i].c != NULL; i++) {
Damien Millerd6d9fa02013-02-12 11:02:46 +11001340 if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001341 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001342 }
1343 cmdnum = cmds[i].n;
1344 cmd = cmds[i].c;
1345
1346 /* Special case */
1347 if (*cp == '!') {
1348 cp++;
1349 cmdnum = I_SHELL;
1350 } else if (cmdnum == -1) {
1351 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001352 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001353 }
1354
1355 /* Get arguments and parse flags */
Damien Millerf29238e2013-10-17 11:48:52 +11001356 *aflag = *fflag = *hflag = *iflag = *lflag = *pflag = 0;
1357 *rflag = *sflag = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001358 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001359 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001360 switch (cmdnum) {
1361 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001362 case I_REGET:
Damien Millerd8accc02014-05-15 13:46:25 +10001363 case I_REPUT:
Damien Miller20e1fab2004-02-18 14:30:55 +11001364 case I_PUT:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001365 if ((optidx = parse_getput_flags(cmd, argv, argc,
Damien Millerf29238e2013-10-17 11:48:52 +11001366 aflag, fflag, pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001367 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001368 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001369 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001370 error("You must specify at least one path after a "
1371 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001372 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001373 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001374 *path1 = xstrdup(argv[optidx]);
1375 /* Get second pathname (optional) */
1376 if (argc - optidx > 1) {
1377 *path2 = xstrdup(argv[optidx + 1]);
1378 /* Destination is not globbed */
1379 undo_glob_escape(*path2);
1380 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001381 break;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001382 case I_LINK:
1383 if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1384 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001385 goto parse_two_paths;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001386 case I_RENAME:
Damien Millerc7dba122013-08-21 02:41:15 +10001387 if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1)
1388 return -1;
1389 goto parse_two_paths;
1390 case I_SYMLINK:
Damien Miller036d3072013-08-21 02:41:46 +10001391 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1392 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001393 parse_two_paths:
Damien Miller1cbc2922007-10-26 14:27:45 +10001394 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001395 error("You must specify two paths after a %s "
1396 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001397 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001398 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001399 *path1 = xstrdup(argv[optidx]);
1400 *path2 = xstrdup(argv[optidx + 1]);
1401 /* Paths are not globbed */
1402 undo_glob_escape(*path1);
1403 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001404 break;
1405 case I_RM:
1406 case I_MKDIR:
1407 case I_RMDIR:
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00001408 case I_LMKDIR:
1409 path1_mandatory = 1;
1410 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001411 case I_CHDIR:
1412 case I_LCHDIR:
Damien Miller036d3072013-08-21 02:41:46 +10001413 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1414 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001415 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001416 if (argc - optidx < 1) {
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00001417 if (!path1_mandatory)
1418 break; /* return a NULL path1 */
Damien Miller20e1fab2004-02-18 14:30:55 +11001419 error("You must specify a path after a %s command.",
1420 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001421 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001422 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001423 *path1 = xstrdup(argv[optidx]);
1424 /* Only "rm" globs */
1425 if (cmdnum != I_RM)
1426 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001427 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001428 case I_DF:
1429 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1430 iflag)) == -1)
1431 return -1;
1432 /* Default to current directory if no path specified */
1433 if (argc - optidx < 1)
1434 *path1 = NULL;
1435 else {
1436 *path1 = xstrdup(argv[optidx]);
1437 undo_glob_escape(*path1);
1438 }
1439 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001440 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001441 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001442 return(-1);
1443 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001444 if (argc - optidx > 0)
1445 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001446 break;
1447 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001448 /* Skip ls command and following whitespace */
1449 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001450 case I_SHELL:
1451 /* Uses the rest of the line */
1452 break;
1453 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001454 case I_CHMOD:
1455 base = 8;
dtucker@openbsd.orgde37ca92018-09-07 04:26:56 +00001456 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001457 case I_CHOWN:
1458 case I_CHGRP:
Damien Miller036d3072013-08-21 02:41:46 +10001459 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1460 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001461 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001462 if (argc - optidx < 1)
1463 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001464 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001465 l = strtol(argv[optidx], &cp2, base);
1466 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1467 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1468 l < 0) {
1469 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001470 error("You must supply a numeric argument "
1471 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001472 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001473 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001474 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001475 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001476 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001477 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001478 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001479 error("You must specify a path after a %s command.",
1480 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001481 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001482 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001483 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001484 break;
1485 case I_QUIT:
1486 case I_PWD:
1487 case I_LPWD:
1488 case I_HELP:
1489 case I_VERSION:
1490 case I_PROGRESS:
Damien Miller036d3072013-08-21 02:41:46 +10001491 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1492 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001493 break;
1494 default:
1495 fatal("Command not implemented");
1496 }
1497
1498 *cpp = cp;
1499 return(cmdnum);
1500}
1501
1502static int
1503parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
djm@openbsd.org5c1a6352018-11-16 02:30:20 +00001504 const char *startdir, int err_abort, int echo_command)
Damien Miller20e1fab2004-02-18 14:30:55 +11001505{
djm@openbsd.org5c1a6352018-11-16 02:30:20 +00001506 const char *ocmd = cmd;
Damien Miller20e1fab2004-02-18 14:30:55 +11001507 char *path1, *path2, *tmp;
djm@openbsd.org5c1a6352018-11-16 02:30:20 +00001508 int ignore_errors = 0, disable_echo = 1;
1509 int aflag = 0, fflag = 0, hflag = 0, iflag = 0;
Damien Millerf29238e2013-10-17 11:48:52 +11001510 int lflag = 0, pflag = 0, rflag = 0, sflag = 0;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001511 int cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001512 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001513 Attrib a, *aa;
deraadt@openbsd.org087266e2015-01-20 23:14:00 +00001514 char path_buf[PATH_MAX];
Damien Miller20e1fab2004-02-18 14:30:55 +11001515 int err = 0;
1516 glob_t g;
1517
1518 path1 = path2 = NULL;
djm@openbsd.org5c1a6352018-11-16 02:30:20 +00001519 cmdnum = parse_args(&cmd, &ignore_errors, &disable_echo, &aflag, &fflag,
1520 &hflag, &iflag, &lflag, &pflag, &rflag, &sflag, &n_arg,
1521 &path1, &path2);
Damien Millerf29238e2013-10-17 11:48:52 +11001522 if (ignore_errors != 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001523 err_abort = 0;
1524
djm@openbsd.org5c1a6352018-11-16 02:30:20 +00001525 if (echo_command && !disable_echo)
1526 mprintf("sftp> %s\n", ocmd);
1527
Damien Miller20e1fab2004-02-18 14:30:55 +11001528 memset(&g, 0, sizeof(g));
1529
1530 /* Perform command */
1531 switch (cmdnum) {
1532 case 0:
1533 /* Blank line */
1534 break;
1535 case -1:
1536 /* Unrecognized command */
1537 err = -1;
1538 break;
Damien Miller0d032412013-07-25 11:56:52 +10001539 case I_REGET:
1540 aflag = 1;
1541 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001542 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001543 err = process_get(conn, path1, path2, *pwd, pflag,
Damien Millerf29238e2013-10-17 11:48:52 +11001544 rflag, aflag, fflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001545 break;
Damien Millerd8accc02014-05-15 13:46:25 +10001546 case I_REPUT:
1547 aflag = 1;
1548 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001549 case I_PUT:
Damien Millerf29238e2013-10-17 11:48:52 +11001550 err = process_put(conn, path1, path2, *pwd, pflag,
Damien Millerd8accc02014-05-15 13:46:25 +10001551 rflag, aflag, fflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001552 break;
1553 case I_RENAME:
1554 path1 = make_absolute(path1, *pwd);
1555 path2 = make_absolute(path2, *pwd);
Damien Millerc7dba122013-08-21 02:41:15 +10001556 err = do_rename(conn, path1, path2, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001557 break;
1558 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001559 sflag = 1;
dtucker@openbsd.orgde37ca92018-09-07 04:26:56 +00001560 /* FALLTHROUGH */
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001561 case I_LINK:
Damien Miller034f27a2013-08-21 02:40:44 +10001562 if (!sflag)
1563 path1 = make_absolute(path1, *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001564 path2 = make_absolute(path2, *pwd);
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001565 err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001566 break;
1567 case I_RM:
1568 path1 = make_absolute(path1, *pwd);
1569 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001570 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001571 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001572 mprintf("Removing %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001573 err = do_rm(conn, g.gl_pathv[i]);
1574 if (err != 0 && err_abort)
1575 break;
1576 }
1577 break;
1578 case I_MKDIR:
1579 path1 = make_absolute(path1, *pwd);
1580 attrib_clear(&a);
1581 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1582 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001583 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001584 break;
1585 case I_RMDIR:
1586 path1 = make_absolute(path1, *pwd);
1587 err = do_rmdir(conn, path1);
1588 break;
1589 case I_CHDIR:
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00001590 if (path1 == NULL || *path1 == '\0')
1591 path1 = xstrdup(startdir);
Damien Miller20e1fab2004-02-18 14:30:55 +11001592 path1 = make_absolute(path1, *pwd);
1593 if ((tmp = do_realpath(conn, path1)) == NULL) {
1594 err = 1;
1595 break;
1596 }
1597 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
Darren Tuckera627d422013-06-02 07:31:17 +10001598 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001599 err = 1;
1600 break;
1601 }
1602 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1603 error("Can't change directory: Can't check target");
Darren Tuckera627d422013-06-02 07:31:17 +10001604 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001605 err = 1;
1606 break;
1607 }
1608 if (!S_ISDIR(aa->perm)) {
1609 error("Can't change directory: \"%s\" is not "
1610 "a directory", tmp);
Darren Tuckera627d422013-06-02 07:31:17 +10001611 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001612 err = 1;
1613 break;
1614 }
Darren Tuckera627d422013-06-02 07:31:17 +10001615 free(*pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001616 *pwd = tmp;
1617 break;
1618 case I_LS:
1619 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001620 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001621 break;
1622 }
1623
1624 /* Strip pwd off beginning of non-absolute paths */
1625 tmp = NULL;
djm@openbsd.org2a358622018-11-16 03:26:01 +00001626 if (!path_absolute(path1))
Damien Miller20e1fab2004-02-18 14:30:55 +11001627 tmp = *pwd;
1628
1629 path1 = make_absolute(path1, *pwd);
1630 err = do_globbed_ls(conn, path1, tmp, lflag);
1631 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001632 case I_DF:
1633 /* Default to current directory if no path specified */
1634 if (path1 == NULL)
1635 path1 = xstrdup(*pwd);
1636 path1 = make_absolute(path1, *pwd);
1637 err = do_df(conn, path1, hflag, iflag);
1638 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001639 case I_LCHDIR:
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00001640 if (path1 == NULL || *path1 == '\0')
1641 path1 = xstrdup("~");
deraadt@openbsd.org40ba4c92014-08-20 01:28:55 +00001642 tmp = tilde_expand_filename(path1, getuid());
djm@openbsd.org7ff880e2014-08-19 23:57:18 +00001643 free(path1);
1644 path1 = tmp;
Damien Miller20e1fab2004-02-18 14:30:55 +11001645 if (chdir(path1) == -1) {
1646 error("Couldn't change local directory to "
1647 "\"%s\": %s", path1, strerror(errno));
1648 err = 1;
1649 }
1650 break;
1651 case I_LMKDIR:
1652 if (mkdir(path1, 0777) == -1) {
1653 error("Couldn't create local directory "
1654 "\"%s\": %s", path1, strerror(errno));
1655 err = 1;
1656 }
1657 break;
1658 case I_LLS:
1659 local_do_ls(cmd);
1660 break;
1661 case I_SHELL:
1662 local_do_shell(cmd);
1663 break;
1664 case I_LUMASK:
1665 umask(n_arg);
1666 printf("Local umask: %03lo\n", n_arg);
1667 break;
1668 case I_CHMOD:
1669 path1 = make_absolute(path1, *pwd);
1670 attrib_clear(&a);
1671 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1672 a.perm = n_arg;
1673 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001674 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001675 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001676 mprintf("Changing mode on %s\n",
1677 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001678 err = do_setstat(conn, g.gl_pathv[i], &a);
1679 if (err != 0 && err_abort)
1680 break;
1681 }
1682 break;
1683 case I_CHOWN:
1684 case I_CHGRP:
1685 path1 = make_absolute(path1, *pwd);
1686 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001687 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001688 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001689 if (err_abort) {
1690 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001691 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001692 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001693 continue;
1694 }
1695 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1696 error("Can't get current ownership of "
1697 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001698 if (err_abort) {
1699 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001700 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001701 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001702 continue;
1703 }
1704 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1705 if (cmdnum == I_CHOWN) {
Damien Miller9303e652013-04-23 15:22:40 +10001706 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001707 mprintf("Changing owner on %s\n",
Damien Miller9303e652013-04-23 15:22:40 +10001708 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001709 aa->uid = n_arg;
1710 } else {
Damien Miller9303e652013-04-23 15:22:40 +10001711 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001712 mprintf("Changing group on %s\n",
Damien Miller9303e652013-04-23 15:22:40 +10001713 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001714 aa->gid = n_arg;
1715 }
1716 err = do_setstat(conn, g.gl_pathv[i], aa);
1717 if (err != 0 && err_abort)
1718 break;
1719 }
1720 break;
1721 case I_PWD:
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001722 mprintf("Remote working directory: %s\n", *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001723 break;
1724 case I_LPWD:
1725 if (!getcwd(path_buf, sizeof(path_buf))) {
1726 error("Couldn't get local cwd: %s", strerror(errno));
1727 err = -1;
1728 break;
1729 }
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001730 mprintf("Local working directory: %s\n", path_buf);
Damien Miller20e1fab2004-02-18 14:30:55 +11001731 break;
1732 case I_QUIT:
1733 /* Processed below */
1734 break;
1735 case I_HELP:
1736 help();
1737 break;
1738 case I_VERSION:
1739 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1740 break;
1741 case I_PROGRESS:
1742 showprogress = !showprogress;
1743 if (showprogress)
1744 printf("Progress meter enabled\n");
1745 else
1746 printf("Progress meter disabled\n");
1747 break;
1748 default:
1749 fatal("%d is not implemented", cmdnum);
1750 }
1751
1752 if (g.gl_pathc)
1753 globfree(&g);
Darren Tuckera627d422013-06-02 07:31:17 +10001754 free(path1);
1755 free(path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001756
1757 /* If an unignored error occurs in batch mode we should abort. */
1758 if (err_abort && err != 0)
1759 return (-1);
1760 else if (cmdnum == I_QUIT)
1761 return (1);
1762
1763 return (0);
1764}
1765
Darren Tucker2d963d82004-11-07 20:04:10 +11001766#ifdef USE_LIBEDIT
1767static char *
1768prompt(EditLine *el)
1769{
1770 return ("sftp> ");
1771}
Darren Tucker2d963d82004-11-07 20:04:10 +11001772
Darren Tucker909d8582010-01-08 19:02:40 +11001773/* Display entries in 'list' after skipping the first 'len' chars */
1774static void
1775complete_display(char **list, u_int len)
1776{
1777 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1778 struct winsize ws;
1779 char *tmp;
1780
1781 /* Count entries for sort and find longest */
Damien Miller02e87802013-08-21 02:38:51 +10001782 for (y = 0; list[y]; y++)
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +00001783 m = MAXIMUM(m, strlen(list[y]));
Darren Tucker909d8582010-01-08 19:02:40 +11001784
1785 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1786 width = ws.ws_col;
1787
1788 m = m > len ? m - len : 0;
1789 columns = width / (m + 2);
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +00001790 columns = MAXIMUM(columns, 1);
Darren Tucker909d8582010-01-08 19:02:40 +11001791 colspace = width / columns;
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +00001792 colspace = MINIMUM(colspace, width);
Darren Tucker909d8582010-01-08 19:02:40 +11001793
1794 printf("\n");
1795 m = 1;
1796 for (y = 0; list[y]; y++) {
1797 llen = strlen(list[y]);
1798 tmp = llen > len ? list[y] + len : "";
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001799 mprintf("%-*s", colspace, tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001800 if (m >= columns) {
1801 printf("\n");
1802 m = 1;
1803 } else
1804 m++;
1805 }
1806 printf("\n");
1807}
1808
1809/*
1810 * Given a "list" of words that begin with a common prefix of "word",
1811 * attempt to find an autocompletion to extends "word" by the next
1812 * characters common to all entries in "list".
1813 */
1814static char *
1815complete_ambiguous(const char *word, char **list, size_t count)
1816{
1817 if (word == NULL)
1818 return NULL;
1819
1820 if (count > 0) {
1821 u_int y, matchlen = strlen(list[0]);
1822
1823 /* Find length of common stem */
1824 for (y = 1; list[y]; y++) {
1825 u_int x;
1826
Damien Miller02e87802013-08-21 02:38:51 +10001827 for (x = 0; x < matchlen; x++)
1828 if (list[0][x] != list[y][x])
Darren Tucker909d8582010-01-08 19:02:40 +11001829 break;
1830
1831 matchlen = x;
1832 }
1833
1834 if (matchlen > strlen(word)) {
1835 char *tmp = xstrdup(list[0]);
1836
Darren Tucker340d1682010-01-09 08:54:31 +11001837 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001838 return tmp;
1839 }
Damien Miller02e87802013-08-21 02:38:51 +10001840 }
Darren Tucker909d8582010-01-08 19:02:40 +11001841
1842 return xstrdup(word);
1843}
1844
1845/* Autocomplete a sftp command */
1846static int
1847complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1848 int terminated)
1849{
1850 u_int y, count = 0, cmdlen, tmplen;
1851 char *tmp, **list, argterm[3];
1852 const LineInfo *lf;
1853
1854 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1855
1856 /* No command specified: display all available commands */
1857 if (cmd == NULL) {
1858 for (y = 0; cmds[y].c; y++)
1859 list[count++] = xstrdup(cmds[y].c);
Damien Miller02e87802013-08-21 02:38:51 +10001860
Darren Tucker909d8582010-01-08 19:02:40 +11001861 list[count] = NULL;
1862 complete_display(list, 0);
1863
Damien Miller02e87802013-08-21 02:38:51 +10001864 for (y = 0; list[y] != NULL; y++)
1865 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001866 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001867 return count;
1868 }
1869
1870 /* Prepare subset of commands that start with "cmd" */
1871 cmdlen = strlen(cmd);
1872 for (y = 0; cmds[y].c; y++) {
Damien Miller02e87802013-08-21 02:38:51 +10001873 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
Darren Tucker909d8582010-01-08 19:02:40 +11001874 list[count++] = xstrdup(cmds[y].c);
1875 }
1876 list[count] = NULL;
1877
Damien Miller47d81152011-11-25 13:53:48 +11001878 if (count == 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10001879 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001880 return 0;
Damien Miller47d81152011-11-25 13:53:48 +11001881 }
Darren Tucker909d8582010-01-08 19:02:40 +11001882
djm@openbsd.org001aa552018-04-10 00:10:49 +00001883 /* Complete ambiguous command */
Darren Tucker909d8582010-01-08 19:02:40 +11001884 tmp = complete_ambiguous(cmd, list, count);
1885 if (count > 1)
1886 complete_display(list, 0);
1887
Damien Miller02e87802013-08-21 02:38:51 +10001888 for (y = 0; list[y]; y++)
1889 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001890 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001891
1892 if (tmp != NULL) {
1893 tmplen = strlen(tmp);
1894 cmdlen = strlen(cmd);
1895 /* If cmd may be extended then do so */
1896 if (tmplen > cmdlen)
1897 if (el_insertstr(el, tmp + cmdlen) == -1)
1898 fatal("el_insertstr failed.");
1899 lf = el_line(el);
1900 /* Terminate argument cleanly */
1901 if (count == 1) {
1902 y = 0;
1903 if (!terminated)
1904 argterm[y++] = quote;
1905 if (lastarg || *(lf->cursor) != ' ')
1906 argterm[y++] = ' ';
1907 argterm[y] = '\0';
1908 if (y > 0 && el_insertstr(el, argterm) == -1)
1909 fatal("el_insertstr failed.");
1910 }
Darren Tuckera627d422013-06-02 07:31:17 +10001911 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001912 }
1913
1914 return count;
1915}
1916
1917/*
1918 * Determine whether a particular sftp command's arguments (if any)
1919 * represent local or remote files.
1920 */
1921static int
1922complete_is_remote(char *cmd) {
1923 int i;
1924
1925 if (cmd == NULL)
1926 return -1;
1927
1928 for (i = 0; cmds[i].c; i++) {
Damien Miller02e87802013-08-21 02:38:51 +10001929 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
Darren Tucker909d8582010-01-08 19:02:40 +11001930 return cmds[i].t;
1931 }
1932
1933 return -1;
1934}
1935
1936/* Autocomplete a filename "file" */
1937static int
1938complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1939 char *file, int remote, int lastarg, char quote, int terminated)
1940{
1941 glob_t g;
Darren Tuckerea647212013-06-06 08:19:09 +10001942 char *tmp, *tmp2, ins[8];
Darren Tucker17146d32012-10-05 10:46:16 +10001943 u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
Darren Tuckerea647212013-06-06 08:19:09 +10001944 int clen;
Darren Tucker909d8582010-01-08 19:02:40 +11001945 const LineInfo *lf;
Damien Miller02e87802013-08-21 02:38:51 +10001946
Darren Tucker909d8582010-01-08 19:02:40 +11001947 /* Glob from "file" location */
1948 if (file == NULL)
1949 tmp = xstrdup("*");
1950 else
1951 xasprintf(&tmp, "%s*", file);
1952
Darren Tucker191fcc62012-10-05 10:45:01 +10001953 /* Check if the path is absolute. */
djm@openbsd.org2a358622018-11-16 03:26:01 +00001954 isabs = path_absolute(tmp);
Darren Tucker191fcc62012-10-05 10:45:01 +10001955
Darren Tucker909d8582010-01-08 19:02:40 +11001956 memset(&g, 0, sizeof(g));
1957 if (remote != LOCAL) {
1958 tmp = make_absolute(tmp, remote_path);
1959 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001960 } else
Darren Tucker909d8582010-01-08 19:02:40 +11001961 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001962
Darren Tucker909d8582010-01-08 19:02:40 +11001963 /* Determine length of pwd so we can trim completion display */
1964 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1965 /* Terminate counting on first unescaped glob metacharacter */
1966 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1967 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1968 hadglob = 1;
1969 break;
1970 }
1971 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1972 tmplen++;
1973 if (tmp[tmplen] == '/')
1974 pwdlen = tmplen + 1; /* track last seen '/' */
1975 }
Darren Tuckera627d422013-06-02 07:31:17 +10001976 free(tmp);
Damien Millerd7fd8be2014-05-15 14:24:59 +10001977 tmp = NULL;
Darren Tucker909d8582010-01-08 19:02:40 +11001978
Damien Miller02e87802013-08-21 02:38:51 +10001979 if (g.gl_matchc == 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001980 goto out;
1981
1982 if (g.gl_matchc > 1)
1983 complete_display(g.gl_pathv, pwdlen);
1984
Darren Tucker909d8582010-01-08 19:02:40 +11001985 /* Don't try to extend globs */
1986 if (file == NULL || hadglob)
1987 goto out;
1988
1989 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
Darren Tucker191fcc62012-10-05 10:45:01 +10001990 tmp = path_strip(tmp2, isabs ? NULL : remote_path);
Darren Tuckera627d422013-06-02 07:31:17 +10001991 free(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001992
1993 if (tmp == NULL)
1994 goto out;
1995
1996 tmplen = strlen(tmp);
1997 filelen = strlen(file);
1998
Darren Tucker17146d32012-10-05 10:46:16 +10001999 /* Count the number of escaped characters in the input string. */
2000 cesc = isesc = 0;
2001 for (i = 0; i < filelen; i++) {
2002 if (!isesc && file[i] == '\\' && i + 1 < filelen){
2003 isesc = 1;
2004 cesc++;
2005 } else
2006 isesc = 0;
2007 }
2008
2009 if (tmplen > (filelen - cesc)) {
2010 tmp2 = tmp + filelen - cesc;
Damien Miller02e87802013-08-21 02:38:51 +10002011 len = strlen(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11002012 /* quote argument on way out */
Darren Tuckerea647212013-06-06 08:19:09 +10002013 for (i = 0; i < len; i += clen) {
2014 if ((clen = mblen(tmp2 + i, len - i)) < 0 ||
2015 (size_t)clen > sizeof(ins) - 2)
2016 fatal("invalid multibyte character");
Darren Tucker909d8582010-01-08 19:02:40 +11002017 ins[0] = '\\';
Darren Tuckerea647212013-06-06 08:19:09 +10002018 memcpy(ins + 1, tmp2 + i, clen);
2019 ins[clen + 1] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11002020 switch (tmp2[i]) {
2021 case '\'':
2022 case '"':
2023 case '\\':
2024 case '\t':
Darren Tuckerd78739a2010-10-24 10:56:32 +11002025 case '[':
Darren Tucker909d8582010-01-08 19:02:40 +11002026 case ' ':
Darren Tucker17146d32012-10-05 10:46:16 +10002027 case '#':
2028 case '*':
Darren Tucker909d8582010-01-08 19:02:40 +11002029 if (quote == '\0' || tmp2[i] == quote) {
2030 if (el_insertstr(el, ins) == -1)
2031 fatal("el_insertstr "
2032 "failed.");
2033 break;
2034 }
2035 /* FALLTHROUGH */
2036 default:
2037 if (el_insertstr(el, ins + 1) == -1)
2038 fatal("el_insertstr failed.");
2039 break;
2040 }
2041 }
2042 }
2043
2044 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11002045 if (g.gl_matchc == 1) {
2046 i = 0;
Damien Miller38094812014-05-15 14:25:18 +10002047 if (!terminated && quote != '\0')
Darren Tucker909d8582010-01-08 19:02:40 +11002048 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11002049 if (*(lf->cursor - 1) != '/' &&
2050 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11002051 ins[i++] = ' ';
2052 ins[i] = '\0';
2053 if (i > 0 && el_insertstr(el, ins) == -1)
2054 fatal("el_insertstr failed.");
2055 }
Darren Tuckera627d422013-06-02 07:31:17 +10002056 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11002057
2058 out:
2059 globfree(&g);
2060 return g.gl_matchc;
2061}
2062
2063/* tab-completion hook function, called via libedit */
2064static unsigned char
2065complete(EditLine *el, int ch)
2066{
Damien Miller02e87802013-08-21 02:38:51 +10002067 char **argv, *line, quote;
Damien Miller746d1a62013-07-18 16:13:02 +10002068 int argc, carg;
2069 u_int cursor, len, terminated, ret = CC_ERROR;
Darren Tucker909d8582010-01-08 19:02:40 +11002070 const LineInfo *lf;
2071 struct complete_ctx *complete_ctx;
2072
2073 lf = el_line(el);
2074 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
2075 fatal("%s: el_get failed", __func__);
2076
2077 /* Figure out which argument the cursor points to */
2078 cursor = lf->cursor - lf->buffer;
deraadt@openbsd.orgce445b02015-08-20 22:32:42 +00002079 line = xmalloc(cursor + 1);
Darren Tucker909d8582010-01-08 19:02:40 +11002080 memcpy(line, lf->buffer, cursor);
2081 line[cursor] = '\0';
2082 argv = makeargv(line, &carg, 1, &quote, &terminated);
Darren Tuckera627d422013-06-02 07:31:17 +10002083 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11002084
2085 /* Get all the arguments on the line */
2086 len = lf->lastchar - lf->buffer;
deraadt@openbsd.orgce445b02015-08-20 22:32:42 +00002087 line = xmalloc(len + 1);
Darren Tucker909d8582010-01-08 19:02:40 +11002088 memcpy(line, lf->buffer, len);
2089 line[len] = '\0';
2090 argv = makeargv(line, &argc, 1, NULL, NULL);
2091
2092 /* Ensure cursor is at EOL or a argument boundary */
2093 if (line[cursor] != ' ' && line[cursor] != '\0' &&
2094 line[cursor] != '\n') {
Darren Tuckera627d422013-06-02 07:31:17 +10002095 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11002096 return ret;
2097 }
2098
2099 if (carg == 0) {
2100 /* Show all available commands */
2101 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
2102 ret = CC_REDISPLAY;
2103 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
2104 /* Handle the command parsing */
2105 if (complete_cmd_parse(el, argv[0], argc == carg,
Damien Miller02e87802013-08-21 02:38:51 +10002106 quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11002107 ret = CC_REDISPLAY;
2108 } else if (carg >= 1) {
2109 /* Handle file parsing */
2110 int remote = complete_is_remote(argv[0]);
2111 char *filematch = NULL;
2112
2113 if (carg > 1 && line[cursor-1] != ' ')
2114 filematch = argv[carg - 1];
2115
2116 if (remote != 0 &&
2117 complete_match(el, complete_ctx->conn,
2118 *complete_ctx->remote_pathp, filematch,
Damien Miller02e87802013-08-21 02:38:51 +10002119 remote, carg == argc, quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11002120 ret = CC_REDISPLAY;
2121 }
2122
Damien Miller02e87802013-08-21 02:38:51 +10002123 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11002124 return ret;
2125}
Darren Tuckere67f7db2010-01-08 19:50:02 +11002126#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11002127
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002128static int
Darren Tucker21063192010-01-08 17:10:36 +11002129interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11002130{
Darren Tucker909d8582010-01-08 19:02:40 +11002131 char *remote_path;
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002132 char *dir = NULL, *startdir = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +11002133 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10002134 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11002135 EditLine *el = NULL;
2136#ifdef USE_LIBEDIT
2137 History *hl = NULL;
2138 HistEvent hev;
2139 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11002140 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11002141
2142 if (!batchmode && isatty(STDIN_FILENO)) {
2143 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
2144 fatal("Couldn't initialise editline");
2145 if ((hl = history_init()) == NULL)
2146 fatal("Couldn't initialise editline history");
2147 history(hl, &hev, H_SETSIZE, 100);
2148 el_set(el, EL_HIST, history, hl);
2149
2150 el_set(el, EL_PROMPT, prompt);
2151 el_set(el, EL_EDITOR, "emacs");
2152 el_set(el, EL_TERMINAL, NULL);
2153 el_set(el, EL_SIGNAL, 1);
2154 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11002155
2156 /* Tab Completion */
Damien Miller02e87802013-08-21 02:38:51 +10002157 el_set(el, EL_ADDFN, "ftp-complete",
Darren Tuckerd78739a2010-10-24 10:56:32 +11002158 "Context sensitive argument completion", complete);
Darren Tucker909d8582010-01-08 19:02:40 +11002159 complete_ctx.conn = conn;
2160 complete_ctx.remote_pathp = &remote_path;
2161 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
2162 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Damien Millere0ee7272013-08-21 02:42:35 +10002163 /* enable ctrl-left-arrow and ctrl-right-arrow */
2164 el_set(el, EL_BIND, "\\e[1;5C", "em-next-word", NULL);
2165 el_set(el, EL_BIND, "\\e[5C", "em-next-word", NULL);
2166 el_set(el, EL_BIND, "\\e[1;5D", "ed-prev-word", NULL);
2167 el_set(el, EL_BIND, "\\e\\e[D", "ed-prev-word", NULL);
Damien Miller61353b32013-09-14 09:45:32 +10002168 /* make ^w match ksh behaviour */
2169 el_set(el, EL_BIND, "^w", "ed-delete-prev-word", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11002170 }
2171#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002172
Darren Tucker909d8582010-01-08 19:02:40 +11002173 remote_path = do_realpath(conn, ".");
2174 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11002175 fatal("Need cwd");
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002176 startdir = xstrdup(remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11002177
2178 if (file1 != NULL) {
2179 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11002180 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11002181
2182 if (remote_is_dir(conn, dir) && file2 == NULL) {
Damien Miller9303e652013-04-23 15:22:40 +10002183 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00002184 mprintf("Changing to: %s\n", dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002185 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11002186 if (parse_dispatch_command(conn, cmd,
djm@openbsd.org5c1a6352018-11-16 02:30:20 +00002187 &remote_path, startdir, 1, 0) != 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10002188 free(dir);
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002189 free(startdir);
Darren Tuckera627d422013-06-02 07:31:17 +10002190 free(remote_path);
2191 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002192 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11002193 }
Damien Miller20e1fab2004-02-18 14:30:55 +11002194 } else {
Darren Tucker0af24052012-10-05 10:41:25 +10002195 /* XXX this is wrong wrt quoting */
Damien Miller0d032412013-07-25 11:56:52 +10002196 snprintf(cmd, sizeof cmd, "get%s %s%s%s",
2197 global_aflag ? " -a" : "", dir,
2198 file2 == NULL ? "" : " ",
2199 file2 == NULL ? "" : file2);
Darren Tucker909d8582010-01-08 19:02:40 +11002200 err = parse_dispatch_command(conn, cmd,
djm@openbsd.org5c1a6352018-11-16 02:30:20 +00002201 &remote_path, startdir, 1, 0);
Darren Tuckera627d422013-06-02 07:31:17 +10002202 free(dir);
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002203 free(startdir);
Darren Tuckera627d422013-06-02 07:31:17 +10002204 free(remote_path);
2205 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002206 return (err);
2207 }
Darren Tuckera627d422013-06-02 07:31:17 +10002208 free(dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002209 }
2210
millert@openbsd.orgdb995f22014-11-26 18:34:51 +00002211 setvbuf(stdout, NULL, _IOLBF, 0);
2212 setvbuf(infile, NULL, _IOLBF, 0);
Damien Miller20e1fab2004-02-18 14:30:55 +11002213
Damien Miller0e2c1022005-08-12 22:16:22 +10002214 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11002215 err = 0;
2216 for (;;) {
Darren Tuckercdf547a2004-05-24 10:12:19 +10002217 signal(SIGINT, SIG_IGN);
2218
Darren Tucker2d963d82004-11-07 20:04:10 +11002219 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002220 if (interactive)
2221 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11002222 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002223 if (interactive)
2224 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11002225 break;
2226 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002227 } else {
2228#ifdef USE_LIBEDIT
2229 const char *line;
2230 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11002231
Darren Tucker909d8582010-01-08 19:02:40 +11002232 if ((line = el_gets(el, &count)) == NULL ||
2233 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002234 printf("\n");
2235 break;
2236 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002237 history(hl, &hev, H_ENTER, line);
2238 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
2239 fprintf(stderr, "Error: input line too long\n");
2240 continue;
2241 }
2242#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002243 }
2244
djm@openbsd.org5c1a6352018-11-16 02:30:20 +00002245 cmd[strcspn(cmd, "\n")] = '\0';
Damien Miller20e1fab2004-02-18 14:30:55 +11002246
Darren Tuckercdf547a2004-05-24 10:12:19 +10002247 /* Handle user interrupts gracefully during commands */
2248 interrupted = 0;
2249 signal(SIGINT, cmd_interrupt);
2250
Darren Tucker909d8582010-01-08 19:02:40 +11002251 err = parse_dispatch_command(conn, cmd, &remote_path,
djm@openbsd.org5c1a6352018-11-16 02:30:20 +00002252 startdir, batchmode, !interactive && el == NULL);
Damien Miller20e1fab2004-02-18 14:30:55 +11002253 if (err != 0)
2254 break;
2255 }
djm@openbsd.org3455f1e2018-04-13 05:04:12 +00002256 signal(SIGCHLD, SIG_DFL);
Darren Tuckera627d422013-06-02 07:31:17 +10002257 free(remote_path);
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002258 free(startdir);
Darren Tuckera627d422013-06-02 07:31:17 +10002259 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002260
Tim Rice027e8b12005-08-15 14:52:50 -07002261#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10002262 if (el != NULL)
2263 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07002264#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10002265
Damien Miller20e1fab2004-02-18 14:30:55 +11002266 /* err == 1 signifies normal "quit" exit */
2267 return (err >= 0 ? 0 : -1);
2268}
Damien Miller62d57f62003-01-10 21:43:24 +11002269
Ben Lindstrombba81212001-06-25 05:01:22 +00002270static void
Damien Millercc685c12003-06-04 22:51:38 +10002271connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11002272{
2273 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002274
Damien Miller33804262001-02-04 23:20:18 +11002275#ifdef USE_PIPES
2276 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002277
Damien Miller33804262001-02-04 23:20:18 +11002278 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2279 fatal("pipe: %s", strerror(errno));
2280 *in = pin[0];
2281 *out = pout[1];
2282 c_in = pout[0];
2283 c_out = pin[1];
2284#else /* USE_PIPES */
2285 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002286
Damien Miller33804262001-02-04 23:20:18 +11002287 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2288 fatal("socketpair: %s", strerror(errno));
2289 *in = *out = inout[0];
2290 c_in = c_out = inout[1];
2291#endif /* USE_PIPES */
2292
Damien Millercc685c12003-06-04 22:51:38 +10002293 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002294 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002295 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002296 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2297 (dup2(c_out, STDOUT_FILENO) == -1)) {
2298 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002299 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002300 }
2301 close(*in);
2302 close(*out);
2303 close(c_in);
2304 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002305
2306 /*
2307 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002308 * ignore SIGINT if we want to gracefully abort commands,
2309 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002310 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2311 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002312 */
2313 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002314 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002315 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002316 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002317 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002318 }
2319
Damien Millercc685c12003-06-04 22:51:38 +10002320 signal(SIGTERM, killchild);
2321 signal(SIGINT, killchild);
2322 signal(SIGHUP, killchild);
millert@openbsd.org2c6697c2016-10-18 12:41:22 +00002323 signal(SIGTSTP, suspchild);
2324 signal(SIGTTIN, suspchild);
2325 signal(SIGTTOU, suspchild);
djm@openbsd.org3455f1e2018-04-13 05:04:12 +00002326 signal(SIGCHLD, sigchld_handler);
Damien Miller33804262001-02-04 23:20:18 +11002327 close(c_in);
2328 close(c_out);
2329}
2330
Ben Lindstrombba81212001-06-25 05:01:22 +00002331static void
Damien Miller33804262001-02-04 23:20:18 +11002332usage(void)
2333{
Damien Miller025e01c2002-02-08 22:06:29 +11002334 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002335
Ben Lindstrom1e243242001-09-18 05:38:44 +00002336 fprintf(stderr,
djm@openbsd.org3575f0b2017-05-02 08:54:19 +00002337 "usage: %s [-46aCfpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002338 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002339 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002340 " [-o ssh_option] [-P port] [-R num_requests] "
2341 "[-S program]\n"
millert@openbsd.org887669e2017-10-21 23:06:24 +00002342 " [-s subsystem | sftp_server] destination\n",
2343 __progname);
Damien Miller33804262001-02-04 23:20:18 +11002344 exit(1);
2345}
2346
Kevin Stevesef4eea92001-02-05 12:42:17 +00002347int
Damien Miller33804262001-02-04 23:20:18 +11002348main(int argc, char **argv)
2349{
millert@openbsd.org887669e2017-10-21 23:06:24 +00002350 int in, out, ch, err, tmp, port = -1;
2351 char *host = NULL, *user, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002352 int debug_level = 0, sshver = 2;
2353 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002354 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002355 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002356 LogLevel ll = SYSLOG_LEVEL_INFO;
2357 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002358 extern int optind;
2359 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002360 struct sftp_conn *conn;
2361 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2362 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002363 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002364
dtucker@openbsd.orgffb1e7e2016-02-15 09:47:49 +00002365 ssh_malloc_init(); /* must be called before any mallocs */
Darren Tuckerce321d82005-10-03 18:11:24 +10002366 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2367 sanitise_stdfd();
Damien Millerdda78a02016-12-12 13:57:10 +11002368 msetlocale();
Darren Tuckerce321d82005-10-03 18:11:24 +10002369
Damien Miller42c5ec42018-11-23 10:40:06 +11002370 seed_rng();
2371
Damien Miller59d3d5b2003-08-22 09:34:41 +10002372 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002373 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002374 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002375 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002376 addargs(&args, "-oForwardX11 no");
2377 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002378 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002379 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002380
Ben Lindstrom387c4722001-05-08 20:27:25 +00002381 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002382 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002383
Darren Tucker282b4022009-10-07 08:23:06 +11002384 while ((ch = getopt(argc, argv,
Damien Millerf29238e2013-10-17 11:48:52 +11002385 "1246afhpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002386 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002387 /* Passed through to ssh(1) */
2388 case '4':
2389 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002390 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002391 addargs(&args, "-%c", ch);
2392 break;
2393 /* Passed through to ssh(1) with argument */
2394 case 'F':
2395 case 'c':
2396 case 'i':
2397 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002398 addargs(&args, "-%c", ch);
2399 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002400 break;
2401 case 'q':
Damien Miller9303e652013-04-23 15:22:40 +10002402 ll = SYSLOG_LEVEL_ERROR;
2403 quiet = 1;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002404 showprogress = 0;
2405 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002406 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002407 case 'P':
millert@openbsd.org887669e2017-10-21 23:06:24 +00002408 port = a2port(optarg);
2409 if (port <= 0)
2410 fatal("Bad port \"%s\"\n", optarg);
Darren Tucker282b4022009-10-07 08:23:06 +11002411 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002412 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002413 if (debug_level < 3) {
2414 addargs(&args, "-v");
2415 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2416 }
2417 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002418 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002419 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002420 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002421 if (sftp_server == NULL)
2422 sftp_server = _PATH_SFTP_SERVER;
2423 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002424 case '2':
2425 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002426 break;
Damien Miller0d032412013-07-25 11:56:52 +10002427 case 'a':
2428 global_aflag = 1;
2429 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002430 case 'B':
2431 copy_buffer_len = strtol(optarg, &cp, 10);
2432 if (copy_buffer_len == 0 || *cp != '\0')
2433 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002434 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002435 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002436 if (batchmode)
2437 fatal("Batch file already specified.");
2438
2439 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002440 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002441 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002442 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002443 showprogress = 0;
Damien Miller9303e652013-04-23 15:22:40 +10002444 quiet = batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002445 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002446 break;
Damien Millerf29238e2013-10-17 11:48:52 +11002447 case 'f':
2448 global_fflag = 1;
2449 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002450 case 'p':
2451 global_pflag = 1;
2452 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002453 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002454 sftp_direct = optarg;
2455 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002456 case 'l':
2457 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2458 &errstr);
2459 if (errstr != NULL)
2460 usage();
2461 limit_kbps *= 1024; /* kbps */
2462 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002463 case 'r':
2464 global_rflag = 1;
2465 break;
Damien Miller16a13332002-02-13 14:03:56 +11002466 case 'R':
2467 num_requests = strtol(optarg, &cp, 10);
2468 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002469 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002470 optarg);
2471 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002472 case 's':
2473 sftp_server = optarg;
2474 break;
2475 case 'S':
2476 ssh_program = optarg;
2477 replacearg(&args, 0, "%s", ssh_program);
2478 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002479 case 'h':
2480 default:
Damien Miller33804262001-02-04 23:20:18 +11002481 usage();
2482 }
2483 }
2484
Damien Millerc0f27d82004-03-08 23:12:19 +11002485 if (!isatty(STDERR_FILENO))
2486 showprogress = 0;
2487
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002488 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2489
Damien Millerd14ee1e2002-02-05 12:27:31 +11002490 if (sftp_direct == NULL) {
2491 if (optind == argc || argc > (optind + 2))
2492 usage();
millert@openbsd.org887669e2017-10-21 23:06:24 +00002493 argv += optind;
Damien Miller33804262001-02-04 23:20:18 +11002494
millert@openbsd.org887669e2017-10-21 23:06:24 +00002495 switch (parse_uri("sftp", *argv, &user, &host, &tmp, &file1)) {
2496 case -1:
2497 usage();
2498 break;
2499 case 0:
2500 if (tmp != -1)
2501 port = tmp;
2502 break;
2503 default:
2504 if (parse_user_host_path(*argv, &user, &host,
2505 &file1) == -1) {
2506 /* Treat as a plain hostname. */
2507 host = xstrdup(*argv);
2508 host = cleanhostname(host);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002509 }
millert@openbsd.org887669e2017-10-21 23:06:24 +00002510 break;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002511 }
millert@openbsd.org887669e2017-10-21 23:06:24 +00002512 file2 = *(argv + 1);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002513
Damien Millerd14ee1e2002-02-05 12:27:31 +11002514 if (!*host) {
2515 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002516 usage();
2517 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002518
millert@openbsd.org887669e2017-10-21 23:06:24 +00002519 if (port != -1)
2520 addargs(&args, "-oPort %d", port);
2521 if (user != NULL) {
2522 addargs(&args, "-l");
2523 addargs(&args, "%s", user);
2524 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002525 addargs(&args, "-oProtocol %d", sshver);
2526
2527 /* no subsystem if the server-spec contains a '/' */
2528 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2529 addargs(&args, "-s");
2530
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002531 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002532 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002533 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002534 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002535
Damien Millercc685c12003-06-04 22:51:38 +10002536 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002537 } else {
2538 args.list = NULL;
2539 addargs(&args, "sftp-server");
2540
Damien Millercc685c12003-06-04 22:51:38 +10002541 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002542 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002543 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002544
Damien Miller65e42f82010-09-24 22:15:11 +10002545 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002546 if (conn == NULL)
2547 fatal("Couldn't initialise connection to server");
2548
Damien Miller9303e652013-04-23 15:22:40 +10002549 if (!quiet) {
Darren Tucker21063192010-01-08 17:10:36 +11002550 if (sftp_direct == NULL)
2551 fprintf(stderr, "Connected to %s.\n", host);
2552 else
2553 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2554 }
2555
2556 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002557
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002558#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002559 shutdown(in, SHUT_RDWR);
2560 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002561#endif
2562
Damien Miller33804262001-02-04 23:20:18 +11002563 close(in);
2564 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002565 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002566 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002567
bluhm@openbsd.orge7751aa2018-04-26 14:47:03 +00002568 while (waitpid(sshpid, NULL, 0) == -1 && sshpid > 1)
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002569 if (errno != EINTR)
2570 fatal("Couldn't wait for ssh process: %s",
2571 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002572
Damien Miller956f3fb2003-01-10 21:40:00 +11002573 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002574}