blob: ff4d63d5ca6be1120b469d1491db9702bf8ae3a7 [file] [log] [blame]
Damien Miller00707762014-07-09 13:07:06 +10001/* $OpenBSD: sftp.c,v 1.164 2014/07/09 01:45:10 djm Exp $ */
Damien Miller33804262001-02-04 23:20:18 +11002/*
Damien Miller4e60ed72004-02-17 17:07:59 +11003 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
Damien Miller33804262001-02-04 23:20:18 +11004 *
Damien Miller4e60ed72004-02-17 17:07:59 +11005 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
Damien Miller33804262001-02-04 23:20:18 +11008 *
Damien Miller4e60ed72004-02-17 17:07:59 +11009 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Damien Miller33804262001-02-04 23:20:18 +110016 */
17
18#include "includes.h"
19
Damien Miller9cf6d072006-03-15 11:29:24 +110020#include <sys/types.h>
Damien Millerd7834352006-08-05 12:39:39 +100021#include <sys/ioctl.h>
Damien Millerf17883e2006-03-15 11:45:54 +110022#ifdef HAVE_SYS_STAT_H
23# include <sys/stat.h>
24#endif
Damien Miller8dbffe72006-08-05 11:02:17 +100025#include <sys/param.h>
Damien Millere3b60b52006-07-10 21:08:03 +100026#include <sys/socket.h>
Damien Miller9cf6d072006-03-15 11:29:24 +110027#include <sys/wait.h>
Darren Tucker5b2e2ba2008-06-08 09:25:28 +100028#ifdef HAVE_SYS_STATVFS_H
Damien Millerd671e5a2008-05-19 14:53:33 +100029#include <sys/statvfs.h>
Darren Tucker5b2e2ba2008-06-08 09:25:28 +100030#endif
Darren Tucker2d963d82004-11-07 20:04:10 +110031
Damien Miller1cbc2922007-10-26 14:27:45 +100032#include <ctype.h>
Darren Tucker39972492006-07-12 22:22:46 +100033#include <errno.h>
34
Damien Miller03e20032006-03-15 11:16:59 +110035#ifdef HAVE_PATHS_H
Damien Millera9263d02006-03-15 11:18:26 +110036# include <paths.h>
Damien Miller03e20032006-03-15 11:16:59 +110037#endif
Darren Tucker1b0dd172009-10-07 08:37:48 +110038#ifdef HAVE_LIBGEN_H
39#include <libgen.h>
40#endif
Darren Tuckerae133d42013-06-06 08:30:20 +100041#ifdef HAVE_LOCALE_H
42# include <locale.h>
43#endif
Darren Tucker2d963d82004-11-07 20:04:10 +110044#ifdef USE_LIBEDIT
45#include <histedit.h>
46#else
47typedef void EditLine;
48#endif
Damien Miller6ff3cad2006-03-15 11:52:09 +110049#include <signal.h>
Damien Millere7a1e5c2006-08-05 11:34:19 +100050#include <stdlib.h>
Damien Millera7a73ee2006-08-05 11:37:59 +100051#include <stdio.h>
Damien Millere3476ed2006-07-24 14:13:33 +100052#include <string.h>
Damien Millere6b3b612006-07-24 14:01:23 +100053#include <unistd.h>
Damien Millerd7834352006-08-05 12:39:39 +100054#include <stdarg.h>
Damien Miller33804262001-02-04 23:20:18 +110055
Damien Millera7058ec2008-05-20 08:57:06 +100056#ifdef HAVE_UTIL_H
57# include <util.h>
58#endif
59
Damien Miller33804262001-02-04 23:20:18 +110060#include "xmalloc.h"
61#include "log.h"
62#include "pathnames.h"
Ben Lindstrom4529b702001-05-03 23:39:53 +000063#include "misc.h"
Damien Miller33804262001-02-04 23:20:18 +110064
65#include "sftp.h"
Damien Millerd7834352006-08-05 12:39:39 +100066#include "buffer.h"
Damien Miller33804262001-02-04 23:20:18 +110067#include "sftp-common.h"
68#include "sftp-client.h"
Damien Millerd7d46bb2004-02-18 14:11:13 +110069
Darren Tucker21063192010-01-08 17:10:36 +110070#define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */
71#define DEFAULT_NUM_REQUESTS 64 /* # concurrent outstanding requests */
72
Damien Miller20e1fab2004-02-18 14:30:55 +110073/* File to read commands from */
74FILE* infile;
75
76/* Are we in batchfile mode? */
77int batchmode = 0;
78
Damien Miller20e1fab2004-02-18 14:30:55 +110079/* PID of ssh transport process */
80static pid_t sshpid = -1;
81
Damien Miller9303e652013-04-23 15:22:40 +100082/* Suppress diagnositic messages */
83int quiet = 0;
84
Damien Miller20e1fab2004-02-18 14:30:55 +110085/* This is set to 0 if the progressmeter is not desired. */
Damien Millerc0f27d82004-03-08 23:12:19 +110086int showprogress = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +110087
Darren Tucker1b0dd172009-10-07 08:37:48 +110088/* When this option is set, we always recursively download/upload directories */
89int global_rflag = 0;
90
Damien Millerd8accc02014-05-15 13:46:25 +100091/* When this option is set, we resume download or upload if possible */
Damien Miller0d032412013-07-25 11:56:52 +100092int global_aflag = 0;
93
Darren Tucker1b0dd172009-10-07 08:37:48 +110094/* When this option is set, the file transfers will always preserve times */
95int global_pflag = 0;
96
Damien Millerf29238e2013-10-17 11:48:52 +110097/* When this option is set, transfers will have fsync() called on each file */
98int global_fflag = 0;
99
Darren Tuckercdf547a2004-05-24 10:12:19 +1000100/* SIGINT received during command processing */
101volatile sig_atomic_t interrupted = 0;
102
Darren Tuckerb9123452004-06-22 13:06:45 +1000103/* I wish qsort() took a separate ctx for the comparison function...*/
104int sort_flag;
105
Darren Tucker909d8582010-01-08 19:02:40 +1100106/* Context used for commandline completion */
107struct complete_ctx {
108 struct sftp_conn *conn;
109 char **remote_pathp;
110};
111
Damien Miller20e1fab2004-02-18 14:30:55 +1100112int remote_glob(struct sftp_conn *, const char *, int,
113 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
Damien Miller33804262001-02-04 23:20:18 +1100114
Kevin Steves12888d12001-03-05 19:50:57 +0000115extern char *__progname;
Kevin Steves12888d12001-03-05 19:50:57 +0000116
Damien Miller20e1fab2004-02-18 14:30:55 +1100117/* Separators for interactive commands */
118#define WHITESPACE " \t\r\n"
Damien Millerd7686fd2001-02-10 00:40:03 +1100119
Darren Tuckerb9123452004-06-22 13:06:45 +1000120/* ls flags */
Darren Tucker2901e2d2010-01-13 22:44:06 +1100121#define LS_LONG_VIEW 0x0001 /* Full view ala ls -l */
122#define LS_SHORT_VIEW 0x0002 /* Single row view ala ls -1 */
123#define LS_NUMERIC_VIEW 0x0004 /* Long view with numeric uid/gid */
124#define LS_NAME_SORT 0x0008 /* Sort by name (default) */
125#define LS_TIME_SORT 0x0010 /* Sort by mtime */
126#define LS_SIZE_SORT 0x0020 /* Sort by file size */
127#define LS_REVERSE_SORT 0x0040 /* Reverse sort order */
128#define LS_SHOW_ALL 0x0080 /* Don't skip filenames starting with '.' */
129#define LS_SI_UNITS 0x0100 /* Display sizes as K, M, G, etc. */
Darren Tuckerb9123452004-06-22 13:06:45 +1000130
Darren Tucker2901e2d2010-01-13 22:44:06 +1100131#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS)
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000132#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
Damien Miller20e1fab2004-02-18 14:30:55 +1100133
134/* Commands for interactive mode */
Damien Miller02e87802013-08-21 02:38:51 +1000135enum sftp_command {
136 I_CHDIR = 1,
137 I_CHGRP,
138 I_CHMOD,
139 I_CHOWN,
140 I_DF,
141 I_GET,
142 I_HELP,
143 I_LCHDIR,
144 I_LINK,
145 I_LLS,
146 I_LMKDIR,
147 I_LPWD,
148 I_LS,
149 I_LUMASK,
150 I_MKDIR,
151 I_PUT,
152 I_PWD,
153 I_QUIT,
Damien Millerb15cd7b2014-05-15 13:46:52 +1000154 I_REGET,
Damien Miller02e87802013-08-21 02:38:51 +1000155 I_RENAME,
Damien Millerb15cd7b2014-05-15 13:46:52 +1000156 I_REPUT,
Damien Miller02e87802013-08-21 02:38:51 +1000157 I_RM,
158 I_RMDIR,
159 I_SHELL,
160 I_SYMLINK,
161 I_VERSION,
162 I_PROGRESS,
Damien Miller02e87802013-08-21 02:38:51 +1000163};
Damien Miller20e1fab2004-02-18 14:30:55 +1100164
165struct CMD {
166 const char *c;
167 const int n;
Darren Tucker909d8582010-01-08 19:02:40 +1100168 const int t;
Damien Miller20e1fab2004-02-18 14:30:55 +1100169};
170
Darren Tucker909d8582010-01-08 19:02:40 +1100171/* Type of completion */
172#define NOARGS 0
173#define REMOTE 1
174#define LOCAL 2
175
Damien Miller20e1fab2004-02-18 14:30:55 +1100176static const struct CMD cmds[] = {
Darren Tucker909d8582010-01-08 19:02:40 +1100177 { "bye", I_QUIT, NOARGS },
178 { "cd", I_CHDIR, REMOTE },
179 { "chdir", I_CHDIR, REMOTE },
180 { "chgrp", I_CHGRP, REMOTE },
181 { "chmod", I_CHMOD, REMOTE },
182 { "chown", I_CHOWN, REMOTE },
183 { "df", I_DF, REMOTE },
184 { "dir", I_LS, REMOTE },
185 { "exit", I_QUIT, NOARGS },
186 { "get", I_GET, REMOTE },
187 { "help", I_HELP, NOARGS },
188 { "lcd", I_LCHDIR, LOCAL },
189 { "lchdir", I_LCHDIR, LOCAL },
190 { "lls", I_LLS, LOCAL },
191 { "lmkdir", I_LMKDIR, LOCAL },
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100192 { "ln", I_LINK, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100193 { "lpwd", I_LPWD, LOCAL },
194 { "ls", I_LS, REMOTE },
195 { "lumask", I_LUMASK, NOARGS },
196 { "mkdir", I_MKDIR, REMOTE },
Damien Miller099fc162010-05-10 11:56:50 +1000197 { "mget", I_GET, REMOTE },
198 { "mput", I_PUT, LOCAL },
Darren Tucker909d8582010-01-08 19:02:40 +1100199 { "progress", I_PROGRESS, NOARGS },
200 { "put", I_PUT, LOCAL },
201 { "pwd", I_PWD, REMOTE },
202 { "quit", I_QUIT, NOARGS },
Damien Miller0d032412013-07-25 11:56:52 +1000203 { "reget", I_REGET, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100204 { "rename", I_RENAME, REMOTE },
Damien Millerd8accc02014-05-15 13:46:25 +1000205 { "reput", I_REPUT, LOCAL },
Darren Tucker909d8582010-01-08 19:02:40 +1100206 { "rm", I_RM, REMOTE },
207 { "rmdir", I_RMDIR, REMOTE },
208 { "symlink", I_SYMLINK, REMOTE },
209 { "version", I_VERSION, NOARGS },
210 { "!", I_SHELL, NOARGS },
211 { "?", I_HELP, NOARGS },
212 { NULL, -1, -1 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100213};
214
Darren Tucker21063192010-01-08 17:10:36 +1100215int interactive_loop(struct sftp_conn *, char *file1, char *file2);
Damien Miller20e1fab2004-02-18 14:30:55 +1100216
Damien Millerb6c85fc2007-01-05 16:30:41 +1100217/* ARGSUSED */
Damien Miller20e1fab2004-02-18 14:30:55 +1100218static void
Darren Tuckercdf547a2004-05-24 10:12:19 +1000219killchild(int signo)
220{
Darren Tuckerba66df82005-01-24 21:57:40 +1100221 if (sshpid > 1) {
Darren Tuckercdf547a2004-05-24 10:12:19 +1000222 kill(sshpid, SIGTERM);
Darren Tuckerba66df82005-01-24 21:57:40 +1100223 waitpid(sshpid, NULL, 0);
224 }
Darren Tuckercdf547a2004-05-24 10:12:19 +1000225
226 _exit(1);
227}
228
Damien Millerb6c85fc2007-01-05 16:30:41 +1100229/* ARGSUSED */
Darren Tuckercdf547a2004-05-24 10:12:19 +1000230static void
231cmd_interrupt(int signo)
232{
233 const char msg[] = "\rInterrupt \n";
Darren Tuckere2f189a2004-12-06 22:45:53 +1100234 int olderrno = errno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000235
Darren Tuckerdbee3082013-05-16 20:32:29 +1000236 (void)write(STDERR_FILENO, msg, sizeof(msg) - 1);
Darren Tuckercdf547a2004-05-24 10:12:19 +1000237 interrupted = 1;
Darren Tuckere2f189a2004-12-06 22:45:53 +1100238 errno = olderrno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000239}
240
241static void
Damien Miller20e1fab2004-02-18 14:30:55 +1100242help(void)
243{
Damien Miller62fd18a2009-01-28 16:14:09 +1100244 printf("Available commands:\n"
245 "bye Quit sftp\n"
246 "cd path Change remote directory to 'path'\n"
247 "chgrp grp path Change group of file 'path' to 'grp'\n"
248 "chmod mode path Change permissions of file 'path' to 'mode'\n"
249 "chown own path Change owner of file 'path' to 'own'\n"
250 "df [-hi] [path] Display statistics for current directory or\n"
251 " filesystem containing 'path'\n"
252 "exit Quit sftp\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100253 "get [-Ppr] remote [local] Download file\n"
Damien Miller0d032412013-07-25 11:56:52 +1000254 "reget remote [local] Resume download file\n"
Damien Millerd8accc02014-05-15 13:46:25 +1000255 "reput [local] remote Resume upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100256 "help Display this help text\n"
257 "lcd path Change local directory to 'path'\n"
258 "lls [ls-options [path]] Display local directory listing\n"
259 "lmkdir path Create local directory\n"
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100260 "ln [-s] oldpath newpath Link remote file (-s for symlink)\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100261 "lpwd Print local working directory\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100262 "ls [-1afhlnrSt] [path] Display remote directory listing\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100263 "lumask umask Set local umask to 'umask'\n"
264 "mkdir path Create remote directory\n"
265 "progress Toggle display of progress meter\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100266 "put [-Ppr] local [remote] Upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100267 "pwd Display remote working directory\n"
268 "quit Quit sftp\n"
269 "rename oldpath newpath Rename remote file\n"
270 "rm path Delete remote file\n"
271 "rmdir path Remove remote directory\n"
272 "symlink oldpath newpath Symlink remote file\n"
273 "version Show SFTP version\n"
274 "!command Execute 'command' in local shell\n"
275 "! Escape to local shell\n"
276 "? Synonym for help\n");
Damien Miller20e1fab2004-02-18 14:30:55 +1100277}
278
279static void
280local_do_shell(const char *args)
281{
282 int status;
283 char *shell;
284 pid_t pid;
285
286 if (!*args)
287 args = NULL;
288
Damien Miller38d9a962010-10-07 22:07:11 +1100289 if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
Damien Miller20e1fab2004-02-18 14:30:55 +1100290 shell = _PATH_BSHELL;
291
292 if ((pid = fork()) == -1)
293 fatal("Couldn't fork: %s", strerror(errno));
294
295 if (pid == 0) {
296 /* XXX: child has pipe fds to ssh subproc open - issue? */
297 if (args) {
298 debug3("Executing %s -c \"%s\"", shell, args);
299 execl(shell, shell, "-c", args, (char *)NULL);
300 } else {
301 debug3("Executing %s", shell);
302 execl(shell, shell, (char *)NULL);
303 }
304 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
305 strerror(errno));
306 _exit(1);
307 }
308 while (waitpid(pid, &status, 0) == -1)
309 if (errno != EINTR)
310 fatal("Couldn't wait for child: %s", strerror(errno));
311 if (!WIFEXITED(status))
Damien Miller55b04f12006-03-26 14:23:17 +1100312 error("Shell exited abnormally");
Damien Miller20e1fab2004-02-18 14:30:55 +1100313 else if (WEXITSTATUS(status))
314 error("Shell exited with status %d", WEXITSTATUS(status));
315}
316
317static void
318local_do_ls(const char *args)
319{
320 if (!args || !*args)
321 local_do_shell(_PATH_LS);
322 else {
323 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
324 char *buf = xmalloc(len);
325
326 /* XXX: quoting - rip quoting code from ftp? */
327 snprintf(buf, len, _PATH_LS " %s", args);
328 local_do_shell(buf);
Darren Tuckera627d422013-06-02 07:31:17 +1000329 free(buf);
Damien Miller20e1fab2004-02-18 14:30:55 +1100330 }
331}
332
333/* Strip one path (usually the pwd) from the start of another */
334static char *
335path_strip(char *path, char *strip)
336{
337 size_t len;
338
339 if (strip == NULL)
340 return (xstrdup(path));
341
342 len = strlen(strip);
Darren Tuckere2f189a2004-12-06 22:45:53 +1100343 if (strncmp(path, strip, len) == 0) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100344 if (strip[len - 1] != '/' && path[len] == '/')
345 len++;
346 return (xstrdup(path + len));
347 }
348
349 return (xstrdup(path));
350}
351
352static char *
Damien Miller20e1fab2004-02-18 14:30:55 +1100353make_absolute(char *p, char *pwd)
354{
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000355 char *abs_str;
Damien Miller20e1fab2004-02-18 14:30:55 +1100356
357 /* Derelativise */
358 if (p && p[0] != '/') {
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000359 abs_str = path_append(pwd, p);
Darren Tuckera627d422013-06-02 07:31:17 +1000360 free(p);
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000361 return(abs_str);
Damien Miller20e1fab2004-02-18 14:30:55 +1100362 } else
363 return(p);
364}
365
366static int
Damien Miller0d032412013-07-25 11:56:52 +1000367parse_getput_flags(const char *cmd, char **argv, int argc,
Damien Millerf29238e2013-10-17 11:48:52 +1100368 int *aflag, int *fflag, int *pflag, int *rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100369{
Damien Millerf184bcf2008-06-29 22:45:13 +1000370 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000371 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100372
Damien Miller1cbc2922007-10-26 14:27:45 +1000373 optind = optreset = 1;
374 opterr = 0;
375
Damien Millerf29238e2013-10-17 11:48:52 +1100376 *aflag = *fflag = *rflag = *pflag = 0;
377 while ((ch = getopt(argc, argv, "afPpRr")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000378 switch (ch) {
Damien Miller0d032412013-07-25 11:56:52 +1000379 case 'a':
380 *aflag = 1;
381 break;
Damien Millerf29238e2013-10-17 11:48:52 +1100382 case 'f':
383 *fflag = 1;
384 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100385 case 'p':
386 case 'P':
387 *pflag = 1;
388 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100389 case 'r':
390 case 'R':
391 *rflag = 1;
392 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100393 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000394 error("%s: Invalid flag -%c", cmd, optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000395 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100396 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100397 }
398
Damien Miller1cbc2922007-10-26 14:27:45 +1000399 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100400}
401
402static int
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100403parse_link_flags(const char *cmd, char **argv, int argc, int *sflag)
404{
405 extern int opterr, optind, optopt, optreset;
406 int ch;
407
408 optind = optreset = 1;
409 opterr = 0;
410
411 *sflag = 0;
412 while ((ch = getopt(argc, argv, "s")) != -1) {
413 switch (ch) {
414 case 's':
415 *sflag = 1;
416 break;
417 default:
418 error("%s: Invalid flag -%c", cmd, optopt);
419 return -1;
420 }
421 }
422
423 return optind;
424}
425
426static int
Damien Millerc7dba122013-08-21 02:41:15 +1000427parse_rename_flags(const char *cmd, char **argv, int argc, int *lflag)
428{
429 extern int opterr, optind, optopt, optreset;
430 int ch;
431
432 optind = optreset = 1;
433 opterr = 0;
434
435 *lflag = 0;
436 while ((ch = getopt(argc, argv, "l")) != -1) {
437 switch (ch) {
438 case 'l':
439 *lflag = 1;
440 break;
441 default:
442 error("%s: Invalid flag -%c", cmd, optopt);
443 return -1;
444 }
445 }
446
447 return optind;
448}
449
450static int
Damien Miller1cbc2922007-10-26 14:27:45 +1000451parse_ls_flags(char **argv, int argc, int *lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100452{
Damien Millerf184bcf2008-06-29 22:45:13 +1000453 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000454 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100455
Damien Miller1cbc2922007-10-26 14:27:45 +1000456 optind = optreset = 1;
457 opterr = 0;
458
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000459 *lflag = LS_NAME_SORT;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100460 while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000461 switch (ch) {
462 case '1':
463 *lflag &= ~VIEW_FLAGS;
464 *lflag |= LS_SHORT_VIEW;
465 break;
466 case 'S':
467 *lflag &= ~SORT_FLAGS;
468 *lflag |= LS_SIZE_SORT;
469 break;
470 case 'a':
471 *lflag |= LS_SHOW_ALL;
472 break;
473 case 'f':
474 *lflag &= ~SORT_FLAGS;
475 break;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100476 case 'h':
477 *lflag |= LS_SI_UNITS;
478 break;
Damien Miller1cbc2922007-10-26 14:27:45 +1000479 case 'l':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100480 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000481 *lflag |= LS_LONG_VIEW;
482 break;
483 case 'n':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100484 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000485 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
486 break;
487 case 'r':
488 *lflag |= LS_REVERSE_SORT;
489 break;
490 case 't':
491 *lflag &= ~SORT_FLAGS;
492 *lflag |= LS_TIME_SORT;
493 break;
494 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000495 error("ls: Invalid flag -%c", optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000496 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100497 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100498 }
499
Damien Miller1cbc2922007-10-26 14:27:45 +1000500 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100501}
502
503static int
Damien Millerd671e5a2008-05-19 14:53:33 +1000504parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
505{
Damien Millerf184bcf2008-06-29 22:45:13 +1000506 extern int opterr, optind, optopt, optreset;
Damien Millerd671e5a2008-05-19 14:53:33 +1000507 int ch;
508
509 optind = optreset = 1;
510 opterr = 0;
511
512 *hflag = *iflag = 0;
513 while ((ch = getopt(argc, argv, "hi")) != -1) {
514 switch (ch) {
515 case 'h':
516 *hflag = 1;
517 break;
518 case 'i':
519 *iflag = 1;
520 break;
521 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000522 error("%s: Invalid flag -%c", cmd, optopt);
Damien Millerd671e5a2008-05-19 14:53:33 +1000523 return -1;
524 }
525 }
526
527 return optind;
528}
529
530static int
Damien Miller036d3072013-08-21 02:41:46 +1000531parse_no_flags(const char *cmd, char **argv, int argc)
532{
533 extern int opterr, optind, optopt, optreset;
534 int ch;
535
536 optind = optreset = 1;
537 opterr = 0;
538
539 while ((ch = getopt(argc, argv, "")) != -1) {
540 switch (ch) {
541 default:
542 error("%s: Invalid flag -%c", cmd, optopt);
543 return -1;
544 }
545 }
546
547 return optind;
548}
549
550static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100551is_dir(char *path)
552{
553 struct stat sb;
554
555 /* XXX: report errors? */
556 if (stat(path, &sb) == -1)
557 return(0);
558
Darren Tucker1e80e402006-09-21 12:59:33 +1000559 return(S_ISDIR(sb.st_mode));
Damien Miller20e1fab2004-02-18 14:30:55 +1100560}
561
562static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100563remote_is_dir(struct sftp_conn *conn, char *path)
564{
565 Attrib *a;
566
567 /* XXX: report errors? */
568 if ((a = do_stat(conn, path, 1)) == NULL)
569 return(0);
570 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
571 return(0);
Darren Tucker1e80e402006-09-21 12:59:33 +1000572 return(S_ISDIR(a->perm));
Damien Miller20e1fab2004-02-18 14:30:55 +1100573}
574
Darren Tucker1b0dd172009-10-07 08:37:48 +1100575/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
Damien Miller20e1fab2004-02-18 14:30:55 +1100576static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100577pathname_is_dir(char *pathname)
578{
579 size_t l = strlen(pathname);
580
581 return l > 0 && pathname[l - 1] == '/';
582}
583
584static int
585process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
Damien Millerf29238e2013-10-17 11:48:52 +1100586 int pflag, int rflag, int resume, int fflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100587{
588 char *abs_src = NULL;
589 char *abs_dst = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100590 glob_t g;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100591 char *filename, *tmp=NULL;
Damien Miller00707762014-07-09 13:07:06 +1000592 int i, r, err = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100593
594 abs_src = xstrdup(src);
595 abs_src = make_absolute(abs_src, pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +1100596 memset(&g, 0, sizeof(g));
Darren Tucker1b0dd172009-10-07 08:37:48 +1100597
Damien Miller20e1fab2004-02-18 14:30:55 +1100598 debug3("Looking up %s", abs_src);
Damien Miller00707762014-07-09 13:07:06 +1000599 if ((r = remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) != 0) {
600 if (r == GLOB_NOSPACE) {
601 error("Too many matches for \"%s\".", abs_src);
602 } else {
603 error("File \"%s\" not found.", abs_src);
604 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100605 err = -1;
606 goto out;
607 }
608
Darren Tucker1b0dd172009-10-07 08:37:48 +1100609 /*
610 * If multiple matches then dst must be a directory or
611 * unspecified.
612 */
613 if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
614 error("Multiple source paths, but destination "
615 "\"%s\" is not a directory", dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100616 err = -1;
617 goto out;
618 }
619
Darren Tuckercdf547a2004-05-24 10:12:19 +1000620 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100621 tmp = xstrdup(g.gl_pathv[i]);
622 if ((filename = basename(tmp)) == NULL) {
623 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000624 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100625 err = -1;
626 goto out;
627 }
628
629 if (g.gl_matchc == 1 && dst) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100630 if (is_dir(dst)) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100631 abs_dst = path_append(dst, filename);
632 } else {
Damien Miller20e1fab2004-02-18 14:30:55 +1100633 abs_dst = xstrdup(dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100634 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100635 } else if (dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100636 abs_dst = path_append(dst, filename);
637 } else {
638 abs_dst = xstrdup(filename);
639 }
Darren Tuckera627d422013-06-02 07:31:17 +1000640 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100641
Damien Miller0d032412013-07-25 11:56:52 +1000642 resume |= global_aflag;
643 if (!quiet && resume)
644 printf("Resuming %s to %s\n", g.gl_pathv[i], abs_dst);
645 else if (!quiet && !resume)
Damien Miller9303e652013-04-23 15:22:40 +1000646 printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100647 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
Damien Miller0d032412013-07-25 11:56:52 +1000648 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
Damien Millerf29238e2013-10-17 11:48:52 +1100649 pflag || global_pflag, 1, resume,
650 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100651 err = -1;
652 } else {
653 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
Damien Millerf29238e2013-10-17 11:48:52 +1100654 pflag || global_pflag, resume,
655 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100656 err = -1;
657 }
Darren Tuckera627d422013-06-02 07:31:17 +1000658 free(abs_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100659 abs_dst = NULL;
660 }
661
662out:
Darren Tuckera627d422013-06-02 07:31:17 +1000663 free(abs_src);
Damien Miller20e1fab2004-02-18 14:30:55 +1100664 globfree(&g);
665 return(err);
666}
667
668static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100669process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
Damien Millerd8accc02014-05-15 13:46:25 +1000670 int pflag, int rflag, int resume, int fflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100671{
672 char *tmp_dst = NULL;
673 char *abs_dst = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100674 char *tmp = NULL, *filename = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100675 glob_t g;
676 int err = 0;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100677 int i, dst_is_dir = 1;
Damien Milleraec5cf82008-02-10 22:26:24 +1100678 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100679
680 if (dst) {
681 tmp_dst = xstrdup(dst);
682 tmp_dst = make_absolute(tmp_dst, pwd);
683 }
684
685 memset(&g, 0, sizeof(g));
686 debug3("Looking up %s", src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100687 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100688 error("File \"%s\" not found.", src);
689 err = -1;
690 goto out;
691 }
692
Darren Tucker1b0dd172009-10-07 08:37:48 +1100693 /* If we aren't fetching to pwd then stash this status for later */
694 if (tmp_dst != NULL)
695 dst_is_dir = remote_is_dir(conn, tmp_dst);
696
Damien Miller20e1fab2004-02-18 14:30:55 +1100697 /* If multiple matches, dst may be directory or unspecified */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100698 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
699 error("Multiple paths match, but destination "
700 "\"%s\" is not a directory", tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100701 err = -1;
702 goto out;
703 }
704
Darren Tuckercdf547a2004-05-24 10:12:19 +1000705 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Milleraec5cf82008-02-10 22:26:24 +1100706 if (stat(g.gl_pathv[i], &sb) == -1) {
707 err = -1;
708 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
709 continue;
710 }
Damien Miller02e87802013-08-21 02:38:51 +1000711
Darren Tucker1b0dd172009-10-07 08:37:48 +1100712 tmp = xstrdup(g.gl_pathv[i]);
713 if ((filename = basename(tmp)) == NULL) {
714 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000715 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100716 err = -1;
717 goto out;
718 }
719
720 if (g.gl_matchc == 1 && tmp_dst) {
721 /* If directory specified, append filename */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100722 if (dst_is_dir)
723 abs_dst = path_append(tmp_dst, filename);
724 else
Damien Miller20e1fab2004-02-18 14:30:55 +1100725 abs_dst = xstrdup(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100726 } else if (tmp_dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100727 abs_dst = path_append(tmp_dst, filename);
728 } else {
729 abs_dst = make_absolute(xstrdup(filename), pwd);
730 }
Darren Tuckera627d422013-06-02 07:31:17 +1000731 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100732
Damien Millerd8accc02014-05-15 13:46:25 +1000733 resume |= global_aflag;
734 if (!quiet && resume)
Damien Miller3dc27172014-05-15 14:37:59 +1000735 printf("Resuming upload of %s to %s\n", g.gl_pathv[i],
Damien Millerd8accc02014-05-15 13:46:25 +1000736 abs_dst);
737 else if (!quiet && !resume)
Damien Miller9303e652013-04-23 15:22:40 +1000738 printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100739 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
740 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
Damien Millerd8accc02014-05-15 13:46:25 +1000741 pflag || global_pflag, 1, resume,
Damien Millerf29238e2013-10-17 11:48:52 +1100742 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100743 err = -1;
744 } else {
745 if (do_upload(conn, g.gl_pathv[i], abs_dst,
Damien Millerd8accc02014-05-15 13:46:25 +1000746 pflag || global_pflag, resume,
Damien Millerf29238e2013-10-17 11:48:52 +1100747 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100748 err = -1;
749 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100750 }
751
752out:
Darren Tuckera627d422013-06-02 07:31:17 +1000753 free(abs_dst);
754 free(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100755 globfree(&g);
756 return(err);
757}
758
759static int
760sdirent_comp(const void *aa, const void *bb)
761{
762 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
763 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000764 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100765
Darren Tuckerb9123452004-06-22 13:06:45 +1000766#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000767 if (sort_flag & LS_NAME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000768 return (rmul * strcmp(a->filename, b->filename));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000769 else if (sort_flag & LS_TIME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000770 return (rmul * NCMP(a->a.mtime, b->a.mtime));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000771 else if (sort_flag & LS_SIZE_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000772 return (rmul * NCMP(a->a.size, b->a.size));
773
774 fatal("Unknown ls sort type");
Damien Miller20e1fab2004-02-18 14:30:55 +1100775}
776
777/* sftp ls.1 replacement for directories */
778static int
779do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
780{
Damien Millereccb9de2005-06-17 12:59:34 +1000781 int n;
782 u_int c = 1, colspace = 0, columns = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100783 SFTP_DIRENT **d;
784
785 if ((n = do_readdir(conn, path, &d)) != 0)
786 return (n);
787
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000788 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000789 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100790 struct winsize ws;
791 char *tmp;
792
793 /* Count entries for sort and find longest filename */
Darren Tucker9a526452004-06-22 13:09:55 +1000794 for (n = 0; d[n] != NULL; n++) {
795 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
796 m = MAX(m, strlen(d[n]->filename));
797 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100798
799 /* Add any subpath that also needs to be counted */
800 tmp = path_strip(path, strip_path);
801 m += strlen(tmp);
Darren Tuckera627d422013-06-02 07:31:17 +1000802 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100803
804 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
805 width = ws.ws_col;
806
807 columns = width / (m + 2);
808 columns = MAX(columns, 1);
809 colspace = width / columns;
810 colspace = MIN(colspace, width);
811 }
812
Darren Tuckerb9123452004-06-22 13:06:45 +1000813 if (lflag & SORT_FLAGS) {
Damien Miller653b93b2005-11-05 15:15:23 +1100814 for (n = 0; d[n] != NULL; n++)
815 ; /* count entries */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000816 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
Darren Tuckerb9123452004-06-22 13:06:45 +1000817 qsort(d, n, sizeof(*d), sdirent_comp);
818 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100819
Darren Tuckercdf547a2004-05-24 10:12:19 +1000820 for (n = 0; d[n] != NULL && !interrupted; n++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100821 char *tmp, *fname;
822
Darren Tucker9a526452004-06-22 13:09:55 +1000823 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
824 continue;
825
Damien Miller20e1fab2004-02-18 14:30:55 +1100826 tmp = path_append(path, d[n]->filename);
827 fname = path_strip(tmp, strip_path);
Darren Tuckera627d422013-06-02 07:31:17 +1000828 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100829
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000830 if (lflag & LS_LONG_VIEW) {
Darren Tucker2901e2d2010-01-13 22:44:06 +1100831 if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000832 char *lname;
833 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100834
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000835 memset(&sb, 0, sizeof(sb));
836 attrib_to_stat(&d[n]->a, &sb);
Darren Tucker2901e2d2010-01-13 22:44:06 +1100837 lname = ls_file(fname, &sb, 1,
838 (lflag & LS_SI_UNITS));
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000839 printf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000840 free(lname);
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000841 } else
842 printf("%s\n", d[n]->longname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100843 } else {
844 printf("%-*s", colspace, fname);
845 if (c >= columns) {
846 printf("\n");
847 c = 1;
848 } else
849 c++;
850 }
851
Darren Tuckera627d422013-06-02 07:31:17 +1000852 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100853 }
854
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000855 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100856 printf("\n");
857
858 free_sftp_dirents(d);
859 return (0);
860}
861
862/* sftp ls.1 replacement which handles path globs */
863static int
864do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
865 int lflag)
866{
Damien Millera6e121a2010-10-07 21:39:17 +1100867 char *fname, *lname;
Damien Miller68e2e562010-10-07 21:39:55 +1100868 glob_t g;
Damien Miller00707762014-07-09 13:07:06 +1000869 int err, r;
Damien Miller68e2e562010-10-07 21:39:55 +1100870 struct winsize ws;
871 u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100872
873 memset(&g, 0, sizeof(g));
874
Damien Miller00707762014-07-09 13:07:06 +1000875 if ((r = remote_glob(conn, path,
Damien Millerd7be70d2011-09-22 21:43:06 +1000876 GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
Damien Miller00707762014-07-09 13:07:06 +1000877 NULL, &g)) != 0 ||
Damien Millera6e121a2010-10-07 21:39:17 +1100878 (g.gl_pathc && !g.gl_matchc)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100879 if (g.gl_pathc)
880 globfree(&g);
Damien Miller00707762014-07-09 13:07:06 +1000881 if (r == GLOB_NOSPACE) {
882 error("Can't ls: Too many matches for \"%s\"", path);
883 } else {
884 error("Can't ls: \"%s\" not found", path);
885 }
Damien Millera6e121a2010-10-07 21:39:17 +1100886 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100887 }
888
Darren Tuckercdf547a2004-05-24 10:12:19 +1000889 if (interrupted)
890 goto out;
891
Damien Miller20e1fab2004-02-18 14:30:55 +1100892 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100893 * If the glob returns a single match and it is a directory,
894 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100895 */
Damien Millera6e121a2010-10-07 21:39:17 +1100896 if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
897 S_ISDIR(g.gl_statv[0]->st_mode)) {
898 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
899 globfree(&g);
900 return err;
Damien Miller20e1fab2004-02-18 14:30:55 +1100901 }
902
Damien Miller68e2e562010-10-07 21:39:55 +1100903 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
904 width = ws.ws_col;
Damien Miller20e1fab2004-02-18 14:30:55 +1100905
Damien Miller68e2e562010-10-07 21:39:55 +1100906 if (!(lflag & LS_SHORT_VIEW)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100907 /* Count entries for sort and find longest filename */
908 for (i = 0; g.gl_pathv[i]; i++)
909 m = MAX(m, strlen(g.gl_pathv[i]));
910
Damien Miller20e1fab2004-02-18 14:30:55 +1100911 columns = width / (m + 2);
912 columns = MAX(columns, 1);
913 colspace = width / columns;
914 }
915
Damien Millerea858292012-06-30 08:33:32 +1000916 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100917 fname = path_strip(g.gl_pathv[i], strip_path);
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000918 if (lflag & LS_LONG_VIEW) {
Damien Millera6e121a2010-10-07 21:39:17 +1100919 if (g.gl_statv[i] == NULL) {
920 error("no stat information for %s", fname);
921 continue;
922 }
923 lname = ls_file(fname, g.gl_statv[i], 1,
924 (lflag & LS_SI_UNITS));
Damien Miller20e1fab2004-02-18 14:30:55 +1100925 printf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000926 free(lname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100927 } else {
928 printf("%-*s", colspace, fname);
929 if (c >= columns) {
930 printf("\n");
931 c = 1;
932 } else
933 c++;
934 }
Darren Tuckera627d422013-06-02 07:31:17 +1000935 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100936 }
937
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000938 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100939 printf("\n");
940
Darren Tuckercdf547a2004-05-24 10:12:19 +1000941 out:
Damien Miller20e1fab2004-02-18 14:30:55 +1100942 if (g.gl_pathc)
943 globfree(&g);
944
Damien Millera6e121a2010-10-07 21:39:17 +1100945 return 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100946}
947
Damien Millerd671e5a2008-05-19 14:53:33 +1000948static int
949do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
950{
Darren Tucker7b598892008-06-09 22:49:36 +1000951 struct sftp_statvfs st;
Damien Millerd671e5a2008-05-19 14:53:33 +1000952 char s_used[FMT_SCALED_STRSIZE];
953 char s_avail[FMT_SCALED_STRSIZE];
954 char s_root[FMT_SCALED_STRSIZE];
955 char s_total[FMT_SCALED_STRSIZE];
Darren Tuckerb5082e92010-01-08 18:51:47 +1100956 unsigned long long ffree;
Damien Millerd671e5a2008-05-19 14:53:33 +1000957
958 if (do_statvfs(conn, path, &st, 1) == -1)
959 return -1;
960 if (iflag) {
Darren Tuckerb5082e92010-01-08 18:51:47 +1100961 ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
Damien Millerd671e5a2008-05-19 14:53:33 +1000962 printf(" Inodes Used Avail "
963 "(root) %%Capacity\n");
964 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
965 (unsigned long long)st.f_files,
966 (unsigned long long)(st.f_files - st.f_ffree),
967 (unsigned long long)st.f_favail,
Darren Tuckerb5082e92010-01-08 18:51:47 +1100968 (unsigned long long)st.f_ffree, ffree);
Damien Millerd671e5a2008-05-19 14:53:33 +1000969 } else if (hflag) {
970 strlcpy(s_used, "error", sizeof(s_used));
971 strlcpy(s_avail, "error", sizeof(s_avail));
972 strlcpy(s_root, "error", sizeof(s_root));
973 strlcpy(s_total, "error", sizeof(s_total));
974 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
975 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
976 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
977 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
978 printf(" Size Used Avail (root) %%Capacity\n");
979 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
980 s_total, s_used, s_avail, s_root,
981 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
982 st.f_blocks));
983 } else {
984 printf(" Size Used Avail "
985 "(root) %%Capacity\n");
986 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
987 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
988 (unsigned long long)(st.f_frsize *
989 (st.f_blocks - st.f_bfree) / 1024),
990 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
991 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
992 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
993 st.f_blocks));
994 }
995 return 0;
996}
997
Damien Miller1cbc2922007-10-26 14:27:45 +1000998/*
999 * Undo escaping of glob sequences in place. Used to undo extra escaping
1000 * applied in makeargv() when the string is destined for a function that
1001 * does not glob it.
1002 */
1003static void
1004undo_glob_escape(char *s)
1005{
1006 size_t i, j;
1007
1008 for (i = j = 0;;) {
1009 if (s[i] == '\0') {
1010 s[j] = '\0';
1011 return;
1012 }
1013 if (s[i] != '\\') {
1014 s[j++] = s[i++];
1015 continue;
1016 }
1017 /* s[i] == '\\' */
1018 ++i;
1019 switch (s[i]) {
1020 case '?':
1021 case '[':
1022 case '*':
1023 case '\\':
1024 s[j++] = s[i++];
1025 break;
1026 case '\0':
1027 s[j++] = '\\';
1028 s[j] = '\0';
1029 return;
1030 default:
1031 s[j++] = '\\';
1032 s[j++] = s[i++];
1033 break;
1034 }
1035 }
1036}
1037
1038/*
1039 * Split a string into an argument vector using sh(1)-style quoting,
1040 * comment and escaping rules, but with some tweaks to handle glob(3)
1041 * wildcards.
Darren Tucker909d8582010-01-08 19:02:40 +11001042 * The "sloppy" flag allows for recovery from missing terminating quote, for
1043 * use in parsing incomplete commandlines during tab autocompletion.
1044 *
Damien Miller1cbc2922007-10-26 14:27:45 +10001045 * Returns NULL on error or a NULL-terminated array of arguments.
Darren Tucker909d8582010-01-08 19:02:40 +11001046 *
1047 * If "lastquote" is not NULL, the quoting character used for the last
1048 * argument is placed in *lastquote ("\0", "'" or "\"").
Damien Miller02e87802013-08-21 02:38:51 +10001049 *
Darren Tucker909d8582010-01-08 19:02:40 +11001050 * If "terminated" is not NULL, *terminated will be set to 1 when the
1051 * last argument's quote has been properly terminated or 0 otherwise.
1052 * This parameter is only of use if "sloppy" is set.
Damien Miller1cbc2922007-10-26 14:27:45 +10001053 */
1054#define MAXARGS 128
1055#define MAXARGLEN 8192
1056static char **
Darren Tucker909d8582010-01-08 19:02:40 +11001057makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
1058 u_int *terminated)
Damien Miller1cbc2922007-10-26 14:27:45 +10001059{
1060 int argc, quot;
1061 size_t i, j;
1062 static char argvs[MAXARGLEN];
1063 static char *argv[MAXARGS + 1];
1064 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
1065
1066 *argcp = argc = 0;
1067 if (strlen(arg) > sizeof(argvs) - 1) {
1068 args_too_longs:
1069 error("string too long");
1070 return NULL;
1071 }
Darren Tucker909d8582010-01-08 19:02:40 +11001072 if (terminated != NULL)
1073 *terminated = 1;
1074 if (lastquote != NULL)
1075 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001076 state = MA_START;
1077 i = j = 0;
1078 for (;;) {
Damien Miller07daed52012-10-31 08:57:55 +11001079 if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){
Darren Tucker063018d2012-10-05 10:43:58 +10001080 error("Too many arguments.");
1081 return NULL;
1082 }
Damien Millerfdb23062013-11-21 13:57:15 +11001083 if (isspace((unsigned char)arg[i])) {
Damien Miller1cbc2922007-10-26 14:27:45 +10001084 if (state == MA_UNQUOTED) {
1085 /* Terminate current argument */
1086 argvs[j++] = '\0';
1087 argc++;
1088 state = MA_START;
1089 } else if (state != MA_START)
1090 argvs[j++] = arg[i];
1091 } else if (arg[i] == '"' || arg[i] == '\'') {
1092 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1093 if (state == MA_START) {
1094 argv[argc] = argvs + j;
1095 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +11001096 if (lastquote != NULL)
1097 *lastquote = arg[i];
Damien Miller02e87802013-08-21 02:38:51 +10001098 } else if (state == MA_UNQUOTED)
Damien Miller1cbc2922007-10-26 14:27:45 +10001099 state = q;
1100 else if (state == q)
1101 state = MA_UNQUOTED;
1102 else
1103 argvs[j++] = arg[i];
1104 } else if (arg[i] == '\\') {
1105 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1106 quot = state == MA_SQUOTE ? '\'' : '"';
1107 /* Unescape quote we are in */
1108 /* XXX support \n and friends? */
1109 if (arg[i + 1] == quot) {
1110 i++;
1111 argvs[j++] = arg[i];
1112 } else if (arg[i + 1] == '?' ||
1113 arg[i + 1] == '[' || arg[i + 1] == '*') {
1114 /*
1115 * Special case for sftp: append
1116 * double-escaped glob sequence -
1117 * glob will undo one level of
1118 * escaping. NB. string can grow here.
1119 */
1120 if (j >= sizeof(argvs) - 5)
1121 goto args_too_longs;
1122 argvs[j++] = '\\';
1123 argvs[j++] = arg[i++];
1124 argvs[j++] = '\\';
1125 argvs[j++] = arg[i];
1126 } else {
1127 argvs[j++] = arg[i++];
1128 argvs[j++] = arg[i];
1129 }
1130 } else {
1131 if (state == MA_START) {
1132 argv[argc] = argvs + j;
1133 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001134 if (lastquote != NULL)
1135 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001136 }
1137 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1138 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1139 /*
1140 * Special case for sftp: append
1141 * escaped glob sequence -
1142 * glob will undo one level of
1143 * escaping.
1144 */
1145 argvs[j++] = arg[i++];
1146 argvs[j++] = arg[i];
1147 } else {
1148 /* Unescape everything */
1149 /* XXX support \n and friends? */
1150 i++;
1151 argvs[j++] = arg[i];
1152 }
1153 }
1154 } else if (arg[i] == '#') {
1155 if (state == MA_SQUOTE || state == MA_DQUOTE)
1156 argvs[j++] = arg[i];
1157 else
1158 goto string_done;
1159 } else if (arg[i] == '\0') {
1160 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001161 if (sloppy) {
1162 state = MA_UNQUOTED;
1163 if (terminated != NULL)
1164 *terminated = 0;
1165 goto string_done;
1166 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001167 error("Unterminated quoted argument");
1168 return NULL;
1169 }
1170 string_done:
1171 if (state == MA_UNQUOTED) {
1172 argvs[j++] = '\0';
1173 argc++;
1174 }
1175 break;
1176 } else {
1177 if (state == MA_START) {
1178 argv[argc] = argvs + j;
1179 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001180 if (lastquote != NULL)
1181 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001182 }
1183 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1184 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1185 /*
1186 * Special case for sftp: escape quoted
1187 * glob(3) wildcards. NB. string can grow
1188 * here.
1189 */
1190 if (j >= sizeof(argvs) - 3)
1191 goto args_too_longs;
1192 argvs[j++] = '\\';
1193 argvs[j++] = arg[i];
1194 } else
1195 argvs[j++] = arg[i];
1196 }
1197 i++;
1198 }
1199 *argcp = argc;
1200 return argv;
1201}
1202
Damien Miller20e1fab2004-02-18 14:30:55 +11001203static int
Damien Millerd8accc02014-05-15 13:46:25 +10001204parse_args(const char **cpp, int *ignore_errors, int *aflag,
1205 int *fflag, int *hflag, int *iflag, int *lflag, int *pflag,
1206 int *rflag, int *sflag,
Damien Millerf29238e2013-10-17 11:48:52 +11001207 unsigned long *n_arg, char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001208{
1209 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001210 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001211 int base = 0;
1212 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001213 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001214
1215 /* Skip leading whitespace */
1216 cp = cp + strspn(cp, WHITESPACE);
1217
Damien Miller20e1fab2004-02-18 14:30:55 +11001218 /* Check for leading '-' (disable error processing) */
Damien Millerf29238e2013-10-17 11:48:52 +11001219 *ignore_errors = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001220 if (*cp == '-') {
Damien Millerf29238e2013-10-17 11:48:52 +11001221 *ignore_errors = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001222 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001223 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001224 }
1225
Darren Tucker70cc0922010-01-09 22:28:03 +11001226 /* Ignore blank lines and lines which begin with comment '#' char */
1227 if (*cp == '\0' || *cp == '#')
1228 return (0);
1229
Darren Tucker909d8582010-01-08 19:02:40 +11001230 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001231 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001232
Damien Miller1cbc2922007-10-26 14:27:45 +10001233 /* Figure out which command we have */
1234 for (i = 0; cmds[i].c != NULL; i++) {
Damien Millerd6d9fa02013-02-12 11:02:46 +11001235 if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001236 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001237 }
1238 cmdnum = cmds[i].n;
1239 cmd = cmds[i].c;
1240
1241 /* Special case */
1242 if (*cp == '!') {
1243 cp++;
1244 cmdnum = I_SHELL;
1245 } else if (cmdnum == -1) {
1246 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001247 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001248 }
1249
1250 /* Get arguments and parse flags */
Damien Millerf29238e2013-10-17 11:48:52 +11001251 *aflag = *fflag = *hflag = *iflag = *lflag = *pflag = 0;
1252 *rflag = *sflag = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001253 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001254 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001255 switch (cmdnum) {
1256 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001257 case I_REGET:
Damien Millerd8accc02014-05-15 13:46:25 +10001258 case I_REPUT:
Damien Miller20e1fab2004-02-18 14:30:55 +11001259 case I_PUT:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001260 if ((optidx = parse_getput_flags(cmd, argv, argc,
Damien Millerf29238e2013-10-17 11:48:52 +11001261 aflag, fflag, pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001262 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001263 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001264 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001265 error("You must specify at least one path after a "
1266 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001267 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001268 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001269 *path1 = xstrdup(argv[optidx]);
1270 /* Get second pathname (optional) */
1271 if (argc - optidx > 1) {
1272 *path2 = xstrdup(argv[optidx + 1]);
1273 /* Destination is not globbed */
1274 undo_glob_escape(*path2);
1275 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001276 break;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001277 case I_LINK:
1278 if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1279 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001280 goto parse_two_paths;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001281 case I_RENAME:
Damien Millerc7dba122013-08-21 02:41:15 +10001282 if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1)
1283 return -1;
1284 goto parse_two_paths;
1285 case I_SYMLINK:
Damien Miller036d3072013-08-21 02:41:46 +10001286 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1287 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001288 parse_two_paths:
Damien Miller1cbc2922007-10-26 14:27:45 +10001289 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001290 error("You must specify two paths after a %s "
1291 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001292 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001293 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001294 *path1 = xstrdup(argv[optidx]);
1295 *path2 = xstrdup(argv[optidx + 1]);
1296 /* Paths are not globbed */
1297 undo_glob_escape(*path1);
1298 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001299 break;
1300 case I_RM:
1301 case I_MKDIR:
1302 case I_RMDIR:
1303 case I_CHDIR:
1304 case I_LCHDIR:
1305 case I_LMKDIR:
Damien Miller036d3072013-08-21 02:41:46 +10001306 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1307 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001308 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001309 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001310 error("You must specify a path after a %s command.",
1311 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001312 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001313 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001314 *path1 = xstrdup(argv[optidx]);
1315 /* Only "rm" globs */
1316 if (cmdnum != I_RM)
1317 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001318 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001319 case I_DF:
1320 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1321 iflag)) == -1)
1322 return -1;
1323 /* Default to current directory if no path specified */
1324 if (argc - optidx < 1)
1325 *path1 = NULL;
1326 else {
1327 *path1 = xstrdup(argv[optidx]);
1328 undo_glob_escape(*path1);
1329 }
1330 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001331 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001332 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001333 return(-1);
1334 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001335 if (argc - optidx > 0)
1336 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001337 break;
1338 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001339 /* Skip ls command and following whitespace */
1340 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001341 case I_SHELL:
1342 /* Uses the rest of the line */
1343 break;
1344 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001345 case I_CHMOD:
1346 base = 8;
1347 case I_CHOWN:
1348 case I_CHGRP:
Damien Miller036d3072013-08-21 02:41:46 +10001349 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1350 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001351 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001352 if (argc - optidx < 1)
1353 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001354 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001355 l = strtol(argv[optidx], &cp2, base);
1356 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1357 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1358 l < 0) {
1359 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001360 error("You must supply a numeric argument "
1361 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001362 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001363 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001364 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001365 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001366 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001367 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001368 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001369 error("You must specify a path after a %s command.",
1370 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001371 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001372 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001373 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001374 break;
1375 case I_QUIT:
1376 case I_PWD:
1377 case I_LPWD:
1378 case I_HELP:
1379 case I_VERSION:
1380 case I_PROGRESS:
Damien Miller036d3072013-08-21 02:41:46 +10001381 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1382 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001383 break;
1384 default:
1385 fatal("Command not implemented");
1386 }
1387
1388 *cpp = cp;
1389 return(cmdnum);
1390}
1391
1392static int
1393parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1394 int err_abort)
1395{
1396 char *path1, *path2, *tmp;
Damien Millerd8accc02014-05-15 13:46:25 +10001397 int ignore_errors = 0, aflag = 0, fflag = 0, hflag = 0,
1398 iflag = 0;
Damien Millerf29238e2013-10-17 11:48:52 +11001399 int lflag = 0, pflag = 0, rflag = 0, sflag = 0;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001400 int cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001401 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001402 Attrib a, *aa;
1403 char path_buf[MAXPATHLEN];
1404 int err = 0;
1405 glob_t g;
1406
1407 path1 = path2 = NULL;
Damien Millerf29238e2013-10-17 11:48:52 +11001408 cmdnum = parse_args(&cmd, &ignore_errors, &aflag, &fflag, &hflag,
1409 &iflag, &lflag, &pflag, &rflag, &sflag, &n_arg, &path1, &path2);
1410 if (ignore_errors != 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001411 err_abort = 0;
1412
1413 memset(&g, 0, sizeof(g));
1414
1415 /* Perform command */
1416 switch (cmdnum) {
1417 case 0:
1418 /* Blank line */
1419 break;
1420 case -1:
1421 /* Unrecognized command */
1422 err = -1;
1423 break;
Damien Miller0d032412013-07-25 11:56:52 +10001424 case I_REGET:
1425 aflag = 1;
1426 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001427 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001428 err = process_get(conn, path1, path2, *pwd, pflag,
Damien Millerf29238e2013-10-17 11:48:52 +11001429 rflag, aflag, fflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001430 break;
Damien Millerd8accc02014-05-15 13:46:25 +10001431 case I_REPUT:
1432 aflag = 1;
1433 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001434 case I_PUT:
Damien Millerf29238e2013-10-17 11:48:52 +11001435 err = process_put(conn, path1, path2, *pwd, pflag,
Damien Millerd8accc02014-05-15 13:46:25 +10001436 rflag, aflag, fflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001437 break;
1438 case I_RENAME:
1439 path1 = make_absolute(path1, *pwd);
1440 path2 = make_absolute(path2, *pwd);
Damien Millerc7dba122013-08-21 02:41:15 +10001441 err = do_rename(conn, path1, path2, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001442 break;
1443 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001444 sflag = 1;
1445 case I_LINK:
Damien Miller034f27a2013-08-21 02:40:44 +10001446 if (!sflag)
1447 path1 = make_absolute(path1, *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001448 path2 = make_absolute(path2, *pwd);
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001449 err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001450 break;
1451 case I_RM:
1452 path1 = make_absolute(path1, *pwd);
1453 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001454 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001455 if (!quiet)
1456 printf("Removing %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001457 err = do_rm(conn, g.gl_pathv[i]);
1458 if (err != 0 && err_abort)
1459 break;
1460 }
1461 break;
1462 case I_MKDIR:
1463 path1 = make_absolute(path1, *pwd);
1464 attrib_clear(&a);
1465 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1466 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001467 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001468 break;
1469 case I_RMDIR:
1470 path1 = make_absolute(path1, *pwd);
1471 err = do_rmdir(conn, path1);
1472 break;
1473 case I_CHDIR:
1474 path1 = make_absolute(path1, *pwd);
1475 if ((tmp = do_realpath(conn, path1)) == NULL) {
1476 err = 1;
1477 break;
1478 }
1479 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
Darren Tuckera627d422013-06-02 07:31:17 +10001480 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001481 err = 1;
1482 break;
1483 }
1484 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1485 error("Can't change directory: Can't check target");
Darren Tuckera627d422013-06-02 07:31:17 +10001486 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001487 err = 1;
1488 break;
1489 }
1490 if (!S_ISDIR(aa->perm)) {
1491 error("Can't change directory: \"%s\" is not "
1492 "a directory", tmp);
Darren Tuckera627d422013-06-02 07:31:17 +10001493 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001494 err = 1;
1495 break;
1496 }
Darren Tuckera627d422013-06-02 07:31:17 +10001497 free(*pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001498 *pwd = tmp;
1499 break;
1500 case I_LS:
1501 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001502 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001503 break;
1504 }
1505
1506 /* Strip pwd off beginning of non-absolute paths */
1507 tmp = NULL;
1508 if (*path1 != '/')
1509 tmp = *pwd;
1510
1511 path1 = make_absolute(path1, *pwd);
1512 err = do_globbed_ls(conn, path1, tmp, lflag);
1513 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001514 case I_DF:
1515 /* Default to current directory if no path specified */
1516 if (path1 == NULL)
1517 path1 = xstrdup(*pwd);
1518 path1 = make_absolute(path1, *pwd);
1519 err = do_df(conn, path1, hflag, iflag);
1520 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001521 case I_LCHDIR:
1522 if (chdir(path1) == -1) {
1523 error("Couldn't change local directory to "
1524 "\"%s\": %s", path1, strerror(errno));
1525 err = 1;
1526 }
1527 break;
1528 case I_LMKDIR:
1529 if (mkdir(path1, 0777) == -1) {
1530 error("Couldn't create local directory "
1531 "\"%s\": %s", path1, strerror(errno));
1532 err = 1;
1533 }
1534 break;
1535 case I_LLS:
1536 local_do_ls(cmd);
1537 break;
1538 case I_SHELL:
1539 local_do_shell(cmd);
1540 break;
1541 case I_LUMASK:
1542 umask(n_arg);
1543 printf("Local umask: %03lo\n", n_arg);
1544 break;
1545 case I_CHMOD:
1546 path1 = make_absolute(path1, *pwd);
1547 attrib_clear(&a);
1548 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1549 a.perm = n_arg;
1550 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001551 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001552 if (!quiet)
1553 printf("Changing mode on %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001554 err = do_setstat(conn, g.gl_pathv[i], &a);
1555 if (err != 0 && err_abort)
1556 break;
1557 }
1558 break;
1559 case I_CHOWN:
1560 case I_CHGRP:
1561 path1 = make_absolute(path1, *pwd);
1562 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001563 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001564 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001565 if (err_abort) {
1566 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001567 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001568 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001569 continue;
1570 }
1571 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1572 error("Can't get current ownership of "
1573 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001574 if (err_abort) {
1575 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001576 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001577 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001578 continue;
1579 }
1580 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1581 if (cmdnum == I_CHOWN) {
Damien Miller9303e652013-04-23 15:22:40 +10001582 if (!quiet)
1583 printf("Changing owner on %s\n",
1584 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001585 aa->uid = n_arg;
1586 } else {
Damien Miller9303e652013-04-23 15:22:40 +10001587 if (!quiet)
1588 printf("Changing group on %s\n",
1589 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001590 aa->gid = n_arg;
1591 }
1592 err = do_setstat(conn, g.gl_pathv[i], aa);
1593 if (err != 0 && err_abort)
1594 break;
1595 }
1596 break;
1597 case I_PWD:
1598 printf("Remote working directory: %s\n", *pwd);
1599 break;
1600 case I_LPWD:
1601 if (!getcwd(path_buf, sizeof(path_buf))) {
1602 error("Couldn't get local cwd: %s", strerror(errno));
1603 err = -1;
1604 break;
1605 }
1606 printf("Local working directory: %s\n", path_buf);
1607 break;
1608 case I_QUIT:
1609 /* Processed below */
1610 break;
1611 case I_HELP:
1612 help();
1613 break;
1614 case I_VERSION:
1615 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1616 break;
1617 case I_PROGRESS:
1618 showprogress = !showprogress;
1619 if (showprogress)
1620 printf("Progress meter enabled\n");
1621 else
1622 printf("Progress meter disabled\n");
1623 break;
1624 default:
1625 fatal("%d is not implemented", cmdnum);
1626 }
1627
1628 if (g.gl_pathc)
1629 globfree(&g);
Darren Tuckera627d422013-06-02 07:31:17 +10001630 free(path1);
1631 free(path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001632
1633 /* If an unignored error occurs in batch mode we should abort. */
1634 if (err_abort && err != 0)
1635 return (-1);
1636 else if (cmdnum == I_QUIT)
1637 return (1);
1638
1639 return (0);
1640}
1641
Darren Tucker2d963d82004-11-07 20:04:10 +11001642#ifdef USE_LIBEDIT
1643static char *
1644prompt(EditLine *el)
1645{
1646 return ("sftp> ");
1647}
Darren Tucker2d963d82004-11-07 20:04:10 +11001648
Darren Tucker909d8582010-01-08 19:02:40 +11001649/* Display entries in 'list' after skipping the first 'len' chars */
1650static void
1651complete_display(char **list, u_int len)
1652{
1653 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1654 struct winsize ws;
1655 char *tmp;
1656
1657 /* Count entries for sort and find longest */
Damien Miller02e87802013-08-21 02:38:51 +10001658 for (y = 0; list[y]; y++)
Darren Tucker909d8582010-01-08 19:02:40 +11001659 m = MAX(m, strlen(list[y]));
1660
1661 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1662 width = ws.ws_col;
1663
1664 m = m > len ? m - len : 0;
1665 columns = width / (m + 2);
1666 columns = MAX(columns, 1);
1667 colspace = width / columns;
1668 colspace = MIN(colspace, width);
1669
1670 printf("\n");
1671 m = 1;
1672 for (y = 0; list[y]; y++) {
1673 llen = strlen(list[y]);
1674 tmp = llen > len ? list[y] + len : "";
1675 printf("%-*s", colspace, tmp);
1676 if (m >= columns) {
1677 printf("\n");
1678 m = 1;
1679 } else
1680 m++;
1681 }
1682 printf("\n");
1683}
1684
1685/*
1686 * Given a "list" of words that begin with a common prefix of "word",
1687 * attempt to find an autocompletion to extends "word" by the next
1688 * characters common to all entries in "list".
1689 */
1690static char *
1691complete_ambiguous(const char *word, char **list, size_t count)
1692{
1693 if (word == NULL)
1694 return NULL;
1695
1696 if (count > 0) {
1697 u_int y, matchlen = strlen(list[0]);
1698
1699 /* Find length of common stem */
1700 for (y = 1; list[y]; y++) {
1701 u_int x;
1702
Damien Miller02e87802013-08-21 02:38:51 +10001703 for (x = 0; x < matchlen; x++)
1704 if (list[0][x] != list[y][x])
Darren Tucker909d8582010-01-08 19:02:40 +11001705 break;
1706
1707 matchlen = x;
1708 }
1709
1710 if (matchlen > strlen(word)) {
1711 char *tmp = xstrdup(list[0]);
1712
Darren Tucker340d1682010-01-09 08:54:31 +11001713 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001714 return tmp;
1715 }
Damien Miller02e87802013-08-21 02:38:51 +10001716 }
Darren Tucker909d8582010-01-08 19:02:40 +11001717
1718 return xstrdup(word);
1719}
1720
1721/* Autocomplete a sftp command */
1722static int
1723complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1724 int terminated)
1725{
1726 u_int y, count = 0, cmdlen, tmplen;
1727 char *tmp, **list, argterm[3];
1728 const LineInfo *lf;
1729
1730 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1731
1732 /* No command specified: display all available commands */
1733 if (cmd == NULL) {
1734 for (y = 0; cmds[y].c; y++)
1735 list[count++] = xstrdup(cmds[y].c);
Damien Miller02e87802013-08-21 02:38:51 +10001736
Darren Tucker909d8582010-01-08 19:02:40 +11001737 list[count] = NULL;
1738 complete_display(list, 0);
1739
Damien Miller02e87802013-08-21 02:38:51 +10001740 for (y = 0; list[y] != NULL; y++)
1741 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001742 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001743 return count;
1744 }
1745
1746 /* Prepare subset of commands that start with "cmd" */
1747 cmdlen = strlen(cmd);
1748 for (y = 0; cmds[y].c; y++) {
Damien Miller02e87802013-08-21 02:38:51 +10001749 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
Darren Tucker909d8582010-01-08 19:02:40 +11001750 list[count++] = xstrdup(cmds[y].c);
1751 }
1752 list[count] = NULL;
1753
Damien Miller47d81152011-11-25 13:53:48 +11001754 if (count == 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10001755 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001756 return 0;
Damien Miller47d81152011-11-25 13:53:48 +11001757 }
Darren Tucker909d8582010-01-08 19:02:40 +11001758
1759 /* Complete ambigious command */
1760 tmp = complete_ambiguous(cmd, list, count);
1761 if (count > 1)
1762 complete_display(list, 0);
1763
Damien Miller02e87802013-08-21 02:38:51 +10001764 for (y = 0; list[y]; y++)
1765 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001766 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001767
1768 if (tmp != NULL) {
1769 tmplen = strlen(tmp);
1770 cmdlen = strlen(cmd);
1771 /* If cmd may be extended then do so */
1772 if (tmplen > cmdlen)
1773 if (el_insertstr(el, tmp + cmdlen) == -1)
1774 fatal("el_insertstr failed.");
1775 lf = el_line(el);
1776 /* Terminate argument cleanly */
1777 if (count == 1) {
1778 y = 0;
1779 if (!terminated)
1780 argterm[y++] = quote;
1781 if (lastarg || *(lf->cursor) != ' ')
1782 argterm[y++] = ' ';
1783 argterm[y] = '\0';
1784 if (y > 0 && el_insertstr(el, argterm) == -1)
1785 fatal("el_insertstr failed.");
1786 }
Darren Tuckera627d422013-06-02 07:31:17 +10001787 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001788 }
1789
1790 return count;
1791}
1792
1793/*
1794 * Determine whether a particular sftp command's arguments (if any)
1795 * represent local or remote files.
1796 */
1797static int
1798complete_is_remote(char *cmd) {
1799 int i;
1800
1801 if (cmd == NULL)
1802 return -1;
1803
1804 for (i = 0; cmds[i].c; i++) {
Damien Miller02e87802013-08-21 02:38:51 +10001805 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
Darren Tucker909d8582010-01-08 19:02:40 +11001806 return cmds[i].t;
1807 }
1808
1809 return -1;
1810}
1811
1812/* Autocomplete a filename "file" */
1813static int
1814complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1815 char *file, int remote, int lastarg, char quote, int terminated)
1816{
1817 glob_t g;
Darren Tuckerea647212013-06-06 08:19:09 +10001818 char *tmp, *tmp2, ins[8];
Darren Tucker17146d32012-10-05 10:46:16 +10001819 u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
Darren Tuckerea647212013-06-06 08:19:09 +10001820 int clen;
Darren Tucker909d8582010-01-08 19:02:40 +11001821 const LineInfo *lf;
Damien Miller02e87802013-08-21 02:38:51 +10001822
Darren Tucker909d8582010-01-08 19:02:40 +11001823 /* Glob from "file" location */
1824 if (file == NULL)
1825 tmp = xstrdup("*");
1826 else
1827 xasprintf(&tmp, "%s*", file);
1828
Darren Tucker191fcc62012-10-05 10:45:01 +10001829 /* Check if the path is absolute. */
1830 isabs = tmp[0] == '/';
1831
Darren Tucker909d8582010-01-08 19:02:40 +11001832 memset(&g, 0, sizeof(g));
1833 if (remote != LOCAL) {
1834 tmp = make_absolute(tmp, remote_path);
1835 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001836 } else
Darren Tucker909d8582010-01-08 19:02:40 +11001837 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001838
Darren Tucker909d8582010-01-08 19:02:40 +11001839 /* Determine length of pwd so we can trim completion display */
1840 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1841 /* Terminate counting on first unescaped glob metacharacter */
1842 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1843 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1844 hadglob = 1;
1845 break;
1846 }
1847 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1848 tmplen++;
1849 if (tmp[tmplen] == '/')
1850 pwdlen = tmplen + 1; /* track last seen '/' */
1851 }
Darren Tuckera627d422013-06-02 07:31:17 +10001852 free(tmp);
Damien Millerd7fd8be2014-05-15 14:24:59 +10001853 tmp = NULL;
Darren Tucker909d8582010-01-08 19:02:40 +11001854
Damien Miller02e87802013-08-21 02:38:51 +10001855 if (g.gl_matchc == 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001856 goto out;
1857
1858 if (g.gl_matchc > 1)
1859 complete_display(g.gl_pathv, pwdlen);
1860
Darren Tucker909d8582010-01-08 19:02:40 +11001861 /* Don't try to extend globs */
1862 if (file == NULL || hadglob)
1863 goto out;
1864
1865 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
Darren Tucker191fcc62012-10-05 10:45:01 +10001866 tmp = path_strip(tmp2, isabs ? NULL : remote_path);
Darren Tuckera627d422013-06-02 07:31:17 +10001867 free(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001868
1869 if (tmp == NULL)
1870 goto out;
1871
1872 tmplen = strlen(tmp);
1873 filelen = strlen(file);
1874
Darren Tucker17146d32012-10-05 10:46:16 +10001875 /* Count the number of escaped characters in the input string. */
1876 cesc = isesc = 0;
1877 for (i = 0; i < filelen; i++) {
1878 if (!isesc && file[i] == '\\' && i + 1 < filelen){
1879 isesc = 1;
1880 cesc++;
1881 } else
1882 isesc = 0;
1883 }
1884
1885 if (tmplen > (filelen - cesc)) {
1886 tmp2 = tmp + filelen - cesc;
Damien Miller02e87802013-08-21 02:38:51 +10001887 len = strlen(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001888 /* quote argument on way out */
Darren Tuckerea647212013-06-06 08:19:09 +10001889 for (i = 0; i < len; i += clen) {
1890 if ((clen = mblen(tmp2 + i, len - i)) < 0 ||
1891 (size_t)clen > sizeof(ins) - 2)
1892 fatal("invalid multibyte character");
Darren Tucker909d8582010-01-08 19:02:40 +11001893 ins[0] = '\\';
Darren Tuckerea647212013-06-06 08:19:09 +10001894 memcpy(ins + 1, tmp2 + i, clen);
1895 ins[clen + 1] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001896 switch (tmp2[i]) {
1897 case '\'':
1898 case '"':
1899 case '\\':
1900 case '\t':
Darren Tuckerd78739a2010-10-24 10:56:32 +11001901 case '[':
Darren Tucker909d8582010-01-08 19:02:40 +11001902 case ' ':
Darren Tucker17146d32012-10-05 10:46:16 +10001903 case '#':
1904 case '*':
Darren Tucker909d8582010-01-08 19:02:40 +11001905 if (quote == '\0' || tmp2[i] == quote) {
1906 if (el_insertstr(el, ins) == -1)
1907 fatal("el_insertstr "
1908 "failed.");
1909 break;
1910 }
1911 /* FALLTHROUGH */
1912 default:
1913 if (el_insertstr(el, ins + 1) == -1)
1914 fatal("el_insertstr failed.");
1915 break;
1916 }
1917 }
1918 }
1919
1920 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11001921 if (g.gl_matchc == 1) {
1922 i = 0;
Damien Miller38094812014-05-15 14:25:18 +10001923 if (!terminated && quote != '\0')
Darren Tucker909d8582010-01-08 19:02:40 +11001924 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11001925 if (*(lf->cursor - 1) != '/' &&
1926 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11001927 ins[i++] = ' ';
1928 ins[i] = '\0';
1929 if (i > 0 && el_insertstr(el, ins) == -1)
1930 fatal("el_insertstr failed.");
1931 }
Darren Tuckera627d422013-06-02 07:31:17 +10001932 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001933
1934 out:
1935 globfree(&g);
1936 return g.gl_matchc;
1937}
1938
1939/* tab-completion hook function, called via libedit */
1940static unsigned char
1941complete(EditLine *el, int ch)
1942{
Damien Miller02e87802013-08-21 02:38:51 +10001943 char **argv, *line, quote;
Damien Miller746d1a62013-07-18 16:13:02 +10001944 int argc, carg;
1945 u_int cursor, len, terminated, ret = CC_ERROR;
Darren Tucker909d8582010-01-08 19:02:40 +11001946 const LineInfo *lf;
1947 struct complete_ctx *complete_ctx;
1948
1949 lf = el_line(el);
1950 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1951 fatal("%s: el_get failed", __func__);
1952
1953 /* Figure out which argument the cursor points to */
1954 cursor = lf->cursor - lf->buffer;
1955 line = (char *)xmalloc(cursor + 1);
1956 memcpy(line, lf->buffer, cursor);
1957 line[cursor] = '\0';
1958 argv = makeargv(line, &carg, 1, &quote, &terminated);
Darren Tuckera627d422013-06-02 07:31:17 +10001959 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001960
1961 /* Get all the arguments on the line */
1962 len = lf->lastchar - lf->buffer;
1963 line = (char *)xmalloc(len + 1);
1964 memcpy(line, lf->buffer, len);
1965 line[len] = '\0';
1966 argv = makeargv(line, &argc, 1, NULL, NULL);
1967
1968 /* Ensure cursor is at EOL or a argument boundary */
1969 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1970 line[cursor] != '\n') {
Darren Tuckera627d422013-06-02 07:31:17 +10001971 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001972 return ret;
1973 }
1974
1975 if (carg == 0) {
1976 /* Show all available commands */
1977 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1978 ret = CC_REDISPLAY;
1979 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1980 /* Handle the command parsing */
1981 if (complete_cmd_parse(el, argv[0], argc == carg,
Damien Miller02e87802013-08-21 02:38:51 +10001982 quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001983 ret = CC_REDISPLAY;
1984 } else if (carg >= 1) {
1985 /* Handle file parsing */
1986 int remote = complete_is_remote(argv[0]);
1987 char *filematch = NULL;
1988
1989 if (carg > 1 && line[cursor-1] != ' ')
1990 filematch = argv[carg - 1];
1991
1992 if (remote != 0 &&
1993 complete_match(el, complete_ctx->conn,
1994 *complete_ctx->remote_pathp, filematch,
Damien Miller02e87802013-08-21 02:38:51 +10001995 remote, carg == argc, quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001996 ret = CC_REDISPLAY;
1997 }
1998
Damien Miller02e87802013-08-21 02:38:51 +10001999 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11002000 return ret;
2001}
Darren Tuckere67f7db2010-01-08 19:50:02 +11002002#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11002003
Damien Miller20e1fab2004-02-18 14:30:55 +11002004int
Darren Tucker21063192010-01-08 17:10:36 +11002005interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11002006{
Darren Tucker909d8582010-01-08 19:02:40 +11002007 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11002008 char *dir = NULL;
2009 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10002010 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11002011 EditLine *el = NULL;
2012#ifdef USE_LIBEDIT
2013 History *hl = NULL;
2014 HistEvent hev;
2015 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11002016 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11002017
2018 if (!batchmode && isatty(STDIN_FILENO)) {
2019 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
2020 fatal("Couldn't initialise editline");
2021 if ((hl = history_init()) == NULL)
2022 fatal("Couldn't initialise editline history");
2023 history(hl, &hev, H_SETSIZE, 100);
2024 el_set(el, EL_HIST, history, hl);
2025
2026 el_set(el, EL_PROMPT, prompt);
2027 el_set(el, EL_EDITOR, "emacs");
2028 el_set(el, EL_TERMINAL, NULL);
2029 el_set(el, EL_SIGNAL, 1);
2030 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11002031
2032 /* Tab Completion */
Damien Miller02e87802013-08-21 02:38:51 +10002033 el_set(el, EL_ADDFN, "ftp-complete",
Darren Tuckerd78739a2010-10-24 10:56:32 +11002034 "Context sensitive argument completion", complete);
Darren Tucker909d8582010-01-08 19:02:40 +11002035 complete_ctx.conn = conn;
2036 complete_ctx.remote_pathp = &remote_path;
2037 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
2038 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Damien Millere0ee7272013-08-21 02:42:35 +10002039 /* enable ctrl-left-arrow and ctrl-right-arrow */
2040 el_set(el, EL_BIND, "\\e[1;5C", "em-next-word", NULL);
2041 el_set(el, EL_BIND, "\\e[5C", "em-next-word", NULL);
2042 el_set(el, EL_BIND, "\\e[1;5D", "ed-prev-word", NULL);
2043 el_set(el, EL_BIND, "\\e\\e[D", "ed-prev-word", NULL);
Damien Miller61353b32013-09-14 09:45:32 +10002044 /* make ^w match ksh behaviour */
2045 el_set(el, EL_BIND, "^w", "ed-delete-prev-word", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11002046 }
2047#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002048
Darren Tucker909d8582010-01-08 19:02:40 +11002049 remote_path = do_realpath(conn, ".");
2050 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11002051 fatal("Need cwd");
2052
2053 if (file1 != NULL) {
2054 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11002055 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11002056
2057 if (remote_is_dir(conn, dir) && file2 == NULL) {
Damien Miller9303e652013-04-23 15:22:40 +10002058 if (!quiet)
2059 printf("Changing to: %s\n", dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002060 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11002061 if (parse_dispatch_command(conn, cmd,
2062 &remote_path, 1) != 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10002063 free(dir);
2064 free(remote_path);
2065 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002066 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11002067 }
Damien Miller20e1fab2004-02-18 14:30:55 +11002068 } else {
Darren Tucker0af24052012-10-05 10:41:25 +10002069 /* XXX this is wrong wrt quoting */
Damien Miller0d032412013-07-25 11:56:52 +10002070 snprintf(cmd, sizeof cmd, "get%s %s%s%s",
2071 global_aflag ? " -a" : "", dir,
2072 file2 == NULL ? "" : " ",
2073 file2 == NULL ? "" : file2);
Darren Tucker909d8582010-01-08 19:02:40 +11002074 err = parse_dispatch_command(conn, cmd,
2075 &remote_path, 1);
Darren Tuckera627d422013-06-02 07:31:17 +10002076 free(dir);
2077 free(remote_path);
2078 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002079 return (err);
2080 }
Darren Tuckera627d422013-06-02 07:31:17 +10002081 free(dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002082 }
2083
Damien Miller37294fb2005-07-17 17:18:49 +10002084 setlinebuf(stdout);
2085 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11002086
Damien Miller0e2c1022005-08-12 22:16:22 +10002087 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11002088 err = 0;
2089 for (;;) {
2090 char *cp;
2091
Darren Tuckercdf547a2004-05-24 10:12:19 +10002092 signal(SIGINT, SIG_IGN);
2093
Darren Tucker2d963d82004-11-07 20:04:10 +11002094 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002095 if (interactive)
2096 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11002097 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002098 if (interactive)
2099 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11002100 break;
2101 }
Damien Miller0e2c1022005-08-12 22:16:22 +10002102 if (!interactive) { /* Echo command */
2103 printf("sftp> %s", cmd);
2104 if (strlen(cmd) > 0 &&
2105 cmd[strlen(cmd) - 1] != '\n')
2106 printf("\n");
2107 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002108 } else {
2109#ifdef USE_LIBEDIT
2110 const char *line;
2111 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11002112
Darren Tucker909d8582010-01-08 19:02:40 +11002113 if ((line = el_gets(el, &count)) == NULL ||
2114 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002115 printf("\n");
2116 break;
2117 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002118 history(hl, &hev, H_ENTER, line);
2119 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
2120 fprintf(stderr, "Error: input line too long\n");
2121 continue;
2122 }
2123#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002124 }
2125
Damien Miller20e1fab2004-02-18 14:30:55 +11002126 cp = strrchr(cmd, '\n');
2127 if (cp)
2128 *cp = '\0';
2129
Darren Tuckercdf547a2004-05-24 10:12:19 +10002130 /* Handle user interrupts gracefully during commands */
2131 interrupted = 0;
2132 signal(SIGINT, cmd_interrupt);
2133
Darren Tucker909d8582010-01-08 19:02:40 +11002134 err = parse_dispatch_command(conn, cmd, &remote_path,
2135 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11002136 if (err != 0)
2137 break;
2138 }
Darren Tuckera627d422013-06-02 07:31:17 +10002139 free(remote_path);
2140 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002141
Tim Rice027e8b12005-08-15 14:52:50 -07002142#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10002143 if (el != NULL)
2144 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07002145#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10002146
Damien Miller20e1fab2004-02-18 14:30:55 +11002147 /* err == 1 signifies normal "quit" exit */
2148 return (err >= 0 ? 0 : -1);
2149}
Damien Miller62d57f62003-01-10 21:43:24 +11002150
Ben Lindstrombba81212001-06-25 05:01:22 +00002151static void
Damien Millercc685c12003-06-04 22:51:38 +10002152connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11002153{
2154 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002155
Damien Miller33804262001-02-04 23:20:18 +11002156#ifdef USE_PIPES
2157 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002158
Damien Miller33804262001-02-04 23:20:18 +11002159 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2160 fatal("pipe: %s", strerror(errno));
2161 *in = pin[0];
2162 *out = pout[1];
2163 c_in = pout[0];
2164 c_out = pin[1];
2165#else /* USE_PIPES */
2166 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002167
Damien Miller33804262001-02-04 23:20:18 +11002168 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2169 fatal("socketpair: %s", strerror(errno));
2170 *in = *out = inout[0];
2171 c_in = c_out = inout[1];
2172#endif /* USE_PIPES */
2173
Damien Millercc685c12003-06-04 22:51:38 +10002174 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002175 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002176 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002177 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2178 (dup2(c_out, STDOUT_FILENO) == -1)) {
2179 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002180 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002181 }
2182 close(*in);
2183 close(*out);
2184 close(c_in);
2185 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002186
2187 /*
2188 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002189 * ignore SIGINT if we want to gracefully abort commands,
2190 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002191 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2192 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002193 */
2194 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002195 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002196 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002197 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002198 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002199 }
2200
Damien Millercc685c12003-06-04 22:51:38 +10002201 signal(SIGTERM, killchild);
2202 signal(SIGINT, killchild);
2203 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11002204 close(c_in);
2205 close(c_out);
2206}
2207
Ben Lindstrombba81212001-06-25 05:01:22 +00002208static void
Damien Miller33804262001-02-04 23:20:18 +11002209usage(void)
2210{
Damien Miller025e01c2002-02-08 22:06:29 +11002211 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002212
Ben Lindstrom1e243242001-09-18 05:38:44 +00002213 fprintf(stderr,
Damien Miller1edcbf62013-10-18 10:17:17 +11002214 "usage: %s [-1246aCfpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002215 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002216 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002217 " [-o ssh_option] [-P port] [-R num_requests] "
2218 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002219 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002220 " %s [user@]host[:file ...]\n"
2221 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002222 " %s -b batchfile [user@]host\n",
2223 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002224 exit(1);
2225}
2226
Kevin Stevesef4eea92001-02-05 12:42:17 +00002227int
Damien Miller33804262001-02-04 23:20:18 +11002228main(int argc, char **argv)
2229{
Damien Miller956f3fb2003-01-10 21:40:00 +11002230 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002231 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002232 int debug_level = 0, sshver = 2;
2233 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002234 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002235 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002236 LogLevel ll = SYSLOG_LEVEL_INFO;
2237 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002238 extern int optind;
2239 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002240 struct sftp_conn *conn;
2241 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2242 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002243 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002244
Darren Tuckerce321d82005-10-03 18:11:24 +10002245 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2246 sanitise_stdfd();
Darren Tuckerea647212013-06-06 08:19:09 +10002247 setlocale(LC_CTYPE, "");
Darren Tuckerce321d82005-10-03 18:11:24 +10002248
Damien Miller59d3d5b2003-08-22 09:34:41 +10002249 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002250 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002251 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002252 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002253 addargs(&args, "-oForwardX11 no");
2254 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002255 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002256 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002257
Ben Lindstrom387c4722001-05-08 20:27:25 +00002258 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002259 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002260
Darren Tucker282b4022009-10-07 08:23:06 +11002261 while ((ch = getopt(argc, argv,
Damien Millerf29238e2013-10-17 11:48:52 +11002262 "1246afhpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002263 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002264 /* Passed through to ssh(1) */
2265 case '4':
2266 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002267 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002268 addargs(&args, "-%c", ch);
2269 break;
2270 /* Passed through to ssh(1) with argument */
2271 case 'F':
2272 case 'c':
2273 case 'i':
2274 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002275 addargs(&args, "-%c", ch);
2276 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002277 break;
2278 case 'q':
Damien Miller9303e652013-04-23 15:22:40 +10002279 ll = SYSLOG_LEVEL_ERROR;
2280 quiet = 1;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002281 showprogress = 0;
2282 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002283 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002284 case 'P':
2285 addargs(&args, "-oPort %s", optarg);
2286 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002287 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002288 if (debug_level < 3) {
2289 addargs(&args, "-v");
2290 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2291 }
2292 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002293 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002294 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002295 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002296 if (sftp_server == NULL)
2297 sftp_server = _PATH_SFTP_SERVER;
2298 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002299 case '2':
2300 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002301 break;
Damien Miller0d032412013-07-25 11:56:52 +10002302 case 'a':
2303 global_aflag = 1;
2304 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002305 case 'B':
2306 copy_buffer_len = strtol(optarg, &cp, 10);
2307 if (copy_buffer_len == 0 || *cp != '\0')
2308 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002309 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002310 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002311 if (batchmode)
2312 fatal("Batch file already specified.");
2313
2314 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002315 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002316 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002317 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002318 showprogress = 0;
Damien Miller9303e652013-04-23 15:22:40 +10002319 quiet = batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002320 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002321 break;
Damien Millerf29238e2013-10-17 11:48:52 +11002322 case 'f':
2323 global_fflag = 1;
2324 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002325 case 'p':
2326 global_pflag = 1;
2327 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002328 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002329 sftp_direct = optarg;
2330 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002331 case 'l':
2332 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2333 &errstr);
2334 if (errstr != NULL)
2335 usage();
2336 limit_kbps *= 1024; /* kbps */
2337 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002338 case 'r':
2339 global_rflag = 1;
2340 break;
Damien Miller16a13332002-02-13 14:03:56 +11002341 case 'R':
2342 num_requests = strtol(optarg, &cp, 10);
2343 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002344 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002345 optarg);
2346 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002347 case 's':
2348 sftp_server = optarg;
2349 break;
2350 case 'S':
2351 ssh_program = optarg;
2352 replacearg(&args, 0, "%s", ssh_program);
2353 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002354 case 'h':
2355 default:
Damien Miller33804262001-02-04 23:20:18 +11002356 usage();
2357 }
2358 }
2359
Damien Millerc0f27d82004-03-08 23:12:19 +11002360 if (!isatty(STDERR_FILENO))
2361 showprogress = 0;
2362
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002363 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2364
Damien Millerd14ee1e2002-02-05 12:27:31 +11002365 if (sftp_direct == NULL) {
2366 if (optind == argc || argc > (optind + 2))
2367 usage();
Damien Miller33804262001-02-04 23:20:18 +11002368
Damien Millerd14ee1e2002-02-05 12:27:31 +11002369 userhost = xstrdup(argv[optind]);
2370 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002371
Ben Lindstromc276c122002-12-23 02:14:51 +00002372 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002373 host = userhost;
2374 else {
2375 *host++ = '\0';
2376 if (!userhost[0]) {
2377 fprintf(stderr, "Missing username\n");
2378 usage();
2379 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002380 addargs(&args, "-l");
2381 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002382 }
2383
Damien Millerec692032004-01-27 21:22:00 +11002384 if ((cp = colon(host)) != NULL) {
2385 *cp++ = '\0';
2386 file1 = cp;
2387 }
2388
Damien Millerd14ee1e2002-02-05 12:27:31 +11002389 host = cleanhostname(host);
2390 if (!*host) {
2391 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002392 usage();
2393 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002394
Damien Millerd14ee1e2002-02-05 12:27:31 +11002395 addargs(&args, "-oProtocol %d", sshver);
2396
2397 /* no subsystem if the server-spec contains a '/' */
2398 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2399 addargs(&args, "-s");
2400
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002401 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002402 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002403 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002404 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002405
Damien Millercc685c12003-06-04 22:51:38 +10002406 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002407 } else {
2408 args.list = NULL;
2409 addargs(&args, "sftp-server");
2410
Damien Millercc685c12003-06-04 22:51:38 +10002411 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002412 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002413 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002414
Damien Miller65e42f82010-09-24 22:15:11 +10002415 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002416 if (conn == NULL)
2417 fatal("Couldn't initialise connection to server");
2418
Damien Miller9303e652013-04-23 15:22:40 +10002419 if (!quiet) {
Darren Tucker21063192010-01-08 17:10:36 +11002420 if (sftp_direct == NULL)
2421 fprintf(stderr, "Connected to %s.\n", host);
2422 else
2423 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2424 }
2425
2426 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002427
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002428#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002429 shutdown(in, SHUT_RDWR);
2430 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002431#endif
2432
Damien Miller33804262001-02-04 23:20:18 +11002433 close(in);
2434 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002435 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002436 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002437
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002438 while (waitpid(sshpid, NULL, 0) == -1)
2439 if (errno != EINTR)
2440 fatal("Couldn't wait for ssh process: %s",
2441 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002442
Damien Miller956f3fb2003-01-10 21:40:00 +11002443 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002444}