blob: e74bed5e001ae1999d72764ae165ffe9e90f52bf [file] [log] [blame]
Damien Millerd8accc02014-05-15 13:46:25 +10001/* $OpenBSD: sftp.c,v 1.159 2014/04/21 14:36:16 logan 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,
154 I_RENAME,
155 I_RM,
156 I_RMDIR,
157 I_SHELL,
158 I_SYMLINK,
159 I_VERSION,
160 I_PROGRESS,
161 I_REGET,
Damien Millerd8accc02014-05-15 13:46:25 +1000162 I_REPUT
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;
592 int i, 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);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100599 if (remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100600 error("File \"%s\" not found.", abs_src);
601 err = -1;
602 goto out;
603 }
604
Darren Tucker1b0dd172009-10-07 08:37:48 +1100605 /*
606 * If multiple matches then dst must be a directory or
607 * unspecified.
608 */
609 if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
610 error("Multiple source paths, but destination "
611 "\"%s\" is not a directory", dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100612 err = -1;
613 goto out;
614 }
615
Darren Tuckercdf547a2004-05-24 10:12:19 +1000616 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100617 tmp = xstrdup(g.gl_pathv[i]);
618 if ((filename = basename(tmp)) == NULL) {
619 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000620 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100621 err = -1;
622 goto out;
623 }
624
625 if (g.gl_matchc == 1 && dst) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100626 if (is_dir(dst)) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100627 abs_dst = path_append(dst, filename);
628 } else {
Damien Miller20e1fab2004-02-18 14:30:55 +1100629 abs_dst = xstrdup(dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100630 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100631 } else if (dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100632 abs_dst = path_append(dst, filename);
633 } else {
634 abs_dst = xstrdup(filename);
635 }
Darren Tuckera627d422013-06-02 07:31:17 +1000636 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100637
Damien Miller0d032412013-07-25 11:56:52 +1000638 resume |= global_aflag;
639 if (!quiet && resume)
640 printf("Resuming %s to %s\n", g.gl_pathv[i], abs_dst);
641 else if (!quiet && !resume)
Damien Miller9303e652013-04-23 15:22:40 +1000642 printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100643 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
Damien Miller0d032412013-07-25 11:56:52 +1000644 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
Damien Millerf29238e2013-10-17 11:48:52 +1100645 pflag || global_pflag, 1, resume,
646 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100647 err = -1;
648 } else {
649 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
Damien Millerf29238e2013-10-17 11:48:52 +1100650 pflag || global_pflag, resume,
651 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100652 err = -1;
653 }
Darren Tuckera627d422013-06-02 07:31:17 +1000654 free(abs_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100655 abs_dst = NULL;
656 }
657
658out:
Darren Tuckera627d422013-06-02 07:31:17 +1000659 free(abs_src);
Damien Miller20e1fab2004-02-18 14:30:55 +1100660 globfree(&g);
661 return(err);
662}
663
664static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100665process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
Damien Millerd8accc02014-05-15 13:46:25 +1000666 int pflag, int rflag, int resume, int fflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100667{
668 char *tmp_dst = NULL;
669 char *abs_dst = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100670 char *tmp = NULL, *filename = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100671 glob_t g;
672 int err = 0;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100673 int i, dst_is_dir = 1;
Damien Milleraec5cf82008-02-10 22:26:24 +1100674 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100675
676 if (dst) {
677 tmp_dst = xstrdup(dst);
678 tmp_dst = make_absolute(tmp_dst, pwd);
679 }
680
681 memset(&g, 0, sizeof(g));
682 debug3("Looking up %s", src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100683 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100684 error("File \"%s\" not found.", src);
685 err = -1;
686 goto out;
687 }
688
Darren Tucker1b0dd172009-10-07 08:37:48 +1100689 /* If we aren't fetching to pwd then stash this status for later */
690 if (tmp_dst != NULL)
691 dst_is_dir = remote_is_dir(conn, tmp_dst);
692
Damien Miller20e1fab2004-02-18 14:30:55 +1100693 /* If multiple matches, dst may be directory or unspecified */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100694 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
695 error("Multiple paths match, but destination "
696 "\"%s\" is not a directory", tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100697 err = -1;
698 goto out;
699 }
700
Darren Tuckercdf547a2004-05-24 10:12:19 +1000701 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Milleraec5cf82008-02-10 22:26:24 +1100702 if (stat(g.gl_pathv[i], &sb) == -1) {
703 err = -1;
704 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
705 continue;
706 }
Damien Miller02e87802013-08-21 02:38:51 +1000707
Darren Tucker1b0dd172009-10-07 08:37:48 +1100708 tmp = xstrdup(g.gl_pathv[i]);
709 if ((filename = basename(tmp)) == NULL) {
710 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000711 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100712 err = -1;
713 goto out;
714 }
715
716 if (g.gl_matchc == 1 && tmp_dst) {
717 /* If directory specified, append filename */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100718 if (dst_is_dir)
719 abs_dst = path_append(tmp_dst, filename);
720 else
Damien Miller20e1fab2004-02-18 14:30:55 +1100721 abs_dst = xstrdup(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100722 } else if (tmp_dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100723 abs_dst = path_append(tmp_dst, filename);
724 } else {
725 abs_dst = make_absolute(xstrdup(filename), pwd);
726 }
Darren Tuckera627d422013-06-02 07:31:17 +1000727 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100728
Damien Millerd8accc02014-05-15 13:46:25 +1000729 resume |= global_aflag;
730 if (!quiet && resume)
731 printf("Resuming upload of %s to %s\n", g.gl_pathv[i],
732 abs_dst);
733 else if (!quiet && !resume)
Damien Miller9303e652013-04-23 15:22:40 +1000734 printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100735 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
736 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
Damien Millerd8accc02014-05-15 13:46:25 +1000737 pflag || global_pflag, 1, resume,
Damien Millerf29238e2013-10-17 11:48:52 +1100738 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100739 err = -1;
740 } else {
741 if (do_upload(conn, g.gl_pathv[i], abs_dst,
Damien Millerd8accc02014-05-15 13:46:25 +1000742 pflag || global_pflag, resume,
Damien Millerf29238e2013-10-17 11:48:52 +1100743 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100744 err = -1;
745 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100746 }
747
748out:
Darren Tuckera627d422013-06-02 07:31:17 +1000749 free(abs_dst);
750 free(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100751 globfree(&g);
752 return(err);
753}
754
755static int
756sdirent_comp(const void *aa, const void *bb)
757{
758 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
759 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000760 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100761
Darren Tuckerb9123452004-06-22 13:06:45 +1000762#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000763 if (sort_flag & LS_NAME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000764 return (rmul * strcmp(a->filename, b->filename));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000765 else if (sort_flag & LS_TIME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000766 return (rmul * NCMP(a->a.mtime, b->a.mtime));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000767 else if (sort_flag & LS_SIZE_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000768 return (rmul * NCMP(a->a.size, b->a.size));
769
770 fatal("Unknown ls sort type");
Damien Miller20e1fab2004-02-18 14:30:55 +1100771}
772
773/* sftp ls.1 replacement for directories */
774static int
775do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
776{
Damien Millereccb9de2005-06-17 12:59:34 +1000777 int n;
778 u_int c = 1, colspace = 0, columns = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100779 SFTP_DIRENT **d;
780
781 if ((n = do_readdir(conn, path, &d)) != 0)
782 return (n);
783
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000784 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000785 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100786 struct winsize ws;
787 char *tmp;
788
789 /* Count entries for sort and find longest filename */
Darren Tucker9a526452004-06-22 13:09:55 +1000790 for (n = 0; d[n] != NULL; n++) {
791 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
792 m = MAX(m, strlen(d[n]->filename));
793 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100794
795 /* Add any subpath that also needs to be counted */
796 tmp = path_strip(path, strip_path);
797 m += strlen(tmp);
Darren Tuckera627d422013-06-02 07:31:17 +1000798 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100799
800 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
801 width = ws.ws_col;
802
803 columns = width / (m + 2);
804 columns = MAX(columns, 1);
805 colspace = width / columns;
806 colspace = MIN(colspace, width);
807 }
808
Darren Tuckerb9123452004-06-22 13:06:45 +1000809 if (lflag & SORT_FLAGS) {
Damien Miller653b93b2005-11-05 15:15:23 +1100810 for (n = 0; d[n] != NULL; n++)
811 ; /* count entries */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000812 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
Darren Tuckerb9123452004-06-22 13:06:45 +1000813 qsort(d, n, sizeof(*d), sdirent_comp);
814 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100815
Darren Tuckercdf547a2004-05-24 10:12:19 +1000816 for (n = 0; d[n] != NULL && !interrupted; n++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100817 char *tmp, *fname;
818
Darren Tucker9a526452004-06-22 13:09:55 +1000819 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
820 continue;
821
Damien Miller20e1fab2004-02-18 14:30:55 +1100822 tmp = path_append(path, d[n]->filename);
823 fname = path_strip(tmp, strip_path);
Darren Tuckera627d422013-06-02 07:31:17 +1000824 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100825
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000826 if (lflag & LS_LONG_VIEW) {
Darren Tucker2901e2d2010-01-13 22:44:06 +1100827 if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000828 char *lname;
829 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100830
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000831 memset(&sb, 0, sizeof(sb));
832 attrib_to_stat(&d[n]->a, &sb);
Darren Tucker2901e2d2010-01-13 22:44:06 +1100833 lname = ls_file(fname, &sb, 1,
834 (lflag & LS_SI_UNITS));
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000835 printf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000836 free(lname);
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000837 } else
838 printf("%s\n", d[n]->longname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100839 } else {
840 printf("%-*s", colspace, fname);
841 if (c >= columns) {
842 printf("\n");
843 c = 1;
844 } else
845 c++;
846 }
847
Darren Tuckera627d422013-06-02 07:31:17 +1000848 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100849 }
850
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000851 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100852 printf("\n");
853
854 free_sftp_dirents(d);
855 return (0);
856}
857
858/* sftp ls.1 replacement which handles path globs */
859static int
860do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
861 int lflag)
862{
Damien Millera6e121a2010-10-07 21:39:17 +1100863 char *fname, *lname;
Damien Miller68e2e562010-10-07 21:39:55 +1100864 glob_t g;
865 int err;
866 struct winsize ws;
867 u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100868
869 memset(&g, 0, sizeof(g));
870
Damien Millera6e121a2010-10-07 21:39:17 +1100871 if (remote_glob(conn, path,
Damien Millerd7be70d2011-09-22 21:43:06 +1000872 GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
873 NULL, &g) ||
Damien Millera6e121a2010-10-07 21:39:17 +1100874 (g.gl_pathc && !g.gl_matchc)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100875 if (g.gl_pathc)
876 globfree(&g);
Damien Miller20e1fab2004-02-18 14:30:55 +1100877 error("Can't ls: \"%s\" not found", path);
Damien Millera6e121a2010-10-07 21:39:17 +1100878 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100879 }
880
Darren Tuckercdf547a2004-05-24 10:12:19 +1000881 if (interrupted)
882 goto out;
883
Damien Miller20e1fab2004-02-18 14:30:55 +1100884 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100885 * If the glob returns a single match and it is a directory,
886 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100887 */
Damien Millera6e121a2010-10-07 21:39:17 +1100888 if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
889 S_ISDIR(g.gl_statv[0]->st_mode)) {
890 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
891 globfree(&g);
892 return err;
Damien Miller20e1fab2004-02-18 14:30:55 +1100893 }
894
Damien Miller68e2e562010-10-07 21:39:55 +1100895 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
896 width = ws.ws_col;
Damien Miller20e1fab2004-02-18 14:30:55 +1100897
Damien Miller68e2e562010-10-07 21:39:55 +1100898 if (!(lflag & LS_SHORT_VIEW)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100899 /* Count entries for sort and find longest filename */
900 for (i = 0; g.gl_pathv[i]; i++)
901 m = MAX(m, strlen(g.gl_pathv[i]));
902
Damien Miller20e1fab2004-02-18 14:30:55 +1100903 columns = width / (m + 2);
904 columns = MAX(columns, 1);
905 colspace = width / columns;
906 }
907
Damien Millerea858292012-06-30 08:33:32 +1000908 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100909 fname = path_strip(g.gl_pathv[i], strip_path);
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000910 if (lflag & LS_LONG_VIEW) {
Damien Millera6e121a2010-10-07 21:39:17 +1100911 if (g.gl_statv[i] == NULL) {
912 error("no stat information for %s", fname);
913 continue;
914 }
915 lname = ls_file(fname, g.gl_statv[i], 1,
916 (lflag & LS_SI_UNITS));
Damien Miller20e1fab2004-02-18 14:30:55 +1100917 printf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000918 free(lname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100919 } else {
920 printf("%-*s", colspace, fname);
921 if (c >= columns) {
922 printf("\n");
923 c = 1;
924 } else
925 c++;
926 }
Darren Tuckera627d422013-06-02 07:31:17 +1000927 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100928 }
929
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000930 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100931 printf("\n");
932
Darren Tuckercdf547a2004-05-24 10:12:19 +1000933 out:
Damien Miller20e1fab2004-02-18 14:30:55 +1100934 if (g.gl_pathc)
935 globfree(&g);
936
Damien Millera6e121a2010-10-07 21:39:17 +1100937 return 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100938}
939
Damien Millerd671e5a2008-05-19 14:53:33 +1000940static int
941do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
942{
Darren Tucker7b598892008-06-09 22:49:36 +1000943 struct sftp_statvfs st;
Damien Millerd671e5a2008-05-19 14:53:33 +1000944 char s_used[FMT_SCALED_STRSIZE];
945 char s_avail[FMT_SCALED_STRSIZE];
946 char s_root[FMT_SCALED_STRSIZE];
947 char s_total[FMT_SCALED_STRSIZE];
Darren Tuckerb5082e92010-01-08 18:51:47 +1100948 unsigned long long ffree;
Damien Millerd671e5a2008-05-19 14:53:33 +1000949
950 if (do_statvfs(conn, path, &st, 1) == -1)
951 return -1;
952 if (iflag) {
Darren Tuckerb5082e92010-01-08 18:51:47 +1100953 ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
Damien Millerd671e5a2008-05-19 14:53:33 +1000954 printf(" Inodes Used Avail "
955 "(root) %%Capacity\n");
956 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
957 (unsigned long long)st.f_files,
958 (unsigned long long)(st.f_files - st.f_ffree),
959 (unsigned long long)st.f_favail,
Darren Tuckerb5082e92010-01-08 18:51:47 +1100960 (unsigned long long)st.f_ffree, ffree);
Damien Millerd671e5a2008-05-19 14:53:33 +1000961 } else if (hflag) {
962 strlcpy(s_used, "error", sizeof(s_used));
963 strlcpy(s_avail, "error", sizeof(s_avail));
964 strlcpy(s_root, "error", sizeof(s_root));
965 strlcpy(s_total, "error", sizeof(s_total));
966 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
967 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
968 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
969 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
970 printf(" Size Used Avail (root) %%Capacity\n");
971 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
972 s_total, s_used, s_avail, s_root,
973 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
974 st.f_blocks));
975 } else {
976 printf(" Size Used Avail "
977 "(root) %%Capacity\n");
978 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
979 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
980 (unsigned long long)(st.f_frsize *
981 (st.f_blocks - st.f_bfree) / 1024),
982 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
983 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
984 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
985 st.f_blocks));
986 }
987 return 0;
988}
989
Damien Miller1cbc2922007-10-26 14:27:45 +1000990/*
991 * Undo escaping of glob sequences in place. Used to undo extra escaping
992 * applied in makeargv() when the string is destined for a function that
993 * does not glob it.
994 */
995static void
996undo_glob_escape(char *s)
997{
998 size_t i, j;
999
1000 for (i = j = 0;;) {
1001 if (s[i] == '\0') {
1002 s[j] = '\0';
1003 return;
1004 }
1005 if (s[i] != '\\') {
1006 s[j++] = s[i++];
1007 continue;
1008 }
1009 /* s[i] == '\\' */
1010 ++i;
1011 switch (s[i]) {
1012 case '?':
1013 case '[':
1014 case '*':
1015 case '\\':
1016 s[j++] = s[i++];
1017 break;
1018 case '\0':
1019 s[j++] = '\\';
1020 s[j] = '\0';
1021 return;
1022 default:
1023 s[j++] = '\\';
1024 s[j++] = s[i++];
1025 break;
1026 }
1027 }
1028}
1029
1030/*
1031 * Split a string into an argument vector using sh(1)-style quoting,
1032 * comment and escaping rules, but with some tweaks to handle glob(3)
1033 * wildcards.
Darren Tucker909d8582010-01-08 19:02:40 +11001034 * The "sloppy" flag allows for recovery from missing terminating quote, for
1035 * use in parsing incomplete commandlines during tab autocompletion.
1036 *
Damien Miller1cbc2922007-10-26 14:27:45 +10001037 * Returns NULL on error or a NULL-terminated array of arguments.
Darren Tucker909d8582010-01-08 19:02:40 +11001038 *
1039 * If "lastquote" is not NULL, the quoting character used for the last
1040 * argument is placed in *lastquote ("\0", "'" or "\"").
Damien Miller02e87802013-08-21 02:38:51 +10001041 *
Darren Tucker909d8582010-01-08 19:02:40 +11001042 * If "terminated" is not NULL, *terminated will be set to 1 when the
1043 * last argument's quote has been properly terminated or 0 otherwise.
1044 * This parameter is only of use if "sloppy" is set.
Damien Miller1cbc2922007-10-26 14:27:45 +10001045 */
1046#define MAXARGS 128
1047#define MAXARGLEN 8192
1048static char **
Darren Tucker909d8582010-01-08 19:02:40 +11001049makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
1050 u_int *terminated)
Damien Miller1cbc2922007-10-26 14:27:45 +10001051{
1052 int argc, quot;
1053 size_t i, j;
1054 static char argvs[MAXARGLEN];
1055 static char *argv[MAXARGS + 1];
1056 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
1057
1058 *argcp = argc = 0;
1059 if (strlen(arg) > sizeof(argvs) - 1) {
1060 args_too_longs:
1061 error("string too long");
1062 return NULL;
1063 }
Darren Tucker909d8582010-01-08 19:02:40 +11001064 if (terminated != NULL)
1065 *terminated = 1;
1066 if (lastquote != NULL)
1067 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001068 state = MA_START;
1069 i = j = 0;
1070 for (;;) {
Damien Miller07daed52012-10-31 08:57:55 +11001071 if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){
Darren Tucker063018d2012-10-05 10:43:58 +10001072 error("Too many arguments.");
1073 return NULL;
1074 }
Damien Millerfdb23062013-11-21 13:57:15 +11001075 if (isspace((unsigned char)arg[i])) {
Damien Miller1cbc2922007-10-26 14:27:45 +10001076 if (state == MA_UNQUOTED) {
1077 /* Terminate current argument */
1078 argvs[j++] = '\0';
1079 argc++;
1080 state = MA_START;
1081 } else if (state != MA_START)
1082 argvs[j++] = arg[i];
1083 } else if (arg[i] == '"' || arg[i] == '\'') {
1084 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1085 if (state == MA_START) {
1086 argv[argc] = argvs + j;
1087 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +11001088 if (lastquote != NULL)
1089 *lastquote = arg[i];
Damien Miller02e87802013-08-21 02:38:51 +10001090 } else if (state == MA_UNQUOTED)
Damien Miller1cbc2922007-10-26 14:27:45 +10001091 state = q;
1092 else if (state == q)
1093 state = MA_UNQUOTED;
1094 else
1095 argvs[j++] = arg[i];
1096 } else if (arg[i] == '\\') {
1097 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1098 quot = state == MA_SQUOTE ? '\'' : '"';
1099 /* Unescape quote we are in */
1100 /* XXX support \n and friends? */
1101 if (arg[i + 1] == quot) {
1102 i++;
1103 argvs[j++] = arg[i];
1104 } else if (arg[i + 1] == '?' ||
1105 arg[i + 1] == '[' || arg[i + 1] == '*') {
1106 /*
1107 * Special case for sftp: append
1108 * double-escaped glob sequence -
1109 * glob will undo one level of
1110 * escaping. NB. string can grow here.
1111 */
1112 if (j >= sizeof(argvs) - 5)
1113 goto args_too_longs;
1114 argvs[j++] = '\\';
1115 argvs[j++] = arg[i++];
1116 argvs[j++] = '\\';
1117 argvs[j++] = arg[i];
1118 } else {
1119 argvs[j++] = arg[i++];
1120 argvs[j++] = arg[i];
1121 }
1122 } else {
1123 if (state == MA_START) {
1124 argv[argc] = argvs + j;
1125 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001126 if (lastquote != NULL)
1127 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001128 }
1129 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1130 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1131 /*
1132 * Special case for sftp: append
1133 * escaped glob sequence -
1134 * glob will undo one level of
1135 * escaping.
1136 */
1137 argvs[j++] = arg[i++];
1138 argvs[j++] = arg[i];
1139 } else {
1140 /* Unescape everything */
1141 /* XXX support \n and friends? */
1142 i++;
1143 argvs[j++] = arg[i];
1144 }
1145 }
1146 } else if (arg[i] == '#') {
1147 if (state == MA_SQUOTE || state == MA_DQUOTE)
1148 argvs[j++] = arg[i];
1149 else
1150 goto string_done;
1151 } else if (arg[i] == '\0') {
1152 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001153 if (sloppy) {
1154 state = MA_UNQUOTED;
1155 if (terminated != NULL)
1156 *terminated = 0;
1157 goto string_done;
1158 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001159 error("Unterminated quoted argument");
1160 return NULL;
1161 }
1162 string_done:
1163 if (state == MA_UNQUOTED) {
1164 argvs[j++] = '\0';
1165 argc++;
1166 }
1167 break;
1168 } else {
1169 if (state == MA_START) {
1170 argv[argc] = argvs + j;
1171 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001172 if (lastquote != NULL)
1173 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001174 }
1175 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1176 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1177 /*
1178 * Special case for sftp: escape quoted
1179 * glob(3) wildcards. NB. string can grow
1180 * here.
1181 */
1182 if (j >= sizeof(argvs) - 3)
1183 goto args_too_longs;
1184 argvs[j++] = '\\';
1185 argvs[j++] = arg[i];
1186 } else
1187 argvs[j++] = arg[i];
1188 }
1189 i++;
1190 }
1191 *argcp = argc;
1192 return argv;
1193}
1194
Damien Miller20e1fab2004-02-18 14:30:55 +11001195static int
Damien Millerd8accc02014-05-15 13:46:25 +10001196parse_args(const char **cpp, int *ignore_errors, int *aflag,
1197 int *fflag, int *hflag, int *iflag, int *lflag, int *pflag,
1198 int *rflag, int *sflag,
Damien Millerf29238e2013-10-17 11:48:52 +11001199 unsigned long *n_arg, char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001200{
1201 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001202 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001203 int base = 0;
1204 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001205 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001206
1207 /* Skip leading whitespace */
1208 cp = cp + strspn(cp, WHITESPACE);
1209
Damien Miller20e1fab2004-02-18 14:30:55 +11001210 /* Check for leading '-' (disable error processing) */
Damien Millerf29238e2013-10-17 11:48:52 +11001211 *ignore_errors = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001212 if (*cp == '-') {
Damien Millerf29238e2013-10-17 11:48:52 +11001213 *ignore_errors = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001214 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001215 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001216 }
1217
Darren Tucker70cc0922010-01-09 22:28:03 +11001218 /* Ignore blank lines and lines which begin with comment '#' char */
1219 if (*cp == '\0' || *cp == '#')
1220 return (0);
1221
Darren Tucker909d8582010-01-08 19:02:40 +11001222 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001223 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001224
Damien Miller1cbc2922007-10-26 14:27:45 +10001225 /* Figure out which command we have */
1226 for (i = 0; cmds[i].c != NULL; i++) {
Damien Millerd6d9fa02013-02-12 11:02:46 +11001227 if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001228 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001229 }
1230 cmdnum = cmds[i].n;
1231 cmd = cmds[i].c;
1232
1233 /* Special case */
1234 if (*cp == '!') {
1235 cp++;
1236 cmdnum = I_SHELL;
1237 } else if (cmdnum == -1) {
1238 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001239 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001240 }
1241
1242 /* Get arguments and parse flags */
Damien Millerf29238e2013-10-17 11:48:52 +11001243 *aflag = *fflag = *hflag = *iflag = *lflag = *pflag = 0;
1244 *rflag = *sflag = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001245 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001246 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001247 switch (cmdnum) {
1248 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001249 case I_REGET:
Damien Millerd8accc02014-05-15 13:46:25 +10001250 case I_REPUT:
Damien Miller20e1fab2004-02-18 14:30:55 +11001251 case I_PUT:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001252 if ((optidx = parse_getput_flags(cmd, argv, argc,
Damien Millerf29238e2013-10-17 11:48:52 +11001253 aflag, fflag, pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001254 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001255 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001256 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001257 error("You must specify at least one path after a "
1258 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001259 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001260 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001261 *path1 = xstrdup(argv[optidx]);
1262 /* Get second pathname (optional) */
1263 if (argc - optidx > 1) {
1264 *path2 = xstrdup(argv[optidx + 1]);
1265 /* Destination is not globbed */
1266 undo_glob_escape(*path2);
1267 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001268 break;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001269 case I_LINK:
1270 if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1271 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001272 goto parse_two_paths;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001273 case I_RENAME:
Damien Millerc7dba122013-08-21 02:41:15 +10001274 if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1)
1275 return -1;
1276 goto parse_two_paths;
1277 case I_SYMLINK:
Damien Miller036d3072013-08-21 02:41:46 +10001278 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1279 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001280 parse_two_paths:
Damien Miller1cbc2922007-10-26 14:27:45 +10001281 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001282 error("You must specify two paths after a %s "
1283 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001284 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001285 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001286 *path1 = xstrdup(argv[optidx]);
1287 *path2 = xstrdup(argv[optidx + 1]);
1288 /* Paths are not globbed */
1289 undo_glob_escape(*path1);
1290 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001291 break;
1292 case I_RM:
1293 case I_MKDIR:
1294 case I_RMDIR:
1295 case I_CHDIR:
1296 case I_LCHDIR:
1297 case I_LMKDIR:
Damien Miller036d3072013-08-21 02:41:46 +10001298 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1299 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001300 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001301 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001302 error("You must specify a path after a %s command.",
1303 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001304 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001305 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001306 *path1 = xstrdup(argv[optidx]);
1307 /* Only "rm" globs */
1308 if (cmdnum != I_RM)
1309 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001310 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001311 case I_DF:
1312 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1313 iflag)) == -1)
1314 return -1;
1315 /* Default to current directory if no path specified */
1316 if (argc - optidx < 1)
1317 *path1 = NULL;
1318 else {
1319 *path1 = xstrdup(argv[optidx]);
1320 undo_glob_escape(*path1);
1321 }
1322 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001323 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001324 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001325 return(-1);
1326 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001327 if (argc - optidx > 0)
1328 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001329 break;
1330 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001331 /* Skip ls command and following whitespace */
1332 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001333 case I_SHELL:
1334 /* Uses the rest of the line */
1335 break;
1336 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001337 case I_CHMOD:
1338 base = 8;
1339 case I_CHOWN:
1340 case I_CHGRP:
Damien Miller036d3072013-08-21 02:41:46 +10001341 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1342 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001343 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001344 if (argc - optidx < 1)
1345 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001346 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001347 l = strtol(argv[optidx], &cp2, base);
1348 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1349 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1350 l < 0) {
1351 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001352 error("You must supply a numeric argument "
1353 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001354 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001355 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001356 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001357 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001358 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001359 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001360 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001361 error("You must specify a path after a %s command.",
1362 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001363 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001364 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001365 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001366 break;
1367 case I_QUIT:
1368 case I_PWD:
1369 case I_LPWD:
1370 case I_HELP:
1371 case I_VERSION:
1372 case I_PROGRESS:
Damien Miller036d3072013-08-21 02:41:46 +10001373 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1374 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001375 break;
1376 default:
1377 fatal("Command not implemented");
1378 }
1379
1380 *cpp = cp;
1381 return(cmdnum);
1382}
1383
1384static int
1385parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1386 int err_abort)
1387{
1388 char *path1, *path2, *tmp;
Damien Millerd8accc02014-05-15 13:46:25 +10001389 int ignore_errors = 0, aflag = 0, fflag = 0, hflag = 0,
1390 iflag = 0;
Damien Millerf29238e2013-10-17 11:48:52 +11001391 int lflag = 0, pflag = 0, rflag = 0, sflag = 0;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001392 int cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001393 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001394 Attrib a, *aa;
1395 char path_buf[MAXPATHLEN];
1396 int err = 0;
1397 glob_t g;
1398
1399 path1 = path2 = NULL;
Damien Millerf29238e2013-10-17 11:48:52 +11001400 cmdnum = parse_args(&cmd, &ignore_errors, &aflag, &fflag, &hflag,
1401 &iflag, &lflag, &pflag, &rflag, &sflag, &n_arg, &path1, &path2);
1402 if (ignore_errors != 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001403 err_abort = 0;
1404
1405 memset(&g, 0, sizeof(g));
1406
1407 /* Perform command */
1408 switch (cmdnum) {
1409 case 0:
1410 /* Blank line */
1411 break;
1412 case -1:
1413 /* Unrecognized command */
1414 err = -1;
1415 break;
Damien Miller0d032412013-07-25 11:56:52 +10001416 case I_REGET:
1417 aflag = 1;
1418 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001419 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001420 err = process_get(conn, path1, path2, *pwd, pflag,
Damien Millerf29238e2013-10-17 11:48:52 +11001421 rflag, aflag, fflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001422 break;
Damien Millerd8accc02014-05-15 13:46:25 +10001423 case I_REPUT:
1424 aflag = 1;
1425 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001426 case I_PUT:
Damien Millerf29238e2013-10-17 11:48:52 +11001427 err = process_put(conn, path1, path2, *pwd, pflag,
Damien Millerd8accc02014-05-15 13:46:25 +10001428 rflag, aflag, fflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001429 break;
1430 case I_RENAME:
1431 path1 = make_absolute(path1, *pwd);
1432 path2 = make_absolute(path2, *pwd);
Damien Millerc7dba122013-08-21 02:41:15 +10001433 err = do_rename(conn, path1, path2, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001434 break;
1435 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001436 sflag = 1;
1437 case I_LINK:
Damien Miller034f27a2013-08-21 02:40:44 +10001438 if (!sflag)
1439 path1 = make_absolute(path1, *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001440 path2 = make_absolute(path2, *pwd);
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001441 err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001442 break;
1443 case I_RM:
1444 path1 = make_absolute(path1, *pwd);
1445 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001446 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001447 if (!quiet)
1448 printf("Removing %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001449 err = do_rm(conn, g.gl_pathv[i]);
1450 if (err != 0 && err_abort)
1451 break;
1452 }
1453 break;
1454 case I_MKDIR:
1455 path1 = make_absolute(path1, *pwd);
1456 attrib_clear(&a);
1457 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1458 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001459 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001460 break;
1461 case I_RMDIR:
1462 path1 = make_absolute(path1, *pwd);
1463 err = do_rmdir(conn, path1);
1464 break;
1465 case I_CHDIR:
1466 path1 = make_absolute(path1, *pwd);
1467 if ((tmp = do_realpath(conn, path1)) == NULL) {
1468 err = 1;
1469 break;
1470 }
1471 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
Darren Tuckera627d422013-06-02 07:31:17 +10001472 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001473 err = 1;
1474 break;
1475 }
1476 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1477 error("Can't change directory: Can't check target");
Darren Tuckera627d422013-06-02 07:31:17 +10001478 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001479 err = 1;
1480 break;
1481 }
1482 if (!S_ISDIR(aa->perm)) {
1483 error("Can't change directory: \"%s\" is not "
1484 "a directory", tmp);
Darren Tuckera627d422013-06-02 07:31:17 +10001485 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001486 err = 1;
1487 break;
1488 }
Darren Tuckera627d422013-06-02 07:31:17 +10001489 free(*pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001490 *pwd = tmp;
1491 break;
1492 case I_LS:
1493 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001494 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001495 break;
1496 }
1497
1498 /* Strip pwd off beginning of non-absolute paths */
1499 tmp = NULL;
1500 if (*path1 != '/')
1501 tmp = *pwd;
1502
1503 path1 = make_absolute(path1, *pwd);
1504 err = do_globbed_ls(conn, path1, tmp, lflag);
1505 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001506 case I_DF:
1507 /* Default to current directory if no path specified */
1508 if (path1 == NULL)
1509 path1 = xstrdup(*pwd);
1510 path1 = make_absolute(path1, *pwd);
1511 err = do_df(conn, path1, hflag, iflag);
1512 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001513 case I_LCHDIR:
1514 if (chdir(path1) == -1) {
1515 error("Couldn't change local directory to "
1516 "\"%s\": %s", path1, strerror(errno));
1517 err = 1;
1518 }
1519 break;
1520 case I_LMKDIR:
1521 if (mkdir(path1, 0777) == -1) {
1522 error("Couldn't create local directory "
1523 "\"%s\": %s", path1, strerror(errno));
1524 err = 1;
1525 }
1526 break;
1527 case I_LLS:
1528 local_do_ls(cmd);
1529 break;
1530 case I_SHELL:
1531 local_do_shell(cmd);
1532 break;
1533 case I_LUMASK:
1534 umask(n_arg);
1535 printf("Local umask: %03lo\n", n_arg);
1536 break;
1537 case I_CHMOD:
1538 path1 = make_absolute(path1, *pwd);
1539 attrib_clear(&a);
1540 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1541 a.perm = n_arg;
1542 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001543 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001544 if (!quiet)
1545 printf("Changing mode on %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001546 err = do_setstat(conn, g.gl_pathv[i], &a);
1547 if (err != 0 && err_abort)
1548 break;
1549 }
1550 break;
1551 case I_CHOWN:
1552 case I_CHGRP:
1553 path1 = make_absolute(path1, *pwd);
1554 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001555 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001556 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001557 if (err_abort) {
1558 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001559 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001560 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001561 continue;
1562 }
1563 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1564 error("Can't get current ownership of "
1565 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001566 if (err_abort) {
1567 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001568 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001569 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001570 continue;
1571 }
1572 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1573 if (cmdnum == I_CHOWN) {
Damien Miller9303e652013-04-23 15:22:40 +10001574 if (!quiet)
1575 printf("Changing owner on %s\n",
1576 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001577 aa->uid = n_arg;
1578 } else {
Damien Miller9303e652013-04-23 15:22:40 +10001579 if (!quiet)
1580 printf("Changing group on %s\n",
1581 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001582 aa->gid = n_arg;
1583 }
1584 err = do_setstat(conn, g.gl_pathv[i], aa);
1585 if (err != 0 && err_abort)
1586 break;
1587 }
1588 break;
1589 case I_PWD:
1590 printf("Remote working directory: %s\n", *pwd);
1591 break;
1592 case I_LPWD:
1593 if (!getcwd(path_buf, sizeof(path_buf))) {
1594 error("Couldn't get local cwd: %s", strerror(errno));
1595 err = -1;
1596 break;
1597 }
1598 printf("Local working directory: %s\n", path_buf);
1599 break;
1600 case I_QUIT:
1601 /* Processed below */
1602 break;
1603 case I_HELP:
1604 help();
1605 break;
1606 case I_VERSION:
1607 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1608 break;
1609 case I_PROGRESS:
1610 showprogress = !showprogress;
1611 if (showprogress)
1612 printf("Progress meter enabled\n");
1613 else
1614 printf("Progress meter disabled\n");
1615 break;
1616 default:
1617 fatal("%d is not implemented", cmdnum);
1618 }
1619
1620 if (g.gl_pathc)
1621 globfree(&g);
Darren Tuckera627d422013-06-02 07:31:17 +10001622 free(path1);
1623 free(path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001624
1625 /* If an unignored error occurs in batch mode we should abort. */
1626 if (err_abort && err != 0)
1627 return (-1);
1628 else if (cmdnum == I_QUIT)
1629 return (1);
1630
1631 return (0);
1632}
1633
Darren Tucker2d963d82004-11-07 20:04:10 +11001634#ifdef USE_LIBEDIT
1635static char *
1636prompt(EditLine *el)
1637{
1638 return ("sftp> ");
1639}
Darren Tucker2d963d82004-11-07 20:04:10 +11001640
Darren Tucker909d8582010-01-08 19:02:40 +11001641/* Display entries in 'list' after skipping the first 'len' chars */
1642static void
1643complete_display(char **list, u_int len)
1644{
1645 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1646 struct winsize ws;
1647 char *tmp;
1648
1649 /* Count entries for sort and find longest */
Damien Miller02e87802013-08-21 02:38:51 +10001650 for (y = 0; list[y]; y++)
Darren Tucker909d8582010-01-08 19:02:40 +11001651 m = MAX(m, strlen(list[y]));
1652
1653 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1654 width = ws.ws_col;
1655
1656 m = m > len ? m - len : 0;
1657 columns = width / (m + 2);
1658 columns = MAX(columns, 1);
1659 colspace = width / columns;
1660 colspace = MIN(colspace, width);
1661
1662 printf("\n");
1663 m = 1;
1664 for (y = 0; list[y]; y++) {
1665 llen = strlen(list[y]);
1666 tmp = llen > len ? list[y] + len : "";
1667 printf("%-*s", colspace, tmp);
1668 if (m >= columns) {
1669 printf("\n");
1670 m = 1;
1671 } else
1672 m++;
1673 }
1674 printf("\n");
1675}
1676
1677/*
1678 * Given a "list" of words that begin with a common prefix of "word",
1679 * attempt to find an autocompletion to extends "word" by the next
1680 * characters common to all entries in "list".
1681 */
1682static char *
1683complete_ambiguous(const char *word, char **list, size_t count)
1684{
1685 if (word == NULL)
1686 return NULL;
1687
1688 if (count > 0) {
1689 u_int y, matchlen = strlen(list[0]);
1690
1691 /* Find length of common stem */
1692 for (y = 1; list[y]; y++) {
1693 u_int x;
1694
Damien Miller02e87802013-08-21 02:38:51 +10001695 for (x = 0; x < matchlen; x++)
1696 if (list[0][x] != list[y][x])
Darren Tucker909d8582010-01-08 19:02:40 +11001697 break;
1698
1699 matchlen = x;
1700 }
1701
1702 if (matchlen > strlen(word)) {
1703 char *tmp = xstrdup(list[0]);
1704
Darren Tucker340d1682010-01-09 08:54:31 +11001705 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001706 return tmp;
1707 }
Damien Miller02e87802013-08-21 02:38:51 +10001708 }
Darren Tucker909d8582010-01-08 19:02:40 +11001709
1710 return xstrdup(word);
1711}
1712
1713/* Autocomplete a sftp command */
1714static int
1715complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1716 int terminated)
1717{
1718 u_int y, count = 0, cmdlen, tmplen;
1719 char *tmp, **list, argterm[3];
1720 const LineInfo *lf;
1721
1722 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1723
1724 /* No command specified: display all available commands */
1725 if (cmd == NULL) {
1726 for (y = 0; cmds[y].c; y++)
1727 list[count++] = xstrdup(cmds[y].c);
Damien Miller02e87802013-08-21 02:38:51 +10001728
Darren Tucker909d8582010-01-08 19:02:40 +11001729 list[count] = NULL;
1730 complete_display(list, 0);
1731
Damien Miller02e87802013-08-21 02:38:51 +10001732 for (y = 0; list[y] != NULL; y++)
1733 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001734 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001735 return count;
1736 }
1737
1738 /* Prepare subset of commands that start with "cmd" */
1739 cmdlen = strlen(cmd);
1740 for (y = 0; cmds[y].c; y++) {
Damien Miller02e87802013-08-21 02:38:51 +10001741 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
Darren Tucker909d8582010-01-08 19:02:40 +11001742 list[count++] = xstrdup(cmds[y].c);
1743 }
1744 list[count] = NULL;
1745
Damien Miller47d81152011-11-25 13:53:48 +11001746 if (count == 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10001747 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001748 return 0;
Damien Miller47d81152011-11-25 13:53:48 +11001749 }
Darren Tucker909d8582010-01-08 19:02:40 +11001750
1751 /* Complete ambigious command */
1752 tmp = complete_ambiguous(cmd, list, count);
1753 if (count > 1)
1754 complete_display(list, 0);
1755
Damien Miller02e87802013-08-21 02:38:51 +10001756 for (y = 0; list[y]; y++)
1757 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001758 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001759
1760 if (tmp != NULL) {
1761 tmplen = strlen(tmp);
1762 cmdlen = strlen(cmd);
1763 /* If cmd may be extended then do so */
1764 if (tmplen > cmdlen)
1765 if (el_insertstr(el, tmp + cmdlen) == -1)
1766 fatal("el_insertstr failed.");
1767 lf = el_line(el);
1768 /* Terminate argument cleanly */
1769 if (count == 1) {
1770 y = 0;
1771 if (!terminated)
1772 argterm[y++] = quote;
1773 if (lastarg || *(lf->cursor) != ' ')
1774 argterm[y++] = ' ';
1775 argterm[y] = '\0';
1776 if (y > 0 && el_insertstr(el, argterm) == -1)
1777 fatal("el_insertstr failed.");
1778 }
Darren Tuckera627d422013-06-02 07:31:17 +10001779 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001780 }
1781
1782 return count;
1783}
1784
1785/*
1786 * Determine whether a particular sftp command's arguments (if any)
1787 * represent local or remote files.
1788 */
1789static int
1790complete_is_remote(char *cmd) {
1791 int i;
1792
1793 if (cmd == NULL)
1794 return -1;
1795
1796 for (i = 0; cmds[i].c; i++) {
Damien Miller02e87802013-08-21 02:38:51 +10001797 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
Darren Tucker909d8582010-01-08 19:02:40 +11001798 return cmds[i].t;
1799 }
1800
1801 return -1;
1802}
1803
1804/* Autocomplete a filename "file" */
1805static int
1806complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1807 char *file, int remote, int lastarg, char quote, int terminated)
1808{
1809 glob_t g;
Darren Tuckerea647212013-06-06 08:19:09 +10001810 char *tmp, *tmp2, ins[8];
Darren Tucker17146d32012-10-05 10:46:16 +10001811 u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
Darren Tuckerea647212013-06-06 08:19:09 +10001812 int clen;
Darren Tucker909d8582010-01-08 19:02:40 +11001813 const LineInfo *lf;
Damien Miller02e87802013-08-21 02:38:51 +10001814
Darren Tucker909d8582010-01-08 19:02:40 +11001815 /* Glob from "file" location */
1816 if (file == NULL)
1817 tmp = xstrdup("*");
1818 else
1819 xasprintf(&tmp, "%s*", file);
1820
Darren Tucker191fcc62012-10-05 10:45:01 +10001821 /* Check if the path is absolute. */
1822 isabs = tmp[0] == '/';
1823
Darren Tucker909d8582010-01-08 19:02:40 +11001824 memset(&g, 0, sizeof(g));
1825 if (remote != LOCAL) {
1826 tmp = make_absolute(tmp, remote_path);
1827 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001828 } else
Darren Tucker909d8582010-01-08 19:02:40 +11001829 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001830
Darren Tucker909d8582010-01-08 19:02:40 +11001831 /* Determine length of pwd so we can trim completion display */
1832 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1833 /* Terminate counting on first unescaped glob metacharacter */
1834 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1835 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1836 hadglob = 1;
1837 break;
1838 }
1839 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1840 tmplen++;
1841 if (tmp[tmplen] == '/')
1842 pwdlen = tmplen + 1; /* track last seen '/' */
1843 }
Darren Tuckera627d422013-06-02 07:31:17 +10001844 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001845
Damien Miller02e87802013-08-21 02:38:51 +10001846 if (g.gl_matchc == 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001847 goto out;
1848
1849 if (g.gl_matchc > 1)
1850 complete_display(g.gl_pathv, pwdlen);
1851
1852 tmp = NULL;
1853 /* Don't try to extend globs */
1854 if (file == NULL || hadglob)
1855 goto out;
1856
1857 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
Darren Tucker191fcc62012-10-05 10:45:01 +10001858 tmp = path_strip(tmp2, isabs ? NULL : remote_path);
Darren Tuckera627d422013-06-02 07:31:17 +10001859 free(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001860
1861 if (tmp == NULL)
1862 goto out;
1863
1864 tmplen = strlen(tmp);
1865 filelen = strlen(file);
1866
Darren Tucker17146d32012-10-05 10:46:16 +10001867 /* Count the number of escaped characters in the input string. */
1868 cesc = isesc = 0;
1869 for (i = 0; i < filelen; i++) {
1870 if (!isesc && file[i] == '\\' && i + 1 < filelen){
1871 isesc = 1;
1872 cesc++;
1873 } else
1874 isesc = 0;
1875 }
1876
1877 if (tmplen > (filelen - cesc)) {
1878 tmp2 = tmp + filelen - cesc;
Damien Miller02e87802013-08-21 02:38:51 +10001879 len = strlen(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001880 /* quote argument on way out */
Darren Tuckerea647212013-06-06 08:19:09 +10001881 for (i = 0; i < len; i += clen) {
1882 if ((clen = mblen(tmp2 + i, len - i)) < 0 ||
1883 (size_t)clen > sizeof(ins) - 2)
1884 fatal("invalid multibyte character");
Darren Tucker909d8582010-01-08 19:02:40 +11001885 ins[0] = '\\';
Darren Tuckerea647212013-06-06 08:19:09 +10001886 memcpy(ins + 1, tmp2 + i, clen);
1887 ins[clen + 1] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001888 switch (tmp2[i]) {
1889 case '\'':
1890 case '"':
1891 case '\\':
1892 case '\t':
Darren Tuckerd78739a2010-10-24 10:56:32 +11001893 case '[':
Darren Tucker909d8582010-01-08 19:02:40 +11001894 case ' ':
Darren Tucker17146d32012-10-05 10:46:16 +10001895 case '#':
1896 case '*':
Darren Tucker909d8582010-01-08 19:02:40 +11001897 if (quote == '\0' || tmp2[i] == quote) {
1898 if (el_insertstr(el, ins) == -1)
1899 fatal("el_insertstr "
1900 "failed.");
1901 break;
1902 }
1903 /* FALLTHROUGH */
1904 default:
1905 if (el_insertstr(el, ins + 1) == -1)
1906 fatal("el_insertstr failed.");
1907 break;
1908 }
1909 }
1910 }
1911
1912 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11001913 if (g.gl_matchc == 1) {
1914 i = 0;
1915 if (!terminated)
1916 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11001917 if (*(lf->cursor - 1) != '/' &&
1918 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11001919 ins[i++] = ' ';
1920 ins[i] = '\0';
1921 if (i > 0 && el_insertstr(el, ins) == -1)
1922 fatal("el_insertstr failed.");
1923 }
Darren Tuckera627d422013-06-02 07:31:17 +10001924 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001925
1926 out:
1927 globfree(&g);
1928 return g.gl_matchc;
1929}
1930
1931/* tab-completion hook function, called via libedit */
1932static unsigned char
1933complete(EditLine *el, int ch)
1934{
Damien Miller02e87802013-08-21 02:38:51 +10001935 char **argv, *line, quote;
Damien Miller746d1a62013-07-18 16:13:02 +10001936 int argc, carg;
1937 u_int cursor, len, terminated, ret = CC_ERROR;
Darren Tucker909d8582010-01-08 19:02:40 +11001938 const LineInfo *lf;
1939 struct complete_ctx *complete_ctx;
1940
1941 lf = el_line(el);
1942 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1943 fatal("%s: el_get failed", __func__);
1944
1945 /* Figure out which argument the cursor points to */
1946 cursor = lf->cursor - lf->buffer;
1947 line = (char *)xmalloc(cursor + 1);
1948 memcpy(line, lf->buffer, cursor);
1949 line[cursor] = '\0';
1950 argv = makeargv(line, &carg, 1, &quote, &terminated);
Darren Tuckera627d422013-06-02 07:31:17 +10001951 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001952
1953 /* Get all the arguments on the line */
1954 len = lf->lastchar - lf->buffer;
1955 line = (char *)xmalloc(len + 1);
1956 memcpy(line, lf->buffer, len);
1957 line[len] = '\0';
1958 argv = makeargv(line, &argc, 1, NULL, NULL);
1959
1960 /* Ensure cursor is at EOL or a argument boundary */
1961 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1962 line[cursor] != '\n') {
Darren Tuckera627d422013-06-02 07:31:17 +10001963 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001964 return ret;
1965 }
1966
1967 if (carg == 0) {
1968 /* Show all available commands */
1969 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1970 ret = CC_REDISPLAY;
1971 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1972 /* Handle the command parsing */
1973 if (complete_cmd_parse(el, argv[0], argc == carg,
Damien Miller02e87802013-08-21 02:38:51 +10001974 quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001975 ret = CC_REDISPLAY;
1976 } else if (carg >= 1) {
1977 /* Handle file parsing */
1978 int remote = complete_is_remote(argv[0]);
1979 char *filematch = NULL;
1980
1981 if (carg > 1 && line[cursor-1] != ' ')
1982 filematch = argv[carg - 1];
1983
1984 if (remote != 0 &&
1985 complete_match(el, complete_ctx->conn,
1986 *complete_ctx->remote_pathp, filematch,
Damien Miller02e87802013-08-21 02:38:51 +10001987 remote, carg == argc, quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001988 ret = CC_REDISPLAY;
1989 }
1990
Damien Miller02e87802013-08-21 02:38:51 +10001991 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001992 return ret;
1993}
Darren Tuckere67f7db2010-01-08 19:50:02 +11001994#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11001995
Damien Miller20e1fab2004-02-18 14:30:55 +11001996int
Darren Tucker21063192010-01-08 17:10:36 +11001997interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001998{
Darren Tucker909d8582010-01-08 19:02:40 +11001999 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11002000 char *dir = NULL;
2001 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10002002 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11002003 EditLine *el = NULL;
2004#ifdef USE_LIBEDIT
2005 History *hl = NULL;
2006 HistEvent hev;
2007 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11002008 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11002009
2010 if (!batchmode && isatty(STDIN_FILENO)) {
2011 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
2012 fatal("Couldn't initialise editline");
2013 if ((hl = history_init()) == NULL)
2014 fatal("Couldn't initialise editline history");
2015 history(hl, &hev, H_SETSIZE, 100);
2016 el_set(el, EL_HIST, history, hl);
2017
2018 el_set(el, EL_PROMPT, prompt);
2019 el_set(el, EL_EDITOR, "emacs");
2020 el_set(el, EL_TERMINAL, NULL);
2021 el_set(el, EL_SIGNAL, 1);
2022 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11002023
2024 /* Tab Completion */
Damien Miller02e87802013-08-21 02:38:51 +10002025 el_set(el, EL_ADDFN, "ftp-complete",
Darren Tuckerd78739a2010-10-24 10:56:32 +11002026 "Context sensitive argument completion", complete);
Darren Tucker909d8582010-01-08 19:02:40 +11002027 complete_ctx.conn = conn;
2028 complete_ctx.remote_pathp = &remote_path;
2029 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
2030 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Damien Millere0ee7272013-08-21 02:42:35 +10002031 /* enable ctrl-left-arrow and ctrl-right-arrow */
2032 el_set(el, EL_BIND, "\\e[1;5C", "em-next-word", NULL);
2033 el_set(el, EL_BIND, "\\e[5C", "em-next-word", NULL);
2034 el_set(el, EL_BIND, "\\e[1;5D", "ed-prev-word", NULL);
2035 el_set(el, EL_BIND, "\\e\\e[D", "ed-prev-word", NULL);
Damien Miller61353b32013-09-14 09:45:32 +10002036 /* make ^w match ksh behaviour */
2037 el_set(el, EL_BIND, "^w", "ed-delete-prev-word", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11002038 }
2039#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002040
Darren Tucker909d8582010-01-08 19:02:40 +11002041 remote_path = do_realpath(conn, ".");
2042 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11002043 fatal("Need cwd");
2044
2045 if (file1 != NULL) {
2046 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11002047 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11002048
2049 if (remote_is_dir(conn, dir) && file2 == NULL) {
Damien Miller9303e652013-04-23 15:22:40 +10002050 if (!quiet)
2051 printf("Changing to: %s\n", dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002052 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11002053 if (parse_dispatch_command(conn, cmd,
2054 &remote_path, 1) != 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10002055 free(dir);
2056 free(remote_path);
2057 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002058 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11002059 }
Damien Miller20e1fab2004-02-18 14:30:55 +11002060 } else {
Darren Tucker0af24052012-10-05 10:41:25 +10002061 /* XXX this is wrong wrt quoting */
Damien Miller0d032412013-07-25 11:56:52 +10002062 snprintf(cmd, sizeof cmd, "get%s %s%s%s",
2063 global_aflag ? " -a" : "", dir,
2064 file2 == NULL ? "" : " ",
2065 file2 == NULL ? "" : file2);
Darren Tucker909d8582010-01-08 19:02:40 +11002066 err = parse_dispatch_command(conn, cmd,
2067 &remote_path, 1);
Darren Tuckera627d422013-06-02 07:31:17 +10002068 free(dir);
2069 free(remote_path);
2070 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002071 return (err);
2072 }
Darren Tuckera627d422013-06-02 07:31:17 +10002073 free(dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002074 }
2075
Damien Miller37294fb2005-07-17 17:18:49 +10002076 setlinebuf(stdout);
2077 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11002078
Damien Miller0e2c1022005-08-12 22:16:22 +10002079 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11002080 err = 0;
2081 for (;;) {
2082 char *cp;
2083
Darren Tuckercdf547a2004-05-24 10:12:19 +10002084 signal(SIGINT, SIG_IGN);
2085
Darren Tucker2d963d82004-11-07 20:04:10 +11002086 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002087 if (interactive)
2088 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11002089 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002090 if (interactive)
2091 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11002092 break;
2093 }
Damien Miller0e2c1022005-08-12 22:16:22 +10002094 if (!interactive) { /* Echo command */
2095 printf("sftp> %s", cmd);
2096 if (strlen(cmd) > 0 &&
2097 cmd[strlen(cmd) - 1] != '\n')
2098 printf("\n");
2099 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002100 } else {
2101#ifdef USE_LIBEDIT
2102 const char *line;
2103 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11002104
Darren Tucker909d8582010-01-08 19:02:40 +11002105 if ((line = el_gets(el, &count)) == NULL ||
2106 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002107 printf("\n");
2108 break;
2109 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002110 history(hl, &hev, H_ENTER, line);
2111 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
2112 fprintf(stderr, "Error: input line too long\n");
2113 continue;
2114 }
2115#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002116 }
2117
Damien Miller20e1fab2004-02-18 14:30:55 +11002118 cp = strrchr(cmd, '\n');
2119 if (cp)
2120 *cp = '\0';
2121
Darren Tuckercdf547a2004-05-24 10:12:19 +10002122 /* Handle user interrupts gracefully during commands */
2123 interrupted = 0;
2124 signal(SIGINT, cmd_interrupt);
2125
Darren Tucker909d8582010-01-08 19:02:40 +11002126 err = parse_dispatch_command(conn, cmd, &remote_path,
2127 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11002128 if (err != 0)
2129 break;
2130 }
Darren Tuckera627d422013-06-02 07:31:17 +10002131 free(remote_path);
2132 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002133
Tim Rice027e8b12005-08-15 14:52:50 -07002134#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10002135 if (el != NULL)
2136 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07002137#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10002138
Damien Miller20e1fab2004-02-18 14:30:55 +11002139 /* err == 1 signifies normal "quit" exit */
2140 return (err >= 0 ? 0 : -1);
2141}
Damien Miller62d57f62003-01-10 21:43:24 +11002142
Ben Lindstrombba81212001-06-25 05:01:22 +00002143static void
Damien Millercc685c12003-06-04 22:51:38 +10002144connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11002145{
2146 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002147
Damien Miller33804262001-02-04 23:20:18 +11002148#ifdef USE_PIPES
2149 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002150
Damien Miller33804262001-02-04 23:20:18 +11002151 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2152 fatal("pipe: %s", strerror(errno));
2153 *in = pin[0];
2154 *out = pout[1];
2155 c_in = pout[0];
2156 c_out = pin[1];
2157#else /* USE_PIPES */
2158 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002159
Damien Miller33804262001-02-04 23:20:18 +11002160 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2161 fatal("socketpair: %s", strerror(errno));
2162 *in = *out = inout[0];
2163 c_in = c_out = inout[1];
2164#endif /* USE_PIPES */
2165
Damien Millercc685c12003-06-04 22:51:38 +10002166 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002167 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002168 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002169 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2170 (dup2(c_out, STDOUT_FILENO) == -1)) {
2171 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002172 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002173 }
2174 close(*in);
2175 close(*out);
2176 close(c_in);
2177 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002178
2179 /*
2180 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002181 * ignore SIGINT if we want to gracefully abort commands,
2182 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002183 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2184 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002185 */
2186 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002187 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002188 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002189 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002190 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002191 }
2192
Damien Millercc685c12003-06-04 22:51:38 +10002193 signal(SIGTERM, killchild);
2194 signal(SIGINT, killchild);
2195 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11002196 close(c_in);
2197 close(c_out);
2198}
2199
Ben Lindstrombba81212001-06-25 05:01:22 +00002200static void
Damien Miller33804262001-02-04 23:20:18 +11002201usage(void)
2202{
Damien Miller025e01c2002-02-08 22:06:29 +11002203 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002204
Ben Lindstrom1e243242001-09-18 05:38:44 +00002205 fprintf(stderr,
Damien Miller1edcbf62013-10-18 10:17:17 +11002206 "usage: %s [-1246aCfpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002207 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002208 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002209 " [-o ssh_option] [-P port] [-R num_requests] "
2210 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002211 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002212 " %s [user@]host[:file ...]\n"
2213 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002214 " %s -b batchfile [user@]host\n",
2215 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002216 exit(1);
2217}
2218
Kevin Stevesef4eea92001-02-05 12:42:17 +00002219int
Damien Miller33804262001-02-04 23:20:18 +11002220main(int argc, char **argv)
2221{
Damien Miller956f3fb2003-01-10 21:40:00 +11002222 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002223 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002224 int debug_level = 0, sshver = 2;
2225 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002226 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002227 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002228 LogLevel ll = SYSLOG_LEVEL_INFO;
2229 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002230 extern int optind;
2231 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002232 struct sftp_conn *conn;
2233 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2234 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002235 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002236
Darren Tuckerce321d82005-10-03 18:11:24 +10002237 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2238 sanitise_stdfd();
Darren Tuckerea647212013-06-06 08:19:09 +10002239 setlocale(LC_CTYPE, "");
Darren Tuckerce321d82005-10-03 18:11:24 +10002240
Damien Miller59d3d5b2003-08-22 09:34:41 +10002241 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002242 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002243 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002244 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002245 addargs(&args, "-oForwardX11 no");
2246 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002247 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002248 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002249
Ben Lindstrom387c4722001-05-08 20:27:25 +00002250 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002251 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002252
Darren Tucker282b4022009-10-07 08:23:06 +11002253 while ((ch = getopt(argc, argv,
Damien Millerf29238e2013-10-17 11:48:52 +11002254 "1246afhpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002255 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002256 /* Passed through to ssh(1) */
2257 case '4':
2258 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002259 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002260 addargs(&args, "-%c", ch);
2261 break;
2262 /* Passed through to ssh(1) with argument */
2263 case 'F':
2264 case 'c':
2265 case 'i':
2266 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002267 addargs(&args, "-%c", ch);
2268 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002269 break;
2270 case 'q':
Damien Miller9303e652013-04-23 15:22:40 +10002271 ll = SYSLOG_LEVEL_ERROR;
2272 quiet = 1;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002273 showprogress = 0;
2274 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002275 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002276 case 'P':
2277 addargs(&args, "-oPort %s", optarg);
2278 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002279 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002280 if (debug_level < 3) {
2281 addargs(&args, "-v");
2282 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2283 }
2284 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002285 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002286 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002287 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002288 if (sftp_server == NULL)
2289 sftp_server = _PATH_SFTP_SERVER;
2290 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002291 case '2':
2292 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002293 break;
Damien Miller0d032412013-07-25 11:56:52 +10002294 case 'a':
2295 global_aflag = 1;
2296 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002297 case 'B':
2298 copy_buffer_len = strtol(optarg, &cp, 10);
2299 if (copy_buffer_len == 0 || *cp != '\0')
2300 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002301 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002302 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002303 if (batchmode)
2304 fatal("Batch file already specified.");
2305
2306 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002307 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002308 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002309 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002310 showprogress = 0;
Damien Miller9303e652013-04-23 15:22:40 +10002311 quiet = batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002312 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002313 break;
Damien Millerf29238e2013-10-17 11:48:52 +11002314 case 'f':
2315 global_fflag = 1;
2316 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002317 case 'p':
2318 global_pflag = 1;
2319 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002320 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002321 sftp_direct = optarg;
2322 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002323 case 'l':
2324 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2325 &errstr);
2326 if (errstr != NULL)
2327 usage();
2328 limit_kbps *= 1024; /* kbps */
2329 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002330 case 'r':
2331 global_rflag = 1;
2332 break;
Damien Miller16a13332002-02-13 14:03:56 +11002333 case 'R':
2334 num_requests = strtol(optarg, &cp, 10);
2335 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002336 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002337 optarg);
2338 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002339 case 's':
2340 sftp_server = optarg;
2341 break;
2342 case 'S':
2343 ssh_program = optarg;
2344 replacearg(&args, 0, "%s", ssh_program);
2345 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002346 case 'h':
2347 default:
Damien Miller33804262001-02-04 23:20:18 +11002348 usage();
2349 }
2350 }
2351
Damien Millerc0f27d82004-03-08 23:12:19 +11002352 if (!isatty(STDERR_FILENO))
2353 showprogress = 0;
2354
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002355 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2356
Damien Millerd14ee1e2002-02-05 12:27:31 +11002357 if (sftp_direct == NULL) {
2358 if (optind == argc || argc > (optind + 2))
2359 usage();
Damien Miller33804262001-02-04 23:20:18 +11002360
Damien Millerd14ee1e2002-02-05 12:27:31 +11002361 userhost = xstrdup(argv[optind]);
2362 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002363
Ben Lindstromc276c122002-12-23 02:14:51 +00002364 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002365 host = userhost;
2366 else {
2367 *host++ = '\0';
2368 if (!userhost[0]) {
2369 fprintf(stderr, "Missing username\n");
2370 usage();
2371 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002372 addargs(&args, "-l");
2373 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002374 }
2375
Damien Millerec692032004-01-27 21:22:00 +11002376 if ((cp = colon(host)) != NULL) {
2377 *cp++ = '\0';
2378 file1 = cp;
2379 }
2380
Damien Millerd14ee1e2002-02-05 12:27:31 +11002381 host = cleanhostname(host);
2382 if (!*host) {
2383 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002384 usage();
2385 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002386
Damien Millerd14ee1e2002-02-05 12:27:31 +11002387 addargs(&args, "-oProtocol %d", sshver);
2388
2389 /* no subsystem if the server-spec contains a '/' */
2390 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2391 addargs(&args, "-s");
2392
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002393 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002394 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002395 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002396 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002397
Damien Millercc685c12003-06-04 22:51:38 +10002398 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002399 } else {
2400 args.list = NULL;
2401 addargs(&args, "sftp-server");
2402
Damien Millercc685c12003-06-04 22:51:38 +10002403 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002404 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002405 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002406
Damien Miller65e42f82010-09-24 22:15:11 +10002407 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002408 if (conn == NULL)
2409 fatal("Couldn't initialise connection to server");
2410
Damien Miller9303e652013-04-23 15:22:40 +10002411 if (!quiet) {
Darren Tucker21063192010-01-08 17:10:36 +11002412 if (sftp_direct == NULL)
2413 fprintf(stderr, "Connected to %s.\n", host);
2414 else
2415 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2416 }
2417
2418 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002419
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002420#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002421 shutdown(in, SHUT_RDWR);
2422 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002423#endif
2424
Damien Miller33804262001-02-04 23:20:18 +11002425 close(in);
2426 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002427 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002428 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002429
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002430 while (waitpid(sshpid, NULL, 0) == -1)
2431 if (errno != EINTR)
2432 fatal("Couldn't wait for ssh process: %s",
2433 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002434
Damien Miller956f3fb2003-01-10 21:40:00 +11002435 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002436}