blob: 7db86c2d3cf087dfbe82860d70cb22056c95034d [file] [log] [blame]
dtucker@openbsd.orgde37ca92018-09-07 04:26:56 +00001/* $OpenBSD: sftp.c,v 1.186 2018/09/07 04:26:56 dtucker 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 */
392 if (p && p[0] != '/') {
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
Damien Millerd8accc02014-05-15 13:46:25 +10001299parse_args(const char **cpp, int *ignore_errors, 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
Damien Miller20e1fab2004-02-18 14:30:55 +11001313 /* Check for leading '-' (disable error processing) */
Damien Millerf29238e2013-10-17 11:48:52 +11001314 *ignore_errors = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001315 if (*cp == '-') {
Damien Millerf29238e2013-10-17 11:48:52 +11001316 *ignore_errors = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001317 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001318 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001319 }
1320
Darren Tucker70cc0922010-01-09 22:28:03 +11001321 /* Ignore blank lines and lines which begin with comment '#' char */
1322 if (*cp == '\0' || *cp == '#')
1323 return (0);
1324
Darren Tucker909d8582010-01-08 19:02:40 +11001325 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001326 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001327
Damien Miller1cbc2922007-10-26 14:27:45 +10001328 /* Figure out which command we have */
1329 for (i = 0; cmds[i].c != NULL; i++) {
Damien Millerd6d9fa02013-02-12 11:02:46 +11001330 if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001331 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001332 }
1333 cmdnum = cmds[i].n;
1334 cmd = cmds[i].c;
1335
1336 /* Special case */
1337 if (*cp == '!') {
1338 cp++;
1339 cmdnum = I_SHELL;
1340 } else if (cmdnum == -1) {
1341 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001342 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001343 }
1344
1345 /* Get arguments and parse flags */
Damien Millerf29238e2013-10-17 11:48:52 +11001346 *aflag = *fflag = *hflag = *iflag = *lflag = *pflag = 0;
1347 *rflag = *sflag = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001348 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001349 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001350 switch (cmdnum) {
1351 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001352 case I_REGET:
Damien Millerd8accc02014-05-15 13:46:25 +10001353 case I_REPUT:
Damien Miller20e1fab2004-02-18 14:30:55 +11001354 case I_PUT:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001355 if ((optidx = parse_getput_flags(cmd, argv, argc,
Damien Millerf29238e2013-10-17 11:48:52 +11001356 aflag, fflag, pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001357 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001358 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001359 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001360 error("You must specify at least one path after a "
1361 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001362 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001363 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001364 *path1 = xstrdup(argv[optidx]);
1365 /* Get second pathname (optional) */
1366 if (argc - optidx > 1) {
1367 *path2 = xstrdup(argv[optidx + 1]);
1368 /* Destination is not globbed */
1369 undo_glob_escape(*path2);
1370 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001371 break;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001372 case I_LINK:
1373 if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1374 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001375 goto parse_two_paths;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001376 case I_RENAME:
Damien Millerc7dba122013-08-21 02:41:15 +10001377 if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1)
1378 return -1;
1379 goto parse_two_paths;
1380 case I_SYMLINK:
Damien Miller036d3072013-08-21 02:41:46 +10001381 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1382 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001383 parse_two_paths:
Damien Miller1cbc2922007-10-26 14:27:45 +10001384 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001385 error("You must specify two paths after a %s "
1386 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001387 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001388 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001389 *path1 = xstrdup(argv[optidx]);
1390 *path2 = xstrdup(argv[optidx + 1]);
1391 /* Paths are not globbed */
1392 undo_glob_escape(*path1);
1393 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001394 break;
1395 case I_RM:
1396 case I_MKDIR:
1397 case I_RMDIR:
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00001398 case I_LMKDIR:
1399 path1_mandatory = 1;
1400 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001401 case I_CHDIR:
1402 case I_LCHDIR:
Damien Miller036d3072013-08-21 02:41:46 +10001403 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1404 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001405 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001406 if (argc - optidx < 1) {
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00001407 if (!path1_mandatory)
1408 break; /* return a NULL path1 */
Damien Miller20e1fab2004-02-18 14:30:55 +11001409 error("You must specify a path after a %s command.",
1410 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001411 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001412 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001413 *path1 = xstrdup(argv[optidx]);
1414 /* Only "rm" globs */
1415 if (cmdnum != I_RM)
1416 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001417 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001418 case I_DF:
1419 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1420 iflag)) == -1)
1421 return -1;
1422 /* Default to current directory if no path specified */
1423 if (argc - optidx < 1)
1424 *path1 = NULL;
1425 else {
1426 *path1 = xstrdup(argv[optidx]);
1427 undo_glob_escape(*path1);
1428 }
1429 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001430 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001431 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001432 return(-1);
1433 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001434 if (argc - optidx > 0)
1435 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001436 break;
1437 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001438 /* Skip ls command and following whitespace */
1439 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001440 case I_SHELL:
1441 /* Uses the rest of the line */
1442 break;
1443 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001444 case I_CHMOD:
1445 base = 8;
dtucker@openbsd.orgde37ca92018-09-07 04:26:56 +00001446 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001447 case I_CHOWN:
1448 case I_CHGRP:
Damien Miller036d3072013-08-21 02:41:46 +10001449 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1450 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001451 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001452 if (argc - optidx < 1)
1453 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001454 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001455 l = strtol(argv[optidx], &cp2, base);
1456 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1457 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1458 l < 0) {
1459 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001460 error("You must supply a numeric argument "
1461 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001462 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001463 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001464 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001465 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001466 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001467 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001468 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001469 error("You must specify a path after a %s command.",
1470 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001471 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001472 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001473 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001474 break;
1475 case I_QUIT:
1476 case I_PWD:
1477 case I_LPWD:
1478 case I_HELP:
1479 case I_VERSION:
1480 case I_PROGRESS:
Damien Miller036d3072013-08-21 02:41:46 +10001481 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1482 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001483 break;
1484 default:
1485 fatal("Command not implemented");
1486 }
1487
1488 *cpp = cp;
1489 return(cmdnum);
1490}
1491
1492static int
1493parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00001494 const char *startdir, int err_abort)
Damien Miller20e1fab2004-02-18 14:30:55 +11001495{
1496 char *path1, *path2, *tmp;
djm@openbsd.org34a01b22016-04-08 08:19:17 +00001497 int ignore_errors = 0, aflag = 0, fflag = 0, hflag = 0,
Damien Millerd8accc02014-05-15 13:46:25 +10001498 iflag = 0;
Damien Millerf29238e2013-10-17 11:48:52 +11001499 int lflag = 0, pflag = 0, rflag = 0, sflag = 0;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001500 int cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001501 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001502 Attrib a, *aa;
deraadt@openbsd.org087266e2015-01-20 23:14:00 +00001503 char path_buf[PATH_MAX];
Damien Miller20e1fab2004-02-18 14:30:55 +11001504 int err = 0;
1505 glob_t g;
1506
1507 path1 = path2 = NULL;
Damien Millerf29238e2013-10-17 11:48:52 +11001508 cmdnum = parse_args(&cmd, &ignore_errors, &aflag, &fflag, &hflag,
1509 &iflag, &lflag, &pflag, &rflag, &sflag, &n_arg, &path1, &path2);
1510 if (ignore_errors != 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001511 err_abort = 0;
1512
1513 memset(&g, 0, sizeof(g));
1514
1515 /* Perform command */
1516 switch (cmdnum) {
1517 case 0:
1518 /* Blank line */
1519 break;
1520 case -1:
1521 /* Unrecognized command */
1522 err = -1;
1523 break;
Damien Miller0d032412013-07-25 11:56:52 +10001524 case I_REGET:
1525 aflag = 1;
1526 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001527 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001528 err = process_get(conn, path1, path2, *pwd, pflag,
Damien Millerf29238e2013-10-17 11:48:52 +11001529 rflag, aflag, fflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001530 break;
Damien Millerd8accc02014-05-15 13:46:25 +10001531 case I_REPUT:
1532 aflag = 1;
1533 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001534 case I_PUT:
Damien Millerf29238e2013-10-17 11:48:52 +11001535 err = process_put(conn, path1, path2, *pwd, pflag,
Damien Millerd8accc02014-05-15 13:46:25 +10001536 rflag, aflag, fflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001537 break;
1538 case I_RENAME:
1539 path1 = make_absolute(path1, *pwd);
1540 path2 = make_absolute(path2, *pwd);
Damien Millerc7dba122013-08-21 02:41:15 +10001541 err = do_rename(conn, path1, path2, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001542 break;
1543 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001544 sflag = 1;
dtucker@openbsd.orgde37ca92018-09-07 04:26:56 +00001545 /* FALLTHROUGH */
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001546 case I_LINK:
Damien Miller034f27a2013-08-21 02:40:44 +10001547 if (!sflag)
1548 path1 = make_absolute(path1, *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001549 path2 = make_absolute(path2, *pwd);
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001550 err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001551 break;
1552 case I_RM:
1553 path1 = make_absolute(path1, *pwd);
1554 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001555 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001556 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001557 mprintf("Removing %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001558 err = do_rm(conn, g.gl_pathv[i]);
1559 if (err != 0 && err_abort)
1560 break;
1561 }
1562 break;
1563 case I_MKDIR:
1564 path1 = make_absolute(path1, *pwd);
1565 attrib_clear(&a);
1566 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1567 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001568 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001569 break;
1570 case I_RMDIR:
1571 path1 = make_absolute(path1, *pwd);
1572 err = do_rmdir(conn, path1);
1573 break;
1574 case I_CHDIR:
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00001575 if (path1 == NULL || *path1 == '\0')
1576 path1 = xstrdup(startdir);
Damien Miller20e1fab2004-02-18 14:30:55 +11001577 path1 = make_absolute(path1, *pwd);
1578 if ((tmp = do_realpath(conn, path1)) == NULL) {
1579 err = 1;
1580 break;
1581 }
1582 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
Darren Tuckera627d422013-06-02 07:31:17 +10001583 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001584 err = 1;
1585 break;
1586 }
1587 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1588 error("Can't change directory: Can't check target");
Darren Tuckera627d422013-06-02 07:31:17 +10001589 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001590 err = 1;
1591 break;
1592 }
1593 if (!S_ISDIR(aa->perm)) {
1594 error("Can't change directory: \"%s\" is not "
1595 "a directory", tmp);
Darren Tuckera627d422013-06-02 07:31:17 +10001596 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001597 err = 1;
1598 break;
1599 }
Darren Tuckera627d422013-06-02 07:31:17 +10001600 free(*pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001601 *pwd = tmp;
1602 break;
1603 case I_LS:
1604 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001605 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001606 break;
1607 }
1608
1609 /* Strip pwd off beginning of non-absolute paths */
1610 tmp = NULL;
1611 if (*path1 != '/')
1612 tmp = *pwd;
1613
1614 path1 = make_absolute(path1, *pwd);
1615 err = do_globbed_ls(conn, path1, tmp, lflag);
1616 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001617 case I_DF:
1618 /* Default to current directory if no path specified */
1619 if (path1 == NULL)
1620 path1 = xstrdup(*pwd);
1621 path1 = make_absolute(path1, *pwd);
1622 err = do_df(conn, path1, hflag, iflag);
1623 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001624 case I_LCHDIR:
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00001625 if (path1 == NULL || *path1 == '\0')
1626 path1 = xstrdup("~");
deraadt@openbsd.org40ba4c92014-08-20 01:28:55 +00001627 tmp = tilde_expand_filename(path1, getuid());
djm@openbsd.org7ff880e2014-08-19 23:57:18 +00001628 free(path1);
1629 path1 = tmp;
Damien Miller20e1fab2004-02-18 14:30:55 +11001630 if (chdir(path1) == -1) {
1631 error("Couldn't change local directory to "
1632 "\"%s\": %s", path1, strerror(errno));
1633 err = 1;
1634 }
1635 break;
1636 case I_LMKDIR:
1637 if (mkdir(path1, 0777) == -1) {
1638 error("Couldn't create local directory "
1639 "\"%s\": %s", path1, strerror(errno));
1640 err = 1;
1641 }
1642 break;
1643 case I_LLS:
1644 local_do_ls(cmd);
1645 break;
1646 case I_SHELL:
1647 local_do_shell(cmd);
1648 break;
1649 case I_LUMASK:
1650 umask(n_arg);
1651 printf("Local umask: %03lo\n", n_arg);
1652 break;
1653 case I_CHMOD:
1654 path1 = make_absolute(path1, *pwd);
1655 attrib_clear(&a);
1656 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1657 a.perm = n_arg;
1658 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001659 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001660 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001661 mprintf("Changing mode on %s\n",
1662 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001663 err = do_setstat(conn, g.gl_pathv[i], &a);
1664 if (err != 0 && err_abort)
1665 break;
1666 }
1667 break;
1668 case I_CHOWN:
1669 case I_CHGRP:
1670 path1 = make_absolute(path1, *pwd);
1671 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001672 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001673 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001674 if (err_abort) {
1675 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001676 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001677 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001678 continue;
1679 }
1680 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1681 error("Can't get current ownership of "
1682 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001683 if (err_abort) {
1684 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001685 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001686 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001687 continue;
1688 }
1689 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1690 if (cmdnum == I_CHOWN) {
Damien Miller9303e652013-04-23 15:22:40 +10001691 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001692 mprintf("Changing owner on %s\n",
Damien Miller9303e652013-04-23 15:22:40 +10001693 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001694 aa->uid = n_arg;
1695 } else {
Damien Miller9303e652013-04-23 15:22:40 +10001696 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001697 mprintf("Changing group on %s\n",
Damien Miller9303e652013-04-23 15:22:40 +10001698 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001699 aa->gid = n_arg;
1700 }
1701 err = do_setstat(conn, g.gl_pathv[i], aa);
1702 if (err != 0 && err_abort)
1703 break;
1704 }
1705 break;
1706 case I_PWD:
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001707 mprintf("Remote working directory: %s\n", *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001708 break;
1709 case I_LPWD:
1710 if (!getcwd(path_buf, sizeof(path_buf))) {
1711 error("Couldn't get local cwd: %s", strerror(errno));
1712 err = -1;
1713 break;
1714 }
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001715 mprintf("Local working directory: %s\n", path_buf);
Damien Miller20e1fab2004-02-18 14:30:55 +11001716 break;
1717 case I_QUIT:
1718 /* Processed below */
1719 break;
1720 case I_HELP:
1721 help();
1722 break;
1723 case I_VERSION:
1724 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1725 break;
1726 case I_PROGRESS:
1727 showprogress = !showprogress;
1728 if (showprogress)
1729 printf("Progress meter enabled\n");
1730 else
1731 printf("Progress meter disabled\n");
1732 break;
1733 default:
1734 fatal("%d is not implemented", cmdnum);
1735 }
1736
1737 if (g.gl_pathc)
1738 globfree(&g);
Darren Tuckera627d422013-06-02 07:31:17 +10001739 free(path1);
1740 free(path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001741
1742 /* If an unignored error occurs in batch mode we should abort. */
1743 if (err_abort && err != 0)
1744 return (-1);
1745 else if (cmdnum == I_QUIT)
1746 return (1);
1747
1748 return (0);
1749}
1750
Darren Tucker2d963d82004-11-07 20:04:10 +11001751#ifdef USE_LIBEDIT
1752static char *
1753prompt(EditLine *el)
1754{
1755 return ("sftp> ");
1756}
Darren Tucker2d963d82004-11-07 20:04:10 +11001757
Darren Tucker909d8582010-01-08 19:02:40 +11001758/* Display entries in 'list' after skipping the first 'len' chars */
1759static void
1760complete_display(char **list, u_int len)
1761{
1762 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1763 struct winsize ws;
1764 char *tmp;
1765
1766 /* Count entries for sort and find longest */
Damien Miller02e87802013-08-21 02:38:51 +10001767 for (y = 0; list[y]; y++)
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +00001768 m = MAXIMUM(m, strlen(list[y]));
Darren Tucker909d8582010-01-08 19:02:40 +11001769
1770 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1771 width = ws.ws_col;
1772
1773 m = m > len ? m - len : 0;
1774 columns = width / (m + 2);
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +00001775 columns = MAXIMUM(columns, 1);
Darren Tucker909d8582010-01-08 19:02:40 +11001776 colspace = width / columns;
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +00001777 colspace = MINIMUM(colspace, width);
Darren Tucker909d8582010-01-08 19:02:40 +11001778
1779 printf("\n");
1780 m = 1;
1781 for (y = 0; list[y]; y++) {
1782 llen = strlen(list[y]);
1783 tmp = llen > len ? list[y] + len : "";
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001784 mprintf("%-*s", colspace, tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001785 if (m >= columns) {
1786 printf("\n");
1787 m = 1;
1788 } else
1789 m++;
1790 }
1791 printf("\n");
1792}
1793
1794/*
1795 * Given a "list" of words that begin with a common prefix of "word",
1796 * attempt to find an autocompletion to extends "word" by the next
1797 * characters common to all entries in "list".
1798 */
1799static char *
1800complete_ambiguous(const char *word, char **list, size_t count)
1801{
1802 if (word == NULL)
1803 return NULL;
1804
1805 if (count > 0) {
1806 u_int y, matchlen = strlen(list[0]);
1807
1808 /* Find length of common stem */
1809 for (y = 1; list[y]; y++) {
1810 u_int x;
1811
Damien Miller02e87802013-08-21 02:38:51 +10001812 for (x = 0; x < matchlen; x++)
1813 if (list[0][x] != list[y][x])
Darren Tucker909d8582010-01-08 19:02:40 +11001814 break;
1815
1816 matchlen = x;
1817 }
1818
1819 if (matchlen > strlen(word)) {
1820 char *tmp = xstrdup(list[0]);
1821
Darren Tucker340d1682010-01-09 08:54:31 +11001822 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001823 return tmp;
1824 }
Damien Miller02e87802013-08-21 02:38:51 +10001825 }
Darren Tucker909d8582010-01-08 19:02:40 +11001826
1827 return xstrdup(word);
1828}
1829
1830/* Autocomplete a sftp command */
1831static int
1832complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1833 int terminated)
1834{
1835 u_int y, count = 0, cmdlen, tmplen;
1836 char *tmp, **list, argterm[3];
1837 const LineInfo *lf;
1838
1839 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1840
1841 /* No command specified: display all available commands */
1842 if (cmd == NULL) {
1843 for (y = 0; cmds[y].c; y++)
1844 list[count++] = xstrdup(cmds[y].c);
Damien Miller02e87802013-08-21 02:38:51 +10001845
Darren Tucker909d8582010-01-08 19:02:40 +11001846 list[count] = NULL;
1847 complete_display(list, 0);
1848
Damien Miller02e87802013-08-21 02:38:51 +10001849 for (y = 0; list[y] != NULL; y++)
1850 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001851 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001852 return count;
1853 }
1854
1855 /* Prepare subset of commands that start with "cmd" */
1856 cmdlen = strlen(cmd);
1857 for (y = 0; cmds[y].c; y++) {
Damien Miller02e87802013-08-21 02:38:51 +10001858 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
Darren Tucker909d8582010-01-08 19:02:40 +11001859 list[count++] = xstrdup(cmds[y].c);
1860 }
1861 list[count] = NULL;
1862
Damien Miller47d81152011-11-25 13:53:48 +11001863 if (count == 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10001864 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001865 return 0;
Damien Miller47d81152011-11-25 13:53:48 +11001866 }
Darren Tucker909d8582010-01-08 19:02:40 +11001867
djm@openbsd.org001aa552018-04-10 00:10:49 +00001868 /* Complete ambiguous command */
Darren Tucker909d8582010-01-08 19:02:40 +11001869 tmp = complete_ambiguous(cmd, list, count);
1870 if (count > 1)
1871 complete_display(list, 0);
1872
Damien Miller02e87802013-08-21 02:38:51 +10001873 for (y = 0; list[y]; y++)
1874 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001875 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001876
1877 if (tmp != NULL) {
1878 tmplen = strlen(tmp);
1879 cmdlen = strlen(cmd);
1880 /* If cmd may be extended then do so */
1881 if (tmplen > cmdlen)
1882 if (el_insertstr(el, tmp + cmdlen) == -1)
1883 fatal("el_insertstr failed.");
1884 lf = el_line(el);
1885 /* Terminate argument cleanly */
1886 if (count == 1) {
1887 y = 0;
1888 if (!terminated)
1889 argterm[y++] = quote;
1890 if (lastarg || *(lf->cursor) != ' ')
1891 argterm[y++] = ' ';
1892 argterm[y] = '\0';
1893 if (y > 0 && el_insertstr(el, argterm) == -1)
1894 fatal("el_insertstr failed.");
1895 }
Darren Tuckera627d422013-06-02 07:31:17 +10001896 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001897 }
1898
1899 return count;
1900}
1901
1902/*
1903 * Determine whether a particular sftp command's arguments (if any)
1904 * represent local or remote files.
1905 */
1906static int
1907complete_is_remote(char *cmd) {
1908 int i;
1909
1910 if (cmd == NULL)
1911 return -1;
1912
1913 for (i = 0; cmds[i].c; i++) {
Damien Miller02e87802013-08-21 02:38:51 +10001914 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
Darren Tucker909d8582010-01-08 19:02:40 +11001915 return cmds[i].t;
1916 }
1917
1918 return -1;
1919}
1920
1921/* Autocomplete a filename "file" */
1922static int
1923complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1924 char *file, int remote, int lastarg, char quote, int terminated)
1925{
1926 glob_t g;
Darren Tuckerea647212013-06-06 08:19:09 +10001927 char *tmp, *tmp2, ins[8];
Darren Tucker17146d32012-10-05 10:46:16 +10001928 u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
Darren Tuckerea647212013-06-06 08:19:09 +10001929 int clen;
Darren Tucker909d8582010-01-08 19:02:40 +11001930 const LineInfo *lf;
Damien Miller02e87802013-08-21 02:38:51 +10001931
Darren Tucker909d8582010-01-08 19:02:40 +11001932 /* Glob from "file" location */
1933 if (file == NULL)
1934 tmp = xstrdup("*");
1935 else
1936 xasprintf(&tmp, "%s*", file);
1937
Darren Tucker191fcc62012-10-05 10:45:01 +10001938 /* Check if the path is absolute. */
1939 isabs = tmp[0] == '/';
1940
Darren Tucker909d8582010-01-08 19:02:40 +11001941 memset(&g, 0, sizeof(g));
1942 if (remote != LOCAL) {
1943 tmp = make_absolute(tmp, remote_path);
1944 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001945 } else
Darren Tucker909d8582010-01-08 19:02:40 +11001946 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001947
Darren Tucker909d8582010-01-08 19:02:40 +11001948 /* Determine length of pwd so we can trim completion display */
1949 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1950 /* Terminate counting on first unescaped glob metacharacter */
1951 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1952 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1953 hadglob = 1;
1954 break;
1955 }
1956 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1957 tmplen++;
1958 if (tmp[tmplen] == '/')
1959 pwdlen = tmplen + 1; /* track last seen '/' */
1960 }
Darren Tuckera627d422013-06-02 07:31:17 +10001961 free(tmp);
Damien Millerd7fd8be2014-05-15 14:24:59 +10001962 tmp = NULL;
Darren Tucker909d8582010-01-08 19:02:40 +11001963
Damien Miller02e87802013-08-21 02:38:51 +10001964 if (g.gl_matchc == 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001965 goto out;
1966
1967 if (g.gl_matchc > 1)
1968 complete_display(g.gl_pathv, pwdlen);
1969
Darren Tucker909d8582010-01-08 19:02:40 +11001970 /* Don't try to extend globs */
1971 if (file == NULL || hadglob)
1972 goto out;
1973
1974 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
Darren Tucker191fcc62012-10-05 10:45:01 +10001975 tmp = path_strip(tmp2, isabs ? NULL : remote_path);
Darren Tuckera627d422013-06-02 07:31:17 +10001976 free(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001977
1978 if (tmp == NULL)
1979 goto out;
1980
1981 tmplen = strlen(tmp);
1982 filelen = strlen(file);
1983
Darren Tucker17146d32012-10-05 10:46:16 +10001984 /* Count the number of escaped characters in the input string. */
1985 cesc = isesc = 0;
1986 for (i = 0; i < filelen; i++) {
1987 if (!isesc && file[i] == '\\' && i + 1 < filelen){
1988 isesc = 1;
1989 cesc++;
1990 } else
1991 isesc = 0;
1992 }
1993
1994 if (tmplen > (filelen - cesc)) {
1995 tmp2 = tmp + filelen - cesc;
Damien Miller02e87802013-08-21 02:38:51 +10001996 len = strlen(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001997 /* quote argument on way out */
Darren Tuckerea647212013-06-06 08:19:09 +10001998 for (i = 0; i < len; i += clen) {
1999 if ((clen = mblen(tmp2 + i, len - i)) < 0 ||
2000 (size_t)clen > sizeof(ins) - 2)
2001 fatal("invalid multibyte character");
Darren Tucker909d8582010-01-08 19:02:40 +11002002 ins[0] = '\\';
Darren Tuckerea647212013-06-06 08:19:09 +10002003 memcpy(ins + 1, tmp2 + i, clen);
2004 ins[clen + 1] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11002005 switch (tmp2[i]) {
2006 case '\'':
2007 case '"':
2008 case '\\':
2009 case '\t':
Darren Tuckerd78739a2010-10-24 10:56:32 +11002010 case '[':
Darren Tucker909d8582010-01-08 19:02:40 +11002011 case ' ':
Darren Tucker17146d32012-10-05 10:46:16 +10002012 case '#':
2013 case '*':
Darren Tucker909d8582010-01-08 19:02:40 +11002014 if (quote == '\0' || tmp2[i] == quote) {
2015 if (el_insertstr(el, ins) == -1)
2016 fatal("el_insertstr "
2017 "failed.");
2018 break;
2019 }
2020 /* FALLTHROUGH */
2021 default:
2022 if (el_insertstr(el, ins + 1) == -1)
2023 fatal("el_insertstr failed.");
2024 break;
2025 }
2026 }
2027 }
2028
2029 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11002030 if (g.gl_matchc == 1) {
2031 i = 0;
Damien Miller38094812014-05-15 14:25:18 +10002032 if (!terminated && quote != '\0')
Darren Tucker909d8582010-01-08 19:02:40 +11002033 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11002034 if (*(lf->cursor - 1) != '/' &&
2035 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11002036 ins[i++] = ' ';
2037 ins[i] = '\0';
2038 if (i > 0 && el_insertstr(el, ins) == -1)
2039 fatal("el_insertstr failed.");
2040 }
Darren Tuckera627d422013-06-02 07:31:17 +10002041 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11002042
2043 out:
2044 globfree(&g);
2045 return g.gl_matchc;
2046}
2047
2048/* tab-completion hook function, called via libedit */
2049static unsigned char
2050complete(EditLine *el, int ch)
2051{
Damien Miller02e87802013-08-21 02:38:51 +10002052 char **argv, *line, quote;
Damien Miller746d1a62013-07-18 16:13:02 +10002053 int argc, carg;
2054 u_int cursor, len, terminated, ret = CC_ERROR;
Darren Tucker909d8582010-01-08 19:02:40 +11002055 const LineInfo *lf;
2056 struct complete_ctx *complete_ctx;
2057
2058 lf = el_line(el);
2059 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
2060 fatal("%s: el_get failed", __func__);
2061
2062 /* Figure out which argument the cursor points to */
2063 cursor = lf->cursor - lf->buffer;
deraadt@openbsd.orgce445b02015-08-20 22:32:42 +00002064 line = xmalloc(cursor + 1);
Darren Tucker909d8582010-01-08 19:02:40 +11002065 memcpy(line, lf->buffer, cursor);
2066 line[cursor] = '\0';
2067 argv = makeargv(line, &carg, 1, &quote, &terminated);
Darren Tuckera627d422013-06-02 07:31:17 +10002068 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11002069
2070 /* Get all the arguments on the line */
2071 len = lf->lastchar - lf->buffer;
deraadt@openbsd.orgce445b02015-08-20 22:32:42 +00002072 line = xmalloc(len + 1);
Darren Tucker909d8582010-01-08 19:02:40 +11002073 memcpy(line, lf->buffer, len);
2074 line[len] = '\0';
2075 argv = makeargv(line, &argc, 1, NULL, NULL);
2076
2077 /* Ensure cursor is at EOL or a argument boundary */
2078 if (line[cursor] != ' ' && line[cursor] != '\0' &&
2079 line[cursor] != '\n') {
Darren Tuckera627d422013-06-02 07:31:17 +10002080 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11002081 return ret;
2082 }
2083
2084 if (carg == 0) {
2085 /* Show all available commands */
2086 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
2087 ret = CC_REDISPLAY;
2088 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
2089 /* Handle the command parsing */
2090 if (complete_cmd_parse(el, argv[0], argc == carg,
Damien Miller02e87802013-08-21 02:38:51 +10002091 quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11002092 ret = CC_REDISPLAY;
2093 } else if (carg >= 1) {
2094 /* Handle file parsing */
2095 int remote = complete_is_remote(argv[0]);
2096 char *filematch = NULL;
2097
2098 if (carg > 1 && line[cursor-1] != ' ')
2099 filematch = argv[carg - 1];
2100
2101 if (remote != 0 &&
2102 complete_match(el, complete_ctx->conn,
2103 *complete_ctx->remote_pathp, filematch,
Damien Miller02e87802013-08-21 02:38:51 +10002104 remote, carg == argc, quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11002105 ret = CC_REDISPLAY;
2106 }
2107
Damien Miller02e87802013-08-21 02:38:51 +10002108 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11002109 return ret;
2110}
Darren Tuckere67f7db2010-01-08 19:50:02 +11002111#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11002112
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002113static int
Darren Tucker21063192010-01-08 17:10:36 +11002114interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11002115{
Darren Tucker909d8582010-01-08 19:02:40 +11002116 char *remote_path;
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002117 char *dir = NULL, *startdir = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +11002118 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10002119 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11002120 EditLine *el = NULL;
2121#ifdef USE_LIBEDIT
2122 History *hl = NULL;
2123 HistEvent hev;
2124 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11002125 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11002126
2127 if (!batchmode && isatty(STDIN_FILENO)) {
2128 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
2129 fatal("Couldn't initialise editline");
2130 if ((hl = history_init()) == NULL)
2131 fatal("Couldn't initialise editline history");
2132 history(hl, &hev, H_SETSIZE, 100);
2133 el_set(el, EL_HIST, history, hl);
2134
2135 el_set(el, EL_PROMPT, prompt);
2136 el_set(el, EL_EDITOR, "emacs");
2137 el_set(el, EL_TERMINAL, NULL);
2138 el_set(el, EL_SIGNAL, 1);
2139 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11002140
2141 /* Tab Completion */
Damien Miller02e87802013-08-21 02:38:51 +10002142 el_set(el, EL_ADDFN, "ftp-complete",
Darren Tuckerd78739a2010-10-24 10:56:32 +11002143 "Context sensitive argument completion", complete);
Darren Tucker909d8582010-01-08 19:02:40 +11002144 complete_ctx.conn = conn;
2145 complete_ctx.remote_pathp = &remote_path;
2146 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
2147 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Damien Millere0ee7272013-08-21 02:42:35 +10002148 /* enable ctrl-left-arrow and ctrl-right-arrow */
2149 el_set(el, EL_BIND, "\\e[1;5C", "em-next-word", NULL);
2150 el_set(el, EL_BIND, "\\e[5C", "em-next-word", NULL);
2151 el_set(el, EL_BIND, "\\e[1;5D", "ed-prev-word", NULL);
2152 el_set(el, EL_BIND, "\\e\\e[D", "ed-prev-word", NULL);
Damien Miller61353b32013-09-14 09:45:32 +10002153 /* make ^w match ksh behaviour */
2154 el_set(el, EL_BIND, "^w", "ed-delete-prev-word", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11002155 }
2156#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002157
Darren Tucker909d8582010-01-08 19:02:40 +11002158 remote_path = do_realpath(conn, ".");
2159 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11002160 fatal("Need cwd");
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002161 startdir = xstrdup(remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11002162
2163 if (file1 != NULL) {
2164 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11002165 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11002166
2167 if (remote_is_dir(conn, dir) && file2 == NULL) {
Damien Miller9303e652013-04-23 15:22:40 +10002168 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00002169 mprintf("Changing to: %s\n", dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002170 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11002171 if (parse_dispatch_command(conn, cmd,
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002172 &remote_path, startdir, 1) != 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10002173 free(dir);
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002174 free(startdir);
Darren Tuckera627d422013-06-02 07:31:17 +10002175 free(remote_path);
2176 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002177 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11002178 }
Damien Miller20e1fab2004-02-18 14:30:55 +11002179 } else {
Darren Tucker0af24052012-10-05 10:41:25 +10002180 /* XXX this is wrong wrt quoting */
Damien Miller0d032412013-07-25 11:56:52 +10002181 snprintf(cmd, sizeof cmd, "get%s %s%s%s",
2182 global_aflag ? " -a" : "", dir,
2183 file2 == NULL ? "" : " ",
2184 file2 == NULL ? "" : file2);
Darren Tucker909d8582010-01-08 19:02:40 +11002185 err = parse_dispatch_command(conn, cmd,
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002186 &remote_path, startdir, 1);
Darren Tuckera627d422013-06-02 07:31:17 +10002187 free(dir);
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002188 free(startdir);
Darren Tuckera627d422013-06-02 07:31:17 +10002189 free(remote_path);
2190 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002191 return (err);
2192 }
Darren Tuckera627d422013-06-02 07:31:17 +10002193 free(dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002194 }
2195
millert@openbsd.orgdb995f22014-11-26 18:34:51 +00002196 setvbuf(stdout, NULL, _IOLBF, 0);
2197 setvbuf(infile, NULL, _IOLBF, 0);
Damien Miller20e1fab2004-02-18 14:30:55 +11002198
Damien Miller0e2c1022005-08-12 22:16:22 +10002199 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11002200 err = 0;
2201 for (;;) {
2202 char *cp;
2203
Darren Tuckercdf547a2004-05-24 10:12:19 +10002204 signal(SIGINT, SIG_IGN);
2205
Darren Tucker2d963d82004-11-07 20:04:10 +11002206 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002207 if (interactive)
2208 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11002209 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002210 if (interactive)
2211 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11002212 break;
2213 }
Damien Miller0e2c1022005-08-12 22:16:22 +10002214 if (!interactive) { /* Echo command */
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00002215 mprintf("sftp> %s", cmd);
Damien Miller0e2c1022005-08-12 22:16:22 +10002216 if (strlen(cmd) > 0 &&
2217 cmd[strlen(cmd) - 1] != '\n')
2218 printf("\n");
2219 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002220 } else {
2221#ifdef USE_LIBEDIT
2222 const char *line;
2223 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11002224
Darren Tucker909d8582010-01-08 19:02:40 +11002225 if ((line = el_gets(el, &count)) == NULL ||
2226 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002227 printf("\n");
2228 break;
2229 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002230 history(hl, &hev, H_ENTER, line);
2231 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
2232 fprintf(stderr, "Error: input line too long\n");
2233 continue;
2234 }
2235#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002236 }
2237
Damien Miller20e1fab2004-02-18 14:30:55 +11002238 cp = strrchr(cmd, '\n');
2239 if (cp)
2240 *cp = '\0';
2241
Darren Tuckercdf547a2004-05-24 10:12:19 +10002242 /* Handle user interrupts gracefully during commands */
2243 interrupted = 0;
2244 signal(SIGINT, cmd_interrupt);
2245
Darren Tucker909d8582010-01-08 19:02:40 +11002246 err = parse_dispatch_command(conn, cmd, &remote_path,
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002247 startdir, batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11002248 if (err != 0)
2249 break;
2250 }
djm@openbsd.org3455f1e2018-04-13 05:04:12 +00002251 signal(SIGCHLD, SIG_DFL);
Darren Tuckera627d422013-06-02 07:31:17 +10002252 free(remote_path);
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002253 free(startdir);
Darren Tuckera627d422013-06-02 07:31:17 +10002254 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002255
Tim Rice027e8b12005-08-15 14:52:50 -07002256#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10002257 if (el != NULL)
2258 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07002259#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10002260
Damien Miller20e1fab2004-02-18 14:30:55 +11002261 /* err == 1 signifies normal "quit" exit */
2262 return (err >= 0 ? 0 : -1);
2263}
Damien Miller62d57f62003-01-10 21:43:24 +11002264
Ben Lindstrombba81212001-06-25 05:01:22 +00002265static void
Damien Millercc685c12003-06-04 22:51:38 +10002266connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11002267{
2268 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002269
Damien Miller33804262001-02-04 23:20:18 +11002270#ifdef USE_PIPES
2271 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002272
Damien Miller33804262001-02-04 23:20:18 +11002273 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2274 fatal("pipe: %s", strerror(errno));
2275 *in = pin[0];
2276 *out = pout[1];
2277 c_in = pout[0];
2278 c_out = pin[1];
2279#else /* USE_PIPES */
2280 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002281
Damien Miller33804262001-02-04 23:20:18 +11002282 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2283 fatal("socketpair: %s", strerror(errno));
2284 *in = *out = inout[0];
2285 c_in = c_out = inout[1];
2286#endif /* USE_PIPES */
2287
Damien Millercc685c12003-06-04 22:51:38 +10002288 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002289 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002290 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002291 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2292 (dup2(c_out, STDOUT_FILENO) == -1)) {
2293 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002294 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002295 }
2296 close(*in);
2297 close(*out);
2298 close(c_in);
2299 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002300
2301 /*
2302 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002303 * ignore SIGINT if we want to gracefully abort commands,
2304 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002305 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2306 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002307 */
2308 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002309 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002310 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002311 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002312 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002313 }
2314
Damien Millercc685c12003-06-04 22:51:38 +10002315 signal(SIGTERM, killchild);
2316 signal(SIGINT, killchild);
2317 signal(SIGHUP, killchild);
millert@openbsd.org2c6697c2016-10-18 12:41:22 +00002318 signal(SIGTSTP, suspchild);
2319 signal(SIGTTIN, suspchild);
2320 signal(SIGTTOU, suspchild);
djm@openbsd.org3455f1e2018-04-13 05:04:12 +00002321 signal(SIGCHLD, sigchld_handler);
Damien Miller33804262001-02-04 23:20:18 +11002322 close(c_in);
2323 close(c_out);
2324}
2325
Ben Lindstrombba81212001-06-25 05:01:22 +00002326static void
Damien Miller33804262001-02-04 23:20:18 +11002327usage(void)
2328{
Damien Miller025e01c2002-02-08 22:06:29 +11002329 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002330
Ben Lindstrom1e243242001-09-18 05:38:44 +00002331 fprintf(stderr,
djm@openbsd.org3575f0b2017-05-02 08:54:19 +00002332 "usage: %s [-46aCfpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002333 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002334 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002335 " [-o ssh_option] [-P port] [-R num_requests] "
2336 "[-S program]\n"
millert@openbsd.org887669e2017-10-21 23:06:24 +00002337 " [-s subsystem | sftp_server] destination\n",
2338 __progname);
Damien Miller33804262001-02-04 23:20:18 +11002339 exit(1);
2340}
2341
Kevin Stevesef4eea92001-02-05 12:42:17 +00002342int
Damien Miller33804262001-02-04 23:20:18 +11002343main(int argc, char **argv)
2344{
millert@openbsd.org887669e2017-10-21 23:06:24 +00002345 int in, out, ch, err, tmp, port = -1;
2346 char *host = NULL, *user, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002347 int debug_level = 0, sshver = 2;
2348 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002349 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002350 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002351 LogLevel ll = SYSLOG_LEVEL_INFO;
2352 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002353 extern int optind;
2354 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002355 struct sftp_conn *conn;
2356 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2357 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002358 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002359
dtucker@openbsd.orgffb1e7e2016-02-15 09:47:49 +00002360 ssh_malloc_init(); /* must be called before any mallocs */
Darren Tuckerce321d82005-10-03 18:11:24 +10002361 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2362 sanitise_stdfd();
Damien Millerdda78a02016-12-12 13:57:10 +11002363 msetlocale();
Darren Tuckerce321d82005-10-03 18:11:24 +10002364
Damien Miller59d3d5b2003-08-22 09:34:41 +10002365 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002366 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002367 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002368 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002369 addargs(&args, "-oForwardX11 no");
2370 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002371 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002372 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002373
Ben Lindstrom387c4722001-05-08 20:27:25 +00002374 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002375 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002376
Darren Tucker282b4022009-10-07 08:23:06 +11002377 while ((ch = getopt(argc, argv,
Damien Millerf29238e2013-10-17 11:48:52 +11002378 "1246afhpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002379 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002380 /* Passed through to ssh(1) */
2381 case '4':
2382 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002383 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002384 addargs(&args, "-%c", ch);
2385 break;
2386 /* Passed through to ssh(1) with argument */
2387 case 'F':
2388 case 'c':
2389 case 'i':
2390 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002391 addargs(&args, "-%c", ch);
2392 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002393 break;
2394 case 'q':
Damien Miller9303e652013-04-23 15:22:40 +10002395 ll = SYSLOG_LEVEL_ERROR;
2396 quiet = 1;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002397 showprogress = 0;
2398 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002399 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002400 case 'P':
millert@openbsd.org887669e2017-10-21 23:06:24 +00002401 port = a2port(optarg);
2402 if (port <= 0)
2403 fatal("Bad port \"%s\"\n", optarg);
Darren Tucker282b4022009-10-07 08:23:06 +11002404 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002405 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002406 if (debug_level < 3) {
2407 addargs(&args, "-v");
2408 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2409 }
2410 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002411 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002412 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002413 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002414 if (sftp_server == NULL)
2415 sftp_server = _PATH_SFTP_SERVER;
2416 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002417 case '2':
2418 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002419 break;
Damien Miller0d032412013-07-25 11:56:52 +10002420 case 'a':
2421 global_aflag = 1;
2422 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002423 case 'B':
2424 copy_buffer_len = strtol(optarg, &cp, 10);
2425 if (copy_buffer_len == 0 || *cp != '\0')
2426 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002427 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002428 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002429 if (batchmode)
2430 fatal("Batch file already specified.");
2431
2432 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002433 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002434 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002435 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002436 showprogress = 0;
Damien Miller9303e652013-04-23 15:22:40 +10002437 quiet = batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002438 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002439 break;
Damien Millerf29238e2013-10-17 11:48:52 +11002440 case 'f':
2441 global_fflag = 1;
2442 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002443 case 'p':
2444 global_pflag = 1;
2445 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002446 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002447 sftp_direct = optarg;
2448 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002449 case 'l':
2450 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2451 &errstr);
2452 if (errstr != NULL)
2453 usage();
2454 limit_kbps *= 1024; /* kbps */
2455 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002456 case 'r':
2457 global_rflag = 1;
2458 break;
Damien Miller16a13332002-02-13 14:03:56 +11002459 case 'R':
2460 num_requests = strtol(optarg, &cp, 10);
2461 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002462 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002463 optarg);
2464 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002465 case 's':
2466 sftp_server = optarg;
2467 break;
2468 case 'S':
2469 ssh_program = optarg;
2470 replacearg(&args, 0, "%s", ssh_program);
2471 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002472 case 'h':
2473 default:
Damien Miller33804262001-02-04 23:20:18 +11002474 usage();
2475 }
2476 }
2477
Damien Millerc0f27d82004-03-08 23:12:19 +11002478 if (!isatty(STDERR_FILENO))
2479 showprogress = 0;
2480
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002481 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2482
Damien Millerd14ee1e2002-02-05 12:27:31 +11002483 if (sftp_direct == NULL) {
2484 if (optind == argc || argc > (optind + 2))
2485 usage();
millert@openbsd.org887669e2017-10-21 23:06:24 +00002486 argv += optind;
Damien Miller33804262001-02-04 23:20:18 +11002487
millert@openbsd.org887669e2017-10-21 23:06:24 +00002488 switch (parse_uri("sftp", *argv, &user, &host, &tmp, &file1)) {
2489 case -1:
2490 usage();
2491 break;
2492 case 0:
2493 if (tmp != -1)
2494 port = tmp;
2495 break;
2496 default:
2497 if (parse_user_host_path(*argv, &user, &host,
2498 &file1) == -1) {
2499 /* Treat as a plain hostname. */
2500 host = xstrdup(*argv);
2501 host = cleanhostname(host);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002502 }
millert@openbsd.org887669e2017-10-21 23:06:24 +00002503 break;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002504 }
millert@openbsd.org887669e2017-10-21 23:06:24 +00002505 file2 = *(argv + 1);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002506
Damien Millerd14ee1e2002-02-05 12:27:31 +11002507 if (!*host) {
2508 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002509 usage();
2510 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002511
millert@openbsd.org887669e2017-10-21 23:06:24 +00002512 if (port != -1)
2513 addargs(&args, "-oPort %d", port);
2514 if (user != NULL) {
2515 addargs(&args, "-l");
2516 addargs(&args, "%s", user);
2517 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002518 addargs(&args, "-oProtocol %d", sshver);
2519
2520 /* no subsystem if the server-spec contains a '/' */
2521 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2522 addargs(&args, "-s");
2523
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002524 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002525 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002526 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002527 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002528
Damien Millercc685c12003-06-04 22:51:38 +10002529 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002530 } else {
2531 args.list = NULL;
2532 addargs(&args, "sftp-server");
2533
Damien Millercc685c12003-06-04 22:51:38 +10002534 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002535 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002536 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002537
Damien Miller65e42f82010-09-24 22:15:11 +10002538 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002539 if (conn == NULL)
2540 fatal("Couldn't initialise connection to server");
2541
Damien Miller9303e652013-04-23 15:22:40 +10002542 if (!quiet) {
Darren Tucker21063192010-01-08 17:10:36 +11002543 if (sftp_direct == NULL)
2544 fprintf(stderr, "Connected to %s.\n", host);
2545 else
2546 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2547 }
2548
2549 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002550
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002551#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002552 shutdown(in, SHUT_RDWR);
2553 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002554#endif
2555
Damien Miller33804262001-02-04 23:20:18 +11002556 close(in);
2557 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002558 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002559 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002560
bluhm@openbsd.orge7751aa2018-04-26 14:47:03 +00002561 while (waitpid(sshpid, NULL, 0) == -1 && sshpid > 1)
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002562 if (errno != EINTR)
2563 fatal("Couldn't wait for ssh process: %s",
2564 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002565
Damien Miller956f3fb2003-01-10 21:40:00 +11002566 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002567}