blob: d068f7e0feed0820985d4b16a8332c8fc7c323ae [file] [log] [blame]
bluhm@openbsd.orge7751aa2018-04-26 14:47:03 +00001/* $OpenBSD: sftp.c,v 1.185 2018/04/26 14:47:03 bluhm 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;
1446 case I_CHOWN:
1447 case I_CHGRP:
Damien Miller036d3072013-08-21 02:41:46 +10001448 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1449 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001450 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001451 if (argc - optidx < 1)
1452 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001453 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001454 l = strtol(argv[optidx], &cp2, base);
1455 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1456 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1457 l < 0) {
1458 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001459 error("You must supply a numeric argument "
1460 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001461 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001462 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001463 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001464 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001465 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001466 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001467 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001468 error("You must specify a path after a %s command.",
1469 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001470 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001471 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001472 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001473 break;
1474 case I_QUIT:
1475 case I_PWD:
1476 case I_LPWD:
1477 case I_HELP:
1478 case I_VERSION:
1479 case I_PROGRESS:
Damien Miller036d3072013-08-21 02:41:46 +10001480 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1481 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001482 break;
1483 default:
1484 fatal("Command not implemented");
1485 }
1486
1487 *cpp = cp;
1488 return(cmdnum);
1489}
1490
1491static int
1492parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00001493 const char *startdir, int err_abort)
Damien Miller20e1fab2004-02-18 14:30:55 +11001494{
1495 char *path1, *path2, *tmp;
djm@openbsd.org34a01b22016-04-08 08:19:17 +00001496 int ignore_errors = 0, aflag = 0, fflag = 0, hflag = 0,
Damien Millerd8accc02014-05-15 13:46:25 +10001497 iflag = 0;
Damien Millerf29238e2013-10-17 11:48:52 +11001498 int lflag = 0, pflag = 0, rflag = 0, sflag = 0;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001499 int cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001500 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001501 Attrib a, *aa;
deraadt@openbsd.org087266e2015-01-20 23:14:00 +00001502 char path_buf[PATH_MAX];
Damien Miller20e1fab2004-02-18 14:30:55 +11001503 int err = 0;
1504 glob_t g;
1505
1506 path1 = path2 = NULL;
Damien Millerf29238e2013-10-17 11:48:52 +11001507 cmdnum = parse_args(&cmd, &ignore_errors, &aflag, &fflag, &hflag,
1508 &iflag, &lflag, &pflag, &rflag, &sflag, &n_arg, &path1, &path2);
1509 if (ignore_errors != 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001510 err_abort = 0;
1511
1512 memset(&g, 0, sizeof(g));
1513
1514 /* Perform command */
1515 switch (cmdnum) {
1516 case 0:
1517 /* Blank line */
1518 break;
1519 case -1:
1520 /* Unrecognized command */
1521 err = -1;
1522 break;
Damien Miller0d032412013-07-25 11:56:52 +10001523 case I_REGET:
1524 aflag = 1;
1525 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001526 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001527 err = process_get(conn, path1, path2, *pwd, pflag,
Damien Millerf29238e2013-10-17 11:48:52 +11001528 rflag, aflag, fflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001529 break;
Damien Millerd8accc02014-05-15 13:46:25 +10001530 case I_REPUT:
1531 aflag = 1;
1532 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001533 case I_PUT:
Damien Millerf29238e2013-10-17 11:48:52 +11001534 err = process_put(conn, path1, path2, *pwd, pflag,
Damien Millerd8accc02014-05-15 13:46:25 +10001535 rflag, aflag, fflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001536 break;
1537 case I_RENAME:
1538 path1 = make_absolute(path1, *pwd);
1539 path2 = make_absolute(path2, *pwd);
Damien Millerc7dba122013-08-21 02:41:15 +10001540 err = do_rename(conn, path1, path2, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001541 break;
1542 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001543 sflag = 1;
1544 case I_LINK:
Damien Miller034f27a2013-08-21 02:40:44 +10001545 if (!sflag)
1546 path1 = make_absolute(path1, *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001547 path2 = make_absolute(path2, *pwd);
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001548 err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001549 break;
1550 case I_RM:
1551 path1 = make_absolute(path1, *pwd);
1552 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001553 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001554 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001555 mprintf("Removing %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001556 err = do_rm(conn, g.gl_pathv[i]);
1557 if (err != 0 && err_abort)
1558 break;
1559 }
1560 break;
1561 case I_MKDIR:
1562 path1 = make_absolute(path1, *pwd);
1563 attrib_clear(&a);
1564 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1565 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001566 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001567 break;
1568 case I_RMDIR:
1569 path1 = make_absolute(path1, *pwd);
1570 err = do_rmdir(conn, path1);
1571 break;
1572 case I_CHDIR:
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00001573 if (path1 == NULL || *path1 == '\0')
1574 path1 = xstrdup(startdir);
Damien Miller20e1fab2004-02-18 14:30:55 +11001575 path1 = make_absolute(path1, *pwd);
1576 if ((tmp = do_realpath(conn, path1)) == NULL) {
1577 err = 1;
1578 break;
1579 }
1580 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
Darren Tuckera627d422013-06-02 07:31:17 +10001581 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001582 err = 1;
1583 break;
1584 }
1585 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1586 error("Can't change directory: Can't check target");
Darren Tuckera627d422013-06-02 07:31:17 +10001587 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001588 err = 1;
1589 break;
1590 }
1591 if (!S_ISDIR(aa->perm)) {
1592 error("Can't change directory: \"%s\" is not "
1593 "a directory", tmp);
Darren Tuckera627d422013-06-02 07:31:17 +10001594 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001595 err = 1;
1596 break;
1597 }
Darren Tuckera627d422013-06-02 07:31:17 +10001598 free(*pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001599 *pwd = tmp;
1600 break;
1601 case I_LS:
1602 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001603 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001604 break;
1605 }
1606
1607 /* Strip pwd off beginning of non-absolute paths */
1608 tmp = NULL;
1609 if (*path1 != '/')
1610 tmp = *pwd;
1611
1612 path1 = make_absolute(path1, *pwd);
1613 err = do_globbed_ls(conn, path1, tmp, lflag);
1614 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001615 case I_DF:
1616 /* Default to current directory if no path specified */
1617 if (path1 == NULL)
1618 path1 = xstrdup(*pwd);
1619 path1 = make_absolute(path1, *pwd);
1620 err = do_df(conn, path1, hflag, iflag);
1621 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001622 case I_LCHDIR:
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00001623 if (path1 == NULL || *path1 == '\0')
1624 path1 = xstrdup("~");
deraadt@openbsd.org40ba4c92014-08-20 01:28:55 +00001625 tmp = tilde_expand_filename(path1, getuid());
djm@openbsd.org7ff880e2014-08-19 23:57:18 +00001626 free(path1);
1627 path1 = tmp;
Damien Miller20e1fab2004-02-18 14:30:55 +11001628 if (chdir(path1) == -1) {
1629 error("Couldn't change local directory to "
1630 "\"%s\": %s", path1, strerror(errno));
1631 err = 1;
1632 }
1633 break;
1634 case I_LMKDIR:
1635 if (mkdir(path1, 0777) == -1) {
1636 error("Couldn't create local directory "
1637 "\"%s\": %s", path1, strerror(errno));
1638 err = 1;
1639 }
1640 break;
1641 case I_LLS:
1642 local_do_ls(cmd);
1643 break;
1644 case I_SHELL:
1645 local_do_shell(cmd);
1646 break;
1647 case I_LUMASK:
1648 umask(n_arg);
1649 printf("Local umask: %03lo\n", n_arg);
1650 break;
1651 case I_CHMOD:
1652 path1 = make_absolute(path1, *pwd);
1653 attrib_clear(&a);
1654 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1655 a.perm = n_arg;
1656 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001657 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001658 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001659 mprintf("Changing mode on %s\n",
1660 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001661 err = do_setstat(conn, g.gl_pathv[i], &a);
1662 if (err != 0 && err_abort)
1663 break;
1664 }
1665 break;
1666 case I_CHOWN:
1667 case I_CHGRP:
1668 path1 = make_absolute(path1, *pwd);
1669 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001670 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001671 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001672 if (err_abort) {
1673 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001674 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001675 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001676 continue;
1677 }
1678 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1679 error("Can't get current ownership of "
1680 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001681 if (err_abort) {
1682 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001683 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001684 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001685 continue;
1686 }
1687 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1688 if (cmdnum == I_CHOWN) {
Damien Miller9303e652013-04-23 15:22:40 +10001689 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001690 mprintf("Changing owner on %s\n",
Damien Miller9303e652013-04-23 15:22:40 +10001691 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001692 aa->uid = n_arg;
1693 } else {
Damien Miller9303e652013-04-23 15:22:40 +10001694 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001695 mprintf("Changing group on %s\n",
Damien Miller9303e652013-04-23 15:22:40 +10001696 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001697 aa->gid = n_arg;
1698 }
1699 err = do_setstat(conn, g.gl_pathv[i], aa);
1700 if (err != 0 && err_abort)
1701 break;
1702 }
1703 break;
1704 case I_PWD:
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001705 mprintf("Remote working directory: %s\n", *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001706 break;
1707 case I_LPWD:
1708 if (!getcwd(path_buf, sizeof(path_buf))) {
1709 error("Couldn't get local cwd: %s", strerror(errno));
1710 err = -1;
1711 break;
1712 }
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001713 mprintf("Local working directory: %s\n", path_buf);
Damien Miller20e1fab2004-02-18 14:30:55 +11001714 break;
1715 case I_QUIT:
1716 /* Processed below */
1717 break;
1718 case I_HELP:
1719 help();
1720 break;
1721 case I_VERSION:
1722 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1723 break;
1724 case I_PROGRESS:
1725 showprogress = !showprogress;
1726 if (showprogress)
1727 printf("Progress meter enabled\n");
1728 else
1729 printf("Progress meter disabled\n");
1730 break;
1731 default:
1732 fatal("%d is not implemented", cmdnum);
1733 }
1734
1735 if (g.gl_pathc)
1736 globfree(&g);
Darren Tuckera627d422013-06-02 07:31:17 +10001737 free(path1);
1738 free(path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001739
1740 /* If an unignored error occurs in batch mode we should abort. */
1741 if (err_abort && err != 0)
1742 return (-1);
1743 else if (cmdnum == I_QUIT)
1744 return (1);
1745
1746 return (0);
1747}
1748
Darren Tucker2d963d82004-11-07 20:04:10 +11001749#ifdef USE_LIBEDIT
1750static char *
1751prompt(EditLine *el)
1752{
1753 return ("sftp> ");
1754}
Darren Tucker2d963d82004-11-07 20:04:10 +11001755
Darren Tucker909d8582010-01-08 19:02:40 +11001756/* Display entries in 'list' after skipping the first 'len' chars */
1757static void
1758complete_display(char **list, u_int len)
1759{
1760 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1761 struct winsize ws;
1762 char *tmp;
1763
1764 /* Count entries for sort and find longest */
Damien Miller02e87802013-08-21 02:38:51 +10001765 for (y = 0; list[y]; y++)
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +00001766 m = MAXIMUM(m, strlen(list[y]));
Darren Tucker909d8582010-01-08 19:02:40 +11001767
1768 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1769 width = ws.ws_col;
1770
1771 m = m > len ? m - len : 0;
1772 columns = width / (m + 2);
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +00001773 columns = MAXIMUM(columns, 1);
Darren Tucker909d8582010-01-08 19:02:40 +11001774 colspace = width / columns;
deraadt@openbsd.org9136ec12016-09-12 01:22:38 +00001775 colspace = MINIMUM(colspace, width);
Darren Tucker909d8582010-01-08 19:02:40 +11001776
1777 printf("\n");
1778 m = 1;
1779 for (y = 0; list[y]; y++) {
1780 llen = strlen(list[y]);
1781 tmp = llen > len ? list[y] + len : "";
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00001782 mprintf("%-*s", colspace, tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001783 if (m >= columns) {
1784 printf("\n");
1785 m = 1;
1786 } else
1787 m++;
1788 }
1789 printf("\n");
1790}
1791
1792/*
1793 * Given a "list" of words that begin with a common prefix of "word",
1794 * attempt to find an autocompletion to extends "word" by the next
1795 * characters common to all entries in "list".
1796 */
1797static char *
1798complete_ambiguous(const char *word, char **list, size_t count)
1799{
1800 if (word == NULL)
1801 return NULL;
1802
1803 if (count > 0) {
1804 u_int y, matchlen = strlen(list[0]);
1805
1806 /* Find length of common stem */
1807 for (y = 1; list[y]; y++) {
1808 u_int x;
1809
Damien Miller02e87802013-08-21 02:38:51 +10001810 for (x = 0; x < matchlen; x++)
1811 if (list[0][x] != list[y][x])
Darren Tucker909d8582010-01-08 19:02:40 +11001812 break;
1813
1814 matchlen = x;
1815 }
1816
1817 if (matchlen > strlen(word)) {
1818 char *tmp = xstrdup(list[0]);
1819
Darren Tucker340d1682010-01-09 08:54:31 +11001820 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001821 return tmp;
1822 }
Damien Miller02e87802013-08-21 02:38:51 +10001823 }
Darren Tucker909d8582010-01-08 19:02:40 +11001824
1825 return xstrdup(word);
1826}
1827
1828/* Autocomplete a sftp command */
1829static int
1830complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1831 int terminated)
1832{
1833 u_int y, count = 0, cmdlen, tmplen;
1834 char *tmp, **list, argterm[3];
1835 const LineInfo *lf;
1836
1837 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1838
1839 /* No command specified: display all available commands */
1840 if (cmd == NULL) {
1841 for (y = 0; cmds[y].c; y++)
1842 list[count++] = xstrdup(cmds[y].c);
Damien Miller02e87802013-08-21 02:38:51 +10001843
Darren Tucker909d8582010-01-08 19:02:40 +11001844 list[count] = NULL;
1845 complete_display(list, 0);
1846
Damien Miller02e87802013-08-21 02:38:51 +10001847 for (y = 0; list[y] != NULL; y++)
1848 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001849 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001850 return count;
1851 }
1852
1853 /* Prepare subset of commands that start with "cmd" */
1854 cmdlen = strlen(cmd);
1855 for (y = 0; cmds[y].c; y++) {
Damien Miller02e87802013-08-21 02:38:51 +10001856 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
Darren Tucker909d8582010-01-08 19:02:40 +11001857 list[count++] = xstrdup(cmds[y].c);
1858 }
1859 list[count] = NULL;
1860
Damien Miller47d81152011-11-25 13:53:48 +11001861 if (count == 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10001862 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001863 return 0;
Damien Miller47d81152011-11-25 13:53:48 +11001864 }
Darren Tucker909d8582010-01-08 19:02:40 +11001865
djm@openbsd.org001aa552018-04-10 00:10:49 +00001866 /* Complete ambiguous command */
Darren Tucker909d8582010-01-08 19:02:40 +11001867 tmp = complete_ambiguous(cmd, list, count);
1868 if (count > 1)
1869 complete_display(list, 0);
1870
Damien Miller02e87802013-08-21 02:38:51 +10001871 for (y = 0; list[y]; y++)
1872 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001873 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001874
1875 if (tmp != NULL) {
1876 tmplen = strlen(tmp);
1877 cmdlen = strlen(cmd);
1878 /* If cmd may be extended then do so */
1879 if (tmplen > cmdlen)
1880 if (el_insertstr(el, tmp + cmdlen) == -1)
1881 fatal("el_insertstr failed.");
1882 lf = el_line(el);
1883 /* Terminate argument cleanly */
1884 if (count == 1) {
1885 y = 0;
1886 if (!terminated)
1887 argterm[y++] = quote;
1888 if (lastarg || *(lf->cursor) != ' ')
1889 argterm[y++] = ' ';
1890 argterm[y] = '\0';
1891 if (y > 0 && el_insertstr(el, argterm) == -1)
1892 fatal("el_insertstr failed.");
1893 }
Darren Tuckera627d422013-06-02 07:31:17 +10001894 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001895 }
1896
1897 return count;
1898}
1899
1900/*
1901 * Determine whether a particular sftp command's arguments (if any)
1902 * represent local or remote files.
1903 */
1904static int
1905complete_is_remote(char *cmd) {
1906 int i;
1907
1908 if (cmd == NULL)
1909 return -1;
1910
1911 for (i = 0; cmds[i].c; i++) {
Damien Miller02e87802013-08-21 02:38:51 +10001912 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
Darren Tucker909d8582010-01-08 19:02:40 +11001913 return cmds[i].t;
1914 }
1915
1916 return -1;
1917}
1918
1919/* Autocomplete a filename "file" */
1920static int
1921complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1922 char *file, int remote, int lastarg, char quote, int terminated)
1923{
1924 glob_t g;
Darren Tuckerea647212013-06-06 08:19:09 +10001925 char *tmp, *tmp2, ins[8];
Darren Tucker17146d32012-10-05 10:46:16 +10001926 u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
Darren Tuckerea647212013-06-06 08:19:09 +10001927 int clen;
Darren Tucker909d8582010-01-08 19:02:40 +11001928 const LineInfo *lf;
Damien Miller02e87802013-08-21 02:38:51 +10001929
Darren Tucker909d8582010-01-08 19:02:40 +11001930 /* Glob from "file" location */
1931 if (file == NULL)
1932 tmp = xstrdup("*");
1933 else
1934 xasprintf(&tmp, "%s*", file);
1935
Darren Tucker191fcc62012-10-05 10:45:01 +10001936 /* Check if the path is absolute. */
1937 isabs = tmp[0] == '/';
1938
Darren Tucker909d8582010-01-08 19:02:40 +11001939 memset(&g, 0, sizeof(g));
1940 if (remote != LOCAL) {
1941 tmp = make_absolute(tmp, remote_path);
1942 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001943 } else
Darren Tucker909d8582010-01-08 19:02:40 +11001944 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001945
Darren Tucker909d8582010-01-08 19:02:40 +11001946 /* Determine length of pwd so we can trim completion display */
1947 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1948 /* Terminate counting on first unescaped glob metacharacter */
1949 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1950 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1951 hadglob = 1;
1952 break;
1953 }
1954 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1955 tmplen++;
1956 if (tmp[tmplen] == '/')
1957 pwdlen = tmplen + 1; /* track last seen '/' */
1958 }
Darren Tuckera627d422013-06-02 07:31:17 +10001959 free(tmp);
Damien Millerd7fd8be2014-05-15 14:24:59 +10001960 tmp = NULL;
Darren Tucker909d8582010-01-08 19:02:40 +11001961
Damien Miller02e87802013-08-21 02:38:51 +10001962 if (g.gl_matchc == 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001963 goto out;
1964
1965 if (g.gl_matchc > 1)
1966 complete_display(g.gl_pathv, pwdlen);
1967
Darren Tucker909d8582010-01-08 19:02:40 +11001968 /* Don't try to extend globs */
1969 if (file == NULL || hadglob)
1970 goto out;
1971
1972 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
Darren Tucker191fcc62012-10-05 10:45:01 +10001973 tmp = path_strip(tmp2, isabs ? NULL : remote_path);
Darren Tuckera627d422013-06-02 07:31:17 +10001974 free(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001975
1976 if (tmp == NULL)
1977 goto out;
1978
1979 tmplen = strlen(tmp);
1980 filelen = strlen(file);
1981
Darren Tucker17146d32012-10-05 10:46:16 +10001982 /* Count the number of escaped characters in the input string. */
1983 cesc = isesc = 0;
1984 for (i = 0; i < filelen; i++) {
1985 if (!isesc && file[i] == '\\' && i + 1 < filelen){
1986 isesc = 1;
1987 cesc++;
1988 } else
1989 isesc = 0;
1990 }
1991
1992 if (tmplen > (filelen - cesc)) {
1993 tmp2 = tmp + filelen - cesc;
Damien Miller02e87802013-08-21 02:38:51 +10001994 len = strlen(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001995 /* quote argument on way out */
Darren Tuckerea647212013-06-06 08:19:09 +10001996 for (i = 0; i < len; i += clen) {
1997 if ((clen = mblen(tmp2 + i, len - i)) < 0 ||
1998 (size_t)clen > sizeof(ins) - 2)
1999 fatal("invalid multibyte character");
Darren Tucker909d8582010-01-08 19:02:40 +11002000 ins[0] = '\\';
Darren Tuckerea647212013-06-06 08:19:09 +10002001 memcpy(ins + 1, tmp2 + i, clen);
2002 ins[clen + 1] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11002003 switch (tmp2[i]) {
2004 case '\'':
2005 case '"':
2006 case '\\':
2007 case '\t':
Darren Tuckerd78739a2010-10-24 10:56:32 +11002008 case '[':
Darren Tucker909d8582010-01-08 19:02:40 +11002009 case ' ':
Darren Tucker17146d32012-10-05 10:46:16 +10002010 case '#':
2011 case '*':
Darren Tucker909d8582010-01-08 19:02:40 +11002012 if (quote == '\0' || tmp2[i] == quote) {
2013 if (el_insertstr(el, ins) == -1)
2014 fatal("el_insertstr "
2015 "failed.");
2016 break;
2017 }
2018 /* FALLTHROUGH */
2019 default:
2020 if (el_insertstr(el, ins + 1) == -1)
2021 fatal("el_insertstr failed.");
2022 break;
2023 }
2024 }
2025 }
2026
2027 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11002028 if (g.gl_matchc == 1) {
2029 i = 0;
Damien Miller38094812014-05-15 14:25:18 +10002030 if (!terminated && quote != '\0')
Darren Tucker909d8582010-01-08 19:02:40 +11002031 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11002032 if (*(lf->cursor - 1) != '/' &&
2033 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11002034 ins[i++] = ' ';
2035 ins[i] = '\0';
2036 if (i > 0 && el_insertstr(el, ins) == -1)
2037 fatal("el_insertstr failed.");
2038 }
Darren Tuckera627d422013-06-02 07:31:17 +10002039 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11002040
2041 out:
2042 globfree(&g);
2043 return g.gl_matchc;
2044}
2045
2046/* tab-completion hook function, called via libedit */
2047static unsigned char
2048complete(EditLine *el, int ch)
2049{
Damien Miller02e87802013-08-21 02:38:51 +10002050 char **argv, *line, quote;
Damien Miller746d1a62013-07-18 16:13:02 +10002051 int argc, carg;
2052 u_int cursor, len, terminated, ret = CC_ERROR;
Darren Tucker909d8582010-01-08 19:02:40 +11002053 const LineInfo *lf;
2054 struct complete_ctx *complete_ctx;
2055
2056 lf = el_line(el);
2057 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
2058 fatal("%s: el_get failed", __func__);
2059
2060 /* Figure out which argument the cursor points to */
2061 cursor = lf->cursor - lf->buffer;
deraadt@openbsd.orgce445b02015-08-20 22:32:42 +00002062 line = xmalloc(cursor + 1);
Darren Tucker909d8582010-01-08 19:02:40 +11002063 memcpy(line, lf->buffer, cursor);
2064 line[cursor] = '\0';
2065 argv = makeargv(line, &carg, 1, &quote, &terminated);
Darren Tuckera627d422013-06-02 07:31:17 +10002066 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11002067
2068 /* Get all the arguments on the line */
2069 len = lf->lastchar - lf->buffer;
deraadt@openbsd.orgce445b02015-08-20 22:32:42 +00002070 line = xmalloc(len + 1);
Darren Tucker909d8582010-01-08 19:02:40 +11002071 memcpy(line, lf->buffer, len);
2072 line[len] = '\0';
2073 argv = makeargv(line, &argc, 1, NULL, NULL);
2074
2075 /* Ensure cursor is at EOL or a argument boundary */
2076 if (line[cursor] != ' ' && line[cursor] != '\0' &&
2077 line[cursor] != '\n') {
Darren Tuckera627d422013-06-02 07:31:17 +10002078 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11002079 return ret;
2080 }
2081
2082 if (carg == 0) {
2083 /* Show all available commands */
2084 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
2085 ret = CC_REDISPLAY;
2086 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
2087 /* Handle the command parsing */
2088 if (complete_cmd_parse(el, argv[0], argc == carg,
Damien Miller02e87802013-08-21 02:38:51 +10002089 quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11002090 ret = CC_REDISPLAY;
2091 } else if (carg >= 1) {
2092 /* Handle file parsing */
2093 int remote = complete_is_remote(argv[0]);
2094 char *filematch = NULL;
2095
2096 if (carg > 1 && line[cursor-1] != ' ')
2097 filematch = argv[carg - 1];
2098
2099 if (remote != 0 &&
2100 complete_match(el, complete_ctx->conn,
2101 *complete_ctx->remote_pathp, filematch,
Damien Miller02e87802013-08-21 02:38:51 +10002102 remote, carg == argc, quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11002103 ret = CC_REDISPLAY;
2104 }
2105
Damien Miller02e87802013-08-21 02:38:51 +10002106 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11002107 return ret;
2108}
Darren Tuckere67f7db2010-01-08 19:50:02 +11002109#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11002110
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002111static int
Darren Tucker21063192010-01-08 17:10:36 +11002112interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11002113{
Darren Tucker909d8582010-01-08 19:02:40 +11002114 char *remote_path;
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002115 char *dir = NULL, *startdir = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +11002116 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10002117 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11002118 EditLine *el = NULL;
2119#ifdef USE_LIBEDIT
2120 History *hl = NULL;
2121 HistEvent hev;
2122 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11002123 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11002124
2125 if (!batchmode && isatty(STDIN_FILENO)) {
2126 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
2127 fatal("Couldn't initialise editline");
2128 if ((hl = history_init()) == NULL)
2129 fatal("Couldn't initialise editline history");
2130 history(hl, &hev, H_SETSIZE, 100);
2131 el_set(el, EL_HIST, history, hl);
2132
2133 el_set(el, EL_PROMPT, prompt);
2134 el_set(el, EL_EDITOR, "emacs");
2135 el_set(el, EL_TERMINAL, NULL);
2136 el_set(el, EL_SIGNAL, 1);
2137 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11002138
2139 /* Tab Completion */
Damien Miller02e87802013-08-21 02:38:51 +10002140 el_set(el, EL_ADDFN, "ftp-complete",
Darren Tuckerd78739a2010-10-24 10:56:32 +11002141 "Context sensitive argument completion", complete);
Darren Tucker909d8582010-01-08 19:02:40 +11002142 complete_ctx.conn = conn;
2143 complete_ctx.remote_pathp = &remote_path;
2144 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
2145 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Damien Millere0ee7272013-08-21 02:42:35 +10002146 /* enable ctrl-left-arrow and ctrl-right-arrow */
2147 el_set(el, EL_BIND, "\\e[1;5C", "em-next-word", NULL);
2148 el_set(el, EL_BIND, "\\e[5C", "em-next-word", NULL);
2149 el_set(el, EL_BIND, "\\e[1;5D", "ed-prev-word", NULL);
2150 el_set(el, EL_BIND, "\\e\\e[D", "ed-prev-word", NULL);
Damien Miller61353b32013-09-14 09:45:32 +10002151 /* make ^w match ksh behaviour */
2152 el_set(el, EL_BIND, "^w", "ed-delete-prev-word", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11002153 }
2154#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002155
Darren Tucker909d8582010-01-08 19:02:40 +11002156 remote_path = do_realpath(conn, ".");
2157 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11002158 fatal("Need cwd");
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002159 startdir = xstrdup(remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11002160
2161 if (file1 != NULL) {
2162 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11002163 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11002164
2165 if (remote_is_dir(conn, dir) && file2 == NULL) {
Damien Miller9303e652013-04-23 15:22:40 +10002166 if (!quiet)
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00002167 mprintf("Changing to: %s\n", dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002168 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11002169 if (parse_dispatch_command(conn, cmd,
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002170 &remote_path, startdir, 1) != 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10002171 free(dir);
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002172 free(startdir);
Darren Tuckera627d422013-06-02 07:31:17 +10002173 free(remote_path);
2174 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002175 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11002176 }
Damien Miller20e1fab2004-02-18 14:30:55 +11002177 } else {
Darren Tucker0af24052012-10-05 10:41:25 +10002178 /* XXX this is wrong wrt quoting */
Damien Miller0d032412013-07-25 11:56:52 +10002179 snprintf(cmd, sizeof cmd, "get%s %s%s%s",
2180 global_aflag ? " -a" : "", dir,
2181 file2 == NULL ? "" : " ",
2182 file2 == NULL ? "" : file2);
Darren Tucker909d8582010-01-08 19:02:40 +11002183 err = parse_dispatch_command(conn, cmd,
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002184 &remote_path, startdir, 1);
Darren Tuckera627d422013-06-02 07:31:17 +10002185 free(dir);
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002186 free(startdir);
Darren Tuckera627d422013-06-02 07:31:17 +10002187 free(remote_path);
2188 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002189 return (err);
2190 }
Darren Tuckera627d422013-06-02 07:31:17 +10002191 free(dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002192 }
2193
millert@openbsd.orgdb995f22014-11-26 18:34:51 +00002194 setvbuf(stdout, NULL, _IOLBF, 0);
2195 setvbuf(infile, NULL, _IOLBF, 0);
Damien Miller20e1fab2004-02-18 14:30:55 +11002196
Damien Miller0e2c1022005-08-12 22:16:22 +10002197 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11002198 err = 0;
2199 for (;;) {
2200 char *cp;
2201
Darren Tuckercdf547a2004-05-24 10:12:19 +10002202 signal(SIGINT, SIG_IGN);
2203
Darren Tucker2d963d82004-11-07 20:04:10 +11002204 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002205 if (interactive)
2206 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11002207 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002208 if (interactive)
2209 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11002210 break;
2211 }
Damien Miller0e2c1022005-08-12 22:16:22 +10002212 if (!interactive) { /* Echo command */
schwarze@openbsd.org0e059cd2016-05-25 23:48:45 +00002213 mprintf("sftp> %s", cmd);
Damien Miller0e2c1022005-08-12 22:16:22 +10002214 if (strlen(cmd) > 0 &&
2215 cmd[strlen(cmd) - 1] != '\n')
2216 printf("\n");
2217 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002218 } else {
2219#ifdef USE_LIBEDIT
2220 const char *line;
2221 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11002222
Darren Tucker909d8582010-01-08 19:02:40 +11002223 if ((line = el_gets(el, &count)) == NULL ||
2224 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002225 printf("\n");
2226 break;
2227 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002228 history(hl, &hev, H_ENTER, line);
2229 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
2230 fprintf(stderr, "Error: input line too long\n");
2231 continue;
2232 }
2233#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002234 }
2235
Damien Miller20e1fab2004-02-18 14:30:55 +11002236 cp = strrchr(cmd, '\n');
2237 if (cp)
2238 *cp = '\0';
2239
Darren Tuckercdf547a2004-05-24 10:12:19 +10002240 /* Handle user interrupts gracefully during commands */
2241 interrupted = 0;
2242 signal(SIGINT, cmd_interrupt);
2243
Darren Tucker909d8582010-01-08 19:02:40 +11002244 err = parse_dispatch_command(conn, cmd, &remote_path,
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002245 startdir, batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11002246 if (err != 0)
2247 break;
2248 }
djm@openbsd.org3455f1e2018-04-13 05:04:12 +00002249 signal(SIGCHLD, SIG_DFL);
Darren Tuckera627d422013-06-02 07:31:17 +10002250 free(remote_path);
djm@openbsd.org@openbsd.orgfbe8e7a2017-11-03 03:46:52 +00002251 free(startdir);
Darren Tuckera627d422013-06-02 07:31:17 +10002252 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002253
Tim Rice027e8b12005-08-15 14:52:50 -07002254#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10002255 if (el != NULL)
2256 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07002257#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10002258
Damien Miller20e1fab2004-02-18 14:30:55 +11002259 /* err == 1 signifies normal "quit" exit */
2260 return (err >= 0 ? 0 : -1);
2261}
Damien Miller62d57f62003-01-10 21:43:24 +11002262
Ben Lindstrombba81212001-06-25 05:01:22 +00002263static void
Damien Millercc685c12003-06-04 22:51:38 +10002264connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11002265{
2266 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002267
Damien Miller33804262001-02-04 23:20:18 +11002268#ifdef USE_PIPES
2269 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002270
Damien Miller33804262001-02-04 23:20:18 +11002271 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2272 fatal("pipe: %s", strerror(errno));
2273 *in = pin[0];
2274 *out = pout[1];
2275 c_in = pout[0];
2276 c_out = pin[1];
2277#else /* USE_PIPES */
2278 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002279
Damien Miller33804262001-02-04 23:20:18 +11002280 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2281 fatal("socketpair: %s", strerror(errno));
2282 *in = *out = inout[0];
2283 c_in = c_out = inout[1];
2284#endif /* USE_PIPES */
2285
Damien Millercc685c12003-06-04 22:51:38 +10002286 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002287 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002288 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002289 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2290 (dup2(c_out, STDOUT_FILENO) == -1)) {
2291 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002292 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002293 }
2294 close(*in);
2295 close(*out);
2296 close(c_in);
2297 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002298
2299 /*
2300 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002301 * ignore SIGINT if we want to gracefully abort commands,
2302 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002303 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2304 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002305 */
2306 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002307 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002308 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002309 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002310 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002311 }
2312
Damien Millercc685c12003-06-04 22:51:38 +10002313 signal(SIGTERM, killchild);
2314 signal(SIGINT, killchild);
2315 signal(SIGHUP, killchild);
millert@openbsd.org2c6697c2016-10-18 12:41:22 +00002316 signal(SIGTSTP, suspchild);
2317 signal(SIGTTIN, suspchild);
2318 signal(SIGTTOU, suspchild);
djm@openbsd.org3455f1e2018-04-13 05:04:12 +00002319 signal(SIGCHLD, sigchld_handler);
Damien Miller33804262001-02-04 23:20:18 +11002320 close(c_in);
2321 close(c_out);
2322}
2323
Ben Lindstrombba81212001-06-25 05:01:22 +00002324static void
Damien Miller33804262001-02-04 23:20:18 +11002325usage(void)
2326{
Damien Miller025e01c2002-02-08 22:06:29 +11002327 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002328
Ben Lindstrom1e243242001-09-18 05:38:44 +00002329 fprintf(stderr,
djm@openbsd.org3575f0b2017-05-02 08:54:19 +00002330 "usage: %s [-46aCfpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002331 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002332 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002333 " [-o ssh_option] [-P port] [-R num_requests] "
2334 "[-S program]\n"
millert@openbsd.org887669e2017-10-21 23:06:24 +00002335 " [-s subsystem | sftp_server] destination\n",
2336 __progname);
Damien Miller33804262001-02-04 23:20:18 +11002337 exit(1);
2338}
2339
Kevin Stevesef4eea92001-02-05 12:42:17 +00002340int
Damien Miller33804262001-02-04 23:20:18 +11002341main(int argc, char **argv)
2342{
millert@openbsd.org887669e2017-10-21 23:06:24 +00002343 int in, out, ch, err, tmp, port = -1;
2344 char *host = NULL, *user, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002345 int debug_level = 0, sshver = 2;
2346 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002347 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002348 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002349 LogLevel ll = SYSLOG_LEVEL_INFO;
2350 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002351 extern int optind;
2352 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002353 struct sftp_conn *conn;
2354 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2355 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002356 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002357
dtucker@openbsd.orgffb1e7e2016-02-15 09:47:49 +00002358 ssh_malloc_init(); /* must be called before any mallocs */
Darren Tuckerce321d82005-10-03 18:11:24 +10002359 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2360 sanitise_stdfd();
Damien Millerdda78a02016-12-12 13:57:10 +11002361 msetlocale();
Darren Tuckerce321d82005-10-03 18:11:24 +10002362
Damien Miller59d3d5b2003-08-22 09:34:41 +10002363 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002364 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002365 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002366 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002367 addargs(&args, "-oForwardX11 no");
2368 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002369 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002370 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002371
Ben Lindstrom387c4722001-05-08 20:27:25 +00002372 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002373 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002374
Darren Tucker282b4022009-10-07 08:23:06 +11002375 while ((ch = getopt(argc, argv,
Damien Millerf29238e2013-10-17 11:48:52 +11002376 "1246afhpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002377 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002378 /* Passed through to ssh(1) */
2379 case '4':
2380 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002381 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002382 addargs(&args, "-%c", ch);
2383 break;
2384 /* Passed through to ssh(1) with argument */
2385 case 'F':
2386 case 'c':
2387 case 'i':
2388 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002389 addargs(&args, "-%c", ch);
2390 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002391 break;
2392 case 'q':
Damien Miller9303e652013-04-23 15:22:40 +10002393 ll = SYSLOG_LEVEL_ERROR;
2394 quiet = 1;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002395 showprogress = 0;
2396 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002397 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002398 case 'P':
millert@openbsd.org887669e2017-10-21 23:06:24 +00002399 port = a2port(optarg);
2400 if (port <= 0)
2401 fatal("Bad port \"%s\"\n", optarg);
Darren Tucker282b4022009-10-07 08:23:06 +11002402 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002403 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002404 if (debug_level < 3) {
2405 addargs(&args, "-v");
2406 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2407 }
2408 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002409 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002410 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002411 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002412 if (sftp_server == NULL)
2413 sftp_server = _PATH_SFTP_SERVER;
2414 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002415 case '2':
2416 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002417 break;
Damien Miller0d032412013-07-25 11:56:52 +10002418 case 'a':
2419 global_aflag = 1;
2420 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002421 case 'B':
2422 copy_buffer_len = strtol(optarg, &cp, 10);
2423 if (copy_buffer_len == 0 || *cp != '\0')
2424 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002425 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002426 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002427 if (batchmode)
2428 fatal("Batch file already specified.");
2429
2430 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002431 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002432 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002433 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002434 showprogress = 0;
Damien Miller9303e652013-04-23 15:22:40 +10002435 quiet = batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002436 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002437 break;
Damien Millerf29238e2013-10-17 11:48:52 +11002438 case 'f':
2439 global_fflag = 1;
2440 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002441 case 'p':
2442 global_pflag = 1;
2443 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002444 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002445 sftp_direct = optarg;
2446 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002447 case 'l':
2448 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2449 &errstr);
2450 if (errstr != NULL)
2451 usage();
2452 limit_kbps *= 1024; /* kbps */
2453 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002454 case 'r':
2455 global_rflag = 1;
2456 break;
Damien Miller16a13332002-02-13 14:03:56 +11002457 case 'R':
2458 num_requests = strtol(optarg, &cp, 10);
2459 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002460 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002461 optarg);
2462 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002463 case 's':
2464 sftp_server = optarg;
2465 break;
2466 case 'S':
2467 ssh_program = optarg;
2468 replacearg(&args, 0, "%s", ssh_program);
2469 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002470 case 'h':
2471 default:
Damien Miller33804262001-02-04 23:20:18 +11002472 usage();
2473 }
2474 }
2475
Damien Millerc0f27d82004-03-08 23:12:19 +11002476 if (!isatty(STDERR_FILENO))
2477 showprogress = 0;
2478
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002479 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2480
Damien Millerd14ee1e2002-02-05 12:27:31 +11002481 if (sftp_direct == NULL) {
2482 if (optind == argc || argc > (optind + 2))
2483 usage();
millert@openbsd.org887669e2017-10-21 23:06:24 +00002484 argv += optind;
Damien Miller33804262001-02-04 23:20:18 +11002485
millert@openbsd.org887669e2017-10-21 23:06:24 +00002486 switch (parse_uri("sftp", *argv, &user, &host, &tmp, &file1)) {
2487 case -1:
2488 usage();
2489 break;
2490 case 0:
2491 if (tmp != -1)
2492 port = tmp;
2493 break;
2494 default:
2495 if (parse_user_host_path(*argv, &user, &host,
2496 &file1) == -1) {
2497 /* Treat as a plain hostname. */
2498 host = xstrdup(*argv);
2499 host = cleanhostname(host);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002500 }
millert@openbsd.org887669e2017-10-21 23:06:24 +00002501 break;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002502 }
millert@openbsd.org887669e2017-10-21 23:06:24 +00002503 file2 = *(argv + 1);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002504
Damien Millerd14ee1e2002-02-05 12:27:31 +11002505 if (!*host) {
2506 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002507 usage();
2508 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002509
millert@openbsd.org887669e2017-10-21 23:06:24 +00002510 if (port != -1)
2511 addargs(&args, "-oPort %d", port);
2512 if (user != NULL) {
2513 addargs(&args, "-l");
2514 addargs(&args, "%s", user);
2515 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002516 addargs(&args, "-oProtocol %d", sshver);
2517
2518 /* no subsystem if the server-spec contains a '/' */
2519 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2520 addargs(&args, "-s");
2521
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002522 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002523 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002524 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002525 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002526
Damien Millercc685c12003-06-04 22:51:38 +10002527 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002528 } else {
2529 args.list = NULL;
2530 addargs(&args, "sftp-server");
2531
Damien Millercc685c12003-06-04 22:51:38 +10002532 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002533 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002534 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002535
Damien Miller65e42f82010-09-24 22:15:11 +10002536 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002537 if (conn == NULL)
2538 fatal("Couldn't initialise connection to server");
2539
Damien Miller9303e652013-04-23 15:22:40 +10002540 if (!quiet) {
Darren Tucker21063192010-01-08 17:10:36 +11002541 if (sftp_direct == NULL)
2542 fprintf(stderr, "Connected to %s.\n", host);
2543 else
2544 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2545 }
2546
2547 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002548
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002549#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002550 shutdown(in, SHUT_RDWR);
2551 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002552#endif
2553
Damien Miller33804262001-02-04 23:20:18 +11002554 close(in);
2555 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002556 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002557 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002558
bluhm@openbsd.orge7751aa2018-04-26 14:47:03 +00002559 while (waitpid(sshpid, NULL, 0) == -1 && sshpid > 1)
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002560 if (errno != EINTR)
2561 fatal("Couldn't wait for ssh process: %s",
2562 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002563
Damien Miller956f3fb2003-01-10 21:40:00 +11002564 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002565}