blob: 1ddfef6b55be712a13f883ab27d37d1e73dbdf53 [file] [log] [blame]
Damien Miller036d3072013-08-21 02:41:46 +10001/* $OpenBSD: sftp.c,v 1.153 2013/08/09 03:37:25 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 Miller0d032412013-07-25 11:56:52 +100091/* When this option is set, we resume download if possible */
92int 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
Darren Tuckercdf547a2004-05-24 10:12:19 +100097/* SIGINT received during command processing */
98volatile sig_atomic_t interrupted = 0;
99
Darren Tuckerb9123452004-06-22 13:06:45 +1000100/* I wish qsort() took a separate ctx for the comparison function...*/
101int sort_flag;
102
Darren Tucker909d8582010-01-08 19:02:40 +1100103/* Context used for commandline completion */
104struct complete_ctx {
105 struct sftp_conn *conn;
106 char **remote_pathp;
107};
108
Damien Miller20e1fab2004-02-18 14:30:55 +1100109int remote_glob(struct sftp_conn *, const char *, int,
110 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
Damien Miller33804262001-02-04 23:20:18 +1100111
Kevin Steves12888d12001-03-05 19:50:57 +0000112extern char *__progname;
Kevin Steves12888d12001-03-05 19:50:57 +0000113
Damien Miller20e1fab2004-02-18 14:30:55 +1100114/* Separators for interactive commands */
115#define WHITESPACE " \t\r\n"
Damien Millerd7686fd2001-02-10 00:40:03 +1100116
Darren Tuckerb9123452004-06-22 13:06:45 +1000117/* ls flags */
Darren Tucker2901e2d2010-01-13 22:44:06 +1100118#define LS_LONG_VIEW 0x0001 /* Full view ala ls -l */
119#define LS_SHORT_VIEW 0x0002 /* Single row view ala ls -1 */
120#define LS_NUMERIC_VIEW 0x0004 /* Long view with numeric uid/gid */
121#define LS_NAME_SORT 0x0008 /* Sort by name (default) */
122#define LS_TIME_SORT 0x0010 /* Sort by mtime */
123#define LS_SIZE_SORT 0x0020 /* Sort by file size */
124#define LS_REVERSE_SORT 0x0040 /* Reverse sort order */
125#define LS_SHOW_ALL 0x0080 /* Don't skip filenames starting with '.' */
126#define LS_SI_UNITS 0x0100 /* Display sizes as K, M, G, etc. */
Darren Tuckerb9123452004-06-22 13:06:45 +1000127
Darren Tucker2901e2d2010-01-13 22:44:06 +1100128#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS)
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000129#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
Damien Miller20e1fab2004-02-18 14:30:55 +1100130
131/* Commands for interactive mode */
Damien Miller02e87802013-08-21 02:38:51 +1000132enum sftp_command {
133 I_CHDIR = 1,
134 I_CHGRP,
135 I_CHMOD,
136 I_CHOWN,
137 I_DF,
138 I_GET,
139 I_HELP,
140 I_LCHDIR,
141 I_LINK,
142 I_LLS,
143 I_LMKDIR,
144 I_LPWD,
145 I_LS,
146 I_LUMASK,
147 I_MKDIR,
148 I_PUT,
149 I_PWD,
150 I_QUIT,
151 I_RENAME,
152 I_RM,
153 I_RMDIR,
154 I_SHELL,
155 I_SYMLINK,
156 I_VERSION,
157 I_PROGRESS,
158 I_REGET,
159};
Damien Miller20e1fab2004-02-18 14:30:55 +1100160
161struct CMD {
162 const char *c;
163 const int n;
Darren Tucker909d8582010-01-08 19:02:40 +1100164 const int t;
Damien Miller20e1fab2004-02-18 14:30:55 +1100165};
166
Darren Tucker909d8582010-01-08 19:02:40 +1100167/* Type of completion */
168#define NOARGS 0
169#define REMOTE 1
170#define LOCAL 2
171
Damien Miller20e1fab2004-02-18 14:30:55 +1100172static const struct CMD cmds[] = {
Darren Tucker909d8582010-01-08 19:02:40 +1100173 { "bye", I_QUIT, NOARGS },
174 { "cd", I_CHDIR, REMOTE },
175 { "chdir", I_CHDIR, REMOTE },
176 { "chgrp", I_CHGRP, REMOTE },
177 { "chmod", I_CHMOD, REMOTE },
178 { "chown", I_CHOWN, REMOTE },
179 { "df", I_DF, REMOTE },
180 { "dir", I_LS, REMOTE },
181 { "exit", I_QUIT, NOARGS },
182 { "get", I_GET, REMOTE },
183 { "help", I_HELP, NOARGS },
184 { "lcd", I_LCHDIR, LOCAL },
185 { "lchdir", I_LCHDIR, LOCAL },
186 { "lls", I_LLS, LOCAL },
187 { "lmkdir", I_LMKDIR, LOCAL },
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100188 { "ln", I_LINK, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100189 { "lpwd", I_LPWD, LOCAL },
190 { "ls", I_LS, REMOTE },
191 { "lumask", I_LUMASK, NOARGS },
192 { "mkdir", I_MKDIR, REMOTE },
Damien Miller099fc162010-05-10 11:56:50 +1000193 { "mget", I_GET, REMOTE },
194 { "mput", I_PUT, LOCAL },
Darren Tucker909d8582010-01-08 19:02:40 +1100195 { "progress", I_PROGRESS, NOARGS },
196 { "put", I_PUT, LOCAL },
197 { "pwd", I_PWD, REMOTE },
198 { "quit", I_QUIT, NOARGS },
Damien Miller0d032412013-07-25 11:56:52 +1000199 { "reget", I_REGET, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100200 { "rename", I_RENAME, REMOTE },
201 { "rm", I_RM, REMOTE },
202 { "rmdir", I_RMDIR, REMOTE },
203 { "symlink", I_SYMLINK, REMOTE },
204 { "version", I_VERSION, NOARGS },
205 { "!", I_SHELL, NOARGS },
206 { "?", I_HELP, NOARGS },
207 { NULL, -1, -1 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100208};
209
Darren Tucker21063192010-01-08 17:10:36 +1100210int interactive_loop(struct sftp_conn *, char *file1, char *file2);
Damien Miller20e1fab2004-02-18 14:30:55 +1100211
Damien Millerb6c85fc2007-01-05 16:30:41 +1100212/* ARGSUSED */
Damien Miller20e1fab2004-02-18 14:30:55 +1100213static void
Darren Tuckercdf547a2004-05-24 10:12:19 +1000214killchild(int signo)
215{
Darren Tuckerba66df82005-01-24 21:57:40 +1100216 if (sshpid > 1) {
Darren Tuckercdf547a2004-05-24 10:12:19 +1000217 kill(sshpid, SIGTERM);
Darren Tuckerba66df82005-01-24 21:57:40 +1100218 waitpid(sshpid, NULL, 0);
219 }
Darren Tuckercdf547a2004-05-24 10:12:19 +1000220
221 _exit(1);
222}
223
Damien Millerb6c85fc2007-01-05 16:30:41 +1100224/* ARGSUSED */
Darren Tuckercdf547a2004-05-24 10:12:19 +1000225static void
226cmd_interrupt(int signo)
227{
228 const char msg[] = "\rInterrupt \n";
Darren Tuckere2f189a2004-12-06 22:45:53 +1100229 int olderrno = errno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000230
Darren Tuckerdbee3082013-05-16 20:32:29 +1000231 (void)write(STDERR_FILENO, msg, sizeof(msg) - 1);
Darren Tuckercdf547a2004-05-24 10:12:19 +1000232 interrupted = 1;
Darren Tuckere2f189a2004-12-06 22:45:53 +1100233 errno = olderrno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000234}
235
236static void
Damien Miller20e1fab2004-02-18 14:30:55 +1100237help(void)
238{
Damien Miller62fd18a2009-01-28 16:14:09 +1100239 printf("Available commands:\n"
240 "bye Quit sftp\n"
241 "cd path Change remote directory to 'path'\n"
242 "chgrp grp path Change group of file 'path' to 'grp'\n"
243 "chmod mode path Change permissions of file 'path' to 'mode'\n"
244 "chown own path Change owner of file 'path' to 'own'\n"
245 "df [-hi] [path] Display statistics for current directory or\n"
246 " filesystem containing 'path'\n"
247 "exit Quit sftp\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100248 "get [-Ppr] remote [local] Download file\n"
Damien Miller0d032412013-07-25 11:56:52 +1000249 "reget remote [local] Resume download file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100250 "help Display this help text\n"
251 "lcd path Change local directory to 'path'\n"
252 "lls [ls-options [path]] Display local directory listing\n"
253 "lmkdir path Create local directory\n"
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100254 "ln [-s] oldpath newpath Link remote file (-s for symlink)\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100255 "lpwd Print local working directory\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100256 "ls [-1afhlnrSt] [path] Display remote directory listing\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100257 "lumask umask Set local umask to 'umask'\n"
258 "mkdir path Create remote directory\n"
259 "progress Toggle display of progress meter\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100260 "put [-Ppr] local [remote] Upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100261 "pwd Display remote working directory\n"
262 "quit Quit sftp\n"
263 "rename oldpath newpath Rename remote file\n"
264 "rm path Delete remote file\n"
265 "rmdir path Remove remote directory\n"
266 "symlink oldpath newpath Symlink remote file\n"
267 "version Show SFTP version\n"
268 "!command Execute 'command' in local shell\n"
269 "! Escape to local shell\n"
270 "? Synonym for help\n");
Damien Miller20e1fab2004-02-18 14:30:55 +1100271}
272
273static void
274local_do_shell(const char *args)
275{
276 int status;
277 char *shell;
278 pid_t pid;
279
280 if (!*args)
281 args = NULL;
282
Damien Miller38d9a962010-10-07 22:07:11 +1100283 if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
Damien Miller20e1fab2004-02-18 14:30:55 +1100284 shell = _PATH_BSHELL;
285
286 if ((pid = fork()) == -1)
287 fatal("Couldn't fork: %s", strerror(errno));
288
289 if (pid == 0) {
290 /* XXX: child has pipe fds to ssh subproc open - issue? */
291 if (args) {
292 debug3("Executing %s -c \"%s\"", shell, args);
293 execl(shell, shell, "-c", args, (char *)NULL);
294 } else {
295 debug3("Executing %s", shell);
296 execl(shell, shell, (char *)NULL);
297 }
298 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
299 strerror(errno));
300 _exit(1);
301 }
302 while (waitpid(pid, &status, 0) == -1)
303 if (errno != EINTR)
304 fatal("Couldn't wait for child: %s", strerror(errno));
305 if (!WIFEXITED(status))
Damien Miller55b04f12006-03-26 14:23:17 +1100306 error("Shell exited abnormally");
Damien Miller20e1fab2004-02-18 14:30:55 +1100307 else if (WEXITSTATUS(status))
308 error("Shell exited with status %d", WEXITSTATUS(status));
309}
310
311static void
312local_do_ls(const char *args)
313{
314 if (!args || !*args)
315 local_do_shell(_PATH_LS);
316 else {
317 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
318 char *buf = xmalloc(len);
319
320 /* XXX: quoting - rip quoting code from ftp? */
321 snprintf(buf, len, _PATH_LS " %s", args);
322 local_do_shell(buf);
Darren Tuckera627d422013-06-02 07:31:17 +1000323 free(buf);
Damien Miller20e1fab2004-02-18 14:30:55 +1100324 }
325}
326
327/* Strip one path (usually the pwd) from the start of another */
328static char *
329path_strip(char *path, char *strip)
330{
331 size_t len;
332
333 if (strip == NULL)
334 return (xstrdup(path));
335
336 len = strlen(strip);
Darren Tuckere2f189a2004-12-06 22:45:53 +1100337 if (strncmp(path, strip, len) == 0) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100338 if (strip[len - 1] != '/' && path[len] == '/')
339 len++;
340 return (xstrdup(path + len));
341 }
342
343 return (xstrdup(path));
344}
345
346static char *
Damien Miller20e1fab2004-02-18 14:30:55 +1100347make_absolute(char *p, char *pwd)
348{
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000349 char *abs_str;
Damien Miller20e1fab2004-02-18 14:30:55 +1100350
351 /* Derelativise */
352 if (p && p[0] != '/') {
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000353 abs_str = path_append(pwd, p);
Darren Tuckera627d422013-06-02 07:31:17 +1000354 free(p);
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000355 return(abs_str);
Damien Miller20e1fab2004-02-18 14:30:55 +1100356 } else
357 return(p);
358}
359
360static int
Damien Miller0d032412013-07-25 11:56:52 +1000361parse_getput_flags(const char *cmd, char **argv, int argc,
362 int *aflag, int *pflag, int *rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100363{
Damien Millerf184bcf2008-06-29 22:45:13 +1000364 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000365 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100366
Damien Miller1cbc2922007-10-26 14:27:45 +1000367 optind = optreset = 1;
368 opterr = 0;
369
Damien Miller0d032412013-07-25 11:56:52 +1000370 *aflag = *rflag = *pflag = 0;
371 while ((ch = getopt(argc, argv, "aPpRr")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000372 switch (ch) {
Damien Miller0d032412013-07-25 11:56:52 +1000373 case 'a':
374 *aflag = 1;
375 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100376 case 'p':
377 case 'P':
378 *pflag = 1;
379 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100380 case 'r':
381 case 'R':
382 *rflag = 1;
383 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100384 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000385 error("%s: Invalid flag -%c", cmd, optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000386 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100387 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100388 }
389
Damien Miller1cbc2922007-10-26 14:27:45 +1000390 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100391}
392
393static int
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100394parse_link_flags(const char *cmd, char **argv, int argc, int *sflag)
395{
396 extern int opterr, optind, optopt, optreset;
397 int ch;
398
399 optind = optreset = 1;
400 opterr = 0;
401
402 *sflag = 0;
403 while ((ch = getopt(argc, argv, "s")) != -1) {
404 switch (ch) {
405 case 's':
406 *sflag = 1;
407 break;
408 default:
409 error("%s: Invalid flag -%c", cmd, optopt);
410 return -1;
411 }
412 }
413
414 return optind;
415}
416
417static int
Damien Millerc7dba122013-08-21 02:41:15 +1000418parse_rename_flags(const char *cmd, char **argv, int argc, int *lflag)
419{
420 extern int opterr, optind, optopt, optreset;
421 int ch;
422
423 optind = optreset = 1;
424 opterr = 0;
425
426 *lflag = 0;
427 while ((ch = getopt(argc, argv, "l")) != -1) {
428 switch (ch) {
429 case 'l':
430 *lflag = 1;
431 break;
432 default:
433 error("%s: Invalid flag -%c", cmd, optopt);
434 return -1;
435 }
436 }
437
438 return optind;
439}
440
441static int
Damien Miller1cbc2922007-10-26 14:27:45 +1000442parse_ls_flags(char **argv, int argc, int *lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100443{
Damien Millerf184bcf2008-06-29 22:45:13 +1000444 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000445 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100446
Damien Miller1cbc2922007-10-26 14:27:45 +1000447 optind = optreset = 1;
448 opterr = 0;
449
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000450 *lflag = LS_NAME_SORT;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100451 while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000452 switch (ch) {
453 case '1':
454 *lflag &= ~VIEW_FLAGS;
455 *lflag |= LS_SHORT_VIEW;
456 break;
457 case 'S':
458 *lflag &= ~SORT_FLAGS;
459 *lflag |= LS_SIZE_SORT;
460 break;
461 case 'a':
462 *lflag |= LS_SHOW_ALL;
463 break;
464 case 'f':
465 *lflag &= ~SORT_FLAGS;
466 break;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100467 case 'h':
468 *lflag |= LS_SI_UNITS;
469 break;
Damien Miller1cbc2922007-10-26 14:27:45 +1000470 case 'l':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100471 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000472 *lflag |= LS_LONG_VIEW;
473 break;
474 case 'n':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100475 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000476 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
477 break;
478 case 'r':
479 *lflag |= LS_REVERSE_SORT;
480 break;
481 case 't':
482 *lflag &= ~SORT_FLAGS;
483 *lflag |= LS_TIME_SORT;
484 break;
485 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000486 error("ls: Invalid flag -%c", optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000487 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100488 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100489 }
490
Damien Miller1cbc2922007-10-26 14:27:45 +1000491 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100492}
493
494static int
Damien Millerd671e5a2008-05-19 14:53:33 +1000495parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
496{
Damien Millerf184bcf2008-06-29 22:45:13 +1000497 extern int opterr, optind, optopt, optreset;
Damien Millerd671e5a2008-05-19 14:53:33 +1000498 int ch;
499
500 optind = optreset = 1;
501 opterr = 0;
502
503 *hflag = *iflag = 0;
504 while ((ch = getopt(argc, argv, "hi")) != -1) {
505 switch (ch) {
506 case 'h':
507 *hflag = 1;
508 break;
509 case 'i':
510 *iflag = 1;
511 break;
512 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000513 error("%s: Invalid flag -%c", cmd, optopt);
Damien Millerd671e5a2008-05-19 14:53:33 +1000514 return -1;
515 }
516 }
517
518 return optind;
519}
520
521static int
Damien Miller036d3072013-08-21 02:41:46 +1000522parse_no_flags(const char *cmd, char **argv, int argc)
523{
524 extern int opterr, optind, optopt, optreset;
525 int ch;
526
527 optind = optreset = 1;
528 opterr = 0;
529
530 while ((ch = getopt(argc, argv, "")) != -1) {
531 switch (ch) {
532 default:
533 error("%s: Invalid flag -%c", cmd, optopt);
534 return -1;
535 }
536 }
537
538 return optind;
539}
540
541static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100542is_dir(char *path)
543{
544 struct stat sb;
545
546 /* XXX: report errors? */
547 if (stat(path, &sb) == -1)
548 return(0);
549
Darren Tucker1e80e402006-09-21 12:59:33 +1000550 return(S_ISDIR(sb.st_mode));
Damien Miller20e1fab2004-02-18 14:30:55 +1100551}
552
553static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100554remote_is_dir(struct sftp_conn *conn, char *path)
555{
556 Attrib *a;
557
558 /* XXX: report errors? */
559 if ((a = do_stat(conn, path, 1)) == NULL)
560 return(0);
561 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
562 return(0);
Darren Tucker1e80e402006-09-21 12:59:33 +1000563 return(S_ISDIR(a->perm));
Damien Miller20e1fab2004-02-18 14:30:55 +1100564}
565
Darren Tucker1b0dd172009-10-07 08:37:48 +1100566/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
Damien Miller20e1fab2004-02-18 14:30:55 +1100567static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100568pathname_is_dir(char *pathname)
569{
570 size_t l = strlen(pathname);
571
572 return l > 0 && pathname[l - 1] == '/';
573}
574
575static int
576process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
Damien Miller0d032412013-07-25 11:56:52 +1000577 int pflag, int rflag, int resume)
Damien Miller20e1fab2004-02-18 14:30:55 +1100578{
579 char *abs_src = NULL;
580 char *abs_dst = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100581 glob_t g;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100582 char *filename, *tmp=NULL;
583 int i, err = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100584
585 abs_src = xstrdup(src);
586 abs_src = make_absolute(abs_src, pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +1100587 memset(&g, 0, sizeof(g));
Darren Tucker1b0dd172009-10-07 08:37:48 +1100588
Damien Miller20e1fab2004-02-18 14:30:55 +1100589 debug3("Looking up %s", abs_src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100590 if (remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100591 error("File \"%s\" not found.", abs_src);
592 err = -1;
593 goto out;
594 }
595
Darren Tucker1b0dd172009-10-07 08:37:48 +1100596 /*
597 * If multiple matches then dst must be a directory or
598 * unspecified.
599 */
600 if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
601 error("Multiple source paths, but destination "
602 "\"%s\" is not a directory", dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100603 err = -1;
604 goto out;
605 }
606
Darren Tuckercdf547a2004-05-24 10:12:19 +1000607 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100608 tmp = xstrdup(g.gl_pathv[i]);
609 if ((filename = basename(tmp)) == NULL) {
610 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000611 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100612 err = -1;
613 goto out;
614 }
615
616 if (g.gl_matchc == 1 && dst) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100617 if (is_dir(dst)) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100618 abs_dst = path_append(dst, filename);
619 } else {
Damien Miller20e1fab2004-02-18 14:30:55 +1100620 abs_dst = xstrdup(dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100621 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100622 } else if (dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100623 abs_dst = path_append(dst, filename);
624 } else {
625 abs_dst = xstrdup(filename);
626 }
Darren Tuckera627d422013-06-02 07:31:17 +1000627 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100628
Damien Miller0d032412013-07-25 11:56:52 +1000629 resume |= global_aflag;
630 if (!quiet && resume)
631 printf("Resuming %s to %s\n", g.gl_pathv[i], abs_dst);
632 else if (!quiet && !resume)
Damien Miller9303e652013-04-23 15:22:40 +1000633 printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100634 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
Damien Miller0d032412013-07-25 11:56:52 +1000635 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
636 pflag || global_pflag, 1, resume) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100637 err = -1;
638 } else {
639 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
Damien Miller0d032412013-07-25 11:56:52 +1000640 pflag || global_pflag, resume) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100641 err = -1;
642 }
Darren Tuckera627d422013-06-02 07:31:17 +1000643 free(abs_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100644 abs_dst = NULL;
645 }
646
647out:
Darren Tuckera627d422013-06-02 07:31:17 +1000648 free(abs_src);
Damien Miller20e1fab2004-02-18 14:30:55 +1100649 globfree(&g);
650 return(err);
651}
652
653static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100654process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
655 int pflag, int rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100656{
657 char *tmp_dst = NULL;
658 char *abs_dst = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100659 char *tmp = NULL, *filename = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100660 glob_t g;
661 int err = 0;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100662 int i, dst_is_dir = 1;
Damien Milleraec5cf82008-02-10 22:26:24 +1100663 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100664
665 if (dst) {
666 tmp_dst = xstrdup(dst);
667 tmp_dst = make_absolute(tmp_dst, pwd);
668 }
669
670 memset(&g, 0, sizeof(g));
671 debug3("Looking up %s", src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100672 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100673 error("File \"%s\" not found.", src);
674 err = -1;
675 goto out;
676 }
677
Darren Tucker1b0dd172009-10-07 08:37:48 +1100678 /* If we aren't fetching to pwd then stash this status for later */
679 if (tmp_dst != NULL)
680 dst_is_dir = remote_is_dir(conn, tmp_dst);
681
Damien Miller20e1fab2004-02-18 14:30:55 +1100682 /* If multiple matches, dst may be directory or unspecified */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100683 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
684 error("Multiple paths match, but destination "
685 "\"%s\" is not a directory", tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100686 err = -1;
687 goto out;
688 }
689
Darren Tuckercdf547a2004-05-24 10:12:19 +1000690 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Milleraec5cf82008-02-10 22:26:24 +1100691 if (stat(g.gl_pathv[i], &sb) == -1) {
692 err = -1;
693 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
694 continue;
695 }
Damien Miller02e87802013-08-21 02:38:51 +1000696
Darren Tucker1b0dd172009-10-07 08:37:48 +1100697 tmp = xstrdup(g.gl_pathv[i]);
698 if ((filename = basename(tmp)) == NULL) {
699 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000700 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100701 err = -1;
702 goto out;
703 }
704
705 if (g.gl_matchc == 1 && tmp_dst) {
706 /* If directory specified, append filename */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100707 if (dst_is_dir)
708 abs_dst = path_append(tmp_dst, filename);
709 else
Damien Miller20e1fab2004-02-18 14:30:55 +1100710 abs_dst = xstrdup(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100711 } else if (tmp_dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100712 abs_dst = path_append(tmp_dst, filename);
713 } else {
714 abs_dst = make_absolute(xstrdup(filename), pwd);
715 }
Darren Tuckera627d422013-06-02 07:31:17 +1000716 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100717
Damien Miller9303e652013-04-23 15:22:40 +1000718 if (!quiet)
719 printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100720 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
721 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
722 pflag || global_pflag, 1) == -1)
723 err = -1;
724 } else {
725 if (do_upload(conn, g.gl_pathv[i], abs_dst,
726 pflag || global_pflag) == -1)
727 err = -1;
728 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100729 }
730
731out:
Darren Tuckera627d422013-06-02 07:31:17 +1000732 free(abs_dst);
733 free(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100734 globfree(&g);
735 return(err);
736}
737
738static int
739sdirent_comp(const void *aa, const void *bb)
740{
741 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
742 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000743 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100744
Darren Tuckerb9123452004-06-22 13:06:45 +1000745#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000746 if (sort_flag & LS_NAME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000747 return (rmul * strcmp(a->filename, b->filename));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000748 else if (sort_flag & LS_TIME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000749 return (rmul * NCMP(a->a.mtime, b->a.mtime));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000750 else if (sort_flag & LS_SIZE_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000751 return (rmul * NCMP(a->a.size, b->a.size));
752
753 fatal("Unknown ls sort type");
Damien Miller20e1fab2004-02-18 14:30:55 +1100754}
755
756/* sftp ls.1 replacement for directories */
757static int
758do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
759{
Damien Millereccb9de2005-06-17 12:59:34 +1000760 int n;
761 u_int c = 1, colspace = 0, columns = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100762 SFTP_DIRENT **d;
763
764 if ((n = do_readdir(conn, path, &d)) != 0)
765 return (n);
766
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000767 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000768 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100769 struct winsize ws;
770 char *tmp;
771
772 /* Count entries for sort and find longest filename */
Darren Tucker9a526452004-06-22 13:09:55 +1000773 for (n = 0; d[n] != NULL; n++) {
774 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
775 m = MAX(m, strlen(d[n]->filename));
776 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100777
778 /* Add any subpath that also needs to be counted */
779 tmp = path_strip(path, strip_path);
780 m += strlen(tmp);
Darren Tuckera627d422013-06-02 07:31:17 +1000781 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100782
783 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
784 width = ws.ws_col;
785
786 columns = width / (m + 2);
787 columns = MAX(columns, 1);
788 colspace = width / columns;
789 colspace = MIN(colspace, width);
790 }
791
Darren Tuckerb9123452004-06-22 13:06:45 +1000792 if (lflag & SORT_FLAGS) {
Damien Miller653b93b2005-11-05 15:15:23 +1100793 for (n = 0; d[n] != NULL; n++)
794 ; /* count entries */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000795 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
Darren Tuckerb9123452004-06-22 13:06:45 +1000796 qsort(d, n, sizeof(*d), sdirent_comp);
797 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100798
Darren Tuckercdf547a2004-05-24 10:12:19 +1000799 for (n = 0; d[n] != NULL && !interrupted; n++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100800 char *tmp, *fname;
801
Darren Tucker9a526452004-06-22 13:09:55 +1000802 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
803 continue;
804
Damien Miller20e1fab2004-02-18 14:30:55 +1100805 tmp = path_append(path, d[n]->filename);
806 fname = path_strip(tmp, strip_path);
Darren Tuckera627d422013-06-02 07:31:17 +1000807 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100808
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000809 if (lflag & LS_LONG_VIEW) {
Darren Tucker2901e2d2010-01-13 22:44:06 +1100810 if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000811 char *lname;
812 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100813
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000814 memset(&sb, 0, sizeof(sb));
815 attrib_to_stat(&d[n]->a, &sb);
Darren Tucker2901e2d2010-01-13 22:44:06 +1100816 lname = ls_file(fname, &sb, 1,
817 (lflag & LS_SI_UNITS));
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000818 printf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000819 free(lname);
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000820 } else
821 printf("%s\n", d[n]->longname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100822 } else {
823 printf("%-*s", colspace, fname);
824 if (c >= columns) {
825 printf("\n");
826 c = 1;
827 } else
828 c++;
829 }
830
Darren Tuckera627d422013-06-02 07:31:17 +1000831 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100832 }
833
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000834 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100835 printf("\n");
836
837 free_sftp_dirents(d);
838 return (0);
839}
840
841/* sftp ls.1 replacement which handles path globs */
842static int
843do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
844 int lflag)
845{
Damien Millera6e121a2010-10-07 21:39:17 +1100846 char *fname, *lname;
Damien Miller68e2e562010-10-07 21:39:55 +1100847 glob_t g;
848 int err;
849 struct winsize ws;
850 u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100851
852 memset(&g, 0, sizeof(g));
853
Damien Millera6e121a2010-10-07 21:39:17 +1100854 if (remote_glob(conn, path,
Damien Millerd7be70d2011-09-22 21:43:06 +1000855 GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
856 NULL, &g) ||
Damien Millera6e121a2010-10-07 21:39:17 +1100857 (g.gl_pathc && !g.gl_matchc)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100858 if (g.gl_pathc)
859 globfree(&g);
Damien Miller20e1fab2004-02-18 14:30:55 +1100860 error("Can't ls: \"%s\" not found", path);
Damien Millera6e121a2010-10-07 21:39:17 +1100861 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100862 }
863
Darren Tuckercdf547a2004-05-24 10:12:19 +1000864 if (interrupted)
865 goto out;
866
Damien Miller20e1fab2004-02-18 14:30:55 +1100867 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100868 * If the glob returns a single match and it is a directory,
869 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100870 */
Damien Millera6e121a2010-10-07 21:39:17 +1100871 if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
872 S_ISDIR(g.gl_statv[0]->st_mode)) {
873 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
874 globfree(&g);
875 return err;
Damien Miller20e1fab2004-02-18 14:30:55 +1100876 }
877
Damien Miller68e2e562010-10-07 21:39:55 +1100878 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
879 width = ws.ws_col;
Damien Miller20e1fab2004-02-18 14:30:55 +1100880
Damien Miller68e2e562010-10-07 21:39:55 +1100881 if (!(lflag & LS_SHORT_VIEW)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100882 /* Count entries for sort and find longest filename */
883 for (i = 0; g.gl_pathv[i]; i++)
884 m = MAX(m, strlen(g.gl_pathv[i]));
885
Damien Miller20e1fab2004-02-18 14:30:55 +1100886 columns = width / (m + 2);
887 columns = MAX(columns, 1);
888 colspace = width / columns;
889 }
890
Damien Millerea858292012-06-30 08:33:32 +1000891 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100892 fname = path_strip(g.gl_pathv[i], strip_path);
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000893 if (lflag & LS_LONG_VIEW) {
Damien Millera6e121a2010-10-07 21:39:17 +1100894 if (g.gl_statv[i] == NULL) {
895 error("no stat information for %s", fname);
896 continue;
897 }
898 lname = ls_file(fname, g.gl_statv[i], 1,
899 (lflag & LS_SI_UNITS));
Damien Miller20e1fab2004-02-18 14:30:55 +1100900 printf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000901 free(lname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100902 } else {
903 printf("%-*s", colspace, fname);
904 if (c >= columns) {
905 printf("\n");
906 c = 1;
907 } else
908 c++;
909 }
Darren Tuckera627d422013-06-02 07:31:17 +1000910 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100911 }
912
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000913 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100914 printf("\n");
915
Darren Tuckercdf547a2004-05-24 10:12:19 +1000916 out:
Damien Miller20e1fab2004-02-18 14:30:55 +1100917 if (g.gl_pathc)
918 globfree(&g);
919
Damien Millera6e121a2010-10-07 21:39:17 +1100920 return 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100921}
922
Damien Millerd671e5a2008-05-19 14:53:33 +1000923static int
924do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
925{
Darren Tucker7b598892008-06-09 22:49:36 +1000926 struct sftp_statvfs st;
Damien Millerd671e5a2008-05-19 14:53:33 +1000927 char s_used[FMT_SCALED_STRSIZE];
928 char s_avail[FMT_SCALED_STRSIZE];
929 char s_root[FMT_SCALED_STRSIZE];
930 char s_total[FMT_SCALED_STRSIZE];
Darren Tuckerb5082e92010-01-08 18:51:47 +1100931 unsigned long long ffree;
Damien Millerd671e5a2008-05-19 14:53:33 +1000932
933 if (do_statvfs(conn, path, &st, 1) == -1)
934 return -1;
935 if (iflag) {
Darren Tuckerb5082e92010-01-08 18:51:47 +1100936 ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
Damien Millerd671e5a2008-05-19 14:53:33 +1000937 printf(" Inodes Used Avail "
938 "(root) %%Capacity\n");
939 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
940 (unsigned long long)st.f_files,
941 (unsigned long long)(st.f_files - st.f_ffree),
942 (unsigned long long)st.f_favail,
Darren Tuckerb5082e92010-01-08 18:51:47 +1100943 (unsigned long long)st.f_ffree, ffree);
Damien Millerd671e5a2008-05-19 14:53:33 +1000944 } else if (hflag) {
945 strlcpy(s_used, "error", sizeof(s_used));
946 strlcpy(s_avail, "error", sizeof(s_avail));
947 strlcpy(s_root, "error", sizeof(s_root));
948 strlcpy(s_total, "error", sizeof(s_total));
949 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
950 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
951 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
952 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
953 printf(" Size Used Avail (root) %%Capacity\n");
954 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
955 s_total, s_used, s_avail, s_root,
956 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
957 st.f_blocks));
958 } else {
959 printf(" Size Used Avail "
960 "(root) %%Capacity\n");
961 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
962 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
963 (unsigned long long)(st.f_frsize *
964 (st.f_blocks - st.f_bfree) / 1024),
965 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
966 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
967 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
968 st.f_blocks));
969 }
970 return 0;
971}
972
Damien Miller1cbc2922007-10-26 14:27:45 +1000973/*
974 * Undo escaping of glob sequences in place. Used to undo extra escaping
975 * applied in makeargv() when the string is destined for a function that
976 * does not glob it.
977 */
978static void
979undo_glob_escape(char *s)
980{
981 size_t i, j;
982
983 for (i = j = 0;;) {
984 if (s[i] == '\0') {
985 s[j] = '\0';
986 return;
987 }
988 if (s[i] != '\\') {
989 s[j++] = s[i++];
990 continue;
991 }
992 /* s[i] == '\\' */
993 ++i;
994 switch (s[i]) {
995 case '?':
996 case '[':
997 case '*':
998 case '\\':
999 s[j++] = s[i++];
1000 break;
1001 case '\0':
1002 s[j++] = '\\';
1003 s[j] = '\0';
1004 return;
1005 default:
1006 s[j++] = '\\';
1007 s[j++] = s[i++];
1008 break;
1009 }
1010 }
1011}
1012
1013/*
1014 * Split a string into an argument vector using sh(1)-style quoting,
1015 * comment and escaping rules, but with some tweaks to handle glob(3)
1016 * wildcards.
Darren Tucker909d8582010-01-08 19:02:40 +11001017 * The "sloppy" flag allows for recovery from missing terminating quote, for
1018 * use in parsing incomplete commandlines during tab autocompletion.
1019 *
Damien Miller1cbc2922007-10-26 14:27:45 +10001020 * Returns NULL on error or a NULL-terminated array of arguments.
Darren Tucker909d8582010-01-08 19:02:40 +11001021 *
1022 * If "lastquote" is not NULL, the quoting character used for the last
1023 * argument is placed in *lastquote ("\0", "'" or "\"").
Damien Miller02e87802013-08-21 02:38:51 +10001024 *
Darren Tucker909d8582010-01-08 19:02:40 +11001025 * If "terminated" is not NULL, *terminated will be set to 1 when the
1026 * last argument's quote has been properly terminated or 0 otherwise.
1027 * This parameter is only of use if "sloppy" is set.
Damien Miller1cbc2922007-10-26 14:27:45 +10001028 */
1029#define MAXARGS 128
1030#define MAXARGLEN 8192
1031static char **
Darren Tucker909d8582010-01-08 19:02:40 +11001032makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
1033 u_int *terminated)
Damien Miller1cbc2922007-10-26 14:27:45 +10001034{
1035 int argc, quot;
1036 size_t i, j;
1037 static char argvs[MAXARGLEN];
1038 static char *argv[MAXARGS + 1];
1039 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
1040
1041 *argcp = argc = 0;
1042 if (strlen(arg) > sizeof(argvs) - 1) {
1043 args_too_longs:
1044 error("string too long");
1045 return NULL;
1046 }
Darren Tucker909d8582010-01-08 19:02:40 +11001047 if (terminated != NULL)
1048 *terminated = 1;
1049 if (lastquote != NULL)
1050 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001051 state = MA_START;
1052 i = j = 0;
1053 for (;;) {
Damien Miller07daed52012-10-31 08:57:55 +11001054 if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){
Darren Tucker063018d2012-10-05 10:43:58 +10001055 error("Too many arguments.");
1056 return NULL;
1057 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001058 if (isspace(arg[i])) {
1059 if (state == MA_UNQUOTED) {
1060 /* Terminate current argument */
1061 argvs[j++] = '\0';
1062 argc++;
1063 state = MA_START;
1064 } else if (state != MA_START)
1065 argvs[j++] = arg[i];
1066 } else if (arg[i] == '"' || arg[i] == '\'') {
1067 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1068 if (state == MA_START) {
1069 argv[argc] = argvs + j;
1070 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +11001071 if (lastquote != NULL)
1072 *lastquote = arg[i];
Damien Miller02e87802013-08-21 02:38:51 +10001073 } else if (state == MA_UNQUOTED)
Damien Miller1cbc2922007-10-26 14:27:45 +10001074 state = q;
1075 else if (state == q)
1076 state = MA_UNQUOTED;
1077 else
1078 argvs[j++] = arg[i];
1079 } else if (arg[i] == '\\') {
1080 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1081 quot = state == MA_SQUOTE ? '\'' : '"';
1082 /* Unescape quote we are in */
1083 /* XXX support \n and friends? */
1084 if (arg[i + 1] == quot) {
1085 i++;
1086 argvs[j++] = arg[i];
1087 } else if (arg[i + 1] == '?' ||
1088 arg[i + 1] == '[' || arg[i + 1] == '*') {
1089 /*
1090 * Special case for sftp: append
1091 * double-escaped glob sequence -
1092 * glob will undo one level of
1093 * escaping. NB. string can grow here.
1094 */
1095 if (j >= sizeof(argvs) - 5)
1096 goto args_too_longs;
1097 argvs[j++] = '\\';
1098 argvs[j++] = arg[i++];
1099 argvs[j++] = '\\';
1100 argvs[j++] = arg[i];
1101 } else {
1102 argvs[j++] = arg[i++];
1103 argvs[j++] = arg[i];
1104 }
1105 } else {
1106 if (state == MA_START) {
1107 argv[argc] = argvs + j;
1108 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001109 if (lastquote != NULL)
1110 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001111 }
1112 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1113 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1114 /*
1115 * Special case for sftp: append
1116 * escaped glob sequence -
1117 * glob will undo one level of
1118 * escaping.
1119 */
1120 argvs[j++] = arg[i++];
1121 argvs[j++] = arg[i];
1122 } else {
1123 /* Unescape everything */
1124 /* XXX support \n and friends? */
1125 i++;
1126 argvs[j++] = arg[i];
1127 }
1128 }
1129 } else if (arg[i] == '#') {
1130 if (state == MA_SQUOTE || state == MA_DQUOTE)
1131 argvs[j++] = arg[i];
1132 else
1133 goto string_done;
1134 } else if (arg[i] == '\0') {
1135 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001136 if (sloppy) {
1137 state = MA_UNQUOTED;
1138 if (terminated != NULL)
1139 *terminated = 0;
1140 goto string_done;
1141 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001142 error("Unterminated quoted argument");
1143 return NULL;
1144 }
1145 string_done:
1146 if (state == MA_UNQUOTED) {
1147 argvs[j++] = '\0';
1148 argc++;
1149 }
1150 break;
1151 } else {
1152 if (state == MA_START) {
1153 argv[argc] = argvs + j;
1154 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001155 if (lastquote != NULL)
1156 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001157 }
1158 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1159 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1160 /*
1161 * Special case for sftp: escape quoted
1162 * glob(3) wildcards. NB. string can grow
1163 * here.
1164 */
1165 if (j >= sizeof(argvs) - 3)
1166 goto args_too_longs;
1167 argvs[j++] = '\\';
1168 argvs[j++] = arg[i];
1169 } else
1170 argvs[j++] = arg[i];
1171 }
1172 i++;
1173 }
1174 *argcp = argc;
1175 return argv;
1176}
1177
Damien Miller20e1fab2004-02-18 14:30:55 +11001178static int
Damien Miller0d032412013-07-25 11:56:52 +10001179parse_args(const char **cpp, int *aflag, int *hflag, int *iflag, int *lflag,
1180 int *pflag, int *rflag, int *sflag, unsigned long *n_arg,
1181 char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001182{
1183 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001184 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001185 int base = 0;
1186 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001187 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001188
1189 /* Skip leading whitespace */
1190 cp = cp + strspn(cp, WHITESPACE);
1191
Damien Miller20e1fab2004-02-18 14:30:55 +11001192 /* Check for leading '-' (disable error processing) */
1193 *iflag = 0;
1194 if (*cp == '-') {
1195 *iflag = 1;
1196 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001197 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001198 }
1199
Darren Tucker70cc0922010-01-09 22:28:03 +11001200 /* Ignore blank lines and lines which begin with comment '#' char */
1201 if (*cp == '\0' || *cp == '#')
1202 return (0);
1203
Darren Tucker909d8582010-01-08 19:02:40 +11001204 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001205 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001206
Damien Miller1cbc2922007-10-26 14:27:45 +10001207 /* Figure out which command we have */
1208 for (i = 0; cmds[i].c != NULL; i++) {
Damien Millerd6d9fa02013-02-12 11:02:46 +11001209 if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001210 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001211 }
1212 cmdnum = cmds[i].n;
1213 cmd = cmds[i].c;
1214
1215 /* Special case */
1216 if (*cp == '!') {
1217 cp++;
1218 cmdnum = I_SHELL;
1219 } else if (cmdnum == -1) {
1220 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001221 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001222 }
1223
1224 /* Get arguments and parse flags */
Damien Miller0d032412013-07-25 11:56:52 +10001225 *aflag = *lflag = *pflag = *rflag = *hflag = *n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001226 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001227 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001228 switch (cmdnum) {
1229 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001230 case I_REGET:
Damien Miller20e1fab2004-02-18 14:30:55 +11001231 case I_PUT:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001232 if ((optidx = parse_getput_flags(cmd, argv, argc,
Damien Miller0d032412013-07-25 11:56:52 +10001233 aflag, pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001234 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001235 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001236 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001237 error("You must specify at least one path after a "
1238 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001239 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001240 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001241 *path1 = xstrdup(argv[optidx]);
1242 /* Get second pathname (optional) */
1243 if (argc - optidx > 1) {
1244 *path2 = xstrdup(argv[optidx + 1]);
1245 /* Destination is not globbed */
1246 undo_glob_escape(*path2);
1247 }
Damien Miller0d032412013-07-25 11:56:52 +10001248 if (*aflag && cmdnum == I_PUT) {
1249 /* XXX implement resume for uploads */
1250 error("Resume is not supported for uploads");
1251 return -1;
1252 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001253 break;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001254 case I_LINK:
1255 if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1256 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001257 goto parse_two_paths;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001258 case I_RENAME:
Damien Millerc7dba122013-08-21 02:41:15 +10001259 if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1)
1260 return -1;
1261 goto parse_two_paths;
1262 case I_SYMLINK:
Damien Miller036d3072013-08-21 02:41:46 +10001263 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1264 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001265 parse_two_paths:
Damien Miller1cbc2922007-10-26 14:27:45 +10001266 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001267 error("You must specify two paths after a %s "
1268 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001269 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001270 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001271 *path1 = xstrdup(argv[optidx]);
1272 *path2 = xstrdup(argv[optidx + 1]);
1273 /* Paths are not globbed */
1274 undo_glob_escape(*path1);
1275 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001276 break;
1277 case I_RM:
1278 case I_MKDIR:
1279 case I_RMDIR:
1280 case I_CHDIR:
1281 case I_LCHDIR:
1282 case I_LMKDIR:
Damien Miller036d3072013-08-21 02:41:46 +10001283 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1284 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001285 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001286 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001287 error("You must specify a path after a %s command.",
1288 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001289 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001290 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001291 *path1 = xstrdup(argv[optidx]);
1292 /* Only "rm" globs */
1293 if (cmdnum != I_RM)
1294 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001295 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001296 case I_DF:
1297 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1298 iflag)) == -1)
1299 return -1;
1300 /* Default to current directory if no path specified */
1301 if (argc - optidx < 1)
1302 *path1 = NULL;
1303 else {
1304 *path1 = xstrdup(argv[optidx]);
1305 undo_glob_escape(*path1);
1306 }
1307 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001308 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001309 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001310 return(-1);
1311 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001312 if (argc - optidx > 0)
1313 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001314 break;
1315 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001316 /* Skip ls command and following whitespace */
1317 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001318 case I_SHELL:
1319 /* Uses the rest of the line */
1320 break;
1321 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001322 case I_CHMOD:
1323 base = 8;
1324 case I_CHOWN:
1325 case I_CHGRP:
Damien Miller036d3072013-08-21 02:41:46 +10001326 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1327 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001328 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001329 if (argc - optidx < 1)
1330 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001331 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001332 l = strtol(argv[optidx], &cp2, base);
1333 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1334 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1335 l < 0) {
1336 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001337 error("You must supply a numeric argument "
1338 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001339 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001340 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001341 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001342 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001343 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001344 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001345 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001346 error("You must specify a path after a %s command.",
1347 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001348 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001349 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001350 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001351 break;
1352 case I_QUIT:
1353 case I_PWD:
1354 case I_LPWD:
1355 case I_HELP:
1356 case I_VERSION:
1357 case I_PROGRESS:
Damien Miller036d3072013-08-21 02:41:46 +10001358 if ((optidx = parse_no_flags(cmd, argv, argc)) == -1)
1359 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001360 break;
1361 default:
1362 fatal("Command not implemented");
1363 }
1364
1365 *cpp = cp;
1366 return(cmdnum);
1367}
1368
1369static int
1370parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1371 int err_abort)
1372{
1373 char *path1, *path2, *tmp;
Damien Miller0d032412013-07-25 11:56:52 +10001374 int aflag = 0, hflag = 0, iflag = 0, lflag = 0, pflag = 0;
1375 int rflag = 0, sflag = 0;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001376 int cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001377 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001378 Attrib a, *aa;
1379 char path_buf[MAXPATHLEN];
1380 int err = 0;
1381 glob_t g;
1382
1383 path1 = path2 = NULL;
Damien Miller0d032412013-07-25 11:56:52 +10001384 cmdnum = parse_args(&cmd, &aflag, &hflag, &iflag, &lflag, &pflag,
1385 &rflag, &sflag, &n_arg, &path1, &path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001386 if (iflag != 0)
1387 err_abort = 0;
1388
1389 memset(&g, 0, sizeof(g));
1390
1391 /* Perform command */
1392 switch (cmdnum) {
1393 case 0:
1394 /* Blank line */
1395 break;
1396 case -1:
1397 /* Unrecognized command */
1398 err = -1;
1399 break;
Damien Miller0d032412013-07-25 11:56:52 +10001400 case I_REGET:
1401 aflag = 1;
1402 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001403 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001404 err = process_get(conn, path1, path2, *pwd, pflag,
1405 rflag, aflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001406 break;
1407 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001408 err = process_put(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001409 break;
1410 case I_RENAME:
1411 path1 = make_absolute(path1, *pwd);
1412 path2 = make_absolute(path2, *pwd);
Damien Millerc7dba122013-08-21 02:41:15 +10001413 err = do_rename(conn, path1, path2, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001414 break;
1415 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001416 sflag = 1;
1417 case I_LINK:
Damien Miller034f27a2013-08-21 02:40:44 +10001418 if (!sflag)
1419 path1 = make_absolute(path1, *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001420 path2 = make_absolute(path2, *pwd);
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001421 err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001422 break;
1423 case I_RM:
1424 path1 = make_absolute(path1, *pwd);
1425 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001426 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001427 if (!quiet)
1428 printf("Removing %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001429 err = do_rm(conn, g.gl_pathv[i]);
1430 if (err != 0 && err_abort)
1431 break;
1432 }
1433 break;
1434 case I_MKDIR:
1435 path1 = make_absolute(path1, *pwd);
1436 attrib_clear(&a);
1437 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1438 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001439 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001440 break;
1441 case I_RMDIR:
1442 path1 = make_absolute(path1, *pwd);
1443 err = do_rmdir(conn, path1);
1444 break;
1445 case I_CHDIR:
1446 path1 = make_absolute(path1, *pwd);
1447 if ((tmp = do_realpath(conn, path1)) == NULL) {
1448 err = 1;
1449 break;
1450 }
1451 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
Darren Tuckera627d422013-06-02 07:31:17 +10001452 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001453 err = 1;
1454 break;
1455 }
1456 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1457 error("Can't change directory: Can't check target");
Darren Tuckera627d422013-06-02 07:31:17 +10001458 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001459 err = 1;
1460 break;
1461 }
1462 if (!S_ISDIR(aa->perm)) {
1463 error("Can't change directory: \"%s\" is not "
1464 "a directory", tmp);
Darren Tuckera627d422013-06-02 07:31:17 +10001465 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001466 err = 1;
1467 break;
1468 }
Darren Tuckera627d422013-06-02 07:31:17 +10001469 free(*pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001470 *pwd = tmp;
1471 break;
1472 case I_LS:
1473 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001474 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001475 break;
1476 }
1477
1478 /* Strip pwd off beginning of non-absolute paths */
1479 tmp = NULL;
1480 if (*path1 != '/')
1481 tmp = *pwd;
1482
1483 path1 = make_absolute(path1, *pwd);
1484 err = do_globbed_ls(conn, path1, tmp, lflag);
1485 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001486 case I_DF:
1487 /* Default to current directory if no path specified */
1488 if (path1 == NULL)
1489 path1 = xstrdup(*pwd);
1490 path1 = make_absolute(path1, *pwd);
1491 err = do_df(conn, path1, hflag, iflag);
1492 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001493 case I_LCHDIR:
1494 if (chdir(path1) == -1) {
1495 error("Couldn't change local directory to "
1496 "\"%s\": %s", path1, strerror(errno));
1497 err = 1;
1498 }
1499 break;
1500 case I_LMKDIR:
1501 if (mkdir(path1, 0777) == -1) {
1502 error("Couldn't create local directory "
1503 "\"%s\": %s", path1, strerror(errno));
1504 err = 1;
1505 }
1506 break;
1507 case I_LLS:
1508 local_do_ls(cmd);
1509 break;
1510 case I_SHELL:
1511 local_do_shell(cmd);
1512 break;
1513 case I_LUMASK:
1514 umask(n_arg);
1515 printf("Local umask: %03lo\n", n_arg);
1516 break;
1517 case I_CHMOD:
1518 path1 = make_absolute(path1, *pwd);
1519 attrib_clear(&a);
1520 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1521 a.perm = n_arg;
1522 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001523 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001524 if (!quiet)
1525 printf("Changing mode on %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001526 err = do_setstat(conn, g.gl_pathv[i], &a);
1527 if (err != 0 && err_abort)
1528 break;
1529 }
1530 break;
1531 case I_CHOWN:
1532 case I_CHGRP:
1533 path1 = make_absolute(path1, *pwd);
1534 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001535 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001536 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001537 if (err_abort) {
1538 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001539 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001540 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001541 continue;
1542 }
1543 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1544 error("Can't get current ownership of "
1545 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001546 if (err_abort) {
1547 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001548 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001549 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001550 continue;
1551 }
1552 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1553 if (cmdnum == I_CHOWN) {
Damien Miller9303e652013-04-23 15:22:40 +10001554 if (!quiet)
1555 printf("Changing owner on %s\n",
1556 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001557 aa->uid = n_arg;
1558 } else {
Damien Miller9303e652013-04-23 15:22:40 +10001559 if (!quiet)
1560 printf("Changing group on %s\n",
1561 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001562 aa->gid = n_arg;
1563 }
1564 err = do_setstat(conn, g.gl_pathv[i], aa);
1565 if (err != 0 && err_abort)
1566 break;
1567 }
1568 break;
1569 case I_PWD:
1570 printf("Remote working directory: %s\n", *pwd);
1571 break;
1572 case I_LPWD:
1573 if (!getcwd(path_buf, sizeof(path_buf))) {
1574 error("Couldn't get local cwd: %s", strerror(errno));
1575 err = -1;
1576 break;
1577 }
1578 printf("Local working directory: %s\n", path_buf);
1579 break;
1580 case I_QUIT:
1581 /* Processed below */
1582 break;
1583 case I_HELP:
1584 help();
1585 break;
1586 case I_VERSION:
1587 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1588 break;
1589 case I_PROGRESS:
1590 showprogress = !showprogress;
1591 if (showprogress)
1592 printf("Progress meter enabled\n");
1593 else
1594 printf("Progress meter disabled\n");
1595 break;
1596 default:
1597 fatal("%d is not implemented", cmdnum);
1598 }
1599
1600 if (g.gl_pathc)
1601 globfree(&g);
Darren Tuckera627d422013-06-02 07:31:17 +10001602 free(path1);
1603 free(path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001604
1605 /* If an unignored error occurs in batch mode we should abort. */
1606 if (err_abort && err != 0)
1607 return (-1);
1608 else if (cmdnum == I_QUIT)
1609 return (1);
1610
1611 return (0);
1612}
1613
Darren Tucker2d963d82004-11-07 20:04:10 +11001614#ifdef USE_LIBEDIT
1615static char *
1616prompt(EditLine *el)
1617{
1618 return ("sftp> ");
1619}
Darren Tucker2d963d82004-11-07 20:04:10 +11001620
Darren Tucker909d8582010-01-08 19:02:40 +11001621/* Display entries in 'list' after skipping the first 'len' chars */
1622static void
1623complete_display(char **list, u_int len)
1624{
1625 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1626 struct winsize ws;
1627 char *tmp;
1628
1629 /* Count entries for sort and find longest */
Damien Miller02e87802013-08-21 02:38:51 +10001630 for (y = 0; list[y]; y++)
Darren Tucker909d8582010-01-08 19:02:40 +11001631 m = MAX(m, strlen(list[y]));
1632
1633 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1634 width = ws.ws_col;
1635
1636 m = m > len ? m - len : 0;
1637 columns = width / (m + 2);
1638 columns = MAX(columns, 1);
1639 colspace = width / columns;
1640 colspace = MIN(colspace, width);
1641
1642 printf("\n");
1643 m = 1;
1644 for (y = 0; list[y]; y++) {
1645 llen = strlen(list[y]);
1646 tmp = llen > len ? list[y] + len : "";
1647 printf("%-*s", colspace, tmp);
1648 if (m >= columns) {
1649 printf("\n");
1650 m = 1;
1651 } else
1652 m++;
1653 }
1654 printf("\n");
1655}
1656
1657/*
1658 * Given a "list" of words that begin with a common prefix of "word",
1659 * attempt to find an autocompletion to extends "word" by the next
1660 * characters common to all entries in "list".
1661 */
1662static char *
1663complete_ambiguous(const char *word, char **list, size_t count)
1664{
1665 if (word == NULL)
1666 return NULL;
1667
1668 if (count > 0) {
1669 u_int y, matchlen = strlen(list[0]);
1670
1671 /* Find length of common stem */
1672 for (y = 1; list[y]; y++) {
1673 u_int x;
1674
Damien Miller02e87802013-08-21 02:38:51 +10001675 for (x = 0; x < matchlen; x++)
1676 if (list[0][x] != list[y][x])
Darren Tucker909d8582010-01-08 19:02:40 +11001677 break;
1678
1679 matchlen = x;
1680 }
1681
1682 if (matchlen > strlen(word)) {
1683 char *tmp = xstrdup(list[0]);
1684
Darren Tucker340d1682010-01-09 08:54:31 +11001685 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001686 return tmp;
1687 }
Damien Miller02e87802013-08-21 02:38:51 +10001688 }
Darren Tucker909d8582010-01-08 19:02:40 +11001689
1690 return xstrdup(word);
1691}
1692
1693/* Autocomplete a sftp command */
1694static int
1695complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1696 int terminated)
1697{
1698 u_int y, count = 0, cmdlen, tmplen;
1699 char *tmp, **list, argterm[3];
1700 const LineInfo *lf;
1701
1702 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1703
1704 /* No command specified: display all available commands */
1705 if (cmd == NULL) {
1706 for (y = 0; cmds[y].c; y++)
1707 list[count++] = xstrdup(cmds[y].c);
Damien Miller02e87802013-08-21 02:38:51 +10001708
Darren Tucker909d8582010-01-08 19:02:40 +11001709 list[count] = NULL;
1710 complete_display(list, 0);
1711
Damien Miller02e87802013-08-21 02:38:51 +10001712 for (y = 0; list[y] != NULL; y++)
1713 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001714 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001715 return count;
1716 }
1717
1718 /* Prepare subset of commands that start with "cmd" */
1719 cmdlen = strlen(cmd);
1720 for (y = 0; cmds[y].c; y++) {
Damien Miller02e87802013-08-21 02:38:51 +10001721 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
Darren Tucker909d8582010-01-08 19:02:40 +11001722 list[count++] = xstrdup(cmds[y].c);
1723 }
1724 list[count] = NULL;
1725
Damien Miller47d81152011-11-25 13:53:48 +11001726 if (count == 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10001727 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001728 return 0;
Damien Miller47d81152011-11-25 13:53:48 +11001729 }
Darren Tucker909d8582010-01-08 19:02:40 +11001730
1731 /* Complete ambigious command */
1732 tmp = complete_ambiguous(cmd, list, count);
1733 if (count > 1)
1734 complete_display(list, 0);
1735
Damien Miller02e87802013-08-21 02:38:51 +10001736 for (y = 0; list[y]; y++)
1737 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001738 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001739
1740 if (tmp != NULL) {
1741 tmplen = strlen(tmp);
1742 cmdlen = strlen(cmd);
1743 /* If cmd may be extended then do so */
1744 if (tmplen > cmdlen)
1745 if (el_insertstr(el, tmp + cmdlen) == -1)
1746 fatal("el_insertstr failed.");
1747 lf = el_line(el);
1748 /* Terminate argument cleanly */
1749 if (count == 1) {
1750 y = 0;
1751 if (!terminated)
1752 argterm[y++] = quote;
1753 if (lastarg || *(lf->cursor) != ' ')
1754 argterm[y++] = ' ';
1755 argterm[y] = '\0';
1756 if (y > 0 && el_insertstr(el, argterm) == -1)
1757 fatal("el_insertstr failed.");
1758 }
Darren Tuckera627d422013-06-02 07:31:17 +10001759 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001760 }
1761
1762 return count;
1763}
1764
1765/*
1766 * Determine whether a particular sftp command's arguments (if any)
1767 * represent local or remote files.
1768 */
1769static int
1770complete_is_remote(char *cmd) {
1771 int i;
1772
1773 if (cmd == NULL)
1774 return -1;
1775
1776 for (i = 0; cmds[i].c; i++) {
Damien Miller02e87802013-08-21 02:38:51 +10001777 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
Darren Tucker909d8582010-01-08 19:02:40 +11001778 return cmds[i].t;
1779 }
1780
1781 return -1;
1782}
1783
1784/* Autocomplete a filename "file" */
1785static int
1786complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1787 char *file, int remote, int lastarg, char quote, int terminated)
1788{
1789 glob_t g;
Darren Tuckerea647212013-06-06 08:19:09 +10001790 char *tmp, *tmp2, ins[8];
Darren Tucker17146d32012-10-05 10:46:16 +10001791 u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
Darren Tuckerea647212013-06-06 08:19:09 +10001792 int clen;
Darren Tucker909d8582010-01-08 19:02:40 +11001793 const LineInfo *lf;
Damien Miller02e87802013-08-21 02:38:51 +10001794
Darren Tucker909d8582010-01-08 19:02:40 +11001795 /* Glob from "file" location */
1796 if (file == NULL)
1797 tmp = xstrdup("*");
1798 else
1799 xasprintf(&tmp, "%s*", file);
1800
Darren Tucker191fcc62012-10-05 10:45:01 +10001801 /* Check if the path is absolute. */
1802 isabs = tmp[0] == '/';
1803
Darren Tucker909d8582010-01-08 19:02:40 +11001804 memset(&g, 0, sizeof(g));
1805 if (remote != LOCAL) {
1806 tmp = make_absolute(tmp, remote_path);
1807 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001808 } else
Darren Tucker909d8582010-01-08 19:02:40 +11001809 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001810
Darren Tucker909d8582010-01-08 19:02:40 +11001811 /* Determine length of pwd so we can trim completion display */
1812 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1813 /* Terminate counting on first unescaped glob metacharacter */
1814 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1815 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1816 hadglob = 1;
1817 break;
1818 }
1819 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1820 tmplen++;
1821 if (tmp[tmplen] == '/')
1822 pwdlen = tmplen + 1; /* track last seen '/' */
1823 }
Darren Tuckera627d422013-06-02 07:31:17 +10001824 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001825
Damien Miller02e87802013-08-21 02:38:51 +10001826 if (g.gl_matchc == 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001827 goto out;
1828
1829 if (g.gl_matchc > 1)
1830 complete_display(g.gl_pathv, pwdlen);
1831
1832 tmp = NULL;
1833 /* Don't try to extend globs */
1834 if (file == NULL || hadglob)
1835 goto out;
1836
1837 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
Darren Tucker191fcc62012-10-05 10:45:01 +10001838 tmp = path_strip(tmp2, isabs ? NULL : remote_path);
Darren Tuckera627d422013-06-02 07:31:17 +10001839 free(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001840
1841 if (tmp == NULL)
1842 goto out;
1843
1844 tmplen = strlen(tmp);
1845 filelen = strlen(file);
1846
Darren Tucker17146d32012-10-05 10:46:16 +10001847 /* Count the number of escaped characters in the input string. */
1848 cesc = isesc = 0;
1849 for (i = 0; i < filelen; i++) {
1850 if (!isesc && file[i] == '\\' && i + 1 < filelen){
1851 isesc = 1;
1852 cesc++;
1853 } else
1854 isesc = 0;
1855 }
1856
1857 if (tmplen > (filelen - cesc)) {
1858 tmp2 = tmp + filelen - cesc;
Damien Miller02e87802013-08-21 02:38:51 +10001859 len = strlen(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001860 /* quote argument on way out */
Darren Tuckerea647212013-06-06 08:19:09 +10001861 for (i = 0; i < len; i += clen) {
1862 if ((clen = mblen(tmp2 + i, len - i)) < 0 ||
1863 (size_t)clen > sizeof(ins) - 2)
1864 fatal("invalid multibyte character");
Darren Tucker909d8582010-01-08 19:02:40 +11001865 ins[0] = '\\';
Darren Tuckerea647212013-06-06 08:19:09 +10001866 memcpy(ins + 1, tmp2 + i, clen);
1867 ins[clen + 1] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001868 switch (tmp2[i]) {
1869 case '\'':
1870 case '"':
1871 case '\\':
1872 case '\t':
Darren Tuckerd78739a2010-10-24 10:56:32 +11001873 case '[':
Darren Tucker909d8582010-01-08 19:02:40 +11001874 case ' ':
Darren Tucker17146d32012-10-05 10:46:16 +10001875 case '#':
1876 case '*':
Darren Tucker909d8582010-01-08 19:02:40 +11001877 if (quote == '\0' || tmp2[i] == quote) {
1878 if (el_insertstr(el, ins) == -1)
1879 fatal("el_insertstr "
1880 "failed.");
1881 break;
1882 }
1883 /* FALLTHROUGH */
1884 default:
1885 if (el_insertstr(el, ins + 1) == -1)
1886 fatal("el_insertstr failed.");
1887 break;
1888 }
1889 }
1890 }
1891
1892 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11001893 if (g.gl_matchc == 1) {
1894 i = 0;
1895 if (!terminated)
1896 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11001897 if (*(lf->cursor - 1) != '/' &&
1898 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11001899 ins[i++] = ' ';
1900 ins[i] = '\0';
1901 if (i > 0 && el_insertstr(el, ins) == -1)
1902 fatal("el_insertstr failed.");
1903 }
Darren Tuckera627d422013-06-02 07:31:17 +10001904 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001905
1906 out:
1907 globfree(&g);
1908 return g.gl_matchc;
1909}
1910
1911/* tab-completion hook function, called via libedit */
1912static unsigned char
1913complete(EditLine *el, int ch)
1914{
Damien Miller02e87802013-08-21 02:38:51 +10001915 char **argv, *line, quote;
Damien Miller746d1a62013-07-18 16:13:02 +10001916 int argc, carg;
1917 u_int cursor, len, terminated, ret = CC_ERROR;
Darren Tucker909d8582010-01-08 19:02:40 +11001918 const LineInfo *lf;
1919 struct complete_ctx *complete_ctx;
1920
1921 lf = el_line(el);
1922 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1923 fatal("%s: el_get failed", __func__);
1924
1925 /* Figure out which argument the cursor points to */
1926 cursor = lf->cursor - lf->buffer;
1927 line = (char *)xmalloc(cursor + 1);
1928 memcpy(line, lf->buffer, cursor);
1929 line[cursor] = '\0';
1930 argv = makeargv(line, &carg, 1, &quote, &terminated);
Darren Tuckera627d422013-06-02 07:31:17 +10001931 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001932
1933 /* Get all the arguments on the line */
1934 len = lf->lastchar - lf->buffer;
1935 line = (char *)xmalloc(len + 1);
1936 memcpy(line, lf->buffer, len);
1937 line[len] = '\0';
1938 argv = makeargv(line, &argc, 1, NULL, NULL);
1939
1940 /* Ensure cursor is at EOL or a argument boundary */
1941 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1942 line[cursor] != '\n') {
Darren Tuckera627d422013-06-02 07:31:17 +10001943 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001944 return ret;
1945 }
1946
1947 if (carg == 0) {
1948 /* Show all available commands */
1949 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1950 ret = CC_REDISPLAY;
1951 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1952 /* Handle the command parsing */
1953 if (complete_cmd_parse(el, argv[0], argc == carg,
Damien Miller02e87802013-08-21 02:38:51 +10001954 quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001955 ret = CC_REDISPLAY;
1956 } else if (carg >= 1) {
1957 /* Handle file parsing */
1958 int remote = complete_is_remote(argv[0]);
1959 char *filematch = NULL;
1960
1961 if (carg > 1 && line[cursor-1] != ' ')
1962 filematch = argv[carg - 1];
1963
1964 if (remote != 0 &&
1965 complete_match(el, complete_ctx->conn,
1966 *complete_ctx->remote_pathp, filematch,
Damien Miller02e87802013-08-21 02:38:51 +10001967 remote, carg == argc, quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001968 ret = CC_REDISPLAY;
1969 }
1970
Damien Miller02e87802013-08-21 02:38:51 +10001971 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001972 return ret;
1973}
Darren Tuckere67f7db2010-01-08 19:50:02 +11001974#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11001975
Damien Miller20e1fab2004-02-18 14:30:55 +11001976int
Darren Tucker21063192010-01-08 17:10:36 +11001977interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001978{
Darren Tucker909d8582010-01-08 19:02:40 +11001979 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11001980 char *dir = NULL;
1981 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10001982 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11001983 EditLine *el = NULL;
1984#ifdef USE_LIBEDIT
1985 History *hl = NULL;
1986 HistEvent hev;
1987 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11001988 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11001989
1990 if (!batchmode && isatty(STDIN_FILENO)) {
1991 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1992 fatal("Couldn't initialise editline");
1993 if ((hl = history_init()) == NULL)
1994 fatal("Couldn't initialise editline history");
1995 history(hl, &hev, H_SETSIZE, 100);
1996 el_set(el, EL_HIST, history, hl);
1997
1998 el_set(el, EL_PROMPT, prompt);
1999 el_set(el, EL_EDITOR, "emacs");
2000 el_set(el, EL_TERMINAL, NULL);
2001 el_set(el, EL_SIGNAL, 1);
2002 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11002003
2004 /* Tab Completion */
Damien Miller02e87802013-08-21 02:38:51 +10002005 el_set(el, EL_ADDFN, "ftp-complete",
Darren Tuckerd78739a2010-10-24 10:56:32 +11002006 "Context sensitive argument completion", complete);
Darren Tucker909d8582010-01-08 19:02:40 +11002007 complete_ctx.conn = conn;
2008 complete_ctx.remote_pathp = &remote_path;
2009 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
2010 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11002011 }
2012#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002013
Darren Tucker909d8582010-01-08 19:02:40 +11002014 remote_path = do_realpath(conn, ".");
2015 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11002016 fatal("Need cwd");
2017
2018 if (file1 != NULL) {
2019 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11002020 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11002021
2022 if (remote_is_dir(conn, dir) && file2 == NULL) {
Damien Miller9303e652013-04-23 15:22:40 +10002023 if (!quiet)
2024 printf("Changing to: %s\n", dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002025 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11002026 if (parse_dispatch_command(conn, cmd,
2027 &remote_path, 1) != 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10002028 free(dir);
2029 free(remote_path);
2030 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002031 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11002032 }
Damien Miller20e1fab2004-02-18 14:30:55 +11002033 } else {
Darren Tucker0af24052012-10-05 10:41:25 +10002034 /* XXX this is wrong wrt quoting */
Damien Miller0d032412013-07-25 11:56:52 +10002035 snprintf(cmd, sizeof cmd, "get%s %s%s%s",
2036 global_aflag ? " -a" : "", dir,
2037 file2 == NULL ? "" : " ",
2038 file2 == NULL ? "" : file2);
Darren Tucker909d8582010-01-08 19:02:40 +11002039 err = parse_dispatch_command(conn, cmd,
2040 &remote_path, 1);
Darren Tuckera627d422013-06-02 07:31:17 +10002041 free(dir);
2042 free(remote_path);
2043 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002044 return (err);
2045 }
Darren Tuckera627d422013-06-02 07:31:17 +10002046 free(dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002047 }
2048
Damien Miller37294fb2005-07-17 17:18:49 +10002049 setlinebuf(stdout);
2050 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11002051
Damien Miller0e2c1022005-08-12 22:16:22 +10002052 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11002053 err = 0;
2054 for (;;) {
2055 char *cp;
2056
Darren Tuckercdf547a2004-05-24 10:12:19 +10002057 signal(SIGINT, SIG_IGN);
2058
Darren Tucker2d963d82004-11-07 20:04:10 +11002059 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002060 if (interactive)
2061 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11002062 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002063 if (interactive)
2064 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11002065 break;
2066 }
Damien Miller0e2c1022005-08-12 22:16:22 +10002067 if (!interactive) { /* Echo command */
2068 printf("sftp> %s", cmd);
2069 if (strlen(cmd) > 0 &&
2070 cmd[strlen(cmd) - 1] != '\n')
2071 printf("\n");
2072 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002073 } else {
2074#ifdef USE_LIBEDIT
2075 const char *line;
2076 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11002077
Darren Tucker909d8582010-01-08 19:02:40 +11002078 if ((line = el_gets(el, &count)) == NULL ||
2079 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002080 printf("\n");
2081 break;
2082 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002083 history(hl, &hev, H_ENTER, line);
2084 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
2085 fprintf(stderr, "Error: input line too long\n");
2086 continue;
2087 }
2088#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002089 }
2090
Damien Miller20e1fab2004-02-18 14:30:55 +11002091 cp = strrchr(cmd, '\n');
2092 if (cp)
2093 *cp = '\0';
2094
Darren Tuckercdf547a2004-05-24 10:12:19 +10002095 /* Handle user interrupts gracefully during commands */
2096 interrupted = 0;
2097 signal(SIGINT, cmd_interrupt);
2098
Darren Tucker909d8582010-01-08 19:02:40 +11002099 err = parse_dispatch_command(conn, cmd, &remote_path,
2100 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11002101 if (err != 0)
2102 break;
2103 }
Darren Tuckera627d422013-06-02 07:31:17 +10002104 free(remote_path);
2105 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002106
Tim Rice027e8b12005-08-15 14:52:50 -07002107#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10002108 if (el != NULL)
2109 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07002110#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10002111
Damien Miller20e1fab2004-02-18 14:30:55 +11002112 /* err == 1 signifies normal "quit" exit */
2113 return (err >= 0 ? 0 : -1);
2114}
Damien Miller62d57f62003-01-10 21:43:24 +11002115
Ben Lindstrombba81212001-06-25 05:01:22 +00002116static void
Damien Millercc685c12003-06-04 22:51:38 +10002117connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11002118{
2119 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002120
Damien Miller33804262001-02-04 23:20:18 +11002121#ifdef USE_PIPES
2122 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002123
Damien Miller33804262001-02-04 23:20:18 +11002124 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2125 fatal("pipe: %s", strerror(errno));
2126 *in = pin[0];
2127 *out = pout[1];
2128 c_in = pout[0];
2129 c_out = pin[1];
2130#else /* USE_PIPES */
2131 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002132
Damien Miller33804262001-02-04 23:20:18 +11002133 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2134 fatal("socketpair: %s", strerror(errno));
2135 *in = *out = inout[0];
2136 c_in = c_out = inout[1];
2137#endif /* USE_PIPES */
2138
Damien Millercc685c12003-06-04 22:51:38 +10002139 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002140 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002141 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002142 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2143 (dup2(c_out, STDOUT_FILENO) == -1)) {
2144 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002145 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002146 }
2147 close(*in);
2148 close(*out);
2149 close(c_in);
2150 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002151
2152 /*
2153 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002154 * ignore SIGINT if we want to gracefully abort commands,
2155 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002156 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2157 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002158 */
2159 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002160 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002161 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002162 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002163 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002164 }
2165
Damien Millercc685c12003-06-04 22:51:38 +10002166 signal(SIGTERM, killchild);
2167 signal(SIGINT, killchild);
2168 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11002169 close(c_in);
2170 close(c_out);
2171}
2172
Ben Lindstrombba81212001-06-25 05:01:22 +00002173static void
Damien Miller33804262001-02-04 23:20:18 +11002174usage(void)
2175{
Damien Miller025e01c2002-02-08 22:06:29 +11002176 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002177
Ben Lindstrom1e243242001-09-18 05:38:44 +00002178 fprintf(stderr,
Damien Millerc6895c52013-08-21 02:40:21 +10002179 "usage: %s [-1246aCpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002180 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002181 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002182 " [-o ssh_option] [-P port] [-R num_requests] "
2183 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002184 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002185 " %s [user@]host[:file ...]\n"
2186 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002187 " %s -b batchfile [user@]host\n",
2188 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002189 exit(1);
2190}
2191
Kevin Stevesef4eea92001-02-05 12:42:17 +00002192int
Damien Miller33804262001-02-04 23:20:18 +11002193main(int argc, char **argv)
2194{
Damien Miller956f3fb2003-01-10 21:40:00 +11002195 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002196 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002197 int debug_level = 0, sshver = 2;
2198 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002199 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002200 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002201 LogLevel ll = SYSLOG_LEVEL_INFO;
2202 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002203 extern int optind;
2204 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002205 struct sftp_conn *conn;
2206 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2207 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002208 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002209
Darren Tuckerce321d82005-10-03 18:11:24 +10002210 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2211 sanitise_stdfd();
Darren Tuckerea647212013-06-06 08:19:09 +10002212 setlocale(LC_CTYPE, "");
Darren Tuckerce321d82005-10-03 18:11:24 +10002213
Damien Miller59d3d5b2003-08-22 09:34:41 +10002214 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002215 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002216 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002217 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002218 addargs(&args, "-oForwardX11 no");
2219 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002220 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002221 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002222
Ben Lindstrom387c4722001-05-08 20:27:25 +00002223 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002224 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002225
Darren Tucker282b4022009-10-07 08:23:06 +11002226 while ((ch = getopt(argc, argv,
Damien Miller0d032412013-07-25 11:56:52 +10002227 "1246ahpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002228 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002229 /* Passed through to ssh(1) */
2230 case '4':
2231 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002232 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002233 addargs(&args, "-%c", ch);
2234 break;
2235 /* Passed through to ssh(1) with argument */
2236 case 'F':
2237 case 'c':
2238 case 'i':
2239 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002240 addargs(&args, "-%c", ch);
2241 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002242 break;
2243 case 'q':
Damien Miller9303e652013-04-23 15:22:40 +10002244 ll = SYSLOG_LEVEL_ERROR;
2245 quiet = 1;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002246 showprogress = 0;
2247 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002248 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002249 case 'P':
2250 addargs(&args, "-oPort %s", optarg);
2251 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002252 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002253 if (debug_level < 3) {
2254 addargs(&args, "-v");
2255 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2256 }
2257 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002258 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002259 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002260 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002261 if (sftp_server == NULL)
2262 sftp_server = _PATH_SFTP_SERVER;
2263 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002264 case '2':
2265 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002266 break;
Damien Miller0d032412013-07-25 11:56:52 +10002267 case 'a':
2268 global_aflag = 1;
2269 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002270 case 'B':
2271 copy_buffer_len = strtol(optarg, &cp, 10);
2272 if (copy_buffer_len == 0 || *cp != '\0')
2273 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002274 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002275 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002276 if (batchmode)
2277 fatal("Batch file already specified.");
2278
2279 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002280 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002281 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002282 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002283 showprogress = 0;
Damien Miller9303e652013-04-23 15:22:40 +10002284 quiet = batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002285 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002286 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002287 case 'p':
2288 global_pflag = 1;
2289 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002290 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002291 sftp_direct = optarg;
2292 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002293 case 'l':
2294 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2295 &errstr);
2296 if (errstr != NULL)
2297 usage();
2298 limit_kbps *= 1024; /* kbps */
2299 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002300 case 'r':
2301 global_rflag = 1;
2302 break;
Damien Miller16a13332002-02-13 14:03:56 +11002303 case 'R':
2304 num_requests = strtol(optarg, &cp, 10);
2305 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002306 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002307 optarg);
2308 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002309 case 's':
2310 sftp_server = optarg;
2311 break;
2312 case 'S':
2313 ssh_program = optarg;
2314 replacearg(&args, 0, "%s", ssh_program);
2315 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002316 case 'h':
2317 default:
Damien Miller33804262001-02-04 23:20:18 +11002318 usage();
2319 }
2320 }
2321
Damien Millerc0f27d82004-03-08 23:12:19 +11002322 if (!isatty(STDERR_FILENO))
2323 showprogress = 0;
2324
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002325 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2326
Damien Millerd14ee1e2002-02-05 12:27:31 +11002327 if (sftp_direct == NULL) {
2328 if (optind == argc || argc > (optind + 2))
2329 usage();
Damien Miller33804262001-02-04 23:20:18 +11002330
Damien Millerd14ee1e2002-02-05 12:27:31 +11002331 userhost = xstrdup(argv[optind]);
2332 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002333
Ben Lindstromc276c122002-12-23 02:14:51 +00002334 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002335 host = userhost;
2336 else {
2337 *host++ = '\0';
2338 if (!userhost[0]) {
2339 fprintf(stderr, "Missing username\n");
2340 usage();
2341 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002342 addargs(&args, "-l");
2343 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002344 }
2345
Damien Millerec692032004-01-27 21:22:00 +11002346 if ((cp = colon(host)) != NULL) {
2347 *cp++ = '\0';
2348 file1 = cp;
2349 }
2350
Damien Millerd14ee1e2002-02-05 12:27:31 +11002351 host = cleanhostname(host);
2352 if (!*host) {
2353 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002354 usage();
2355 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002356
Damien Millerd14ee1e2002-02-05 12:27:31 +11002357 addargs(&args, "-oProtocol %d", sshver);
2358
2359 /* no subsystem if the server-spec contains a '/' */
2360 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2361 addargs(&args, "-s");
2362
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002363 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002364 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002365 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002366 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002367
Damien Millercc685c12003-06-04 22:51:38 +10002368 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002369 } else {
2370 args.list = NULL;
2371 addargs(&args, "sftp-server");
2372
Damien Millercc685c12003-06-04 22:51:38 +10002373 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002374 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002375 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002376
Damien Miller65e42f82010-09-24 22:15:11 +10002377 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002378 if (conn == NULL)
2379 fatal("Couldn't initialise connection to server");
2380
Damien Miller9303e652013-04-23 15:22:40 +10002381 if (!quiet) {
Darren Tucker21063192010-01-08 17:10:36 +11002382 if (sftp_direct == NULL)
2383 fprintf(stderr, "Connected to %s.\n", host);
2384 else
2385 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2386 }
2387
2388 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002389
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002390#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002391 shutdown(in, SHUT_RDWR);
2392 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002393#endif
2394
Damien Miller33804262001-02-04 23:20:18 +11002395 close(in);
2396 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002397 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002398 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002399
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002400 while (waitpid(sshpid, NULL, 0) == -1)
2401 if (errno != EINTR)
2402 fatal("Couldn't wait for ssh process: %s",
2403 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002404
Damien Miller956f3fb2003-01-10 21:40:00 +11002405 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002406}