blob: 6f16f7cc66d6cb6b32cf975fbcd206684468d70d [file] [log] [blame]
Damien Miller034f27a2013-08-21 02:40:44 +10001/* $OpenBSD: sftp.c,v 1.151 2013/08/08 04:52:04 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 Miller1cbc2922007-10-26 14:27:45 +1000418parse_ls_flags(char **argv, int argc, int *lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100419{
Damien Millerf184bcf2008-06-29 22:45:13 +1000420 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000421 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100422
Damien Miller1cbc2922007-10-26 14:27:45 +1000423 optind = optreset = 1;
424 opterr = 0;
425
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000426 *lflag = LS_NAME_SORT;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100427 while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000428 switch (ch) {
429 case '1':
430 *lflag &= ~VIEW_FLAGS;
431 *lflag |= LS_SHORT_VIEW;
432 break;
433 case 'S':
434 *lflag &= ~SORT_FLAGS;
435 *lflag |= LS_SIZE_SORT;
436 break;
437 case 'a':
438 *lflag |= LS_SHOW_ALL;
439 break;
440 case 'f':
441 *lflag &= ~SORT_FLAGS;
442 break;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100443 case 'h':
444 *lflag |= LS_SI_UNITS;
445 break;
Damien Miller1cbc2922007-10-26 14:27:45 +1000446 case 'l':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100447 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000448 *lflag |= LS_LONG_VIEW;
449 break;
450 case 'n':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100451 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000452 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
453 break;
454 case 'r':
455 *lflag |= LS_REVERSE_SORT;
456 break;
457 case 't':
458 *lflag &= ~SORT_FLAGS;
459 *lflag |= LS_TIME_SORT;
460 break;
461 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000462 error("ls: Invalid flag -%c", optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000463 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100464 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100465 }
466
Damien Miller1cbc2922007-10-26 14:27:45 +1000467 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100468}
469
470static int
Damien Millerd671e5a2008-05-19 14:53:33 +1000471parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
472{
Damien Millerf184bcf2008-06-29 22:45:13 +1000473 extern int opterr, optind, optopt, optreset;
Damien Millerd671e5a2008-05-19 14:53:33 +1000474 int ch;
475
476 optind = optreset = 1;
477 opterr = 0;
478
479 *hflag = *iflag = 0;
480 while ((ch = getopt(argc, argv, "hi")) != -1) {
481 switch (ch) {
482 case 'h':
483 *hflag = 1;
484 break;
485 case 'i':
486 *iflag = 1;
487 break;
488 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000489 error("%s: Invalid flag -%c", cmd, optopt);
Damien Millerd671e5a2008-05-19 14:53:33 +1000490 return -1;
491 }
492 }
493
494 return optind;
495}
496
497static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100498is_dir(char *path)
499{
500 struct stat sb;
501
502 /* XXX: report errors? */
503 if (stat(path, &sb) == -1)
504 return(0);
505
Darren Tucker1e80e402006-09-21 12:59:33 +1000506 return(S_ISDIR(sb.st_mode));
Damien Miller20e1fab2004-02-18 14:30:55 +1100507}
508
509static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100510remote_is_dir(struct sftp_conn *conn, char *path)
511{
512 Attrib *a;
513
514 /* XXX: report errors? */
515 if ((a = do_stat(conn, path, 1)) == NULL)
516 return(0);
517 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
518 return(0);
Darren Tucker1e80e402006-09-21 12:59:33 +1000519 return(S_ISDIR(a->perm));
Damien Miller20e1fab2004-02-18 14:30:55 +1100520}
521
Darren Tucker1b0dd172009-10-07 08:37:48 +1100522/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
Damien Miller20e1fab2004-02-18 14:30:55 +1100523static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100524pathname_is_dir(char *pathname)
525{
526 size_t l = strlen(pathname);
527
528 return l > 0 && pathname[l - 1] == '/';
529}
530
531static int
532process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
Damien Miller0d032412013-07-25 11:56:52 +1000533 int pflag, int rflag, int resume)
Damien Miller20e1fab2004-02-18 14:30:55 +1100534{
535 char *abs_src = NULL;
536 char *abs_dst = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100537 glob_t g;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100538 char *filename, *tmp=NULL;
539 int i, err = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100540
541 abs_src = xstrdup(src);
542 abs_src = make_absolute(abs_src, pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +1100543 memset(&g, 0, sizeof(g));
Darren Tucker1b0dd172009-10-07 08:37:48 +1100544
Damien Miller20e1fab2004-02-18 14:30:55 +1100545 debug3("Looking up %s", abs_src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100546 if (remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100547 error("File \"%s\" not found.", abs_src);
548 err = -1;
549 goto out;
550 }
551
Darren Tucker1b0dd172009-10-07 08:37:48 +1100552 /*
553 * If multiple matches then dst must be a directory or
554 * unspecified.
555 */
556 if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
557 error("Multiple source paths, but destination "
558 "\"%s\" is not a directory", dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100559 err = -1;
560 goto out;
561 }
562
Darren Tuckercdf547a2004-05-24 10:12:19 +1000563 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100564 tmp = xstrdup(g.gl_pathv[i]);
565 if ((filename = basename(tmp)) == NULL) {
566 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000567 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100568 err = -1;
569 goto out;
570 }
571
572 if (g.gl_matchc == 1 && dst) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100573 if (is_dir(dst)) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100574 abs_dst = path_append(dst, filename);
575 } else {
Damien Miller20e1fab2004-02-18 14:30:55 +1100576 abs_dst = xstrdup(dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100577 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100578 } else if (dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100579 abs_dst = path_append(dst, filename);
580 } else {
581 abs_dst = xstrdup(filename);
582 }
Darren Tuckera627d422013-06-02 07:31:17 +1000583 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100584
Damien Miller0d032412013-07-25 11:56:52 +1000585 resume |= global_aflag;
586 if (!quiet && resume)
587 printf("Resuming %s to %s\n", g.gl_pathv[i], abs_dst);
588 else if (!quiet && !resume)
Damien Miller9303e652013-04-23 15:22:40 +1000589 printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100590 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
Damien Miller0d032412013-07-25 11:56:52 +1000591 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
592 pflag || global_pflag, 1, resume) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100593 err = -1;
594 } else {
595 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
Damien Miller0d032412013-07-25 11:56:52 +1000596 pflag || global_pflag, resume) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100597 err = -1;
598 }
Darren Tuckera627d422013-06-02 07:31:17 +1000599 free(abs_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100600 abs_dst = NULL;
601 }
602
603out:
Darren Tuckera627d422013-06-02 07:31:17 +1000604 free(abs_src);
Damien Miller20e1fab2004-02-18 14:30:55 +1100605 globfree(&g);
606 return(err);
607}
608
609static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100610process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
611 int pflag, int rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100612{
613 char *tmp_dst = NULL;
614 char *abs_dst = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100615 char *tmp = NULL, *filename = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100616 glob_t g;
617 int err = 0;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100618 int i, dst_is_dir = 1;
Damien Milleraec5cf82008-02-10 22:26:24 +1100619 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100620
621 if (dst) {
622 tmp_dst = xstrdup(dst);
623 tmp_dst = make_absolute(tmp_dst, pwd);
624 }
625
626 memset(&g, 0, sizeof(g));
627 debug3("Looking up %s", src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100628 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100629 error("File \"%s\" not found.", src);
630 err = -1;
631 goto out;
632 }
633
Darren Tucker1b0dd172009-10-07 08:37:48 +1100634 /* If we aren't fetching to pwd then stash this status for later */
635 if (tmp_dst != NULL)
636 dst_is_dir = remote_is_dir(conn, tmp_dst);
637
Damien Miller20e1fab2004-02-18 14:30:55 +1100638 /* If multiple matches, dst may be directory or unspecified */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100639 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
640 error("Multiple paths match, but destination "
641 "\"%s\" is not a directory", tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100642 err = -1;
643 goto out;
644 }
645
Darren Tuckercdf547a2004-05-24 10:12:19 +1000646 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Milleraec5cf82008-02-10 22:26:24 +1100647 if (stat(g.gl_pathv[i], &sb) == -1) {
648 err = -1;
649 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
650 continue;
651 }
Damien Miller02e87802013-08-21 02:38:51 +1000652
Darren Tucker1b0dd172009-10-07 08:37:48 +1100653 tmp = xstrdup(g.gl_pathv[i]);
654 if ((filename = basename(tmp)) == NULL) {
655 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000656 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100657 err = -1;
658 goto out;
659 }
660
661 if (g.gl_matchc == 1 && tmp_dst) {
662 /* If directory specified, append filename */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100663 if (dst_is_dir)
664 abs_dst = path_append(tmp_dst, filename);
665 else
Damien Miller20e1fab2004-02-18 14:30:55 +1100666 abs_dst = xstrdup(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100667 } else if (tmp_dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100668 abs_dst = path_append(tmp_dst, filename);
669 } else {
670 abs_dst = make_absolute(xstrdup(filename), pwd);
671 }
Darren Tuckera627d422013-06-02 07:31:17 +1000672 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100673
Damien Miller9303e652013-04-23 15:22:40 +1000674 if (!quiet)
675 printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100676 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
677 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
678 pflag || global_pflag, 1) == -1)
679 err = -1;
680 } else {
681 if (do_upload(conn, g.gl_pathv[i], abs_dst,
682 pflag || global_pflag) == -1)
683 err = -1;
684 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100685 }
686
687out:
Darren Tuckera627d422013-06-02 07:31:17 +1000688 free(abs_dst);
689 free(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100690 globfree(&g);
691 return(err);
692}
693
694static int
695sdirent_comp(const void *aa, const void *bb)
696{
697 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
698 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000699 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100700
Darren Tuckerb9123452004-06-22 13:06:45 +1000701#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000702 if (sort_flag & LS_NAME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000703 return (rmul * strcmp(a->filename, b->filename));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000704 else if (sort_flag & LS_TIME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000705 return (rmul * NCMP(a->a.mtime, b->a.mtime));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000706 else if (sort_flag & LS_SIZE_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000707 return (rmul * NCMP(a->a.size, b->a.size));
708
709 fatal("Unknown ls sort type");
Damien Miller20e1fab2004-02-18 14:30:55 +1100710}
711
712/* sftp ls.1 replacement for directories */
713static int
714do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
715{
Damien Millereccb9de2005-06-17 12:59:34 +1000716 int n;
717 u_int c = 1, colspace = 0, columns = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100718 SFTP_DIRENT **d;
719
720 if ((n = do_readdir(conn, path, &d)) != 0)
721 return (n);
722
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000723 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000724 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100725 struct winsize ws;
726 char *tmp;
727
728 /* Count entries for sort and find longest filename */
Darren Tucker9a526452004-06-22 13:09:55 +1000729 for (n = 0; d[n] != NULL; n++) {
730 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
731 m = MAX(m, strlen(d[n]->filename));
732 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100733
734 /* Add any subpath that also needs to be counted */
735 tmp = path_strip(path, strip_path);
736 m += strlen(tmp);
Darren Tuckera627d422013-06-02 07:31:17 +1000737 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100738
739 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
740 width = ws.ws_col;
741
742 columns = width / (m + 2);
743 columns = MAX(columns, 1);
744 colspace = width / columns;
745 colspace = MIN(colspace, width);
746 }
747
Darren Tuckerb9123452004-06-22 13:06:45 +1000748 if (lflag & SORT_FLAGS) {
Damien Miller653b93b2005-11-05 15:15:23 +1100749 for (n = 0; d[n] != NULL; n++)
750 ; /* count entries */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000751 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
Darren Tuckerb9123452004-06-22 13:06:45 +1000752 qsort(d, n, sizeof(*d), sdirent_comp);
753 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100754
Darren Tuckercdf547a2004-05-24 10:12:19 +1000755 for (n = 0; d[n] != NULL && !interrupted; n++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100756 char *tmp, *fname;
757
Darren Tucker9a526452004-06-22 13:09:55 +1000758 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
759 continue;
760
Damien Miller20e1fab2004-02-18 14:30:55 +1100761 tmp = path_append(path, d[n]->filename);
762 fname = path_strip(tmp, strip_path);
Darren Tuckera627d422013-06-02 07:31:17 +1000763 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100764
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000765 if (lflag & LS_LONG_VIEW) {
Darren Tucker2901e2d2010-01-13 22:44:06 +1100766 if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000767 char *lname;
768 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100769
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000770 memset(&sb, 0, sizeof(sb));
771 attrib_to_stat(&d[n]->a, &sb);
Darren Tucker2901e2d2010-01-13 22:44:06 +1100772 lname = ls_file(fname, &sb, 1,
773 (lflag & LS_SI_UNITS));
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000774 printf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000775 free(lname);
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000776 } else
777 printf("%s\n", d[n]->longname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100778 } else {
779 printf("%-*s", colspace, fname);
780 if (c >= columns) {
781 printf("\n");
782 c = 1;
783 } else
784 c++;
785 }
786
Darren Tuckera627d422013-06-02 07:31:17 +1000787 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100788 }
789
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000790 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100791 printf("\n");
792
793 free_sftp_dirents(d);
794 return (0);
795}
796
797/* sftp ls.1 replacement which handles path globs */
798static int
799do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
800 int lflag)
801{
Damien Millera6e121a2010-10-07 21:39:17 +1100802 char *fname, *lname;
Damien Miller68e2e562010-10-07 21:39:55 +1100803 glob_t g;
804 int err;
805 struct winsize ws;
806 u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100807
808 memset(&g, 0, sizeof(g));
809
Damien Millera6e121a2010-10-07 21:39:17 +1100810 if (remote_glob(conn, path,
Damien Millerd7be70d2011-09-22 21:43:06 +1000811 GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
812 NULL, &g) ||
Damien Millera6e121a2010-10-07 21:39:17 +1100813 (g.gl_pathc && !g.gl_matchc)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100814 if (g.gl_pathc)
815 globfree(&g);
Damien Miller20e1fab2004-02-18 14:30:55 +1100816 error("Can't ls: \"%s\" not found", path);
Damien Millera6e121a2010-10-07 21:39:17 +1100817 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100818 }
819
Darren Tuckercdf547a2004-05-24 10:12:19 +1000820 if (interrupted)
821 goto out;
822
Damien Miller20e1fab2004-02-18 14:30:55 +1100823 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100824 * If the glob returns a single match and it is a directory,
825 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100826 */
Damien Millera6e121a2010-10-07 21:39:17 +1100827 if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
828 S_ISDIR(g.gl_statv[0]->st_mode)) {
829 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
830 globfree(&g);
831 return err;
Damien Miller20e1fab2004-02-18 14:30:55 +1100832 }
833
Damien Miller68e2e562010-10-07 21:39:55 +1100834 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
835 width = ws.ws_col;
Damien Miller20e1fab2004-02-18 14:30:55 +1100836
Damien Miller68e2e562010-10-07 21:39:55 +1100837 if (!(lflag & LS_SHORT_VIEW)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100838 /* Count entries for sort and find longest filename */
839 for (i = 0; g.gl_pathv[i]; i++)
840 m = MAX(m, strlen(g.gl_pathv[i]));
841
Damien Miller20e1fab2004-02-18 14:30:55 +1100842 columns = width / (m + 2);
843 columns = MAX(columns, 1);
844 colspace = width / columns;
845 }
846
Damien Millerea858292012-06-30 08:33:32 +1000847 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100848 fname = path_strip(g.gl_pathv[i], strip_path);
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000849 if (lflag & LS_LONG_VIEW) {
Damien Millera6e121a2010-10-07 21:39:17 +1100850 if (g.gl_statv[i] == NULL) {
851 error("no stat information for %s", fname);
852 continue;
853 }
854 lname = ls_file(fname, g.gl_statv[i], 1,
855 (lflag & LS_SI_UNITS));
Damien Miller20e1fab2004-02-18 14:30:55 +1100856 printf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000857 free(lname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100858 } else {
859 printf("%-*s", colspace, fname);
860 if (c >= columns) {
861 printf("\n");
862 c = 1;
863 } else
864 c++;
865 }
Darren Tuckera627d422013-06-02 07:31:17 +1000866 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100867 }
868
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000869 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100870 printf("\n");
871
Darren Tuckercdf547a2004-05-24 10:12:19 +1000872 out:
Damien Miller20e1fab2004-02-18 14:30:55 +1100873 if (g.gl_pathc)
874 globfree(&g);
875
Damien Millera6e121a2010-10-07 21:39:17 +1100876 return 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100877}
878
Damien Millerd671e5a2008-05-19 14:53:33 +1000879static int
880do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
881{
Darren Tucker7b598892008-06-09 22:49:36 +1000882 struct sftp_statvfs st;
Damien Millerd671e5a2008-05-19 14:53:33 +1000883 char s_used[FMT_SCALED_STRSIZE];
884 char s_avail[FMT_SCALED_STRSIZE];
885 char s_root[FMT_SCALED_STRSIZE];
886 char s_total[FMT_SCALED_STRSIZE];
Darren Tuckerb5082e92010-01-08 18:51:47 +1100887 unsigned long long ffree;
Damien Millerd671e5a2008-05-19 14:53:33 +1000888
889 if (do_statvfs(conn, path, &st, 1) == -1)
890 return -1;
891 if (iflag) {
Darren Tuckerb5082e92010-01-08 18:51:47 +1100892 ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
Damien Millerd671e5a2008-05-19 14:53:33 +1000893 printf(" Inodes Used Avail "
894 "(root) %%Capacity\n");
895 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
896 (unsigned long long)st.f_files,
897 (unsigned long long)(st.f_files - st.f_ffree),
898 (unsigned long long)st.f_favail,
Darren Tuckerb5082e92010-01-08 18:51:47 +1100899 (unsigned long long)st.f_ffree, ffree);
Damien Millerd671e5a2008-05-19 14:53:33 +1000900 } else if (hflag) {
901 strlcpy(s_used, "error", sizeof(s_used));
902 strlcpy(s_avail, "error", sizeof(s_avail));
903 strlcpy(s_root, "error", sizeof(s_root));
904 strlcpy(s_total, "error", sizeof(s_total));
905 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
906 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
907 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
908 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
909 printf(" Size Used Avail (root) %%Capacity\n");
910 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
911 s_total, s_used, s_avail, s_root,
912 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
913 st.f_blocks));
914 } else {
915 printf(" Size Used Avail "
916 "(root) %%Capacity\n");
917 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
918 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
919 (unsigned long long)(st.f_frsize *
920 (st.f_blocks - st.f_bfree) / 1024),
921 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
922 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
923 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
924 st.f_blocks));
925 }
926 return 0;
927}
928
Damien Miller1cbc2922007-10-26 14:27:45 +1000929/*
930 * Undo escaping of glob sequences in place. Used to undo extra escaping
931 * applied in makeargv() when the string is destined for a function that
932 * does not glob it.
933 */
934static void
935undo_glob_escape(char *s)
936{
937 size_t i, j;
938
939 for (i = j = 0;;) {
940 if (s[i] == '\0') {
941 s[j] = '\0';
942 return;
943 }
944 if (s[i] != '\\') {
945 s[j++] = s[i++];
946 continue;
947 }
948 /* s[i] == '\\' */
949 ++i;
950 switch (s[i]) {
951 case '?':
952 case '[':
953 case '*':
954 case '\\':
955 s[j++] = s[i++];
956 break;
957 case '\0':
958 s[j++] = '\\';
959 s[j] = '\0';
960 return;
961 default:
962 s[j++] = '\\';
963 s[j++] = s[i++];
964 break;
965 }
966 }
967}
968
969/*
970 * Split a string into an argument vector using sh(1)-style quoting,
971 * comment and escaping rules, but with some tweaks to handle glob(3)
972 * wildcards.
Darren Tucker909d8582010-01-08 19:02:40 +1100973 * The "sloppy" flag allows for recovery from missing terminating quote, for
974 * use in parsing incomplete commandlines during tab autocompletion.
975 *
Damien Miller1cbc2922007-10-26 14:27:45 +1000976 * Returns NULL on error or a NULL-terminated array of arguments.
Darren Tucker909d8582010-01-08 19:02:40 +1100977 *
978 * If "lastquote" is not NULL, the quoting character used for the last
979 * argument is placed in *lastquote ("\0", "'" or "\"").
Damien Miller02e87802013-08-21 02:38:51 +1000980 *
Darren Tucker909d8582010-01-08 19:02:40 +1100981 * If "terminated" is not NULL, *terminated will be set to 1 when the
982 * last argument's quote has been properly terminated or 0 otherwise.
983 * This parameter is only of use if "sloppy" is set.
Damien Miller1cbc2922007-10-26 14:27:45 +1000984 */
985#define MAXARGS 128
986#define MAXARGLEN 8192
987static char **
Darren Tucker909d8582010-01-08 19:02:40 +1100988makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
989 u_int *terminated)
Damien Miller1cbc2922007-10-26 14:27:45 +1000990{
991 int argc, quot;
992 size_t i, j;
993 static char argvs[MAXARGLEN];
994 static char *argv[MAXARGS + 1];
995 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
996
997 *argcp = argc = 0;
998 if (strlen(arg) > sizeof(argvs) - 1) {
999 args_too_longs:
1000 error("string too long");
1001 return NULL;
1002 }
Darren Tucker909d8582010-01-08 19:02:40 +11001003 if (terminated != NULL)
1004 *terminated = 1;
1005 if (lastquote != NULL)
1006 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001007 state = MA_START;
1008 i = j = 0;
1009 for (;;) {
Damien Miller07daed52012-10-31 08:57:55 +11001010 if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){
Darren Tucker063018d2012-10-05 10:43:58 +10001011 error("Too many arguments.");
1012 return NULL;
1013 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001014 if (isspace(arg[i])) {
1015 if (state == MA_UNQUOTED) {
1016 /* Terminate current argument */
1017 argvs[j++] = '\0';
1018 argc++;
1019 state = MA_START;
1020 } else if (state != MA_START)
1021 argvs[j++] = arg[i];
1022 } else if (arg[i] == '"' || arg[i] == '\'') {
1023 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1024 if (state == MA_START) {
1025 argv[argc] = argvs + j;
1026 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +11001027 if (lastquote != NULL)
1028 *lastquote = arg[i];
Damien Miller02e87802013-08-21 02:38:51 +10001029 } else if (state == MA_UNQUOTED)
Damien Miller1cbc2922007-10-26 14:27:45 +10001030 state = q;
1031 else if (state == q)
1032 state = MA_UNQUOTED;
1033 else
1034 argvs[j++] = arg[i];
1035 } else if (arg[i] == '\\') {
1036 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1037 quot = state == MA_SQUOTE ? '\'' : '"';
1038 /* Unescape quote we are in */
1039 /* XXX support \n and friends? */
1040 if (arg[i + 1] == quot) {
1041 i++;
1042 argvs[j++] = arg[i];
1043 } else if (arg[i + 1] == '?' ||
1044 arg[i + 1] == '[' || arg[i + 1] == '*') {
1045 /*
1046 * Special case for sftp: append
1047 * double-escaped glob sequence -
1048 * glob will undo one level of
1049 * escaping. NB. string can grow here.
1050 */
1051 if (j >= sizeof(argvs) - 5)
1052 goto args_too_longs;
1053 argvs[j++] = '\\';
1054 argvs[j++] = arg[i++];
1055 argvs[j++] = '\\';
1056 argvs[j++] = arg[i];
1057 } else {
1058 argvs[j++] = arg[i++];
1059 argvs[j++] = arg[i];
1060 }
1061 } else {
1062 if (state == MA_START) {
1063 argv[argc] = argvs + j;
1064 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001065 if (lastquote != NULL)
1066 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001067 }
1068 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1069 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1070 /*
1071 * Special case for sftp: append
1072 * escaped glob sequence -
1073 * glob will undo one level of
1074 * escaping.
1075 */
1076 argvs[j++] = arg[i++];
1077 argvs[j++] = arg[i];
1078 } else {
1079 /* Unescape everything */
1080 /* XXX support \n and friends? */
1081 i++;
1082 argvs[j++] = arg[i];
1083 }
1084 }
1085 } else if (arg[i] == '#') {
1086 if (state == MA_SQUOTE || state == MA_DQUOTE)
1087 argvs[j++] = arg[i];
1088 else
1089 goto string_done;
1090 } else if (arg[i] == '\0') {
1091 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001092 if (sloppy) {
1093 state = MA_UNQUOTED;
1094 if (terminated != NULL)
1095 *terminated = 0;
1096 goto string_done;
1097 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001098 error("Unterminated quoted argument");
1099 return NULL;
1100 }
1101 string_done:
1102 if (state == MA_UNQUOTED) {
1103 argvs[j++] = '\0';
1104 argc++;
1105 }
1106 break;
1107 } else {
1108 if (state == MA_START) {
1109 argv[argc] = argvs + j;
1110 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001111 if (lastquote != NULL)
1112 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001113 }
1114 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1115 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1116 /*
1117 * Special case for sftp: escape quoted
1118 * glob(3) wildcards. NB. string can grow
1119 * here.
1120 */
1121 if (j >= sizeof(argvs) - 3)
1122 goto args_too_longs;
1123 argvs[j++] = '\\';
1124 argvs[j++] = arg[i];
1125 } else
1126 argvs[j++] = arg[i];
1127 }
1128 i++;
1129 }
1130 *argcp = argc;
1131 return argv;
1132}
1133
Damien Miller20e1fab2004-02-18 14:30:55 +11001134static int
Damien Miller0d032412013-07-25 11:56:52 +10001135parse_args(const char **cpp, int *aflag, int *hflag, int *iflag, int *lflag,
1136 int *pflag, int *rflag, int *sflag, unsigned long *n_arg,
1137 char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001138{
1139 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001140 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001141 int base = 0;
1142 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001143 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001144
1145 /* Skip leading whitespace */
1146 cp = cp + strspn(cp, WHITESPACE);
1147
Damien Miller20e1fab2004-02-18 14:30:55 +11001148 /* Check for leading '-' (disable error processing) */
1149 *iflag = 0;
1150 if (*cp == '-') {
1151 *iflag = 1;
1152 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001153 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001154 }
1155
Darren Tucker70cc0922010-01-09 22:28:03 +11001156 /* Ignore blank lines and lines which begin with comment '#' char */
1157 if (*cp == '\0' || *cp == '#')
1158 return (0);
1159
Darren Tucker909d8582010-01-08 19:02:40 +11001160 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001161 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001162
Damien Miller1cbc2922007-10-26 14:27:45 +10001163 /* Figure out which command we have */
1164 for (i = 0; cmds[i].c != NULL; i++) {
Damien Millerd6d9fa02013-02-12 11:02:46 +11001165 if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001166 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001167 }
1168 cmdnum = cmds[i].n;
1169 cmd = cmds[i].c;
1170
1171 /* Special case */
1172 if (*cp == '!') {
1173 cp++;
1174 cmdnum = I_SHELL;
1175 } else if (cmdnum == -1) {
1176 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001177 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001178 }
1179
1180 /* Get arguments and parse flags */
Damien Miller0d032412013-07-25 11:56:52 +10001181 *aflag = *lflag = *pflag = *rflag = *hflag = *n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001182 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001183 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001184 switch (cmdnum) {
1185 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001186 case I_REGET:
Damien Miller20e1fab2004-02-18 14:30:55 +11001187 case I_PUT:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001188 if ((optidx = parse_getput_flags(cmd, argv, argc,
Damien Miller0d032412013-07-25 11:56:52 +10001189 aflag, pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001190 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001191 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001192 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001193 error("You must specify at least one path after a "
1194 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001195 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001196 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001197 *path1 = xstrdup(argv[optidx]);
1198 /* Get second pathname (optional) */
1199 if (argc - optidx > 1) {
1200 *path2 = xstrdup(argv[optidx + 1]);
1201 /* Destination is not globbed */
1202 undo_glob_escape(*path2);
1203 }
Damien Miller0d032412013-07-25 11:56:52 +10001204 if (*aflag && cmdnum == I_PUT) {
1205 /* XXX implement resume for uploads */
1206 error("Resume is not supported for uploads");
1207 return -1;
1208 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001209 break;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001210 case I_LINK:
1211 if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1212 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001213 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001214 case I_RENAME:
Damien Miller1cbc2922007-10-26 14:27:45 +10001215 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001216 error("You must specify two paths after a %s "
1217 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001218 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001219 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001220 *path1 = xstrdup(argv[optidx]);
1221 *path2 = xstrdup(argv[optidx + 1]);
1222 /* Paths are not globbed */
1223 undo_glob_escape(*path1);
1224 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001225 break;
1226 case I_RM:
1227 case I_MKDIR:
1228 case I_RMDIR:
1229 case I_CHDIR:
1230 case I_LCHDIR:
1231 case I_LMKDIR:
1232 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001233 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001234 error("You must specify a path after a %s command.",
1235 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001236 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001237 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001238 *path1 = xstrdup(argv[optidx]);
1239 /* Only "rm" globs */
1240 if (cmdnum != I_RM)
1241 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001242 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001243 case I_DF:
1244 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1245 iflag)) == -1)
1246 return -1;
1247 /* Default to current directory if no path specified */
1248 if (argc - optidx < 1)
1249 *path1 = NULL;
1250 else {
1251 *path1 = xstrdup(argv[optidx]);
1252 undo_glob_escape(*path1);
1253 }
1254 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001255 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001256 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001257 return(-1);
1258 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001259 if (argc - optidx > 0)
1260 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001261 break;
1262 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001263 /* Skip ls command and following whitespace */
1264 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001265 case I_SHELL:
1266 /* Uses the rest of the line */
1267 break;
1268 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001269 case I_CHMOD:
1270 base = 8;
1271 case I_CHOWN:
1272 case I_CHGRP:
1273 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001274 if (argc - optidx < 1)
1275 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001276 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001277 l = strtol(argv[optidx], &cp2, base);
1278 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1279 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1280 l < 0) {
1281 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001282 error("You must supply a numeric argument "
1283 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001284 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001285 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001286 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001287 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001288 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001289 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001290 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001291 error("You must specify a path after a %s command.",
1292 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001293 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001294 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001295 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001296 break;
1297 case I_QUIT:
1298 case I_PWD:
1299 case I_LPWD:
1300 case I_HELP:
1301 case I_VERSION:
1302 case I_PROGRESS:
1303 break;
1304 default:
1305 fatal("Command not implemented");
1306 }
1307
1308 *cpp = cp;
1309 return(cmdnum);
1310}
1311
1312static int
1313parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1314 int err_abort)
1315{
1316 char *path1, *path2, *tmp;
Damien Miller0d032412013-07-25 11:56:52 +10001317 int aflag = 0, hflag = 0, iflag = 0, lflag = 0, pflag = 0;
1318 int rflag = 0, sflag = 0;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001319 int cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001320 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001321 Attrib a, *aa;
1322 char path_buf[MAXPATHLEN];
1323 int err = 0;
1324 glob_t g;
1325
1326 path1 = path2 = NULL;
Damien Miller0d032412013-07-25 11:56:52 +10001327 cmdnum = parse_args(&cmd, &aflag, &hflag, &iflag, &lflag, &pflag,
1328 &rflag, &sflag, &n_arg, &path1, &path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001329 if (iflag != 0)
1330 err_abort = 0;
1331
1332 memset(&g, 0, sizeof(g));
1333
1334 /* Perform command */
1335 switch (cmdnum) {
1336 case 0:
1337 /* Blank line */
1338 break;
1339 case -1:
1340 /* Unrecognized command */
1341 err = -1;
1342 break;
Damien Miller0d032412013-07-25 11:56:52 +10001343 case I_REGET:
1344 aflag = 1;
1345 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001346 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001347 err = process_get(conn, path1, path2, *pwd, pflag,
1348 rflag, aflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001349 break;
1350 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001351 err = process_put(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001352 break;
1353 case I_RENAME:
1354 path1 = make_absolute(path1, *pwd);
1355 path2 = make_absolute(path2, *pwd);
1356 err = do_rename(conn, path1, path2);
1357 break;
1358 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001359 sflag = 1;
1360 case I_LINK:
Damien Miller034f27a2013-08-21 02:40:44 +10001361 if (!sflag)
1362 path1 = make_absolute(path1, *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001363 path2 = make_absolute(path2, *pwd);
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001364 err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001365 break;
1366 case I_RM:
1367 path1 = make_absolute(path1, *pwd);
1368 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001369 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001370 if (!quiet)
1371 printf("Removing %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001372 err = do_rm(conn, g.gl_pathv[i]);
1373 if (err != 0 && err_abort)
1374 break;
1375 }
1376 break;
1377 case I_MKDIR:
1378 path1 = make_absolute(path1, *pwd);
1379 attrib_clear(&a);
1380 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1381 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001382 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001383 break;
1384 case I_RMDIR:
1385 path1 = make_absolute(path1, *pwd);
1386 err = do_rmdir(conn, path1);
1387 break;
1388 case I_CHDIR:
1389 path1 = make_absolute(path1, *pwd);
1390 if ((tmp = do_realpath(conn, path1)) == NULL) {
1391 err = 1;
1392 break;
1393 }
1394 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
Darren Tuckera627d422013-06-02 07:31:17 +10001395 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001396 err = 1;
1397 break;
1398 }
1399 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1400 error("Can't change directory: Can't check target");
Darren Tuckera627d422013-06-02 07:31:17 +10001401 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001402 err = 1;
1403 break;
1404 }
1405 if (!S_ISDIR(aa->perm)) {
1406 error("Can't change directory: \"%s\" is not "
1407 "a directory", tmp);
Darren Tuckera627d422013-06-02 07:31:17 +10001408 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001409 err = 1;
1410 break;
1411 }
Darren Tuckera627d422013-06-02 07:31:17 +10001412 free(*pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001413 *pwd = tmp;
1414 break;
1415 case I_LS:
1416 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001417 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001418 break;
1419 }
1420
1421 /* Strip pwd off beginning of non-absolute paths */
1422 tmp = NULL;
1423 if (*path1 != '/')
1424 tmp = *pwd;
1425
1426 path1 = make_absolute(path1, *pwd);
1427 err = do_globbed_ls(conn, path1, tmp, lflag);
1428 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001429 case I_DF:
1430 /* Default to current directory if no path specified */
1431 if (path1 == NULL)
1432 path1 = xstrdup(*pwd);
1433 path1 = make_absolute(path1, *pwd);
1434 err = do_df(conn, path1, hflag, iflag);
1435 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001436 case I_LCHDIR:
1437 if (chdir(path1) == -1) {
1438 error("Couldn't change local directory to "
1439 "\"%s\": %s", path1, strerror(errno));
1440 err = 1;
1441 }
1442 break;
1443 case I_LMKDIR:
1444 if (mkdir(path1, 0777) == -1) {
1445 error("Couldn't create local directory "
1446 "\"%s\": %s", path1, strerror(errno));
1447 err = 1;
1448 }
1449 break;
1450 case I_LLS:
1451 local_do_ls(cmd);
1452 break;
1453 case I_SHELL:
1454 local_do_shell(cmd);
1455 break;
1456 case I_LUMASK:
1457 umask(n_arg);
1458 printf("Local umask: %03lo\n", n_arg);
1459 break;
1460 case I_CHMOD:
1461 path1 = make_absolute(path1, *pwd);
1462 attrib_clear(&a);
1463 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1464 a.perm = n_arg;
1465 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001466 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001467 if (!quiet)
1468 printf("Changing mode on %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001469 err = do_setstat(conn, g.gl_pathv[i], &a);
1470 if (err != 0 && err_abort)
1471 break;
1472 }
1473 break;
1474 case I_CHOWN:
1475 case I_CHGRP:
1476 path1 = make_absolute(path1, *pwd);
1477 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001478 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001479 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001480 if (err_abort) {
1481 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001482 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001483 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001484 continue;
1485 }
1486 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1487 error("Can't get current ownership of "
1488 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001489 if (err_abort) {
1490 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001491 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001492 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001493 continue;
1494 }
1495 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1496 if (cmdnum == I_CHOWN) {
Damien Miller9303e652013-04-23 15:22:40 +10001497 if (!quiet)
1498 printf("Changing owner on %s\n",
1499 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001500 aa->uid = n_arg;
1501 } else {
Damien Miller9303e652013-04-23 15:22:40 +10001502 if (!quiet)
1503 printf("Changing group on %s\n",
1504 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001505 aa->gid = n_arg;
1506 }
1507 err = do_setstat(conn, g.gl_pathv[i], aa);
1508 if (err != 0 && err_abort)
1509 break;
1510 }
1511 break;
1512 case I_PWD:
1513 printf("Remote working directory: %s\n", *pwd);
1514 break;
1515 case I_LPWD:
1516 if (!getcwd(path_buf, sizeof(path_buf))) {
1517 error("Couldn't get local cwd: %s", strerror(errno));
1518 err = -1;
1519 break;
1520 }
1521 printf("Local working directory: %s\n", path_buf);
1522 break;
1523 case I_QUIT:
1524 /* Processed below */
1525 break;
1526 case I_HELP:
1527 help();
1528 break;
1529 case I_VERSION:
1530 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1531 break;
1532 case I_PROGRESS:
1533 showprogress = !showprogress;
1534 if (showprogress)
1535 printf("Progress meter enabled\n");
1536 else
1537 printf("Progress meter disabled\n");
1538 break;
1539 default:
1540 fatal("%d is not implemented", cmdnum);
1541 }
1542
1543 if (g.gl_pathc)
1544 globfree(&g);
Darren Tuckera627d422013-06-02 07:31:17 +10001545 free(path1);
1546 free(path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001547
1548 /* If an unignored error occurs in batch mode we should abort. */
1549 if (err_abort && err != 0)
1550 return (-1);
1551 else if (cmdnum == I_QUIT)
1552 return (1);
1553
1554 return (0);
1555}
1556
Darren Tucker2d963d82004-11-07 20:04:10 +11001557#ifdef USE_LIBEDIT
1558static char *
1559prompt(EditLine *el)
1560{
1561 return ("sftp> ");
1562}
Darren Tucker2d963d82004-11-07 20:04:10 +11001563
Darren Tucker909d8582010-01-08 19:02:40 +11001564/* Display entries in 'list' after skipping the first 'len' chars */
1565static void
1566complete_display(char **list, u_int len)
1567{
1568 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1569 struct winsize ws;
1570 char *tmp;
1571
1572 /* Count entries for sort and find longest */
Damien Miller02e87802013-08-21 02:38:51 +10001573 for (y = 0; list[y]; y++)
Darren Tucker909d8582010-01-08 19:02:40 +11001574 m = MAX(m, strlen(list[y]));
1575
1576 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1577 width = ws.ws_col;
1578
1579 m = m > len ? m - len : 0;
1580 columns = width / (m + 2);
1581 columns = MAX(columns, 1);
1582 colspace = width / columns;
1583 colspace = MIN(colspace, width);
1584
1585 printf("\n");
1586 m = 1;
1587 for (y = 0; list[y]; y++) {
1588 llen = strlen(list[y]);
1589 tmp = llen > len ? list[y] + len : "";
1590 printf("%-*s", colspace, tmp);
1591 if (m >= columns) {
1592 printf("\n");
1593 m = 1;
1594 } else
1595 m++;
1596 }
1597 printf("\n");
1598}
1599
1600/*
1601 * Given a "list" of words that begin with a common prefix of "word",
1602 * attempt to find an autocompletion to extends "word" by the next
1603 * characters common to all entries in "list".
1604 */
1605static char *
1606complete_ambiguous(const char *word, char **list, size_t count)
1607{
1608 if (word == NULL)
1609 return NULL;
1610
1611 if (count > 0) {
1612 u_int y, matchlen = strlen(list[0]);
1613
1614 /* Find length of common stem */
1615 for (y = 1; list[y]; y++) {
1616 u_int x;
1617
Damien Miller02e87802013-08-21 02:38:51 +10001618 for (x = 0; x < matchlen; x++)
1619 if (list[0][x] != list[y][x])
Darren Tucker909d8582010-01-08 19:02:40 +11001620 break;
1621
1622 matchlen = x;
1623 }
1624
1625 if (matchlen > strlen(word)) {
1626 char *tmp = xstrdup(list[0]);
1627
Darren Tucker340d1682010-01-09 08:54:31 +11001628 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001629 return tmp;
1630 }
Damien Miller02e87802013-08-21 02:38:51 +10001631 }
Darren Tucker909d8582010-01-08 19:02:40 +11001632
1633 return xstrdup(word);
1634}
1635
1636/* Autocomplete a sftp command */
1637static int
1638complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1639 int terminated)
1640{
1641 u_int y, count = 0, cmdlen, tmplen;
1642 char *tmp, **list, argterm[3];
1643 const LineInfo *lf;
1644
1645 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1646
1647 /* No command specified: display all available commands */
1648 if (cmd == NULL) {
1649 for (y = 0; cmds[y].c; y++)
1650 list[count++] = xstrdup(cmds[y].c);
Damien Miller02e87802013-08-21 02:38:51 +10001651
Darren Tucker909d8582010-01-08 19:02:40 +11001652 list[count] = NULL;
1653 complete_display(list, 0);
1654
Damien Miller02e87802013-08-21 02:38:51 +10001655 for (y = 0; list[y] != NULL; y++)
1656 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001657 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001658 return count;
1659 }
1660
1661 /* Prepare subset of commands that start with "cmd" */
1662 cmdlen = strlen(cmd);
1663 for (y = 0; cmds[y].c; y++) {
Damien Miller02e87802013-08-21 02:38:51 +10001664 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
Darren Tucker909d8582010-01-08 19:02:40 +11001665 list[count++] = xstrdup(cmds[y].c);
1666 }
1667 list[count] = NULL;
1668
Damien Miller47d81152011-11-25 13:53:48 +11001669 if (count == 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10001670 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001671 return 0;
Damien Miller47d81152011-11-25 13:53:48 +11001672 }
Darren Tucker909d8582010-01-08 19:02:40 +11001673
1674 /* Complete ambigious command */
1675 tmp = complete_ambiguous(cmd, list, count);
1676 if (count > 1)
1677 complete_display(list, 0);
1678
Damien Miller02e87802013-08-21 02:38:51 +10001679 for (y = 0; list[y]; y++)
1680 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001681 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001682
1683 if (tmp != NULL) {
1684 tmplen = strlen(tmp);
1685 cmdlen = strlen(cmd);
1686 /* If cmd may be extended then do so */
1687 if (tmplen > cmdlen)
1688 if (el_insertstr(el, tmp + cmdlen) == -1)
1689 fatal("el_insertstr failed.");
1690 lf = el_line(el);
1691 /* Terminate argument cleanly */
1692 if (count == 1) {
1693 y = 0;
1694 if (!terminated)
1695 argterm[y++] = quote;
1696 if (lastarg || *(lf->cursor) != ' ')
1697 argterm[y++] = ' ';
1698 argterm[y] = '\0';
1699 if (y > 0 && el_insertstr(el, argterm) == -1)
1700 fatal("el_insertstr failed.");
1701 }
Darren Tuckera627d422013-06-02 07:31:17 +10001702 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001703 }
1704
1705 return count;
1706}
1707
1708/*
1709 * Determine whether a particular sftp command's arguments (if any)
1710 * represent local or remote files.
1711 */
1712static int
1713complete_is_remote(char *cmd) {
1714 int i;
1715
1716 if (cmd == NULL)
1717 return -1;
1718
1719 for (i = 0; cmds[i].c; i++) {
Damien Miller02e87802013-08-21 02:38:51 +10001720 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
Darren Tucker909d8582010-01-08 19:02:40 +11001721 return cmds[i].t;
1722 }
1723
1724 return -1;
1725}
1726
1727/* Autocomplete a filename "file" */
1728static int
1729complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1730 char *file, int remote, int lastarg, char quote, int terminated)
1731{
1732 glob_t g;
Darren Tuckerea647212013-06-06 08:19:09 +10001733 char *tmp, *tmp2, ins[8];
Darren Tucker17146d32012-10-05 10:46:16 +10001734 u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
Darren Tuckerea647212013-06-06 08:19:09 +10001735 int clen;
Darren Tucker909d8582010-01-08 19:02:40 +11001736 const LineInfo *lf;
Damien Miller02e87802013-08-21 02:38:51 +10001737
Darren Tucker909d8582010-01-08 19:02:40 +11001738 /* Glob from "file" location */
1739 if (file == NULL)
1740 tmp = xstrdup("*");
1741 else
1742 xasprintf(&tmp, "%s*", file);
1743
Darren Tucker191fcc62012-10-05 10:45:01 +10001744 /* Check if the path is absolute. */
1745 isabs = tmp[0] == '/';
1746
Darren Tucker909d8582010-01-08 19:02:40 +11001747 memset(&g, 0, sizeof(g));
1748 if (remote != LOCAL) {
1749 tmp = make_absolute(tmp, remote_path);
1750 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001751 } else
Darren Tucker909d8582010-01-08 19:02:40 +11001752 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001753
Darren Tucker909d8582010-01-08 19:02:40 +11001754 /* Determine length of pwd so we can trim completion display */
1755 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1756 /* Terminate counting on first unescaped glob metacharacter */
1757 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1758 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1759 hadglob = 1;
1760 break;
1761 }
1762 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1763 tmplen++;
1764 if (tmp[tmplen] == '/')
1765 pwdlen = tmplen + 1; /* track last seen '/' */
1766 }
Darren Tuckera627d422013-06-02 07:31:17 +10001767 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001768
Damien Miller02e87802013-08-21 02:38:51 +10001769 if (g.gl_matchc == 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001770 goto out;
1771
1772 if (g.gl_matchc > 1)
1773 complete_display(g.gl_pathv, pwdlen);
1774
1775 tmp = NULL;
1776 /* Don't try to extend globs */
1777 if (file == NULL || hadglob)
1778 goto out;
1779
1780 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
Darren Tucker191fcc62012-10-05 10:45:01 +10001781 tmp = path_strip(tmp2, isabs ? NULL : remote_path);
Darren Tuckera627d422013-06-02 07:31:17 +10001782 free(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001783
1784 if (tmp == NULL)
1785 goto out;
1786
1787 tmplen = strlen(tmp);
1788 filelen = strlen(file);
1789
Darren Tucker17146d32012-10-05 10:46:16 +10001790 /* Count the number of escaped characters in the input string. */
1791 cesc = isesc = 0;
1792 for (i = 0; i < filelen; i++) {
1793 if (!isesc && file[i] == '\\' && i + 1 < filelen){
1794 isesc = 1;
1795 cesc++;
1796 } else
1797 isesc = 0;
1798 }
1799
1800 if (tmplen > (filelen - cesc)) {
1801 tmp2 = tmp + filelen - cesc;
Damien Miller02e87802013-08-21 02:38:51 +10001802 len = strlen(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001803 /* quote argument on way out */
Darren Tuckerea647212013-06-06 08:19:09 +10001804 for (i = 0; i < len; i += clen) {
1805 if ((clen = mblen(tmp2 + i, len - i)) < 0 ||
1806 (size_t)clen > sizeof(ins) - 2)
1807 fatal("invalid multibyte character");
Darren Tucker909d8582010-01-08 19:02:40 +11001808 ins[0] = '\\';
Darren Tuckerea647212013-06-06 08:19:09 +10001809 memcpy(ins + 1, tmp2 + i, clen);
1810 ins[clen + 1] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001811 switch (tmp2[i]) {
1812 case '\'':
1813 case '"':
1814 case '\\':
1815 case '\t':
Darren Tuckerd78739a2010-10-24 10:56:32 +11001816 case '[':
Darren Tucker909d8582010-01-08 19:02:40 +11001817 case ' ':
Darren Tucker17146d32012-10-05 10:46:16 +10001818 case '#':
1819 case '*':
Darren Tucker909d8582010-01-08 19:02:40 +11001820 if (quote == '\0' || tmp2[i] == quote) {
1821 if (el_insertstr(el, ins) == -1)
1822 fatal("el_insertstr "
1823 "failed.");
1824 break;
1825 }
1826 /* FALLTHROUGH */
1827 default:
1828 if (el_insertstr(el, ins + 1) == -1)
1829 fatal("el_insertstr failed.");
1830 break;
1831 }
1832 }
1833 }
1834
1835 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11001836 if (g.gl_matchc == 1) {
1837 i = 0;
1838 if (!terminated)
1839 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11001840 if (*(lf->cursor - 1) != '/' &&
1841 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11001842 ins[i++] = ' ';
1843 ins[i] = '\0';
1844 if (i > 0 && el_insertstr(el, ins) == -1)
1845 fatal("el_insertstr failed.");
1846 }
Darren Tuckera627d422013-06-02 07:31:17 +10001847 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001848
1849 out:
1850 globfree(&g);
1851 return g.gl_matchc;
1852}
1853
1854/* tab-completion hook function, called via libedit */
1855static unsigned char
1856complete(EditLine *el, int ch)
1857{
Damien Miller02e87802013-08-21 02:38:51 +10001858 char **argv, *line, quote;
Damien Miller746d1a62013-07-18 16:13:02 +10001859 int argc, carg;
1860 u_int cursor, len, terminated, ret = CC_ERROR;
Darren Tucker909d8582010-01-08 19:02:40 +11001861 const LineInfo *lf;
1862 struct complete_ctx *complete_ctx;
1863
1864 lf = el_line(el);
1865 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1866 fatal("%s: el_get failed", __func__);
1867
1868 /* Figure out which argument the cursor points to */
1869 cursor = lf->cursor - lf->buffer;
1870 line = (char *)xmalloc(cursor + 1);
1871 memcpy(line, lf->buffer, cursor);
1872 line[cursor] = '\0';
1873 argv = makeargv(line, &carg, 1, &quote, &terminated);
Darren Tuckera627d422013-06-02 07:31:17 +10001874 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001875
1876 /* Get all the arguments on the line */
1877 len = lf->lastchar - lf->buffer;
1878 line = (char *)xmalloc(len + 1);
1879 memcpy(line, lf->buffer, len);
1880 line[len] = '\0';
1881 argv = makeargv(line, &argc, 1, NULL, NULL);
1882
1883 /* Ensure cursor is at EOL or a argument boundary */
1884 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1885 line[cursor] != '\n') {
Darren Tuckera627d422013-06-02 07:31:17 +10001886 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001887 return ret;
1888 }
1889
1890 if (carg == 0) {
1891 /* Show all available commands */
1892 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1893 ret = CC_REDISPLAY;
1894 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1895 /* Handle the command parsing */
1896 if (complete_cmd_parse(el, argv[0], argc == carg,
Damien Miller02e87802013-08-21 02:38:51 +10001897 quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001898 ret = CC_REDISPLAY;
1899 } else if (carg >= 1) {
1900 /* Handle file parsing */
1901 int remote = complete_is_remote(argv[0]);
1902 char *filematch = NULL;
1903
1904 if (carg > 1 && line[cursor-1] != ' ')
1905 filematch = argv[carg - 1];
1906
1907 if (remote != 0 &&
1908 complete_match(el, complete_ctx->conn,
1909 *complete_ctx->remote_pathp, filematch,
Damien Miller02e87802013-08-21 02:38:51 +10001910 remote, carg == argc, quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001911 ret = CC_REDISPLAY;
1912 }
1913
Damien Miller02e87802013-08-21 02:38:51 +10001914 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001915 return ret;
1916}
Darren Tuckere67f7db2010-01-08 19:50:02 +11001917#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11001918
Damien Miller20e1fab2004-02-18 14:30:55 +11001919int
Darren Tucker21063192010-01-08 17:10:36 +11001920interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001921{
Darren Tucker909d8582010-01-08 19:02:40 +11001922 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11001923 char *dir = NULL;
1924 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10001925 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11001926 EditLine *el = NULL;
1927#ifdef USE_LIBEDIT
1928 History *hl = NULL;
1929 HistEvent hev;
1930 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11001931 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11001932
1933 if (!batchmode && isatty(STDIN_FILENO)) {
1934 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1935 fatal("Couldn't initialise editline");
1936 if ((hl = history_init()) == NULL)
1937 fatal("Couldn't initialise editline history");
1938 history(hl, &hev, H_SETSIZE, 100);
1939 el_set(el, EL_HIST, history, hl);
1940
1941 el_set(el, EL_PROMPT, prompt);
1942 el_set(el, EL_EDITOR, "emacs");
1943 el_set(el, EL_TERMINAL, NULL);
1944 el_set(el, EL_SIGNAL, 1);
1945 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11001946
1947 /* Tab Completion */
Damien Miller02e87802013-08-21 02:38:51 +10001948 el_set(el, EL_ADDFN, "ftp-complete",
Darren Tuckerd78739a2010-10-24 10:56:32 +11001949 "Context sensitive argument completion", complete);
Darren Tucker909d8582010-01-08 19:02:40 +11001950 complete_ctx.conn = conn;
1951 complete_ctx.remote_pathp = &remote_path;
1952 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
1953 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11001954 }
1955#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001956
Darren Tucker909d8582010-01-08 19:02:40 +11001957 remote_path = do_realpath(conn, ".");
1958 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11001959 fatal("Need cwd");
1960
1961 if (file1 != NULL) {
1962 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11001963 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11001964
1965 if (remote_is_dir(conn, dir) && file2 == NULL) {
Damien Miller9303e652013-04-23 15:22:40 +10001966 if (!quiet)
1967 printf("Changing to: %s\n", dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11001968 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001969 if (parse_dispatch_command(conn, cmd,
1970 &remote_path, 1) != 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10001971 free(dir);
1972 free(remote_path);
1973 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001974 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11001975 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001976 } else {
Darren Tucker0af24052012-10-05 10:41:25 +10001977 /* XXX this is wrong wrt quoting */
Damien Miller0d032412013-07-25 11:56:52 +10001978 snprintf(cmd, sizeof cmd, "get%s %s%s%s",
1979 global_aflag ? " -a" : "", dir,
1980 file2 == NULL ? "" : " ",
1981 file2 == NULL ? "" : file2);
Darren Tucker909d8582010-01-08 19:02:40 +11001982 err = parse_dispatch_command(conn, cmd,
1983 &remote_path, 1);
Darren Tuckera627d422013-06-02 07:31:17 +10001984 free(dir);
1985 free(remote_path);
1986 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001987 return (err);
1988 }
Darren Tuckera627d422013-06-02 07:31:17 +10001989 free(dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11001990 }
1991
Damien Miller37294fb2005-07-17 17:18:49 +10001992 setlinebuf(stdout);
1993 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11001994
Damien Miller0e2c1022005-08-12 22:16:22 +10001995 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11001996 err = 0;
1997 for (;;) {
1998 char *cp;
1999
Darren Tuckercdf547a2004-05-24 10:12:19 +10002000 signal(SIGINT, SIG_IGN);
2001
Darren Tucker2d963d82004-11-07 20:04:10 +11002002 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002003 if (interactive)
2004 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11002005 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002006 if (interactive)
2007 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11002008 break;
2009 }
Damien Miller0e2c1022005-08-12 22:16:22 +10002010 if (!interactive) { /* Echo command */
2011 printf("sftp> %s", cmd);
2012 if (strlen(cmd) > 0 &&
2013 cmd[strlen(cmd) - 1] != '\n')
2014 printf("\n");
2015 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002016 } else {
2017#ifdef USE_LIBEDIT
2018 const char *line;
2019 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11002020
Darren Tucker909d8582010-01-08 19:02:40 +11002021 if ((line = el_gets(el, &count)) == NULL ||
2022 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002023 printf("\n");
2024 break;
2025 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002026 history(hl, &hev, H_ENTER, line);
2027 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
2028 fprintf(stderr, "Error: input line too long\n");
2029 continue;
2030 }
2031#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002032 }
2033
Damien Miller20e1fab2004-02-18 14:30:55 +11002034 cp = strrchr(cmd, '\n');
2035 if (cp)
2036 *cp = '\0';
2037
Darren Tuckercdf547a2004-05-24 10:12:19 +10002038 /* Handle user interrupts gracefully during commands */
2039 interrupted = 0;
2040 signal(SIGINT, cmd_interrupt);
2041
Darren Tucker909d8582010-01-08 19:02:40 +11002042 err = parse_dispatch_command(conn, cmd, &remote_path,
2043 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11002044 if (err != 0)
2045 break;
2046 }
Darren Tuckera627d422013-06-02 07:31:17 +10002047 free(remote_path);
2048 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002049
Tim Rice027e8b12005-08-15 14:52:50 -07002050#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10002051 if (el != NULL)
2052 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07002053#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10002054
Damien Miller20e1fab2004-02-18 14:30:55 +11002055 /* err == 1 signifies normal "quit" exit */
2056 return (err >= 0 ? 0 : -1);
2057}
Damien Miller62d57f62003-01-10 21:43:24 +11002058
Ben Lindstrombba81212001-06-25 05:01:22 +00002059static void
Damien Millercc685c12003-06-04 22:51:38 +10002060connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11002061{
2062 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002063
Damien Miller33804262001-02-04 23:20:18 +11002064#ifdef USE_PIPES
2065 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002066
Damien Miller33804262001-02-04 23:20:18 +11002067 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2068 fatal("pipe: %s", strerror(errno));
2069 *in = pin[0];
2070 *out = pout[1];
2071 c_in = pout[0];
2072 c_out = pin[1];
2073#else /* USE_PIPES */
2074 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002075
Damien Miller33804262001-02-04 23:20:18 +11002076 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2077 fatal("socketpair: %s", strerror(errno));
2078 *in = *out = inout[0];
2079 c_in = c_out = inout[1];
2080#endif /* USE_PIPES */
2081
Damien Millercc685c12003-06-04 22:51:38 +10002082 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002083 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002084 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002085 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2086 (dup2(c_out, STDOUT_FILENO) == -1)) {
2087 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002088 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002089 }
2090 close(*in);
2091 close(*out);
2092 close(c_in);
2093 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002094
2095 /*
2096 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002097 * ignore SIGINT if we want to gracefully abort commands,
2098 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002099 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2100 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002101 */
2102 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002103 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002104 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002105 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002106 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002107 }
2108
Damien Millercc685c12003-06-04 22:51:38 +10002109 signal(SIGTERM, killchild);
2110 signal(SIGINT, killchild);
2111 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11002112 close(c_in);
2113 close(c_out);
2114}
2115
Ben Lindstrombba81212001-06-25 05:01:22 +00002116static void
Damien Miller33804262001-02-04 23:20:18 +11002117usage(void)
2118{
Damien Miller025e01c2002-02-08 22:06:29 +11002119 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002120
Ben Lindstrom1e243242001-09-18 05:38:44 +00002121 fprintf(stderr,
Damien Millerc6895c52013-08-21 02:40:21 +10002122 "usage: %s [-1246aCpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002123 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002124 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002125 " [-o ssh_option] [-P port] [-R num_requests] "
2126 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002127 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002128 " %s [user@]host[:file ...]\n"
2129 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002130 " %s -b batchfile [user@]host\n",
2131 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002132 exit(1);
2133}
2134
Kevin Stevesef4eea92001-02-05 12:42:17 +00002135int
Damien Miller33804262001-02-04 23:20:18 +11002136main(int argc, char **argv)
2137{
Damien Miller956f3fb2003-01-10 21:40:00 +11002138 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002139 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002140 int debug_level = 0, sshver = 2;
2141 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002142 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002143 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002144 LogLevel ll = SYSLOG_LEVEL_INFO;
2145 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002146 extern int optind;
2147 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002148 struct sftp_conn *conn;
2149 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2150 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002151 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002152
Darren Tuckerce321d82005-10-03 18:11:24 +10002153 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2154 sanitise_stdfd();
Darren Tuckerea647212013-06-06 08:19:09 +10002155 setlocale(LC_CTYPE, "");
Darren Tuckerce321d82005-10-03 18:11:24 +10002156
Damien Miller59d3d5b2003-08-22 09:34:41 +10002157 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002158 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002159 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002160 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002161 addargs(&args, "-oForwardX11 no");
2162 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002163 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002164 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002165
Ben Lindstrom387c4722001-05-08 20:27:25 +00002166 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002167 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002168
Darren Tucker282b4022009-10-07 08:23:06 +11002169 while ((ch = getopt(argc, argv,
Damien Miller0d032412013-07-25 11:56:52 +10002170 "1246ahpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002171 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002172 /* Passed through to ssh(1) */
2173 case '4':
2174 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002175 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002176 addargs(&args, "-%c", ch);
2177 break;
2178 /* Passed through to ssh(1) with argument */
2179 case 'F':
2180 case 'c':
2181 case 'i':
2182 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002183 addargs(&args, "-%c", ch);
2184 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002185 break;
2186 case 'q':
Damien Miller9303e652013-04-23 15:22:40 +10002187 ll = SYSLOG_LEVEL_ERROR;
2188 quiet = 1;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002189 showprogress = 0;
2190 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002191 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002192 case 'P':
2193 addargs(&args, "-oPort %s", optarg);
2194 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002195 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002196 if (debug_level < 3) {
2197 addargs(&args, "-v");
2198 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2199 }
2200 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002201 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002202 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002203 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002204 if (sftp_server == NULL)
2205 sftp_server = _PATH_SFTP_SERVER;
2206 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002207 case '2':
2208 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002209 break;
Damien Miller0d032412013-07-25 11:56:52 +10002210 case 'a':
2211 global_aflag = 1;
2212 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002213 case 'B':
2214 copy_buffer_len = strtol(optarg, &cp, 10);
2215 if (copy_buffer_len == 0 || *cp != '\0')
2216 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002217 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002218 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002219 if (batchmode)
2220 fatal("Batch file already specified.");
2221
2222 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002223 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002224 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002225 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002226 showprogress = 0;
Damien Miller9303e652013-04-23 15:22:40 +10002227 quiet = batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002228 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002229 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002230 case 'p':
2231 global_pflag = 1;
2232 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002233 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002234 sftp_direct = optarg;
2235 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002236 case 'l':
2237 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2238 &errstr);
2239 if (errstr != NULL)
2240 usage();
2241 limit_kbps *= 1024; /* kbps */
2242 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002243 case 'r':
2244 global_rflag = 1;
2245 break;
Damien Miller16a13332002-02-13 14:03:56 +11002246 case 'R':
2247 num_requests = strtol(optarg, &cp, 10);
2248 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002249 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002250 optarg);
2251 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002252 case 's':
2253 sftp_server = optarg;
2254 break;
2255 case 'S':
2256 ssh_program = optarg;
2257 replacearg(&args, 0, "%s", ssh_program);
2258 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002259 case 'h':
2260 default:
Damien Miller33804262001-02-04 23:20:18 +11002261 usage();
2262 }
2263 }
2264
Damien Millerc0f27d82004-03-08 23:12:19 +11002265 if (!isatty(STDERR_FILENO))
2266 showprogress = 0;
2267
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002268 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2269
Damien Millerd14ee1e2002-02-05 12:27:31 +11002270 if (sftp_direct == NULL) {
2271 if (optind == argc || argc > (optind + 2))
2272 usage();
Damien Miller33804262001-02-04 23:20:18 +11002273
Damien Millerd14ee1e2002-02-05 12:27:31 +11002274 userhost = xstrdup(argv[optind]);
2275 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002276
Ben Lindstromc276c122002-12-23 02:14:51 +00002277 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002278 host = userhost;
2279 else {
2280 *host++ = '\0';
2281 if (!userhost[0]) {
2282 fprintf(stderr, "Missing username\n");
2283 usage();
2284 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002285 addargs(&args, "-l");
2286 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002287 }
2288
Damien Millerec692032004-01-27 21:22:00 +11002289 if ((cp = colon(host)) != NULL) {
2290 *cp++ = '\0';
2291 file1 = cp;
2292 }
2293
Damien Millerd14ee1e2002-02-05 12:27:31 +11002294 host = cleanhostname(host);
2295 if (!*host) {
2296 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002297 usage();
2298 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002299
Damien Millerd14ee1e2002-02-05 12:27:31 +11002300 addargs(&args, "-oProtocol %d", sshver);
2301
2302 /* no subsystem if the server-spec contains a '/' */
2303 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2304 addargs(&args, "-s");
2305
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002306 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002307 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002308 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002309 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002310
Damien Millercc685c12003-06-04 22:51:38 +10002311 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002312 } else {
2313 args.list = NULL;
2314 addargs(&args, "sftp-server");
2315
Damien Millercc685c12003-06-04 22:51:38 +10002316 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002317 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002318 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002319
Damien Miller65e42f82010-09-24 22:15:11 +10002320 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002321 if (conn == NULL)
2322 fatal("Couldn't initialise connection to server");
2323
Damien Miller9303e652013-04-23 15:22:40 +10002324 if (!quiet) {
Darren Tucker21063192010-01-08 17:10:36 +11002325 if (sftp_direct == NULL)
2326 fprintf(stderr, "Connected to %s.\n", host);
2327 else
2328 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2329 }
2330
2331 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002332
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002333#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002334 shutdown(in, SHUT_RDWR);
2335 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002336#endif
2337
Damien Miller33804262001-02-04 23:20:18 +11002338 close(in);
2339 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002340 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002341 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002342
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002343 while (waitpid(sshpid, NULL, 0) == -1)
2344 if (errno != EINTR)
2345 fatal("Couldn't wait for ssh process: %s",
2346 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002347
Damien Miller956f3fb2003-01-10 21:40:00 +11002348 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002349}