blob: e86bb3b18dcea9056844cc4bc0e347772b47d8f3 [file] [log] [blame]
djm@openbsd.org4a459222014-10-06 00:47:15 +00001/* $OpenBSD: sftp.c,v 1.167 2014/10/06 00:47:15 djm Exp $ */
Damien Miller33804262001-02-04 23:20:18 +11002/*
Damien Miller4e60ed72004-02-17 17:07:59 +11003 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
Damien Miller33804262001-02-04 23:20:18 +11004 *
Damien Miller4e60ed72004-02-17 17:07:59 +11005 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
Damien Miller33804262001-02-04 23:20:18 +11008 *
Damien Miller4e60ed72004-02-17 17:07:59 +11009 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Damien Miller33804262001-02-04 23:20:18 +110016 */
17
18#include "includes.h"
19
Damien Miller9cf6d072006-03-15 11:29:24 +110020#include <sys/types.h>
Damien Millerd7834352006-08-05 12:39:39 +100021#include <sys/ioctl.h>
Damien Millerf17883e2006-03-15 11:45:54 +110022#ifdef HAVE_SYS_STAT_H
23# include <sys/stat.h>
24#endif
Damien Miller8dbffe72006-08-05 11:02:17 +100025#include <sys/param.h>
Damien Millere3b60b52006-07-10 21:08:03 +100026#include <sys/socket.h>
Damien Miller9cf6d072006-03-15 11:29:24 +110027#include <sys/wait.h>
Darren Tucker5b2e2ba2008-06-08 09:25:28 +100028#ifdef HAVE_SYS_STATVFS_H
Damien Millerd671e5a2008-05-19 14:53:33 +100029#include <sys/statvfs.h>
Darren Tucker5b2e2ba2008-06-08 09:25:28 +100030#endif
Darren Tucker2d963d82004-11-07 20:04:10 +110031
Damien Miller1cbc2922007-10-26 14:27:45 +100032#include <ctype.h>
Darren Tucker39972492006-07-12 22:22:46 +100033#include <errno.h>
34
Damien Miller03e20032006-03-15 11:16:59 +110035#ifdef HAVE_PATHS_H
Damien Millera9263d02006-03-15 11:18:26 +110036# include <paths.h>
Damien Miller03e20032006-03-15 11:16:59 +110037#endif
Darren Tucker1b0dd172009-10-07 08:37:48 +110038#ifdef HAVE_LIBGEN_H
39#include <libgen.h>
40#endif
Darren Tuckerae133d42013-06-06 08:30:20 +100041#ifdef HAVE_LOCALE_H
42# include <locale.h>
43#endif
Darren Tucker2d963d82004-11-07 20:04:10 +110044#ifdef USE_LIBEDIT
45#include <histedit.h>
46#else
47typedef void EditLine;
48#endif
Damien Miller6ff3cad2006-03-15 11:52:09 +110049#include <signal.h>
Damien Millere7a1e5c2006-08-05 11:34:19 +100050#include <stdlib.h>
Damien Millera7a73ee2006-08-05 11:37:59 +100051#include <stdio.h>
Damien Millere3476ed2006-07-24 14:13:33 +100052#include <string.h>
Damien Millere6b3b612006-07-24 14:01:23 +100053#include <unistd.h>
Damien Millerd7834352006-08-05 12:39:39 +100054#include <stdarg.h>
Damien Miller33804262001-02-04 23:20:18 +110055
Damien Millera7058ec2008-05-20 08:57:06 +100056#ifdef HAVE_UTIL_H
57# include <util.h>
58#endif
59
Damien Miller33804262001-02-04 23:20:18 +110060#include "xmalloc.h"
61#include "log.h"
62#include "pathnames.h"
Ben Lindstrom4529b702001-05-03 23:39:53 +000063#include "misc.h"
Damien Miller33804262001-02-04 23:20:18 +110064
65#include "sftp.h"
Damien Millerd7834352006-08-05 12:39:39 +100066#include "buffer.h"
Damien Miller33804262001-02-04 23:20:18 +110067#include "sftp-common.h"
68#include "sftp-client.h"
Damien Millerd7d46bb2004-02-18 14:11:13 +110069
Darren Tucker21063192010-01-08 17:10:36 +110070#define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */
71#define DEFAULT_NUM_REQUESTS 64 /* # concurrent outstanding requests */
72
Damien Miller20e1fab2004-02-18 14:30:55 +110073/* File to read commands from */
74FILE* infile;
75
76/* Are we in batchfile mode? */
77int batchmode = 0;
78
Damien Miller20e1fab2004-02-18 14:30:55 +110079/* PID of ssh transport process */
80static pid_t sshpid = -1;
81
Damien Miller9303e652013-04-23 15:22:40 +100082/* Suppress diagnositic messages */
83int quiet = 0;
84
Damien Miller20e1fab2004-02-18 14:30:55 +110085/* This is set to 0 if the progressmeter is not desired. */
Damien Millerc0f27d82004-03-08 23:12:19 +110086int showprogress = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +110087
Darren Tucker1b0dd172009-10-07 08:37:48 +110088/* When this option is set, we always recursively download/upload directories */
89int global_rflag = 0;
90
Damien Millerd8accc02014-05-15 13:46:25 +100091/* When this option is set, we resume download or upload if possible */
Damien Miller0d032412013-07-25 11:56:52 +100092int global_aflag = 0;
93
Darren Tucker1b0dd172009-10-07 08:37:48 +110094/* When this option is set, the file transfers will always preserve times */
95int global_pflag = 0;
96
Damien Millerf29238e2013-10-17 11:48:52 +110097/* When this option is set, transfers will have fsync() called on each file */
98int global_fflag = 0;
99
Darren Tuckercdf547a2004-05-24 10:12:19 +1000100/* SIGINT received during command processing */
101volatile sig_atomic_t interrupted = 0;
102
Darren Tuckerb9123452004-06-22 13:06:45 +1000103/* I wish qsort() took a separate ctx for the comparison function...*/
104int sort_flag;
105
Darren Tucker909d8582010-01-08 19:02:40 +1100106/* Context used for commandline completion */
107struct complete_ctx {
108 struct sftp_conn *conn;
109 char **remote_pathp;
110};
111
Damien Miller20e1fab2004-02-18 14:30:55 +1100112int remote_glob(struct sftp_conn *, const char *, int,
113 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
Damien Miller33804262001-02-04 23:20:18 +1100114
Kevin Steves12888d12001-03-05 19:50:57 +0000115extern char *__progname;
Kevin Steves12888d12001-03-05 19:50:57 +0000116
Damien Miller20e1fab2004-02-18 14:30:55 +1100117/* Separators for interactive commands */
118#define WHITESPACE " \t\r\n"
Damien Millerd7686fd2001-02-10 00:40:03 +1100119
Darren Tuckerb9123452004-06-22 13:06:45 +1000120/* ls flags */
Darren Tucker2901e2d2010-01-13 22:44:06 +1100121#define LS_LONG_VIEW 0x0001 /* Full view ala ls -l */
122#define LS_SHORT_VIEW 0x0002 /* Single row view ala ls -1 */
123#define LS_NUMERIC_VIEW 0x0004 /* Long view with numeric uid/gid */
124#define LS_NAME_SORT 0x0008 /* Sort by name (default) */
125#define LS_TIME_SORT 0x0010 /* Sort by mtime */
126#define LS_SIZE_SORT 0x0020 /* Sort by file size */
127#define LS_REVERSE_SORT 0x0040 /* Reverse sort order */
128#define LS_SHOW_ALL 0x0080 /* Don't skip filenames starting with '.' */
129#define LS_SI_UNITS 0x0100 /* Display sizes as K, M, G, etc. */
Darren Tuckerb9123452004-06-22 13:06:45 +1000130
Darren Tucker2901e2d2010-01-13 22:44:06 +1100131#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS)
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000132#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
Damien Miller20e1fab2004-02-18 14:30:55 +1100133
134/* Commands for interactive mode */
Damien Miller02e87802013-08-21 02:38:51 +1000135enum sftp_command {
136 I_CHDIR = 1,
137 I_CHGRP,
138 I_CHMOD,
139 I_CHOWN,
140 I_DF,
141 I_GET,
142 I_HELP,
143 I_LCHDIR,
144 I_LINK,
145 I_LLS,
146 I_LMKDIR,
147 I_LPWD,
148 I_LS,
149 I_LUMASK,
150 I_MKDIR,
151 I_PUT,
152 I_PWD,
153 I_QUIT,
Damien Millerb15cd7b2014-05-15 13:46:52 +1000154 I_REGET,
Damien Miller02e87802013-08-21 02:38:51 +1000155 I_RENAME,
Damien Millerb15cd7b2014-05-15 13:46:52 +1000156 I_REPUT,
Damien Miller02e87802013-08-21 02:38:51 +1000157 I_RM,
158 I_RMDIR,
159 I_SHELL,
160 I_SYMLINK,
161 I_VERSION,
162 I_PROGRESS,
Damien Miller02e87802013-08-21 02:38:51 +1000163};
Damien Miller20e1fab2004-02-18 14:30:55 +1100164
165struct CMD {
166 const char *c;
167 const int n;
Darren Tucker909d8582010-01-08 19:02:40 +1100168 const int t;
Damien Miller20e1fab2004-02-18 14:30:55 +1100169};
170
Darren Tucker909d8582010-01-08 19:02:40 +1100171/* Type of completion */
172#define NOARGS 0
173#define REMOTE 1
174#define LOCAL 2
175
Damien Miller20e1fab2004-02-18 14:30:55 +1100176static const struct CMD cmds[] = {
Darren Tucker909d8582010-01-08 19:02:40 +1100177 { "bye", I_QUIT, NOARGS },
178 { "cd", I_CHDIR, REMOTE },
179 { "chdir", I_CHDIR, REMOTE },
180 { "chgrp", I_CHGRP, REMOTE },
181 { "chmod", I_CHMOD, REMOTE },
182 { "chown", I_CHOWN, REMOTE },
183 { "df", I_DF, REMOTE },
184 { "dir", I_LS, REMOTE },
185 { "exit", I_QUIT, NOARGS },
186 { "get", I_GET, REMOTE },
187 { "help", I_HELP, NOARGS },
188 { "lcd", I_LCHDIR, LOCAL },
189 { "lchdir", I_LCHDIR, LOCAL },
190 { "lls", I_LLS, LOCAL },
191 { "lmkdir", I_LMKDIR, LOCAL },
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100192 { "ln", I_LINK, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100193 { "lpwd", I_LPWD, LOCAL },
194 { "ls", I_LS, REMOTE },
195 { "lumask", I_LUMASK, NOARGS },
196 { "mkdir", I_MKDIR, REMOTE },
Damien Miller099fc162010-05-10 11:56:50 +1000197 { "mget", I_GET, REMOTE },
198 { "mput", I_PUT, LOCAL },
Darren Tucker909d8582010-01-08 19:02:40 +1100199 { "progress", I_PROGRESS, NOARGS },
200 { "put", I_PUT, LOCAL },
201 { "pwd", I_PWD, REMOTE },
202 { "quit", I_QUIT, NOARGS },
Damien Miller0d032412013-07-25 11:56:52 +1000203 { "reget", I_REGET, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100204 { "rename", I_RENAME, REMOTE },
djm@openbsd.org4a459222014-10-06 00:47:15 +0000205 { "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"
djm@openbsd.org4a459222014-10-06 00:47:15 +0000253 "get [-afPpRr] remote [local] Download file\n"
254 "reget [-fPpRr] remote [local] Resume download file\n"
255 "reput [-fPpRr] [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"
djm@openbsd.org4a459222014-10-06 00:47:15 +0000266 "put [-afPpRr] local [remote] Upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100267 "pwd Display remote working directory\n"
268 "quit Quit sftp\n"
269 "rename oldpath newpath Rename remote file\n"
270 "rm path Delete remote file\n"
271 "rmdir path Remove remote directory\n"
272 "symlink oldpath newpath Symlink remote file\n"
273 "version Show SFTP version\n"
274 "!command Execute 'command' in local shell\n"
275 "! Escape to local shell\n"
276 "? Synonym for help\n");
Damien Miller20e1fab2004-02-18 14:30:55 +1100277}
278
279static void
280local_do_shell(const char *args)
281{
282 int status;
283 char *shell;
284 pid_t pid;
285
286 if (!*args)
287 args = NULL;
288
Damien Miller38d9a962010-10-07 22:07:11 +1100289 if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
Damien Miller20e1fab2004-02-18 14:30:55 +1100290 shell = _PATH_BSHELL;
291
292 if ((pid = fork()) == -1)
293 fatal("Couldn't fork: %s", strerror(errno));
294
295 if (pid == 0) {
296 /* XXX: child has pipe fds to ssh subproc open - issue? */
297 if (args) {
298 debug3("Executing %s -c \"%s\"", shell, args);
299 execl(shell, shell, "-c", args, (char *)NULL);
300 } else {
301 debug3("Executing %s", shell);
302 execl(shell, shell, (char *)NULL);
303 }
304 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
305 strerror(errno));
306 _exit(1);
307 }
308 while (waitpid(pid, &status, 0) == -1)
309 if (errno != EINTR)
310 fatal("Couldn't wait for child: %s", strerror(errno));
311 if (!WIFEXITED(status))
Damien Miller55b04f12006-03-26 14:23:17 +1100312 error("Shell exited abnormally");
Damien Miller20e1fab2004-02-18 14:30:55 +1100313 else if (WEXITSTATUS(status))
314 error("Shell exited with status %d", WEXITSTATUS(status));
315}
316
317static void
318local_do_ls(const char *args)
319{
320 if (!args || !*args)
321 local_do_shell(_PATH_LS);
322 else {
323 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
324 char *buf = xmalloc(len);
325
326 /* XXX: quoting - rip quoting code from ftp? */
327 snprintf(buf, len, _PATH_LS " %s", args);
328 local_do_shell(buf);
Darren Tuckera627d422013-06-02 07:31:17 +1000329 free(buf);
Damien Miller20e1fab2004-02-18 14:30:55 +1100330 }
331}
332
333/* Strip one path (usually the pwd) from the start of another */
334static char *
335path_strip(char *path, char *strip)
336{
337 size_t len;
338
339 if (strip == NULL)
340 return (xstrdup(path));
341
342 len = strlen(strip);
Darren Tuckere2f189a2004-12-06 22:45:53 +1100343 if (strncmp(path, strip, len) == 0) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100344 if (strip[len - 1] != '/' && path[len] == '/')
345 len++;
346 return (xstrdup(path + len));
347 }
348
349 return (xstrdup(path));
350}
351
352static char *
Damien Miller20e1fab2004-02-18 14:30:55 +1100353make_absolute(char *p, char *pwd)
354{
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000355 char *abs_str;
Damien Miller20e1fab2004-02-18 14:30:55 +1100356
357 /* Derelativise */
358 if (p && p[0] != '/') {
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000359 abs_str = path_append(pwd, p);
Darren Tuckera627d422013-06-02 07:31:17 +1000360 free(p);
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000361 return(abs_str);
Damien Miller20e1fab2004-02-18 14:30:55 +1100362 } else
363 return(p);
364}
365
366static int
Damien Miller0d032412013-07-25 11:56:52 +1000367parse_getput_flags(const char *cmd, char **argv, int argc,
Damien Millerf29238e2013-10-17 11:48:52 +1100368 int *aflag, int *fflag, int *pflag, int *rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100369{
Damien Millerf184bcf2008-06-29 22:45:13 +1000370 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000371 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100372
Damien Miller1cbc2922007-10-26 14:27:45 +1000373 optind = optreset = 1;
374 opterr = 0;
375
Damien Millerf29238e2013-10-17 11:48:52 +1100376 *aflag = *fflag = *rflag = *pflag = 0;
377 while ((ch = getopt(argc, argv, "afPpRr")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000378 switch (ch) {
Damien Miller0d032412013-07-25 11:56:52 +1000379 case 'a':
380 *aflag = 1;
381 break;
Damien Millerf29238e2013-10-17 11:48:52 +1100382 case 'f':
383 *fflag = 1;
384 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100385 case 'p':
386 case 'P':
387 *pflag = 1;
388 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100389 case 'r':
390 case 'R':
391 *rflag = 1;
392 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100393 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000394 error("%s: Invalid flag -%c", cmd, optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000395 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100396 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100397 }
398
Damien Miller1cbc2922007-10-26 14:27:45 +1000399 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100400}
401
402static int
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100403parse_link_flags(const char *cmd, char **argv, int argc, int *sflag)
404{
405 extern int opterr, optind, optopt, optreset;
406 int ch;
407
408 optind = optreset = 1;
409 opterr = 0;
410
411 *sflag = 0;
412 while ((ch = getopt(argc, argv, "s")) != -1) {
413 switch (ch) {
414 case 's':
415 *sflag = 1;
416 break;
417 default:
418 error("%s: Invalid flag -%c", cmd, optopt);
419 return -1;
420 }
421 }
422
423 return optind;
424}
425
426static int
Damien Millerc7dba122013-08-21 02:41:15 +1000427parse_rename_flags(const char *cmd, char **argv, int argc, int *lflag)
428{
429 extern int opterr, optind, optopt, optreset;
430 int ch;
431
432 optind = optreset = 1;
433 opterr = 0;
434
435 *lflag = 0;
436 while ((ch = getopt(argc, argv, "l")) != -1) {
437 switch (ch) {
438 case 'l':
439 *lflag = 1;
440 break;
441 default:
442 error("%s: Invalid flag -%c", cmd, optopt);
443 return -1;
444 }
445 }
446
447 return optind;
448}
449
450static int
Damien Miller1cbc2922007-10-26 14:27:45 +1000451parse_ls_flags(char **argv, int argc, int *lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100452{
Damien Millerf184bcf2008-06-29 22:45:13 +1000453 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000454 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100455
Damien Miller1cbc2922007-10-26 14:27:45 +1000456 optind = optreset = 1;
457 opterr = 0;
458
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000459 *lflag = LS_NAME_SORT;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100460 while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000461 switch (ch) {
462 case '1':
463 *lflag &= ~VIEW_FLAGS;
464 *lflag |= LS_SHORT_VIEW;
465 break;
466 case 'S':
467 *lflag &= ~SORT_FLAGS;
468 *lflag |= LS_SIZE_SORT;
469 break;
470 case 'a':
471 *lflag |= LS_SHOW_ALL;
472 break;
473 case 'f':
474 *lflag &= ~SORT_FLAGS;
475 break;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100476 case 'h':
477 *lflag |= LS_SI_UNITS;
478 break;
Damien Miller1cbc2922007-10-26 14:27:45 +1000479 case 'l':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100480 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000481 *lflag |= LS_LONG_VIEW;
482 break;
483 case 'n':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100484 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000485 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
486 break;
487 case 'r':
488 *lflag |= LS_REVERSE_SORT;
489 break;
490 case 't':
491 *lflag &= ~SORT_FLAGS;
492 *lflag |= LS_TIME_SORT;
493 break;
494 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000495 error("ls: Invalid flag -%c", optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000496 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100497 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100498 }
499
Damien Miller1cbc2922007-10-26 14:27:45 +1000500 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100501}
502
503static int
Damien Millerd671e5a2008-05-19 14:53:33 +1000504parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
505{
Damien Millerf184bcf2008-06-29 22:45:13 +1000506 extern int opterr, optind, optopt, optreset;
Damien Millerd671e5a2008-05-19 14:53:33 +1000507 int ch;
508
509 optind = optreset = 1;
510 opterr = 0;
511
512 *hflag = *iflag = 0;
513 while ((ch = getopt(argc, argv, "hi")) != -1) {
514 switch (ch) {
515 case 'h':
516 *hflag = 1;
517 break;
518 case 'i':
519 *iflag = 1;
520 break;
521 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000522 error("%s: Invalid flag -%c", cmd, optopt);
Damien Millerd671e5a2008-05-19 14:53:33 +1000523 return -1;
524 }
525 }
526
527 return optind;
528}
529
530static int
Damien Miller036d3072013-08-21 02:41:46 +1000531parse_no_flags(const char *cmd, char **argv, int argc)
532{
533 extern int opterr, optind, optopt, optreset;
534 int ch;
535
536 optind = optreset = 1;
537 opterr = 0;
538
539 while ((ch = getopt(argc, argv, "")) != -1) {
540 switch (ch) {
541 default:
542 error("%s: Invalid flag -%c", cmd, optopt);
543 return -1;
544 }
545 }
546
547 return optind;
548}
549
550static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100551is_dir(char *path)
552{
553 struct stat sb;
554
555 /* XXX: report errors? */
556 if (stat(path, &sb) == -1)
557 return(0);
558
Darren Tucker1e80e402006-09-21 12:59:33 +1000559 return(S_ISDIR(sb.st_mode));
Damien Miller20e1fab2004-02-18 14:30:55 +1100560}
561
562static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100563remote_is_dir(struct sftp_conn *conn, char *path)
564{
565 Attrib *a;
566
567 /* XXX: report errors? */
568 if ((a = do_stat(conn, path, 1)) == NULL)
569 return(0);
570 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
571 return(0);
Darren Tucker1e80e402006-09-21 12:59:33 +1000572 return(S_ISDIR(a->perm));
Damien Miller20e1fab2004-02-18 14:30:55 +1100573}
574
Darren Tucker1b0dd172009-10-07 08:37:48 +1100575/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
Damien Miller20e1fab2004-02-18 14:30:55 +1100576static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100577pathname_is_dir(char *pathname)
578{
579 size_t l = strlen(pathname);
580
581 return l > 0 && pathname[l - 1] == '/';
582}
583
584static int
585process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
Damien Millerf29238e2013-10-17 11:48:52 +1100586 int pflag, int rflag, int resume, int fflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100587{
588 char *abs_src = NULL;
589 char *abs_dst = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100590 glob_t g;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100591 char *filename, *tmp=NULL;
Damien Miller00707762014-07-09 13:07:06 +1000592 int i, r, err = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100593
594 abs_src = xstrdup(src);
595 abs_src = make_absolute(abs_src, pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +1100596 memset(&g, 0, sizeof(g));
Darren Tucker1b0dd172009-10-07 08:37:48 +1100597
Damien Miller20e1fab2004-02-18 14:30:55 +1100598 debug3("Looking up %s", abs_src);
Damien Miller00707762014-07-09 13:07:06 +1000599 if ((r = remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) != 0) {
600 if (r == GLOB_NOSPACE) {
601 error("Too many matches for \"%s\".", abs_src);
602 } else {
603 error("File \"%s\" not found.", abs_src);
604 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100605 err = -1;
606 goto out;
607 }
608
Darren Tucker1b0dd172009-10-07 08:37:48 +1100609 /*
610 * If multiple matches then dst must be a directory or
611 * unspecified.
612 */
613 if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
614 error("Multiple source paths, but destination "
615 "\"%s\" is not a directory", dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100616 err = -1;
617 goto out;
618 }
619
Darren Tuckercdf547a2004-05-24 10:12:19 +1000620 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100621 tmp = xstrdup(g.gl_pathv[i]);
622 if ((filename = basename(tmp)) == NULL) {
623 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000624 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100625 err = -1;
626 goto out;
627 }
628
629 if (g.gl_matchc == 1 && dst) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100630 if (is_dir(dst)) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100631 abs_dst = path_append(dst, filename);
632 } else {
Damien Miller20e1fab2004-02-18 14:30:55 +1100633 abs_dst = xstrdup(dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100634 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100635 } else if (dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100636 abs_dst = path_append(dst, filename);
637 } else {
638 abs_dst = xstrdup(filename);
639 }
Darren Tuckera627d422013-06-02 07:31:17 +1000640 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100641
Damien Miller0d032412013-07-25 11:56:52 +1000642 resume |= global_aflag;
643 if (!quiet && resume)
644 printf("Resuming %s to %s\n", g.gl_pathv[i], abs_dst);
645 else if (!quiet && !resume)
Damien Miller9303e652013-04-23 15:22:40 +1000646 printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100647 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
Damien Miller0d032412013-07-25 11:56:52 +1000648 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
Damien Millerf29238e2013-10-17 11:48:52 +1100649 pflag || global_pflag, 1, resume,
650 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100651 err = -1;
652 } else {
653 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
Damien Millerf29238e2013-10-17 11:48:52 +1100654 pflag || global_pflag, resume,
655 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100656 err = -1;
657 }
Darren Tuckera627d422013-06-02 07:31:17 +1000658 free(abs_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100659 abs_dst = NULL;
660 }
661
662out:
Darren Tuckera627d422013-06-02 07:31:17 +1000663 free(abs_src);
Damien Miller20e1fab2004-02-18 14:30:55 +1100664 globfree(&g);
665 return(err);
666}
667
668static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100669process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
Damien Millerd8accc02014-05-15 13:46:25 +1000670 int pflag, int rflag, int resume, int fflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100671{
672 char *tmp_dst = NULL;
673 char *abs_dst = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100674 char *tmp = NULL, *filename = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100675 glob_t g;
676 int err = 0;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100677 int i, dst_is_dir = 1;
Damien Milleraec5cf82008-02-10 22:26:24 +1100678 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100679
680 if (dst) {
681 tmp_dst = xstrdup(dst);
682 tmp_dst = make_absolute(tmp_dst, pwd);
683 }
684
685 memset(&g, 0, sizeof(g));
686 debug3("Looking up %s", src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100687 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100688 error("File \"%s\" not found.", src);
689 err = -1;
690 goto out;
691 }
692
Darren Tucker1b0dd172009-10-07 08:37:48 +1100693 /* If we aren't fetching to pwd then stash this status for later */
694 if (tmp_dst != NULL)
695 dst_is_dir = remote_is_dir(conn, tmp_dst);
696
Damien Miller20e1fab2004-02-18 14:30:55 +1100697 /* If multiple matches, dst may be directory or unspecified */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100698 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
699 error("Multiple paths match, but destination "
700 "\"%s\" is not a directory", tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100701 err = -1;
702 goto out;
703 }
704
Darren Tuckercdf547a2004-05-24 10:12:19 +1000705 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Milleraec5cf82008-02-10 22:26:24 +1100706 if (stat(g.gl_pathv[i], &sb) == -1) {
707 err = -1;
708 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
709 continue;
710 }
Damien Miller02e87802013-08-21 02:38:51 +1000711
Darren Tucker1b0dd172009-10-07 08:37:48 +1100712 tmp = xstrdup(g.gl_pathv[i]);
713 if ((filename = basename(tmp)) == NULL) {
714 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000715 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100716 err = -1;
717 goto out;
718 }
719
720 if (g.gl_matchc == 1 && tmp_dst) {
721 /* If directory specified, append filename */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100722 if (dst_is_dir)
723 abs_dst = path_append(tmp_dst, filename);
724 else
Damien Miller20e1fab2004-02-18 14:30:55 +1100725 abs_dst = xstrdup(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100726 } else if (tmp_dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100727 abs_dst = path_append(tmp_dst, filename);
728 } else {
729 abs_dst = make_absolute(xstrdup(filename), pwd);
730 }
Darren Tuckera627d422013-06-02 07:31:17 +1000731 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100732
Damien Millerd8accc02014-05-15 13:46:25 +1000733 resume |= global_aflag;
734 if (!quiet && resume)
Damien Miller3dc27172014-05-15 14:37:59 +1000735 printf("Resuming upload of %s to %s\n", g.gl_pathv[i],
Damien Millerd8accc02014-05-15 13:46:25 +1000736 abs_dst);
737 else if (!quiet && !resume)
Damien Miller9303e652013-04-23 15:22:40 +1000738 printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100739 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
740 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
Damien Millerd8accc02014-05-15 13:46:25 +1000741 pflag || global_pflag, 1, resume,
Damien Millerf29238e2013-10-17 11:48:52 +1100742 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100743 err = -1;
744 } else {
745 if (do_upload(conn, g.gl_pathv[i], abs_dst,
Damien Millerd8accc02014-05-15 13:46:25 +1000746 pflag || global_pflag, resume,
Damien Millerf29238e2013-10-17 11:48:52 +1100747 fflag || global_fflag) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100748 err = -1;
749 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100750 }
751
752out:
Darren Tuckera627d422013-06-02 07:31:17 +1000753 free(abs_dst);
754 free(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100755 globfree(&g);
756 return(err);
757}
758
759static int
760sdirent_comp(const void *aa, const void *bb)
761{
762 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
763 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000764 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100765
Darren Tuckerb9123452004-06-22 13:06:45 +1000766#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000767 if (sort_flag & LS_NAME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000768 return (rmul * strcmp(a->filename, b->filename));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000769 else if (sort_flag & LS_TIME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000770 return (rmul * NCMP(a->a.mtime, b->a.mtime));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000771 else if (sort_flag & LS_SIZE_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000772 return (rmul * NCMP(a->a.size, b->a.size));
773
774 fatal("Unknown ls sort type");
Damien Miller20e1fab2004-02-18 14:30:55 +1100775}
776
777/* sftp ls.1 replacement for directories */
778static int
779do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
780{
Damien Millereccb9de2005-06-17 12:59:34 +1000781 int n;
782 u_int c = 1, colspace = 0, columns = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100783 SFTP_DIRENT **d;
784
785 if ((n = do_readdir(conn, path, &d)) != 0)
786 return (n);
787
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000788 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000789 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100790 struct winsize ws;
791 char *tmp;
792
793 /* Count entries for sort and find longest filename */
Darren Tucker9a526452004-06-22 13:09:55 +1000794 for (n = 0; d[n] != NULL; n++) {
795 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
796 m = MAX(m, strlen(d[n]->filename));
797 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100798
799 /* Add any subpath that also needs to be counted */
800 tmp = path_strip(path, strip_path);
801 m += strlen(tmp);
Darren Tuckera627d422013-06-02 07:31:17 +1000802 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100803
804 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
805 width = ws.ws_col;
806
807 columns = width / (m + 2);
808 columns = MAX(columns, 1);
809 colspace = width / columns;
810 colspace = MIN(colspace, width);
811 }
812
Darren Tuckerb9123452004-06-22 13:06:45 +1000813 if (lflag & SORT_FLAGS) {
Damien Miller653b93b2005-11-05 15:15:23 +1100814 for (n = 0; d[n] != NULL; n++)
815 ; /* count entries */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000816 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
Darren Tuckerb9123452004-06-22 13:06:45 +1000817 qsort(d, n, sizeof(*d), sdirent_comp);
818 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100819
Darren Tuckercdf547a2004-05-24 10:12:19 +1000820 for (n = 0; d[n] != NULL && !interrupted; n++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100821 char *tmp, *fname;
822
Darren Tucker9a526452004-06-22 13:09:55 +1000823 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
824 continue;
825
Damien Miller20e1fab2004-02-18 14:30:55 +1100826 tmp = path_append(path, d[n]->filename);
827 fname = path_strip(tmp, strip_path);
Darren Tuckera627d422013-06-02 07:31:17 +1000828 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100829
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000830 if (lflag & LS_LONG_VIEW) {
Darren Tucker2901e2d2010-01-13 22:44:06 +1100831 if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000832 char *lname;
833 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100834
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000835 memset(&sb, 0, sizeof(sb));
836 attrib_to_stat(&d[n]->a, &sb);
Darren Tucker2901e2d2010-01-13 22:44:06 +1100837 lname = ls_file(fname, &sb, 1,
838 (lflag & LS_SI_UNITS));
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000839 printf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000840 free(lname);
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000841 } else
842 printf("%s\n", d[n]->longname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100843 } else {
844 printf("%-*s", colspace, fname);
845 if (c >= columns) {
846 printf("\n");
847 c = 1;
848 } else
849 c++;
850 }
851
Darren Tuckera627d422013-06-02 07:31:17 +1000852 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100853 }
854
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000855 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100856 printf("\n");
857
858 free_sftp_dirents(d);
859 return (0);
860}
861
862/* sftp ls.1 replacement which handles path globs */
863static int
864do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
865 int lflag)
866{
Damien Millera6e121a2010-10-07 21:39:17 +1100867 char *fname, *lname;
Damien Miller68e2e562010-10-07 21:39:55 +1100868 glob_t g;
Damien Miller00707762014-07-09 13:07:06 +1000869 int err, r;
Damien Miller68e2e562010-10-07 21:39:55 +1100870 struct winsize ws;
871 u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100872
873 memset(&g, 0, sizeof(g));
874
Damien Miller00707762014-07-09 13:07:06 +1000875 if ((r = remote_glob(conn, path,
Damien Millerd7be70d2011-09-22 21:43:06 +1000876 GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
Damien Miller00707762014-07-09 13:07:06 +1000877 NULL, &g)) != 0 ||
Damien Millera6e121a2010-10-07 21:39:17 +1100878 (g.gl_pathc && !g.gl_matchc)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100879 if (g.gl_pathc)
880 globfree(&g);
Damien Miller00707762014-07-09 13:07:06 +1000881 if (r == GLOB_NOSPACE) {
882 error("Can't ls: Too many matches for \"%s\"", path);
883 } else {
884 error("Can't ls: \"%s\" not found", path);
885 }
Damien Millera6e121a2010-10-07 21:39:17 +1100886 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100887 }
888
Darren Tuckercdf547a2004-05-24 10:12:19 +1000889 if (interrupted)
890 goto out;
891
Damien Miller20e1fab2004-02-18 14:30:55 +1100892 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100893 * If the glob returns a single match and it is a directory,
894 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100895 */
Damien Millera6e121a2010-10-07 21:39:17 +1100896 if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
897 S_ISDIR(g.gl_statv[0]->st_mode)) {
898 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
899 globfree(&g);
900 return err;
Damien Miller20e1fab2004-02-18 14:30:55 +1100901 }
902
Damien Miller68e2e562010-10-07 21:39:55 +1100903 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
904 width = ws.ws_col;
Damien Miller20e1fab2004-02-18 14:30:55 +1100905
Damien Miller68e2e562010-10-07 21:39:55 +1100906 if (!(lflag & LS_SHORT_VIEW)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100907 /* Count entries for sort and find longest filename */
908 for (i = 0; g.gl_pathv[i]; i++)
909 m = MAX(m, strlen(g.gl_pathv[i]));
910
Damien Miller20e1fab2004-02-18 14:30:55 +1100911 columns = width / (m + 2);
912 columns = MAX(columns, 1);
913 colspace = width / columns;
914 }
915
Damien Millerea858292012-06-30 08:33:32 +1000916 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100917 fname = path_strip(g.gl_pathv[i], strip_path);
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000918 if (lflag & LS_LONG_VIEW) {
Damien Millera6e121a2010-10-07 21:39:17 +1100919 if (g.gl_statv[i] == NULL) {
920 error("no stat information for %s", fname);
921 continue;
922 }
923 lname = ls_file(fname, g.gl_statv[i], 1,
924 (lflag & LS_SI_UNITS));
Damien Miller20e1fab2004-02-18 14:30:55 +1100925 printf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000926 free(lname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100927 } else {
928 printf("%-*s", colspace, fname);
929 if (c >= columns) {
930 printf("\n");
931 c = 1;
932 } else
933 c++;
934 }
Darren Tuckera627d422013-06-02 07:31:17 +1000935 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100936 }
937
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000938 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100939 printf("\n");
940
Darren Tuckercdf547a2004-05-24 10:12:19 +1000941 out:
Damien Miller20e1fab2004-02-18 14:30:55 +1100942 if (g.gl_pathc)
943 globfree(&g);
944
Damien Millera6e121a2010-10-07 21:39:17 +1100945 return 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100946}
947
Damien Millerd671e5a2008-05-19 14:53:33 +1000948static int
949do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
950{
Darren Tucker7b598892008-06-09 22:49:36 +1000951 struct sftp_statvfs st;
Damien Millerd671e5a2008-05-19 14:53:33 +1000952 char s_used[FMT_SCALED_STRSIZE];
953 char s_avail[FMT_SCALED_STRSIZE];
954 char s_root[FMT_SCALED_STRSIZE];
955 char s_total[FMT_SCALED_STRSIZE];
Darren Tuckerb5082e92010-01-08 18:51:47 +1100956 unsigned long long ffree;
Damien Millerd671e5a2008-05-19 14:53:33 +1000957
958 if (do_statvfs(conn, path, &st, 1) == -1)
959 return -1;
960 if (iflag) {
Darren Tuckerb5082e92010-01-08 18:51:47 +1100961 ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
Damien Millerd671e5a2008-05-19 14:53:33 +1000962 printf(" Inodes Used Avail "
963 "(root) %%Capacity\n");
964 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
965 (unsigned long long)st.f_files,
966 (unsigned long long)(st.f_files - st.f_ffree),
967 (unsigned long long)st.f_favail,
Darren Tuckerb5082e92010-01-08 18:51:47 +1100968 (unsigned long long)st.f_ffree, ffree);
Damien Millerd671e5a2008-05-19 14:53:33 +1000969 } else if (hflag) {
970 strlcpy(s_used, "error", sizeof(s_used));
971 strlcpy(s_avail, "error", sizeof(s_avail));
972 strlcpy(s_root, "error", sizeof(s_root));
973 strlcpy(s_total, "error", sizeof(s_total));
974 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
975 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
976 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
977 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
978 printf(" Size Used Avail (root) %%Capacity\n");
979 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
980 s_total, s_used, s_avail, s_root,
981 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
982 st.f_blocks));
983 } else {
984 printf(" Size Used Avail "
985 "(root) %%Capacity\n");
986 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
987 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
988 (unsigned long long)(st.f_frsize *
989 (st.f_blocks - st.f_bfree) / 1024),
990 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
991 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
992 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
993 st.f_blocks));
994 }
995 return 0;
996}
997
Damien Miller1cbc2922007-10-26 14:27:45 +1000998/*
999 * Undo escaping of glob sequences in place. Used to undo extra escaping
1000 * applied in makeargv() when the string is destined for a function that
1001 * does not glob it.
1002 */
1003static void
1004undo_glob_escape(char *s)
1005{
1006 size_t i, j;
1007
1008 for (i = j = 0;;) {
1009 if (s[i] == '\0') {
1010 s[j] = '\0';
1011 return;
1012 }
1013 if (s[i] != '\\') {
1014 s[j++] = s[i++];
1015 continue;
1016 }
1017 /* s[i] == '\\' */
1018 ++i;
1019 switch (s[i]) {
1020 case '?':
1021 case '[':
1022 case '*':
1023 case '\\':
1024 s[j++] = s[i++];
1025 break;
1026 case '\0':
1027 s[j++] = '\\';
1028 s[j] = '\0';
1029 return;
1030 default:
1031 s[j++] = '\\';
1032 s[j++] = s[i++];
1033 break;
1034 }
1035 }
1036}
1037
1038/*
1039 * Split a string into an argument vector using sh(1)-style quoting,
1040 * comment and escaping rules, but with some tweaks to handle glob(3)
1041 * wildcards.
Darren Tucker909d8582010-01-08 19:02:40 +11001042 * The "sloppy" flag allows for recovery from missing terminating quote, for
1043 * use in parsing incomplete commandlines during tab autocompletion.
1044 *
Damien Miller1cbc2922007-10-26 14:27:45 +10001045 * Returns NULL on error or a NULL-terminated array of arguments.
Darren Tucker909d8582010-01-08 19:02:40 +11001046 *
1047 * If "lastquote" is not NULL, the quoting character used for the last
1048 * argument is placed in *lastquote ("\0", "'" or "\"").
Damien Miller02e87802013-08-21 02:38:51 +10001049 *
Darren Tucker909d8582010-01-08 19:02:40 +11001050 * If "terminated" is not NULL, *terminated will be set to 1 when the
1051 * last argument's quote has been properly terminated or 0 otherwise.
1052 * This parameter is only of use if "sloppy" is set.
Damien Miller1cbc2922007-10-26 14:27:45 +10001053 */
1054#define MAXARGS 128
1055#define MAXARGLEN 8192
1056static char **
Darren Tucker909d8582010-01-08 19:02:40 +11001057makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
1058 u_int *terminated)
Damien Miller1cbc2922007-10-26 14:27:45 +10001059{
1060 int argc, quot;
1061 size_t i, j;
1062 static char argvs[MAXARGLEN];
1063 static char *argv[MAXARGS + 1];
1064 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
1065
1066 *argcp = argc = 0;
1067 if (strlen(arg) > sizeof(argvs) - 1) {
1068 args_too_longs:
1069 error("string too long");
1070 return NULL;
1071 }
Darren Tucker909d8582010-01-08 19:02:40 +11001072 if (terminated != NULL)
1073 *terminated = 1;
1074 if (lastquote != NULL)
1075 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001076 state = MA_START;
1077 i = j = 0;
1078 for (;;) {
Damien Miller07daed52012-10-31 08:57:55 +11001079 if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){
Darren Tucker063018d2012-10-05 10:43:58 +10001080 error("Too many arguments.");
1081 return NULL;
1082 }
Damien Millerfdb23062013-11-21 13:57:15 +11001083 if (isspace((unsigned char)arg[i])) {
Damien Miller1cbc2922007-10-26 14:27:45 +10001084 if (state == MA_UNQUOTED) {
1085 /* Terminate current argument */
1086 argvs[j++] = '\0';
1087 argc++;
1088 state = MA_START;
1089 } else if (state != MA_START)
1090 argvs[j++] = arg[i];
1091 } else if (arg[i] == '"' || arg[i] == '\'') {
1092 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1093 if (state == MA_START) {
1094 argv[argc] = argvs + j;
1095 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +11001096 if (lastquote != NULL)
1097 *lastquote = arg[i];
Damien Miller02e87802013-08-21 02:38:51 +10001098 } else if (state == MA_UNQUOTED)
Damien Miller1cbc2922007-10-26 14:27:45 +10001099 state = q;
1100 else if (state == q)
1101 state = MA_UNQUOTED;
1102 else
1103 argvs[j++] = arg[i];
1104 } else if (arg[i] == '\\') {
1105 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1106 quot = state == MA_SQUOTE ? '\'' : '"';
1107 /* Unescape quote we are in */
1108 /* XXX support \n and friends? */
1109 if (arg[i + 1] == quot) {
1110 i++;
1111 argvs[j++] = arg[i];
1112 } else if (arg[i + 1] == '?' ||
1113 arg[i + 1] == '[' || arg[i + 1] == '*') {
1114 /*
1115 * Special case for sftp: append
1116 * double-escaped glob sequence -
1117 * glob will undo one level of
1118 * escaping. NB. string can grow here.
1119 */
1120 if (j >= sizeof(argvs) - 5)
1121 goto args_too_longs;
1122 argvs[j++] = '\\';
1123 argvs[j++] = arg[i++];
1124 argvs[j++] = '\\';
1125 argvs[j++] = arg[i];
1126 } else {
1127 argvs[j++] = arg[i++];
1128 argvs[j++] = arg[i];
1129 }
1130 } else {
1131 if (state == MA_START) {
1132 argv[argc] = argvs + j;
1133 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001134 if (lastquote != NULL)
1135 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001136 }
1137 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1138 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1139 /*
1140 * Special case for sftp: append
1141 * escaped glob sequence -
1142 * glob will undo one level of
1143 * escaping.
1144 */
1145 argvs[j++] = arg[i++];
1146 argvs[j++] = arg[i];
1147 } else {
1148 /* Unescape everything */
1149 /* XXX support \n and friends? */
1150 i++;
1151 argvs[j++] = arg[i];
1152 }
1153 }
1154 } else if (arg[i] == '#') {
1155 if (state == MA_SQUOTE || state == MA_DQUOTE)
1156 argvs[j++] = arg[i];
1157 else
1158 goto string_done;
1159 } else if (arg[i] == '\0') {
1160 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001161 if (sloppy) {
1162 state = MA_UNQUOTED;
1163 if (terminated != NULL)
1164 *terminated = 0;
1165 goto string_done;
1166 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001167 error("Unterminated quoted argument");
1168 return NULL;
1169 }
1170 string_done:
1171 if (state == MA_UNQUOTED) {
1172 argvs[j++] = '\0';
1173 argc++;
1174 }
1175 break;
1176 } else {
1177 if (state == MA_START) {
1178 argv[argc] = argvs + j;
1179 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001180 if (lastquote != NULL)
1181 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001182 }
1183 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1184 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1185 /*
1186 * Special case for sftp: escape quoted
1187 * glob(3) wildcards. NB. string can grow
1188 * here.
1189 */
1190 if (j >= sizeof(argvs) - 3)
1191 goto args_too_longs;
1192 argvs[j++] = '\\';
1193 argvs[j++] = arg[i];
1194 } else
1195 argvs[j++] = arg[i];
1196 }
1197 i++;
1198 }
1199 *argcp = argc;
1200 return argv;
1201}
1202
Damien Miller20e1fab2004-02-18 14:30:55 +11001203static int
Damien Millerd8accc02014-05-15 13:46:25 +10001204parse_args(const char **cpp, int *ignore_errors, int *aflag,
1205 int *fflag, int *hflag, int *iflag, int *lflag, int *pflag,
1206 int *rflag, int *sflag,
Damien Millerf29238e2013-10-17 11:48:52 +11001207 unsigned long *n_arg, char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001208{
1209 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001210 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001211 int base = 0;
1212 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001213 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001214
1215 /* Skip leading whitespace */
1216 cp = cp + strspn(cp, WHITESPACE);
1217
Damien Miller20e1fab2004-02-18 14:30:55 +11001218 /* Check for leading '-' (disable error processing) */
Damien Millerf29238e2013-10-17 11:48:52 +11001219 *ignore_errors = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001220 if (*cp == '-') {
Damien Millerf29238e2013-10-17 11:48:52 +11001221 *ignore_errors = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001222 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001223 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001224 }
1225
Darren Tucker70cc0922010-01-09 22:28:03 +11001226 /* Ignore blank lines and lines which begin with comment '#' char */
1227 if (*cp == '\0' || *cp == '#')
1228 return (0);
1229
Darren Tucker909d8582010-01-08 19:02:40 +11001230 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001231 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001232
Damien Miller1cbc2922007-10-26 14:27:45 +10001233 /* Figure out which command we have */
1234 for (i = 0; cmds[i].c != NULL; i++) {
Damien Millerd6d9fa02013-02-12 11:02:46 +11001235 if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001236 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001237 }
1238 cmdnum = cmds[i].n;
1239 cmd = cmds[i].c;
1240
1241 /* Special case */
1242 if (*cp == '!') {
1243 cp++;
1244 cmdnum = I_SHELL;
1245 } else if (cmdnum == -1) {
1246 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001247 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001248 }
1249
1250 /* Get arguments and parse flags */
Damien Millerf29238e2013-10-17 11:48:52 +11001251 *aflag = *fflag = *hflag = *iflag = *lflag = *pflag = 0;
1252 *rflag = *sflag = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001253 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001254 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001255 switch (cmdnum) {
1256 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001257 case I_REGET:
Damien Millerd8accc02014-05-15 13:46:25 +10001258 case I_REPUT:
Damien Miller20e1fab2004-02-18 14:30:55 +11001259 case I_PUT:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001260 if ((optidx = parse_getput_flags(cmd, argv, argc,
Damien Millerf29238e2013-10-17 11:48:52 +11001261 aflag, fflag, pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001262 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001263 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001264 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001265 error("You must specify at least one path after a "
1266 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001267 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001268 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001269 *path1 = xstrdup(argv[optidx]);
1270 /* Get second pathname (optional) */
1271 if (argc - optidx > 1) {
1272 *path2 = xstrdup(argv[optidx + 1]);
1273 /* Destination is not globbed */
1274 undo_glob_escape(*path2);
1275 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001276 break;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001277 case I_LINK:
1278 if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1279 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001280 goto parse_two_paths;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001281 case I_RENAME:
Damien Millerc7dba122013-08-21 02:41:15 +10001282 if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1)
1283 return -1;
1284 goto parse_two_paths;
1285 case I_SYMLINK:
Damien Miller036d3072013-08-21 02:41:46 +10001286 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1287 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001288 parse_two_paths:
Damien Miller1cbc2922007-10-26 14:27:45 +10001289 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001290 error("You must specify two paths after a %s "
1291 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001292 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001293 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001294 *path1 = xstrdup(argv[optidx]);
1295 *path2 = xstrdup(argv[optidx + 1]);
1296 /* Paths are not globbed */
1297 undo_glob_escape(*path1);
1298 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001299 break;
1300 case I_RM:
1301 case I_MKDIR:
1302 case I_RMDIR:
1303 case I_CHDIR:
1304 case I_LCHDIR:
1305 case I_LMKDIR:
Damien Miller036d3072013-08-21 02:41:46 +10001306 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1307 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001308 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001309 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001310 error("You must specify a path after a %s command.",
1311 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001312 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001313 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001314 *path1 = xstrdup(argv[optidx]);
1315 /* Only "rm" globs */
1316 if (cmdnum != I_RM)
1317 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001318 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001319 case I_DF:
1320 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1321 iflag)) == -1)
1322 return -1;
1323 /* Default to current directory if no path specified */
1324 if (argc - optidx < 1)
1325 *path1 = NULL;
1326 else {
1327 *path1 = xstrdup(argv[optidx]);
1328 undo_glob_escape(*path1);
1329 }
1330 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001331 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001332 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001333 return(-1);
1334 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001335 if (argc - optidx > 0)
1336 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001337 break;
1338 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001339 /* Skip ls command and following whitespace */
1340 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001341 case I_SHELL:
1342 /* Uses the rest of the line */
1343 break;
1344 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001345 case I_CHMOD:
1346 base = 8;
1347 case I_CHOWN:
1348 case I_CHGRP:
Damien Miller036d3072013-08-21 02:41:46 +10001349 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1350 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001351 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001352 if (argc - optidx < 1)
1353 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001354 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001355 l = strtol(argv[optidx], &cp2, base);
1356 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1357 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1358 l < 0) {
1359 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001360 error("You must supply a numeric argument "
1361 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001362 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001363 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001364 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001365 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001366 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001367 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001368 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001369 error("You must specify a path after a %s command.",
1370 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001371 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001372 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001373 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001374 break;
1375 case I_QUIT:
1376 case I_PWD:
1377 case I_LPWD:
1378 case I_HELP:
1379 case I_VERSION:
1380 case I_PROGRESS:
Damien Miller036d3072013-08-21 02:41:46 +10001381 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1382 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001383 break;
1384 default:
1385 fatal("Command not implemented");
1386 }
1387
1388 *cpp = cp;
1389 return(cmdnum);
1390}
1391
1392static int
1393parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1394 int err_abort)
1395{
1396 char *path1, *path2, *tmp;
Damien Millerd8accc02014-05-15 13:46:25 +10001397 int ignore_errors = 0, aflag = 0, fflag = 0, hflag = 0,
1398 iflag = 0;
Damien Millerf29238e2013-10-17 11:48:52 +11001399 int lflag = 0, pflag = 0, rflag = 0, sflag = 0;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001400 int cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001401 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001402 Attrib a, *aa;
1403 char path_buf[MAXPATHLEN];
1404 int err = 0;
1405 glob_t g;
1406
1407 path1 = path2 = NULL;
Damien Millerf29238e2013-10-17 11:48:52 +11001408 cmdnum = parse_args(&cmd, &ignore_errors, &aflag, &fflag, &hflag,
1409 &iflag, &lflag, &pflag, &rflag, &sflag, &n_arg, &path1, &path2);
1410 if (ignore_errors != 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001411 err_abort = 0;
1412
1413 memset(&g, 0, sizeof(g));
1414
1415 /* Perform command */
1416 switch (cmdnum) {
1417 case 0:
1418 /* Blank line */
1419 break;
1420 case -1:
1421 /* Unrecognized command */
1422 err = -1;
1423 break;
Damien Miller0d032412013-07-25 11:56:52 +10001424 case I_REGET:
1425 aflag = 1;
1426 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001427 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001428 err = process_get(conn, path1, path2, *pwd, pflag,
Damien Millerf29238e2013-10-17 11:48:52 +11001429 rflag, aflag, fflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001430 break;
Damien Millerd8accc02014-05-15 13:46:25 +10001431 case I_REPUT:
1432 aflag = 1;
1433 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001434 case I_PUT:
Damien Millerf29238e2013-10-17 11:48:52 +11001435 err = process_put(conn, path1, path2, *pwd, pflag,
Damien Millerd8accc02014-05-15 13:46:25 +10001436 rflag, aflag, fflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001437 break;
1438 case I_RENAME:
1439 path1 = make_absolute(path1, *pwd);
1440 path2 = make_absolute(path2, *pwd);
Damien Millerc7dba122013-08-21 02:41:15 +10001441 err = do_rename(conn, path1, path2, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001442 break;
1443 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001444 sflag = 1;
1445 case I_LINK:
Damien Miller034f27a2013-08-21 02:40:44 +10001446 if (!sflag)
1447 path1 = make_absolute(path1, *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001448 path2 = make_absolute(path2, *pwd);
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001449 err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001450 break;
1451 case I_RM:
1452 path1 = make_absolute(path1, *pwd);
1453 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001454 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001455 if (!quiet)
1456 printf("Removing %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001457 err = do_rm(conn, g.gl_pathv[i]);
1458 if (err != 0 && err_abort)
1459 break;
1460 }
1461 break;
1462 case I_MKDIR:
1463 path1 = make_absolute(path1, *pwd);
1464 attrib_clear(&a);
1465 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1466 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001467 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001468 break;
1469 case I_RMDIR:
1470 path1 = make_absolute(path1, *pwd);
1471 err = do_rmdir(conn, path1);
1472 break;
1473 case I_CHDIR:
1474 path1 = make_absolute(path1, *pwd);
1475 if ((tmp = do_realpath(conn, path1)) == NULL) {
1476 err = 1;
1477 break;
1478 }
1479 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
Darren Tuckera627d422013-06-02 07:31:17 +10001480 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001481 err = 1;
1482 break;
1483 }
1484 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1485 error("Can't change directory: Can't check target");
Darren Tuckera627d422013-06-02 07:31:17 +10001486 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001487 err = 1;
1488 break;
1489 }
1490 if (!S_ISDIR(aa->perm)) {
1491 error("Can't change directory: \"%s\" is not "
1492 "a directory", tmp);
Darren Tuckera627d422013-06-02 07:31:17 +10001493 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001494 err = 1;
1495 break;
1496 }
Darren Tuckera627d422013-06-02 07:31:17 +10001497 free(*pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001498 *pwd = tmp;
1499 break;
1500 case I_LS:
1501 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001502 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001503 break;
1504 }
1505
1506 /* Strip pwd off beginning of non-absolute paths */
1507 tmp = NULL;
1508 if (*path1 != '/')
1509 tmp = *pwd;
1510
1511 path1 = make_absolute(path1, *pwd);
1512 err = do_globbed_ls(conn, path1, tmp, lflag);
1513 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001514 case I_DF:
1515 /* Default to current directory if no path specified */
1516 if (path1 == NULL)
1517 path1 = xstrdup(*pwd);
1518 path1 = make_absolute(path1, *pwd);
1519 err = do_df(conn, path1, hflag, iflag);
1520 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001521 case I_LCHDIR:
deraadt@openbsd.org40ba4c92014-08-20 01:28:55 +00001522 tmp = tilde_expand_filename(path1, getuid());
djm@openbsd.org7ff880e2014-08-19 23:57:18 +00001523 free(path1);
1524 path1 = tmp;
Damien Miller20e1fab2004-02-18 14:30:55 +11001525 if (chdir(path1) == -1) {
1526 error("Couldn't change local directory to "
1527 "\"%s\": %s", path1, strerror(errno));
1528 err = 1;
1529 }
1530 break;
1531 case I_LMKDIR:
1532 if (mkdir(path1, 0777) == -1) {
1533 error("Couldn't create local directory "
1534 "\"%s\": %s", path1, strerror(errno));
1535 err = 1;
1536 }
1537 break;
1538 case I_LLS:
1539 local_do_ls(cmd);
1540 break;
1541 case I_SHELL:
1542 local_do_shell(cmd);
1543 break;
1544 case I_LUMASK:
1545 umask(n_arg);
1546 printf("Local umask: %03lo\n", n_arg);
1547 break;
1548 case I_CHMOD:
1549 path1 = make_absolute(path1, *pwd);
1550 attrib_clear(&a);
1551 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1552 a.perm = n_arg;
1553 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001554 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001555 if (!quiet)
1556 printf("Changing mode on %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001557 err = do_setstat(conn, g.gl_pathv[i], &a);
1558 if (err != 0 && err_abort)
1559 break;
1560 }
1561 break;
1562 case I_CHOWN:
1563 case I_CHGRP:
1564 path1 = make_absolute(path1, *pwd);
1565 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001566 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001567 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001568 if (err_abort) {
1569 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001570 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001571 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001572 continue;
1573 }
1574 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1575 error("Can't get current ownership of "
1576 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001577 if (err_abort) {
1578 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001579 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001580 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001581 continue;
1582 }
1583 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1584 if (cmdnum == I_CHOWN) {
Damien Miller9303e652013-04-23 15:22:40 +10001585 if (!quiet)
1586 printf("Changing owner on %s\n",
1587 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001588 aa->uid = n_arg;
1589 } else {
Damien Miller9303e652013-04-23 15:22:40 +10001590 if (!quiet)
1591 printf("Changing group on %s\n",
1592 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001593 aa->gid = n_arg;
1594 }
1595 err = do_setstat(conn, g.gl_pathv[i], aa);
1596 if (err != 0 && err_abort)
1597 break;
1598 }
1599 break;
1600 case I_PWD:
1601 printf("Remote working directory: %s\n", *pwd);
1602 break;
1603 case I_LPWD:
1604 if (!getcwd(path_buf, sizeof(path_buf))) {
1605 error("Couldn't get local cwd: %s", strerror(errno));
1606 err = -1;
1607 break;
1608 }
1609 printf("Local working directory: %s\n", path_buf);
1610 break;
1611 case I_QUIT:
1612 /* Processed below */
1613 break;
1614 case I_HELP:
1615 help();
1616 break;
1617 case I_VERSION:
1618 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1619 break;
1620 case I_PROGRESS:
1621 showprogress = !showprogress;
1622 if (showprogress)
1623 printf("Progress meter enabled\n");
1624 else
1625 printf("Progress meter disabled\n");
1626 break;
1627 default:
1628 fatal("%d is not implemented", cmdnum);
1629 }
1630
1631 if (g.gl_pathc)
1632 globfree(&g);
Darren Tuckera627d422013-06-02 07:31:17 +10001633 free(path1);
1634 free(path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001635
1636 /* If an unignored error occurs in batch mode we should abort. */
1637 if (err_abort && err != 0)
1638 return (-1);
1639 else if (cmdnum == I_QUIT)
1640 return (1);
1641
1642 return (0);
1643}
1644
Darren Tucker2d963d82004-11-07 20:04:10 +11001645#ifdef USE_LIBEDIT
1646static char *
1647prompt(EditLine *el)
1648{
1649 return ("sftp> ");
1650}
Darren Tucker2d963d82004-11-07 20:04:10 +11001651
Darren Tucker909d8582010-01-08 19:02:40 +11001652/* Display entries in 'list' after skipping the first 'len' chars */
1653static void
1654complete_display(char **list, u_int len)
1655{
1656 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1657 struct winsize ws;
1658 char *tmp;
1659
1660 /* Count entries for sort and find longest */
Damien Miller02e87802013-08-21 02:38:51 +10001661 for (y = 0; list[y]; y++)
Darren Tucker909d8582010-01-08 19:02:40 +11001662 m = MAX(m, strlen(list[y]));
1663
1664 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1665 width = ws.ws_col;
1666
1667 m = m > len ? m - len : 0;
1668 columns = width / (m + 2);
1669 columns = MAX(columns, 1);
1670 colspace = width / columns;
1671 colspace = MIN(colspace, width);
1672
1673 printf("\n");
1674 m = 1;
1675 for (y = 0; list[y]; y++) {
1676 llen = strlen(list[y]);
1677 tmp = llen > len ? list[y] + len : "";
1678 printf("%-*s", colspace, tmp);
1679 if (m >= columns) {
1680 printf("\n");
1681 m = 1;
1682 } else
1683 m++;
1684 }
1685 printf("\n");
1686}
1687
1688/*
1689 * Given a "list" of words that begin with a common prefix of "word",
1690 * attempt to find an autocompletion to extends "word" by the next
1691 * characters common to all entries in "list".
1692 */
1693static char *
1694complete_ambiguous(const char *word, char **list, size_t count)
1695{
1696 if (word == NULL)
1697 return NULL;
1698
1699 if (count > 0) {
1700 u_int y, matchlen = strlen(list[0]);
1701
1702 /* Find length of common stem */
1703 for (y = 1; list[y]; y++) {
1704 u_int x;
1705
Damien Miller02e87802013-08-21 02:38:51 +10001706 for (x = 0; x < matchlen; x++)
1707 if (list[0][x] != list[y][x])
Darren Tucker909d8582010-01-08 19:02:40 +11001708 break;
1709
1710 matchlen = x;
1711 }
1712
1713 if (matchlen > strlen(word)) {
1714 char *tmp = xstrdup(list[0]);
1715
Darren Tucker340d1682010-01-09 08:54:31 +11001716 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001717 return tmp;
1718 }
Damien Miller02e87802013-08-21 02:38:51 +10001719 }
Darren Tucker909d8582010-01-08 19:02:40 +11001720
1721 return xstrdup(word);
1722}
1723
1724/* Autocomplete a sftp command */
1725static int
1726complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1727 int terminated)
1728{
1729 u_int y, count = 0, cmdlen, tmplen;
1730 char *tmp, **list, argterm[3];
1731 const LineInfo *lf;
1732
1733 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1734
1735 /* No command specified: display all available commands */
1736 if (cmd == NULL) {
1737 for (y = 0; cmds[y].c; y++)
1738 list[count++] = xstrdup(cmds[y].c);
Damien Miller02e87802013-08-21 02:38:51 +10001739
Darren Tucker909d8582010-01-08 19:02:40 +11001740 list[count] = NULL;
1741 complete_display(list, 0);
1742
Damien Miller02e87802013-08-21 02:38:51 +10001743 for (y = 0; list[y] != NULL; y++)
1744 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001745 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001746 return count;
1747 }
1748
1749 /* Prepare subset of commands that start with "cmd" */
1750 cmdlen = strlen(cmd);
1751 for (y = 0; cmds[y].c; y++) {
Damien Miller02e87802013-08-21 02:38:51 +10001752 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
Darren Tucker909d8582010-01-08 19:02:40 +11001753 list[count++] = xstrdup(cmds[y].c);
1754 }
1755 list[count] = NULL;
1756
Damien Miller47d81152011-11-25 13:53:48 +11001757 if (count == 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10001758 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001759 return 0;
Damien Miller47d81152011-11-25 13:53:48 +11001760 }
Darren Tucker909d8582010-01-08 19:02:40 +11001761
1762 /* Complete ambigious command */
1763 tmp = complete_ambiguous(cmd, list, count);
1764 if (count > 1)
1765 complete_display(list, 0);
1766
Damien Miller02e87802013-08-21 02:38:51 +10001767 for (y = 0; list[y]; y++)
1768 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001769 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001770
1771 if (tmp != NULL) {
1772 tmplen = strlen(tmp);
1773 cmdlen = strlen(cmd);
1774 /* If cmd may be extended then do so */
1775 if (tmplen > cmdlen)
1776 if (el_insertstr(el, tmp + cmdlen) == -1)
1777 fatal("el_insertstr failed.");
1778 lf = el_line(el);
1779 /* Terminate argument cleanly */
1780 if (count == 1) {
1781 y = 0;
1782 if (!terminated)
1783 argterm[y++] = quote;
1784 if (lastarg || *(lf->cursor) != ' ')
1785 argterm[y++] = ' ';
1786 argterm[y] = '\0';
1787 if (y > 0 && el_insertstr(el, argterm) == -1)
1788 fatal("el_insertstr failed.");
1789 }
Darren Tuckera627d422013-06-02 07:31:17 +10001790 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001791 }
1792
1793 return count;
1794}
1795
1796/*
1797 * Determine whether a particular sftp command's arguments (if any)
1798 * represent local or remote files.
1799 */
1800static int
1801complete_is_remote(char *cmd) {
1802 int i;
1803
1804 if (cmd == NULL)
1805 return -1;
1806
1807 for (i = 0; cmds[i].c; i++) {
Damien Miller02e87802013-08-21 02:38:51 +10001808 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
Darren Tucker909d8582010-01-08 19:02:40 +11001809 return cmds[i].t;
1810 }
1811
1812 return -1;
1813}
1814
1815/* Autocomplete a filename "file" */
1816static int
1817complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1818 char *file, int remote, int lastarg, char quote, int terminated)
1819{
1820 glob_t g;
Darren Tuckerea647212013-06-06 08:19:09 +10001821 char *tmp, *tmp2, ins[8];
Darren Tucker17146d32012-10-05 10:46:16 +10001822 u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
Darren Tuckerea647212013-06-06 08:19:09 +10001823 int clen;
Darren Tucker909d8582010-01-08 19:02:40 +11001824 const LineInfo *lf;
Damien Miller02e87802013-08-21 02:38:51 +10001825
Darren Tucker909d8582010-01-08 19:02:40 +11001826 /* Glob from "file" location */
1827 if (file == NULL)
1828 tmp = xstrdup("*");
1829 else
1830 xasprintf(&tmp, "%s*", file);
1831
Darren Tucker191fcc62012-10-05 10:45:01 +10001832 /* Check if the path is absolute. */
1833 isabs = tmp[0] == '/';
1834
Darren Tucker909d8582010-01-08 19:02:40 +11001835 memset(&g, 0, sizeof(g));
1836 if (remote != LOCAL) {
1837 tmp = make_absolute(tmp, remote_path);
1838 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001839 } else
Darren Tucker909d8582010-01-08 19:02:40 +11001840 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001841
Darren Tucker909d8582010-01-08 19:02:40 +11001842 /* Determine length of pwd so we can trim completion display */
1843 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1844 /* Terminate counting on first unescaped glob metacharacter */
1845 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1846 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1847 hadglob = 1;
1848 break;
1849 }
1850 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1851 tmplen++;
1852 if (tmp[tmplen] == '/')
1853 pwdlen = tmplen + 1; /* track last seen '/' */
1854 }
Darren Tuckera627d422013-06-02 07:31:17 +10001855 free(tmp);
Damien Millerd7fd8be2014-05-15 14:24:59 +10001856 tmp = NULL;
Darren Tucker909d8582010-01-08 19:02:40 +11001857
Damien Miller02e87802013-08-21 02:38:51 +10001858 if (g.gl_matchc == 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001859 goto out;
1860
1861 if (g.gl_matchc > 1)
1862 complete_display(g.gl_pathv, pwdlen);
1863
Darren Tucker909d8582010-01-08 19:02:40 +11001864 /* Don't try to extend globs */
1865 if (file == NULL || hadglob)
1866 goto out;
1867
1868 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
Darren Tucker191fcc62012-10-05 10:45:01 +10001869 tmp = path_strip(tmp2, isabs ? NULL : remote_path);
Darren Tuckera627d422013-06-02 07:31:17 +10001870 free(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001871
1872 if (tmp == NULL)
1873 goto out;
1874
1875 tmplen = strlen(tmp);
1876 filelen = strlen(file);
1877
Darren Tucker17146d32012-10-05 10:46:16 +10001878 /* Count the number of escaped characters in the input string. */
1879 cesc = isesc = 0;
1880 for (i = 0; i < filelen; i++) {
1881 if (!isesc && file[i] == '\\' && i + 1 < filelen){
1882 isesc = 1;
1883 cesc++;
1884 } else
1885 isesc = 0;
1886 }
1887
1888 if (tmplen > (filelen - cesc)) {
1889 tmp2 = tmp + filelen - cesc;
Damien Miller02e87802013-08-21 02:38:51 +10001890 len = strlen(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001891 /* quote argument on way out */
Darren Tuckerea647212013-06-06 08:19:09 +10001892 for (i = 0; i < len; i += clen) {
1893 if ((clen = mblen(tmp2 + i, len - i)) < 0 ||
1894 (size_t)clen > sizeof(ins) - 2)
1895 fatal("invalid multibyte character");
Darren Tucker909d8582010-01-08 19:02:40 +11001896 ins[0] = '\\';
Darren Tuckerea647212013-06-06 08:19:09 +10001897 memcpy(ins + 1, tmp2 + i, clen);
1898 ins[clen + 1] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001899 switch (tmp2[i]) {
1900 case '\'':
1901 case '"':
1902 case '\\':
1903 case '\t':
Darren Tuckerd78739a2010-10-24 10:56:32 +11001904 case '[':
Darren Tucker909d8582010-01-08 19:02:40 +11001905 case ' ':
Darren Tucker17146d32012-10-05 10:46:16 +10001906 case '#':
1907 case '*':
Darren Tucker909d8582010-01-08 19:02:40 +11001908 if (quote == '\0' || tmp2[i] == quote) {
1909 if (el_insertstr(el, ins) == -1)
1910 fatal("el_insertstr "
1911 "failed.");
1912 break;
1913 }
1914 /* FALLTHROUGH */
1915 default:
1916 if (el_insertstr(el, ins + 1) == -1)
1917 fatal("el_insertstr failed.");
1918 break;
1919 }
1920 }
1921 }
1922
1923 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11001924 if (g.gl_matchc == 1) {
1925 i = 0;
Damien Miller38094812014-05-15 14:25:18 +10001926 if (!terminated && quote != '\0')
Darren Tucker909d8582010-01-08 19:02:40 +11001927 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11001928 if (*(lf->cursor - 1) != '/' &&
1929 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11001930 ins[i++] = ' ';
1931 ins[i] = '\0';
1932 if (i > 0 && el_insertstr(el, ins) == -1)
1933 fatal("el_insertstr failed.");
1934 }
Darren Tuckera627d422013-06-02 07:31:17 +10001935 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001936
1937 out:
1938 globfree(&g);
1939 return g.gl_matchc;
1940}
1941
1942/* tab-completion hook function, called via libedit */
1943static unsigned char
1944complete(EditLine *el, int ch)
1945{
Damien Miller02e87802013-08-21 02:38:51 +10001946 char **argv, *line, quote;
Damien Miller746d1a62013-07-18 16:13:02 +10001947 int argc, carg;
1948 u_int cursor, len, terminated, ret = CC_ERROR;
Darren Tucker909d8582010-01-08 19:02:40 +11001949 const LineInfo *lf;
1950 struct complete_ctx *complete_ctx;
1951
1952 lf = el_line(el);
1953 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1954 fatal("%s: el_get failed", __func__);
1955
1956 /* Figure out which argument the cursor points to */
1957 cursor = lf->cursor - lf->buffer;
1958 line = (char *)xmalloc(cursor + 1);
1959 memcpy(line, lf->buffer, cursor);
1960 line[cursor] = '\0';
1961 argv = makeargv(line, &carg, 1, &quote, &terminated);
Darren Tuckera627d422013-06-02 07:31:17 +10001962 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001963
1964 /* Get all the arguments on the line */
1965 len = lf->lastchar - lf->buffer;
1966 line = (char *)xmalloc(len + 1);
1967 memcpy(line, lf->buffer, len);
1968 line[len] = '\0';
1969 argv = makeargv(line, &argc, 1, NULL, NULL);
1970
1971 /* Ensure cursor is at EOL or a argument boundary */
1972 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1973 line[cursor] != '\n') {
Darren Tuckera627d422013-06-02 07:31:17 +10001974 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001975 return ret;
1976 }
1977
1978 if (carg == 0) {
1979 /* Show all available commands */
1980 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1981 ret = CC_REDISPLAY;
1982 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1983 /* Handle the command parsing */
1984 if (complete_cmd_parse(el, argv[0], argc == carg,
Damien Miller02e87802013-08-21 02:38:51 +10001985 quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001986 ret = CC_REDISPLAY;
1987 } else if (carg >= 1) {
1988 /* Handle file parsing */
1989 int remote = complete_is_remote(argv[0]);
1990 char *filematch = NULL;
1991
1992 if (carg > 1 && line[cursor-1] != ' ')
1993 filematch = argv[carg - 1];
1994
1995 if (remote != 0 &&
1996 complete_match(el, complete_ctx->conn,
1997 *complete_ctx->remote_pathp, filematch,
Damien Miller02e87802013-08-21 02:38:51 +10001998 remote, carg == argc, quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001999 ret = CC_REDISPLAY;
2000 }
2001
Damien Miller02e87802013-08-21 02:38:51 +10002002 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11002003 return ret;
2004}
Darren Tuckere67f7db2010-01-08 19:50:02 +11002005#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11002006
Damien Miller20e1fab2004-02-18 14:30:55 +11002007int
Darren Tucker21063192010-01-08 17:10:36 +11002008interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11002009{
Darren Tucker909d8582010-01-08 19:02:40 +11002010 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11002011 char *dir = NULL;
2012 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10002013 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11002014 EditLine *el = NULL;
2015#ifdef USE_LIBEDIT
2016 History *hl = NULL;
2017 HistEvent hev;
2018 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11002019 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11002020
2021 if (!batchmode && isatty(STDIN_FILENO)) {
2022 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
2023 fatal("Couldn't initialise editline");
2024 if ((hl = history_init()) == NULL)
2025 fatal("Couldn't initialise editline history");
2026 history(hl, &hev, H_SETSIZE, 100);
2027 el_set(el, EL_HIST, history, hl);
2028
2029 el_set(el, EL_PROMPT, prompt);
2030 el_set(el, EL_EDITOR, "emacs");
2031 el_set(el, EL_TERMINAL, NULL);
2032 el_set(el, EL_SIGNAL, 1);
2033 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11002034
2035 /* Tab Completion */
Damien Miller02e87802013-08-21 02:38:51 +10002036 el_set(el, EL_ADDFN, "ftp-complete",
Darren Tuckerd78739a2010-10-24 10:56:32 +11002037 "Context sensitive argument completion", complete);
Darren Tucker909d8582010-01-08 19:02:40 +11002038 complete_ctx.conn = conn;
2039 complete_ctx.remote_pathp = &remote_path;
2040 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
2041 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Damien Millere0ee7272013-08-21 02:42:35 +10002042 /* enable ctrl-left-arrow and ctrl-right-arrow */
2043 el_set(el, EL_BIND, "\\e[1;5C", "em-next-word", NULL);
2044 el_set(el, EL_BIND, "\\e[5C", "em-next-word", NULL);
2045 el_set(el, EL_BIND, "\\e[1;5D", "ed-prev-word", NULL);
2046 el_set(el, EL_BIND, "\\e\\e[D", "ed-prev-word", NULL);
Damien Miller61353b32013-09-14 09:45:32 +10002047 /* make ^w match ksh behaviour */
2048 el_set(el, EL_BIND, "^w", "ed-delete-prev-word", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11002049 }
2050#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002051
Darren Tucker909d8582010-01-08 19:02:40 +11002052 remote_path = do_realpath(conn, ".");
2053 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11002054 fatal("Need cwd");
2055
2056 if (file1 != NULL) {
2057 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11002058 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11002059
2060 if (remote_is_dir(conn, dir) && file2 == NULL) {
Damien Miller9303e652013-04-23 15:22:40 +10002061 if (!quiet)
2062 printf("Changing to: %s\n", dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002063 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11002064 if (parse_dispatch_command(conn, cmd,
2065 &remote_path, 1) != 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10002066 free(dir);
2067 free(remote_path);
2068 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002069 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11002070 }
Damien Miller20e1fab2004-02-18 14:30:55 +11002071 } else {
Darren Tucker0af24052012-10-05 10:41:25 +10002072 /* XXX this is wrong wrt quoting */
Damien Miller0d032412013-07-25 11:56:52 +10002073 snprintf(cmd, sizeof cmd, "get%s %s%s%s",
2074 global_aflag ? " -a" : "", dir,
2075 file2 == NULL ? "" : " ",
2076 file2 == NULL ? "" : file2);
Darren Tucker909d8582010-01-08 19:02:40 +11002077 err = parse_dispatch_command(conn, cmd,
2078 &remote_path, 1);
Darren Tuckera627d422013-06-02 07:31:17 +10002079 free(dir);
2080 free(remote_path);
2081 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002082 return (err);
2083 }
Darren Tuckera627d422013-06-02 07:31:17 +10002084 free(dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002085 }
2086
Damien Miller37294fb2005-07-17 17:18:49 +10002087 setlinebuf(stdout);
2088 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11002089
Damien Miller0e2c1022005-08-12 22:16:22 +10002090 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11002091 err = 0;
2092 for (;;) {
2093 char *cp;
2094
Darren Tuckercdf547a2004-05-24 10:12:19 +10002095 signal(SIGINT, SIG_IGN);
2096
Darren Tucker2d963d82004-11-07 20:04:10 +11002097 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002098 if (interactive)
2099 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11002100 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002101 if (interactive)
2102 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11002103 break;
2104 }
Damien Miller0e2c1022005-08-12 22:16:22 +10002105 if (!interactive) { /* Echo command */
2106 printf("sftp> %s", cmd);
2107 if (strlen(cmd) > 0 &&
2108 cmd[strlen(cmd) - 1] != '\n')
2109 printf("\n");
2110 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002111 } else {
2112#ifdef USE_LIBEDIT
2113 const char *line;
2114 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11002115
Darren Tucker909d8582010-01-08 19:02:40 +11002116 if ((line = el_gets(el, &count)) == NULL ||
2117 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002118 printf("\n");
2119 break;
2120 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002121 history(hl, &hev, H_ENTER, line);
2122 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
2123 fprintf(stderr, "Error: input line too long\n");
2124 continue;
2125 }
2126#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002127 }
2128
Damien Miller20e1fab2004-02-18 14:30:55 +11002129 cp = strrchr(cmd, '\n');
2130 if (cp)
2131 *cp = '\0';
2132
Darren Tuckercdf547a2004-05-24 10:12:19 +10002133 /* Handle user interrupts gracefully during commands */
2134 interrupted = 0;
2135 signal(SIGINT, cmd_interrupt);
2136
Darren Tucker909d8582010-01-08 19:02:40 +11002137 err = parse_dispatch_command(conn, cmd, &remote_path,
2138 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11002139 if (err != 0)
2140 break;
2141 }
Darren Tuckera627d422013-06-02 07:31:17 +10002142 free(remote_path);
2143 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002144
Tim Rice027e8b12005-08-15 14:52:50 -07002145#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10002146 if (el != NULL)
2147 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07002148#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10002149
Damien Miller20e1fab2004-02-18 14:30:55 +11002150 /* err == 1 signifies normal "quit" exit */
2151 return (err >= 0 ? 0 : -1);
2152}
Damien Miller62d57f62003-01-10 21:43:24 +11002153
Ben Lindstrombba81212001-06-25 05:01:22 +00002154static void
Damien Millercc685c12003-06-04 22:51:38 +10002155connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11002156{
2157 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002158
Damien Miller33804262001-02-04 23:20:18 +11002159#ifdef USE_PIPES
2160 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002161
Damien Miller33804262001-02-04 23:20:18 +11002162 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2163 fatal("pipe: %s", strerror(errno));
2164 *in = pin[0];
2165 *out = pout[1];
2166 c_in = pout[0];
2167 c_out = pin[1];
2168#else /* USE_PIPES */
2169 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002170
Damien Miller33804262001-02-04 23:20:18 +11002171 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2172 fatal("socketpair: %s", strerror(errno));
2173 *in = *out = inout[0];
2174 c_in = c_out = inout[1];
2175#endif /* USE_PIPES */
2176
Damien Millercc685c12003-06-04 22:51:38 +10002177 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002178 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002179 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002180 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2181 (dup2(c_out, STDOUT_FILENO) == -1)) {
2182 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002183 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002184 }
2185 close(*in);
2186 close(*out);
2187 close(c_in);
2188 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002189
2190 /*
2191 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002192 * ignore SIGINT if we want to gracefully abort commands,
2193 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002194 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2195 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002196 */
2197 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002198 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002199 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002200 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002201 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002202 }
2203
Damien Millercc685c12003-06-04 22:51:38 +10002204 signal(SIGTERM, killchild);
2205 signal(SIGINT, killchild);
2206 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11002207 close(c_in);
2208 close(c_out);
2209}
2210
Ben Lindstrombba81212001-06-25 05:01:22 +00002211static void
Damien Miller33804262001-02-04 23:20:18 +11002212usage(void)
2213{
Damien Miller025e01c2002-02-08 22:06:29 +11002214 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002215
Ben Lindstrom1e243242001-09-18 05:38:44 +00002216 fprintf(stderr,
Damien Miller1edcbf62013-10-18 10:17:17 +11002217 "usage: %s [-1246aCfpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002218 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002219 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002220 " [-o ssh_option] [-P port] [-R num_requests] "
2221 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002222 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002223 " %s [user@]host[:file ...]\n"
2224 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002225 " %s -b batchfile [user@]host\n",
2226 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002227 exit(1);
2228}
2229
Kevin Stevesef4eea92001-02-05 12:42:17 +00002230int
Damien Miller33804262001-02-04 23:20:18 +11002231main(int argc, char **argv)
2232{
Damien Miller956f3fb2003-01-10 21:40:00 +11002233 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002234 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002235 int debug_level = 0, sshver = 2;
2236 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002237 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002238 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002239 LogLevel ll = SYSLOG_LEVEL_INFO;
2240 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002241 extern int optind;
2242 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002243 struct sftp_conn *conn;
2244 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2245 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002246 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002247
Darren Tuckerce321d82005-10-03 18:11:24 +10002248 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2249 sanitise_stdfd();
Darren Tuckerea647212013-06-06 08:19:09 +10002250 setlocale(LC_CTYPE, "");
Darren Tuckerce321d82005-10-03 18:11:24 +10002251
Damien Miller59d3d5b2003-08-22 09:34:41 +10002252 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002253 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002254 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002255 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002256 addargs(&args, "-oForwardX11 no");
2257 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002258 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002259 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002260
Ben Lindstrom387c4722001-05-08 20:27:25 +00002261 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002262 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002263
Darren Tucker282b4022009-10-07 08:23:06 +11002264 while ((ch = getopt(argc, argv,
Damien Millerf29238e2013-10-17 11:48:52 +11002265 "1246afhpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002266 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002267 /* Passed through to ssh(1) */
2268 case '4':
2269 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002270 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002271 addargs(&args, "-%c", ch);
2272 break;
2273 /* Passed through to ssh(1) with argument */
2274 case 'F':
2275 case 'c':
2276 case 'i':
2277 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002278 addargs(&args, "-%c", ch);
2279 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002280 break;
2281 case 'q':
Damien Miller9303e652013-04-23 15:22:40 +10002282 ll = SYSLOG_LEVEL_ERROR;
2283 quiet = 1;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002284 showprogress = 0;
2285 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002286 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002287 case 'P':
2288 addargs(&args, "-oPort %s", optarg);
2289 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002290 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002291 if (debug_level < 3) {
2292 addargs(&args, "-v");
2293 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2294 }
2295 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002296 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002297 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002298 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002299 if (sftp_server == NULL)
2300 sftp_server = _PATH_SFTP_SERVER;
2301 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002302 case '2':
2303 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002304 break;
Damien Miller0d032412013-07-25 11:56:52 +10002305 case 'a':
2306 global_aflag = 1;
2307 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002308 case 'B':
2309 copy_buffer_len = strtol(optarg, &cp, 10);
2310 if (copy_buffer_len == 0 || *cp != '\0')
2311 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002312 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002313 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002314 if (batchmode)
2315 fatal("Batch file already specified.");
2316
2317 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002318 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002319 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002320 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002321 showprogress = 0;
Damien Miller9303e652013-04-23 15:22:40 +10002322 quiet = batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002323 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002324 break;
Damien Millerf29238e2013-10-17 11:48:52 +11002325 case 'f':
2326 global_fflag = 1;
2327 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002328 case 'p':
2329 global_pflag = 1;
2330 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002331 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002332 sftp_direct = optarg;
2333 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002334 case 'l':
2335 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2336 &errstr);
2337 if (errstr != NULL)
2338 usage();
2339 limit_kbps *= 1024; /* kbps */
2340 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002341 case 'r':
2342 global_rflag = 1;
2343 break;
Damien Miller16a13332002-02-13 14:03:56 +11002344 case 'R':
2345 num_requests = strtol(optarg, &cp, 10);
2346 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002347 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002348 optarg);
2349 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002350 case 's':
2351 sftp_server = optarg;
2352 break;
2353 case 'S':
2354 ssh_program = optarg;
2355 replacearg(&args, 0, "%s", ssh_program);
2356 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002357 case 'h':
2358 default:
Damien Miller33804262001-02-04 23:20:18 +11002359 usage();
2360 }
2361 }
2362
Damien Millerc0f27d82004-03-08 23:12:19 +11002363 if (!isatty(STDERR_FILENO))
2364 showprogress = 0;
2365
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002366 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2367
Damien Millerd14ee1e2002-02-05 12:27:31 +11002368 if (sftp_direct == NULL) {
2369 if (optind == argc || argc > (optind + 2))
2370 usage();
Damien Miller33804262001-02-04 23:20:18 +11002371
Damien Millerd14ee1e2002-02-05 12:27:31 +11002372 userhost = xstrdup(argv[optind]);
2373 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002374
Ben Lindstromc276c122002-12-23 02:14:51 +00002375 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002376 host = userhost;
2377 else {
2378 *host++ = '\0';
2379 if (!userhost[0]) {
2380 fprintf(stderr, "Missing username\n");
2381 usage();
2382 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002383 addargs(&args, "-l");
2384 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002385 }
2386
Damien Millerec692032004-01-27 21:22:00 +11002387 if ((cp = colon(host)) != NULL) {
2388 *cp++ = '\0';
2389 file1 = cp;
2390 }
2391
Damien Millerd14ee1e2002-02-05 12:27:31 +11002392 host = cleanhostname(host);
2393 if (!*host) {
2394 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002395 usage();
2396 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002397
Damien Millerd14ee1e2002-02-05 12:27:31 +11002398 addargs(&args, "-oProtocol %d", sshver);
2399
2400 /* no subsystem if the server-spec contains a '/' */
2401 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2402 addargs(&args, "-s");
2403
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002404 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002405 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002406 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002407 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002408
Damien Millercc685c12003-06-04 22:51:38 +10002409 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002410 } else {
2411 args.list = NULL;
2412 addargs(&args, "sftp-server");
2413
Damien Millercc685c12003-06-04 22:51:38 +10002414 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002415 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002416 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002417
Damien Miller65e42f82010-09-24 22:15:11 +10002418 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002419 if (conn == NULL)
2420 fatal("Couldn't initialise connection to server");
2421
Damien Miller9303e652013-04-23 15:22:40 +10002422 if (!quiet) {
Darren Tucker21063192010-01-08 17:10:36 +11002423 if (sftp_direct == NULL)
2424 fprintf(stderr, "Connected to %s.\n", host);
2425 else
2426 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2427 }
2428
2429 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002430
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002431#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002432 shutdown(in, SHUT_RDWR);
2433 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002434#endif
2435
Damien Miller33804262001-02-04 23:20:18 +11002436 close(in);
2437 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002438 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002439 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002440
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002441 while (waitpid(sshpid, NULL, 0) == -1)
2442 if (errno != EINTR)
2443 fatal("Couldn't wait for ssh process: %s",
2444 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002445
Damien Miller956f3fb2003-01-10 21:40:00 +11002446 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002447}