blob: 66ab2b0d4e1e11e6e79d4a01473e5110029974a9 [file] [log] [blame]
Damien Millerc7dba122013-08-21 02:41:15 +10001/* $OpenBSD: sftp.c,v 1.152 2013/08/08 05:04:03 djm Exp $ */
Damien Miller33804262001-02-04 23:20:18 +11002/*
Damien Miller4e60ed72004-02-17 17:07:59 +11003 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
Damien Miller33804262001-02-04 23:20:18 +11004 *
Damien Miller4e60ed72004-02-17 17:07:59 +11005 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
Damien Miller33804262001-02-04 23:20:18 +11008 *
Damien Miller4e60ed72004-02-17 17:07:59 +11009 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Damien Miller33804262001-02-04 23:20:18 +110016 */
17
18#include "includes.h"
19
Damien Miller9cf6d072006-03-15 11:29:24 +110020#include <sys/types.h>
Damien Millerd7834352006-08-05 12:39:39 +100021#include <sys/ioctl.h>
Damien Millerf17883e2006-03-15 11:45:54 +110022#ifdef HAVE_SYS_STAT_H
23# include <sys/stat.h>
24#endif
Damien Miller8dbffe72006-08-05 11:02:17 +100025#include <sys/param.h>
Damien Millere3b60b52006-07-10 21:08:03 +100026#include <sys/socket.h>
Damien Miller9cf6d072006-03-15 11:29:24 +110027#include <sys/wait.h>
Darren Tucker5b2e2ba2008-06-08 09:25:28 +100028#ifdef HAVE_SYS_STATVFS_H
Damien Millerd671e5a2008-05-19 14:53:33 +100029#include <sys/statvfs.h>
Darren Tucker5b2e2ba2008-06-08 09:25:28 +100030#endif
Darren Tucker2d963d82004-11-07 20:04:10 +110031
Damien Miller1cbc2922007-10-26 14:27:45 +100032#include <ctype.h>
Darren Tucker39972492006-07-12 22:22:46 +100033#include <errno.h>
34
Damien Miller03e20032006-03-15 11:16:59 +110035#ifdef HAVE_PATHS_H
Damien Millera9263d02006-03-15 11:18:26 +110036# include <paths.h>
Damien Miller03e20032006-03-15 11:16:59 +110037#endif
Darren Tucker1b0dd172009-10-07 08:37:48 +110038#ifdef HAVE_LIBGEN_H
39#include <libgen.h>
40#endif
Darren Tuckerae133d42013-06-06 08:30:20 +100041#ifdef HAVE_LOCALE_H
42# include <locale.h>
43#endif
Darren Tucker2d963d82004-11-07 20:04:10 +110044#ifdef USE_LIBEDIT
45#include <histedit.h>
46#else
47typedef void EditLine;
48#endif
Damien Miller6ff3cad2006-03-15 11:52:09 +110049#include <signal.h>
Damien Millere7a1e5c2006-08-05 11:34:19 +100050#include <stdlib.h>
Damien Millera7a73ee2006-08-05 11:37:59 +100051#include <stdio.h>
Damien Millere3476ed2006-07-24 14:13:33 +100052#include <string.h>
Damien Millere6b3b612006-07-24 14:01:23 +100053#include <unistd.h>
Damien Millerd7834352006-08-05 12:39:39 +100054#include <stdarg.h>
Damien Miller33804262001-02-04 23:20:18 +110055
Damien Millera7058ec2008-05-20 08:57:06 +100056#ifdef HAVE_UTIL_H
57# include <util.h>
58#endif
59
Damien Miller33804262001-02-04 23:20:18 +110060#include "xmalloc.h"
61#include "log.h"
62#include "pathnames.h"
Ben Lindstrom4529b702001-05-03 23:39:53 +000063#include "misc.h"
Damien Miller33804262001-02-04 23:20:18 +110064
65#include "sftp.h"
Damien Millerd7834352006-08-05 12:39:39 +100066#include "buffer.h"
Damien Miller33804262001-02-04 23:20:18 +110067#include "sftp-common.h"
68#include "sftp-client.h"
Damien Millerd7d46bb2004-02-18 14:11:13 +110069
Darren Tucker21063192010-01-08 17:10:36 +110070#define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */
71#define DEFAULT_NUM_REQUESTS 64 /* # concurrent outstanding requests */
72
Damien Miller20e1fab2004-02-18 14:30:55 +110073/* File to read commands from */
74FILE* infile;
75
76/* Are we in batchfile mode? */
77int batchmode = 0;
78
Damien Miller20e1fab2004-02-18 14:30:55 +110079/* PID of ssh transport process */
80static pid_t sshpid = -1;
81
Damien Miller9303e652013-04-23 15:22:40 +100082/* Suppress diagnositic messages */
83int quiet = 0;
84
Damien Miller20e1fab2004-02-18 14:30:55 +110085/* This is set to 0 if the progressmeter is not desired. */
Damien Millerc0f27d82004-03-08 23:12:19 +110086int showprogress = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +110087
Darren Tucker1b0dd172009-10-07 08:37:48 +110088/* When this option is set, we always recursively download/upload directories */
89int global_rflag = 0;
90
Damien Miller0d032412013-07-25 11:56:52 +100091/* When this option is set, we resume download if possible */
92int global_aflag = 0;
93
Darren Tucker1b0dd172009-10-07 08:37:48 +110094/* When this option is set, the file transfers will always preserve times */
95int global_pflag = 0;
96
Darren Tuckercdf547a2004-05-24 10:12:19 +100097/* SIGINT received during command processing */
98volatile sig_atomic_t interrupted = 0;
99
Darren Tuckerb9123452004-06-22 13:06:45 +1000100/* I wish qsort() took a separate ctx for the comparison function...*/
101int sort_flag;
102
Darren Tucker909d8582010-01-08 19:02:40 +1100103/* Context used for commandline completion */
104struct complete_ctx {
105 struct sftp_conn *conn;
106 char **remote_pathp;
107};
108
Damien Miller20e1fab2004-02-18 14:30:55 +1100109int remote_glob(struct sftp_conn *, const char *, int,
110 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
Damien Miller33804262001-02-04 23:20:18 +1100111
Kevin Steves12888d12001-03-05 19:50:57 +0000112extern char *__progname;
Kevin Steves12888d12001-03-05 19:50:57 +0000113
Damien Miller20e1fab2004-02-18 14:30:55 +1100114/* Separators for interactive commands */
115#define WHITESPACE " \t\r\n"
Damien Millerd7686fd2001-02-10 00:40:03 +1100116
Darren Tuckerb9123452004-06-22 13:06:45 +1000117/* ls flags */
Darren Tucker2901e2d2010-01-13 22:44:06 +1100118#define LS_LONG_VIEW 0x0001 /* Full view ala ls -l */
119#define LS_SHORT_VIEW 0x0002 /* Single row view ala ls -1 */
120#define LS_NUMERIC_VIEW 0x0004 /* Long view with numeric uid/gid */
121#define LS_NAME_SORT 0x0008 /* Sort by name (default) */
122#define LS_TIME_SORT 0x0010 /* Sort by mtime */
123#define LS_SIZE_SORT 0x0020 /* Sort by file size */
124#define LS_REVERSE_SORT 0x0040 /* Reverse sort order */
125#define LS_SHOW_ALL 0x0080 /* Don't skip filenames starting with '.' */
126#define LS_SI_UNITS 0x0100 /* Display sizes as K, M, G, etc. */
Darren Tuckerb9123452004-06-22 13:06:45 +1000127
Darren Tucker2901e2d2010-01-13 22:44:06 +1100128#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS)
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000129#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
Damien Miller20e1fab2004-02-18 14:30:55 +1100130
131/* Commands for interactive mode */
Damien Miller02e87802013-08-21 02:38:51 +1000132enum sftp_command {
133 I_CHDIR = 1,
134 I_CHGRP,
135 I_CHMOD,
136 I_CHOWN,
137 I_DF,
138 I_GET,
139 I_HELP,
140 I_LCHDIR,
141 I_LINK,
142 I_LLS,
143 I_LMKDIR,
144 I_LPWD,
145 I_LS,
146 I_LUMASK,
147 I_MKDIR,
148 I_PUT,
149 I_PWD,
150 I_QUIT,
151 I_RENAME,
152 I_RM,
153 I_RMDIR,
154 I_SHELL,
155 I_SYMLINK,
156 I_VERSION,
157 I_PROGRESS,
158 I_REGET,
159};
Damien Miller20e1fab2004-02-18 14:30:55 +1100160
161struct CMD {
162 const char *c;
163 const int n;
Darren Tucker909d8582010-01-08 19:02:40 +1100164 const int t;
Damien Miller20e1fab2004-02-18 14:30:55 +1100165};
166
Darren Tucker909d8582010-01-08 19:02:40 +1100167/* Type of completion */
168#define NOARGS 0
169#define REMOTE 1
170#define LOCAL 2
171
Damien Miller20e1fab2004-02-18 14:30:55 +1100172static const struct CMD cmds[] = {
Darren Tucker909d8582010-01-08 19:02:40 +1100173 { "bye", I_QUIT, NOARGS },
174 { "cd", I_CHDIR, REMOTE },
175 { "chdir", I_CHDIR, REMOTE },
176 { "chgrp", I_CHGRP, REMOTE },
177 { "chmod", I_CHMOD, REMOTE },
178 { "chown", I_CHOWN, REMOTE },
179 { "df", I_DF, REMOTE },
180 { "dir", I_LS, REMOTE },
181 { "exit", I_QUIT, NOARGS },
182 { "get", I_GET, REMOTE },
183 { "help", I_HELP, NOARGS },
184 { "lcd", I_LCHDIR, LOCAL },
185 { "lchdir", I_LCHDIR, LOCAL },
186 { "lls", I_LLS, LOCAL },
187 { "lmkdir", I_LMKDIR, LOCAL },
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100188 { "ln", I_LINK, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100189 { "lpwd", I_LPWD, LOCAL },
190 { "ls", I_LS, REMOTE },
191 { "lumask", I_LUMASK, NOARGS },
192 { "mkdir", I_MKDIR, REMOTE },
Damien Miller099fc162010-05-10 11:56:50 +1000193 { "mget", I_GET, REMOTE },
194 { "mput", I_PUT, LOCAL },
Darren Tucker909d8582010-01-08 19:02:40 +1100195 { "progress", I_PROGRESS, NOARGS },
196 { "put", I_PUT, LOCAL },
197 { "pwd", I_PWD, REMOTE },
198 { "quit", I_QUIT, NOARGS },
Damien Miller0d032412013-07-25 11:56:52 +1000199 { "reget", I_REGET, REMOTE },
Darren Tucker909d8582010-01-08 19:02:40 +1100200 { "rename", I_RENAME, REMOTE },
201 { "rm", I_RM, REMOTE },
202 { "rmdir", I_RMDIR, REMOTE },
203 { "symlink", I_SYMLINK, REMOTE },
204 { "version", I_VERSION, NOARGS },
205 { "!", I_SHELL, NOARGS },
206 { "?", I_HELP, NOARGS },
207 { NULL, -1, -1 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100208};
209
Darren Tucker21063192010-01-08 17:10:36 +1100210int interactive_loop(struct sftp_conn *, char *file1, char *file2);
Damien Miller20e1fab2004-02-18 14:30:55 +1100211
Damien Millerb6c85fc2007-01-05 16:30:41 +1100212/* ARGSUSED */
Damien Miller20e1fab2004-02-18 14:30:55 +1100213static void
Darren Tuckercdf547a2004-05-24 10:12:19 +1000214killchild(int signo)
215{
Darren Tuckerba66df82005-01-24 21:57:40 +1100216 if (sshpid > 1) {
Darren Tuckercdf547a2004-05-24 10:12:19 +1000217 kill(sshpid, SIGTERM);
Darren Tuckerba66df82005-01-24 21:57:40 +1100218 waitpid(sshpid, NULL, 0);
219 }
Darren Tuckercdf547a2004-05-24 10:12:19 +1000220
221 _exit(1);
222}
223
Damien Millerb6c85fc2007-01-05 16:30:41 +1100224/* ARGSUSED */
Darren Tuckercdf547a2004-05-24 10:12:19 +1000225static void
226cmd_interrupt(int signo)
227{
228 const char msg[] = "\rInterrupt \n";
Darren Tuckere2f189a2004-12-06 22:45:53 +1100229 int olderrno = errno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000230
Darren Tuckerdbee3082013-05-16 20:32:29 +1000231 (void)write(STDERR_FILENO, msg, sizeof(msg) - 1);
Darren Tuckercdf547a2004-05-24 10:12:19 +1000232 interrupted = 1;
Darren Tuckere2f189a2004-12-06 22:45:53 +1100233 errno = olderrno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000234}
235
236static void
Damien Miller20e1fab2004-02-18 14:30:55 +1100237help(void)
238{
Damien Miller62fd18a2009-01-28 16:14:09 +1100239 printf("Available commands:\n"
240 "bye Quit sftp\n"
241 "cd path Change remote directory to 'path'\n"
242 "chgrp grp path Change group of file 'path' to 'grp'\n"
243 "chmod mode path Change permissions of file 'path' to 'mode'\n"
244 "chown own path Change owner of file 'path' to 'own'\n"
245 "df [-hi] [path] Display statistics for current directory or\n"
246 " filesystem containing 'path'\n"
247 "exit Quit sftp\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100248 "get [-Ppr] remote [local] Download file\n"
Damien Miller0d032412013-07-25 11:56:52 +1000249 "reget remote [local] Resume download file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100250 "help Display this help text\n"
251 "lcd path Change local directory to 'path'\n"
252 "lls [ls-options [path]] Display local directory listing\n"
253 "lmkdir path Create local directory\n"
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100254 "ln [-s] oldpath newpath Link remote file (-s for symlink)\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100255 "lpwd Print local working directory\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100256 "ls [-1afhlnrSt] [path] Display remote directory listing\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100257 "lumask umask Set local umask to 'umask'\n"
258 "mkdir path Create remote directory\n"
259 "progress Toggle display of progress meter\n"
Darren Tucker75fe6262010-01-15 11:42:51 +1100260 "put [-Ppr] local [remote] Upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100261 "pwd Display remote working directory\n"
262 "quit Quit sftp\n"
263 "rename oldpath newpath Rename remote file\n"
264 "rm path Delete remote file\n"
265 "rmdir path Remove remote directory\n"
266 "symlink oldpath newpath Symlink remote file\n"
267 "version Show SFTP version\n"
268 "!command Execute 'command' in local shell\n"
269 "! Escape to local shell\n"
270 "? Synonym for help\n");
Damien Miller20e1fab2004-02-18 14:30:55 +1100271}
272
273static void
274local_do_shell(const char *args)
275{
276 int status;
277 char *shell;
278 pid_t pid;
279
280 if (!*args)
281 args = NULL;
282
Damien Miller38d9a962010-10-07 22:07:11 +1100283 if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
Damien Miller20e1fab2004-02-18 14:30:55 +1100284 shell = _PATH_BSHELL;
285
286 if ((pid = fork()) == -1)
287 fatal("Couldn't fork: %s", strerror(errno));
288
289 if (pid == 0) {
290 /* XXX: child has pipe fds to ssh subproc open - issue? */
291 if (args) {
292 debug3("Executing %s -c \"%s\"", shell, args);
293 execl(shell, shell, "-c", args, (char *)NULL);
294 } else {
295 debug3("Executing %s", shell);
296 execl(shell, shell, (char *)NULL);
297 }
298 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
299 strerror(errno));
300 _exit(1);
301 }
302 while (waitpid(pid, &status, 0) == -1)
303 if (errno != EINTR)
304 fatal("Couldn't wait for child: %s", strerror(errno));
305 if (!WIFEXITED(status))
Damien Miller55b04f12006-03-26 14:23:17 +1100306 error("Shell exited abnormally");
Damien Miller20e1fab2004-02-18 14:30:55 +1100307 else if (WEXITSTATUS(status))
308 error("Shell exited with status %d", WEXITSTATUS(status));
309}
310
311static void
312local_do_ls(const char *args)
313{
314 if (!args || !*args)
315 local_do_shell(_PATH_LS);
316 else {
317 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
318 char *buf = xmalloc(len);
319
320 /* XXX: quoting - rip quoting code from ftp? */
321 snprintf(buf, len, _PATH_LS " %s", args);
322 local_do_shell(buf);
Darren Tuckera627d422013-06-02 07:31:17 +1000323 free(buf);
Damien Miller20e1fab2004-02-18 14:30:55 +1100324 }
325}
326
327/* Strip one path (usually the pwd) from the start of another */
328static char *
329path_strip(char *path, char *strip)
330{
331 size_t len;
332
333 if (strip == NULL)
334 return (xstrdup(path));
335
336 len = strlen(strip);
Darren Tuckere2f189a2004-12-06 22:45:53 +1100337 if (strncmp(path, strip, len) == 0) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100338 if (strip[len - 1] != '/' && path[len] == '/')
339 len++;
340 return (xstrdup(path + len));
341 }
342
343 return (xstrdup(path));
344}
345
346static char *
Damien Miller20e1fab2004-02-18 14:30:55 +1100347make_absolute(char *p, char *pwd)
348{
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000349 char *abs_str;
Damien Miller20e1fab2004-02-18 14:30:55 +1100350
351 /* Derelativise */
352 if (p && p[0] != '/') {
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000353 abs_str = path_append(pwd, p);
Darren Tuckera627d422013-06-02 07:31:17 +1000354 free(p);
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000355 return(abs_str);
Damien Miller20e1fab2004-02-18 14:30:55 +1100356 } else
357 return(p);
358}
359
360static int
Damien Miller0d032412013-07-25 11:56:52 +1000361parse_getput_flags(const char *cmd, char **argv, int argc,
362 int *aflag, int *pflag, int *rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100363{
Damien Millerf184bcf2008-06-29 22:45:13 +1000364 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000365 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100366
Damien Miller1cbc2922007-10-26 14:27:45 +1000367 optind = optreset = 1;
368 opterr = 0;
369
Damien Miller0d032412013-07-25 11:56:52 +1000370 *aflag = *rflag = *pflag = 0;
371 while ((ch = getopt(argc, argv, "aPpRr")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000372 switch (ch) {
Damien Miller0d032412013-07-25 11:56:52 +1000373 case 'a':
374 *aflag = 1;
375 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100376 case 'p':
377 case 'P':
378 *pflag = 1;
379 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100380 case 'r':
381 case 'R':
382 *rflag = 1;
383 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100384 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000385 error("%s: Invalid flag -%c", cmd, optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000386 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100387 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100388 }
389
Damien Miller1cbc2922007-10-26 14:27:45 +1000390 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100391}
392
393static int
Darren Tuckeraf1f9092010-12-05 09:02:47 +1100394parse_link_flags(const char *cmd, char **argv, int argc, int *sflag)
395{
396 extern int opterr, optind, optopt, optreset;
397 int ch;
398
399 optind = optreset = 1;
400 opterr = 0;
401
402 *sflag = 0;
403 while ((ch = getopt(argc, argv, "s")) != -1) {
404 switch (ch) {
405 case 's':
406 *sflag = 1;
407 break;
408 default:
409 error("%s: Invalid flag -%c", cmd, optopt);
410 return -1;
411 }
412 }
413
414 return optind;
415}
416
417static int
Damien Millerc7dba122013-08-21 02:41:15 +1000418parse_rename_flags(const char *cmd, char **argv, int argc, int *lflag)
419{
420 extern int opterr, optind, optopt, optreset;
421 int ch;
422
423 optind = optreset = 1;
424 opterr = 0;
425
426 *lflag = 0;
427 while ((ch = getopt(argc, argv, "l")) != -1) {
428 switch (ch) {
429 case 'l':
430 *lflag = 1;
431 break;
432 default:
433 error("%s: Invalid flag -%c", cmd, optopt);
434 return -1;
435 }
436 }
437
438 return optind;
439}
440
441static int
Damien Miller1cbc2922007-10-26 14:27:45 +1000442parse_ls_flags(char **argv, int argc, int *lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100443{
Damien Millerf184bcf2008-06-29 22:45:13 +1000444 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000445 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100446
Damien Miller1cbc2922007-10-26 14:27:45 +1000447 optind = optreset = 1;
448 opterr = 0;
449
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000450 *lflag = LS_NAME_SORT;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100451 while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000452 switch (ch) {
453 case '1':
454 *lflag &= ~VIEW_FLAGS;
455 *lflag |= LS_SHORT_VIEW;
456 break;
457 case 'S':
458 *lflag &= ~SORT_FLAGS;
459 *lflag |= LS_SIZE_SORT;
460 break;
461 case 'a':
462 *lflag |= LS_SHOW_ALL;
463 break;
464 case 'f':
465 *lflag &= ~SORT_FLAGS;
466 break;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100467 case 'h':
468 *lflag |= LS_SI_UNITS;
469 break;
Damien Miller1cbc2922007-10-26 14:27:45 +1000470 case 'l':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100471 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000472 *lflag |= LS_LONG_VIEW;
473 break;
474 case 'n':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100475 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000476 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
477 break;
478 case 'r':
479 *lflag |= LS_REVERSE_SORT;
480 break;
481 case 't':
482 *lflag &= ~SORT_FLAGS;
483 *lflag |= LS_TIME_SORT;
484 break;
485 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000486 error("ls: Invalid flag -%c", optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000487 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100488 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100489 }
490
Damien Miller1cbc2922007-10-26 14:27:45 +1000491 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100492}
493
494static int
Damien Millerd671e5a2008-05-19 14:53:33 +1000495parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
496{
Damien Millerf184bcf2008-06-29 22:45:13 +1000497 extern int opterr, optind, optopt, optreset;
Damien Millerd671e5a2008-05-19 14:53:33 +1000498 int ch;
499
500 optind = optreset = 1;
501 opterr = 0;
502
503 *hflag = *iflag = 0;
504 while ((ch = getopt(argc, argv, "hi")) != -1) {
505 switch (ch) {
506 case 'h':
507 *hflag = 1;
508 break;
509 case 'i':
510 *iflag = 1;
511 break;
512 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000513 error("%s: Invalid flag -%c", cmd, optopt);
Damien Millerd671e5a2008-05-19 14:53:33 +1000514 return -1;
515 }
516 }
517
518 return optind;
519}
520
521static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100522is_dir(char *path)
523{
524 struct stat sb;
525
526 /* XXX: report errors? */
527 if (stat(path, &sb) == -1)
528 return(0);
529
Darren Tucker1e80e402006-09-21 12:59:33 +1000530 return(S_ISDIR(sb.st_mode));
Damien Miller20e1fab2004-02-18 14:30:55 +1100531}
532
533static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100534remote_is_dir(struct sftp_conn *conn, char *path)
535{
536 Attrib *a;
537
538 /* XXX: report errors? */
539 if ((a = do_stat(conn, path, 1)) == NULL)
540 return(0);
541 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
542 return(0);
Darren Tucker1e80e402006-09-21 12:59:33 +1000543 return(S_ISDIR(a->perm));
Damien Miller20e1fab2004-02-18 14:30:55 +1100544}
545
Darren Tucker1b0dd172009-10-07 08:37:48 +1100546/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
Damien Miller20e1fab2004-02-18 14:30:55 +1100547static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100548pathname_is_dir(char *pathname)
549{
550 size_t l = strlen(pathname);
551
552 return l > 0 && pathname[l - 1] == '/';
553}
554
555static int
556process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
Damien Miller0d032412013-07-25 11:56:52 +1000557 int pflag, int rflag, int resume)
Damien Miller20e1fab2004-02-18 14:30:55 +1100558{
559 char *abs_src = NULL;
560 char *abs_dst = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100561 glob_t g;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100562 char *filename, *tmp=NULL;
563 int i, err = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100564
565 abs_src = xstrdup(src);
566 abs_src = make_absolute(abs_src, pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +1100567 memset(&g, 0, sizeof(g));
Darren Tucker1b0dd172009-10-07 08:37:48 +1100568
Damien Miller20e1fab2004-02-18 14:30:55 +1100569 debug3("Looking up %s", abs_src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100570 if (remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100571 error("File \"%s\" not found.", abs_src);
572 err = -1;
573 goto out;
574 }
575
Darren Tucker1b0dd172009-10-07 08:37:48 +1100576 /*
577 * If multiple matches then dst must be a directory or
578 * unspecified.
579 */
580 if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
581 error("Multiple source paths, but destination "
582 "\"%s\" is not a directory", dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100583 err = -1;
584 goto out;
585 }
586
Darren Tuckercdf547a2004-05-24 10:12:19 +1000587 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100588 tmp = xstrdup(g.gl_pathv[i]);
589 if ((filename = basename(tmp)) == NULL) {
590 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000591 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100592 err = -1;
593 goto out;
594 }
595
596 if (g.gl_matchc == 1 && dst) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100597 if (is_dir(dst)) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100598 abs_dst = path_append(dst, filename);
599 } else {
Damien Miller20e1fab2004-02-18 14:30:55 +1100600 abs_dst = xstrdup(dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100601 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100602 } else if (dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100603 abs_dst = path_append(dst, filename);
604 } else {
605 abs_dst = xstrdup(filename);
606 }
Darren Tuckera627d422013-06-02 07:31:17 +1000607 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100608
Damien Miller0d032412013-07-25 11:56:52 +1000609 resume |= global_aflag;
610 if (!quiet && resume)
611 printf("Resuming %s to %s\n", g.gl_pathv[i], abs_dst);
612 else if (!quiet && !resume)
Damien Miller9303e652013-04-23 15:22:40 +1000613 printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100614 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
Damien Miller0d032412013-07-25 11:56:52 +1000615 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
616 pflag || global_pflag, 1, resume) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100617 err = -1;
618 } else {
619 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
Damien Miller0d032412013-07-25 11:56:52 +1000620 pflag || global_pflag, resume) == -1)
Darren Tucker1b0dd172009-10-07 08:37:48 +1100621 err = -1;
622 }
Darren Tuckera627d422013-06-02 07:31:17 +1000623 free(abs_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100624 abs_dst = NULL;
625 }
626
627out:
Darren Tuckera627d422013-06-02 07:31:17 +1000628 free(abs_src);
Damien Miller20e1fab2004-02-18 14:30:55 +1100629 globfree(&g);
630 return(err);
631}
632
633static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100634process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
635 int pflag, int rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100636{
637 char *tmp_dst = NULL;
638 char *abs_dst = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100639 char *tmp = NULL, *filename = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100640 glob_t g;
641 int err = 0;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100642 int i, dst_is_dir = 1;
Damien Milleraec5cf82008-02-10 22:26:24 +1100643 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100644
645 if (dst) {
646 tmp_dst = xstrdup(dst);
647 tmp_dst = make_absolute(tmp_dst, pwd);
648 }
649
650 memset(&g, 0, sizeof(g));
651 debug3("Looking up %s", src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100652 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100653 error("File \"%s\" not found.", src);
654 err = -1;
655 goto out;
656 }
657
Darren Tucker1b0dd172009-10-07 08:37:48 +1100658 /* If we aren't fetching to pwd then stash this status for later */
659 if (tmp_dst != NULL)
660 dst_is_dir = remote_is_dir(conn, tmp_dst);
661
Damien Miller20e1fab2004-02-18 14:30:55 +1100662 /* If multiple matches, dst may be directory or unspecified */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100663 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
664 error("Multiple paths match, but destination "
665 "\"%s\" is not a directory", tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100666 err = -1;
667 goto out;
668 }
669
Darren Tuckercdf547a2004-05-24 10:12:19 +1000670 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Milleraec5cf82008-02-10 22:26:24 +1100671 if (stat(g.gl_pathv[i], &sb) == -1) {
672 err = -1;
673 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
674 continue;
675 }
Damien Miller02e87802013-08-21 02:38:51 +1000676
Darren Tucker1b0dd172009-10-07 08:37:48 +1100677 tmp = xstrdup(g.gl_pathv[i]);
678 if ((filename = basename(tmp)) == NULL) {
679 error("basename %s: %s", tmp, strerror(errno));
Darren Tuckera627d422013-06-02 07:31:17 +1000680 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100681 err = -1;
682 goto out;
683 }
684
685 if (g.gl_matchc == 1 && tmp_dst) {
686 /* If directory specified, append filename */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100687 if (dst_is_dir)
688 abs_dst = path_append(tmp_dst, filename);
689 else
Damien Miller20e1fab2004-02-18 14:30:55 +1100690 abs_dst = xstrdup(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100691 } else if (tmp_dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100692 abs_dst = path_append(tmp_dst, filename);
693 } else {
694 abs_dst = make_absolute(xstrdup(filename), pwd);
695 }
Darren Tuckera627d422013-06-02 07:31:17 +1000696 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100697
Damien Miller9303e652013-04-23 15:22:40 +1000698 if (!quiet)
699 printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100700 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
701 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
702 pflag || global_pflag, 1) == -1)
703 err = -1;
704 } else {
705 if (do_upload(conn, g.gl_pathv[i], abs_dst,
706 pflag || global_pflag) == -1)
707 err = -1;
708 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100709 }
710
711out:
Darren Tuckera627d422013-06-02 07:31:17 +1000712 free(abs_dst);
713 free(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100714 globfree(&g);
715 return(err);
716}
717
718static int
719sdirent_comp(const void *aa, const void *bb)
720{
721 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
722 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000723 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100724
Darren Tuckerb9123452004-06-22 13:06:45 +1000725#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000726 if (sort_flag & LS_NAME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000727 return (rmul * strcmp(a->filename, b->filename));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000728 else if (sort_flag & LS_TIME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000729 return (rmul * NCMP(a->a.mtime, b->a.mtime));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000730 else if (sort_flag & LS_SIZE_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000731 return (rmul * NCMP(a->a.size, b->a.size));
732
733 fatal("Unknown ls sort type");
Damien Miller20e1fab2004-02-18 14:30:55 +1100734}
735
736/* sftp ls.1 replacement for directories */
737static int
738do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
739{
Damien Millereccb9de2005-06-17 12:59:34 +1000740 int n;
741 u_int c = 1, colspace = 0, columns = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100742 SFTP_DIRENT **d;
743
744 if ((n = do_readdir(conn, path, &d)) != 0)
745 return (n);
746
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000747 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000748 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100749 struct winsize ws;
750 char *tmp;
751
752 /* Count entries for sort and find longest filename */
Darren Tucker9a526452004-06-22 13:09:55 +1000753 for (n = 0; d[n] != NULL; n++) {
754 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
755 m = MAX(m, strlen(d[n]->filename));
756 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100757
758 /* Add any subpath that also needs to be counted */
759 tmp = path_strip(path, strip_path);
760 m += strlen(tmp);
Darren Tuckera627d422013-06-02 07:31:17 +1000761 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100762
763 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
764 width = ws.ws_col;
765
766 columns = width / (m + 2);
767 columns = MAX(columns, 1);
768 colspace = width / columns;
769 colspace = MIN(colspace, width);
770 }
771
Darren Tuckerb9123452004-06-22 13:06:45 +1000772 if (lflag & SORT_FLAGS) {
Damien Miller653b93b2005-11-05 15:15:23 +1100773 for (n = 0; d[n] != NULL; n++)
774 ; /* count entries */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000775 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
Darren Tuckerb9123452004-06-22 13:06:45 +1000776 qsort(d, n, sizeof(*d), sdirent_comp);
777 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100778
Darren Tuckercdf547a2004-05-24 10:12:19 +1000779 for (n = 0; d[n] != NULL && !interrupted; n++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100780 char *tmp, *fname;
781
Darren Tucker9a526452004-06-22 13:09:55 +1000782 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
783 continue;
784
Damien Miller20e1fab2004-02-18 14:30:55 +1100785 tmp = path_append(path, d[n]->filename);
786 fname = path_strip(tmp, strip_path);
Darren Tuckera627d422013-06-02 07:31:17 +1000787 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100788
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000789 if (lflag & LS_LONG_VIEW) {
Darren Tucker2901e2d2010-01-13 22:44:06 +1100790 if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000791 char *lname;
792 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100793
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000794 memset(&sb, 0, sizeof(sb));
795 attrib_to_stat(&d[n]->a, &sb);
Darren Tucker2901e2d2010-01-13 22:44:06 +1100796 lname = ls_file(fname, &sb, 1,
797 (lflag & LS_SI_UNITS));
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000798 printf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000799 free(lname);
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000800 } else
801 printf("%s\n", d[n]->longname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100802 } else {
803 printf("%-*s", colspace, fname);
804 if (c >= columns) {
805 printf("\n");
806 c = 1;
807 } else
808 c++;
809 }
810
Darren Tuckera627d422013-06-02 07:31:17 +1000811 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100812 }
813
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000814 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100815 printf("\n");
816
817 free_sftp_dirents(d);
818 return (0);
819}
820
821/* sftp ls.1 replacement which handles path globs */
822static int
823do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
824 int lflag)
825{
Damien Millera6e121a2010-10-07 21:39:17 +1100826 char *fname, *lname;
Damien Miller68e2e562010-10-07 21:39:55 +1100827 glob_t g;
828 int err;
829 struct winsize ws;
830 u_int i, c = 1, colspace = 0, columns = 1, m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100831
832 memset(&g, 0, sizeof(g));
833
Damien Millera6e121a2010-10-07 21:39:17 +1100834 if (remote_glob(conn, path,
Damien Millerd7be70d2011-09-22 21:43:06 +1000835 GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE|GLOB_KEEPSTAT|GLOB_NOSORT,
836 NULL, &g) ||
Damien Millera6e121a2010-10-07 21:39:17 +1100837 (g.gl_pathc && !g.gl_matchc)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100838 if (g.gl_pathc)
839 globfree(&g);
Damien Miller20e1fab2004-02-18 14:30:55 +1100840 error("Can't ls: \"%s\" not found", path);
Damien Millera6e121a2010-10-07 21:39:17 +1100841 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100842 }
843
Darren Tuckercdf547a2004-05-24 10:12:19 +1000844 if (interrupted)
845 goto out;
846
Damien Miller20e1fab2004-02-18 14:30:55 +1100847 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100848 * If the glob returns a single match and it is a directory,
849 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100850 */
Damien Millera6e121a2010-10-07 21:39:17 +1100851 if (g.gl_matchc == 1 && g.gl_statv[0] != NULL &&
852 S_ISDIR(g.gl_statv[0]->st_mode)) {
853 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
854 globfree(&g);
855 return err;
Damien Miller20e1fab2004-02-18 14:30:55 +1100856 }
857
Damien Miller68e2e562010-10-07 21:39:55 +1100858 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
859 width = ws.ws_col;
Damien Miller20e1fab2004-02-18 14:30:55 +1100860
Damien Miller68e2e562010-10-07 21:39:55 +1100861 if (!(lflag & LS_SHORT_VIEW)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100862 /* Count entries for sort and find longest filename */
863 for (i = 0; g.gl_pathv[i]; i++)
864 m = MAX(m, strlen(g.gl_pathv[i]));
865
Damien Miller20e1fab2004-02-18 14:30:55 +1100866 columns = width / (m + 2);
867 columns = MAX(columns, 1);
868 colspace = width / columns;
869 }
870
Damien Millerea858292012-06-30 08:33:32 +1000871 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100872 fname = path_strip(g.gl_pathv[i], strip_path);
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000873 if (lflag & LS_LONG_VIEW) {
Damien Millera6e121a2010-10-07 21:39:17 +1100874 if (g.gl_statv[i] == NULL) {
875 error("no stat information for %s", fname);
876 continue;
877 }
878 lname = ls_file(fname, g.gl_statv[i], 1,
879 (lflag & LS_SI_UNITS));
Damien Miller20e1fab2004-02-18 14:30:55 +1100880 printf("%s\n", lname);
Darren Tuckera627d422013-06-02 07:31:17 +1000881 free(lname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100882 } else {
883 printf("%-*s", colspace, fname);
884 if (c >= columns) {
885 printf("\n");
886 c = 1;
887 } else
888 c++;
889 }
Darren Tuckera627d422013-06-02 07:31:17 +1000890 free(fname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100891 }
892
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000893 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100894 printf("\n");
895
Darren Tuckercdf547a2004-05-24 10:12:19 +1000896 out:
Damien Miller20e1fab2004-02-18 14:30:55 +1100897 if (g.gl_pathc)
898 globfree(&g);
899
Damien Millera6e121a2010-10-07 21:39:17 +1100900 return 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100901}
902
Damien Millerd671e5a2008-05-19 14:53:33 +1000903static int
904do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
905{
Darren Tucker7b598892008-06-09 22:49:36 +1000906 struct sftp_statvfs st;
Damien Millerd671e5a2008-05-19 14:53:33 +1000907 char s_used[FMT_SCALED_STRSIZE];
908 char s_avail[FMT_SCALED_STRSIZE];
909 char s_root[FMT_SCALED_STRSIZE];
910 char s_total[FMT_SCALED_STRSIZE];
Darren Tuckerb5082e92010-01-08 18:51:47 +1100911 unsigned long long ffree;
Damien Millerd671e5a2008-05-19 14:53:33 +1000912
913 if (do_statvfs(conn, path, &st, 1) == -1)
914 return -1;
915 if (iflag) {
Darren Tuckerb5082e92010-01-08 18:51:47 +1100916 ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
Damien Millerd671e5a2008-05-19 14:53:33 +1000917 printf(" Inodes Used Avail "
918 "(root) %%Capacity\n");
919 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
920 (unsigned long long)st.f_files,
921 (unsigned long long)(st.f_files - st.f_ffree),
922 (unsigned long long)st.f_favail,
Darren Tuckerb5082e92010-01-08 18:51:47 +1100923 (unsigned long long)st.f_ffree, ffree);
Damien Millerd671e5a2008-05-19 14:53:33 +1000924 } else if (hflag) {
925 strlcpy(s_used, "error", sizeof(s_used));
926 strlcpy(s_avail, "error", sizeof(s_avail));
927 strlcpy(s_root, "error", sizeof(s_root));
928 strlcpy(s_total, "error", sizeof(s_total));
929 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
930 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
931 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
932 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
933 printf(" Size Used Avail (root) %%Capacity\n");
934 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
935 s_total, s_used, s_avail, s_root,
936 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
937 st.f_blocks));
938 } else {
939 printf(" Size Used Avail "
940 "(root) %%Capacity\n");
941 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
942 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
943 (unsigned long long)(st.f_frsize *
944 (st.f_blocks - st.f_bfree) / 1024),
945 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
946 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
947 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
948 st.f_blocks));
949 }
950 return 0;
951}
952
Damien Miller1cbc2922007-10-26 14:27:45 +1000953/*
954 * Undo escaping of glob sequences in place. Used to undo extra escaping
955 * applied in makeargv() when the string is destined for a function that
956 * does not glob it.
957 */
958static void
959undo_glob_escape(char *s)
960{
961 size_t i, j;
962
963 for (i = j = 0;;) {
964 if (s[i] == '\0') {
965 s[j] = '\0';
966 return;
967 }
968 if (s[i] != '\\') {
969 s[j++] = s[i++];
970 continue;
971 }
972 /* s[i] == '\\' */
973 ++i;
974 switch (s[i]) {
975 case '?':
976 case '[':
977 case '*':
978 case '\\':
979 s[j++] = s[i++];
980 break;
981 case '\0':
982 s[j++] = '\\';
983 s[j] = '\0';
984 return;
985 default:
986 s[j++] = '\\';
987 s[j++] = s[i++];
988 break;
989 }
990 }
991}
992
993/*
994 * Split a string into an argument vector using sh(1)-style quoting,
995 * comment and escaping rules, but with some tweaks to handle glob(3)
996 * wildcards.
Darren Tucker909d8582010-01-08 19:02:40 +1100997 * The "sloppy" flag allows for recovery from missing terminating quote, for
998 * use in parsing incomplete commandlines during tab autocompletion.
999 *
Damien Miller1cbc2922007-10-26 14:27:45 +10001000 * Returns NULL on error or a NULL-terminated array of arguments.
Darren Tucker909d8582010-01-08 19:02:40 +11001001 *
1002 * If "lastquote" is not NULL, the quoting character used for the last
1003 * argument is placed in *lastquote ("\0", "'" or "\"").
Damien Miller02e87802013-08-21 02:38:51 +10001004 *
Darren Tucker909d8582010-01-08 19:02:40 +11001005 * If "terminated" is not NULL, *terminated will be set to 1 when the
1006 * last argument's quote has been properly terminated or 0 otherwise.
1007 * This parameter is only of use if "sloppy" is set.
Damien Miller1cbc2922007-10-26 14:27:45 +10001008 */
1009#define MAXARGS 128
1010#define MAXARGLEN 8192
1011static char **
Darren Tucker909d8582010-01-08 19:02:40 +11001012makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
1013 u_int *terminated)
Damien Miller1cbc2922007-10-26 14:27:45 +10001014{
1015 int argc, quot;
1016 size_t i, j;
1017 static char argvs[MAXARGLEN];
1018 static char *argv[MAXARGS + 1];
1019 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
1020
1021 *argcp = argc = 0;
1022 if (strlen(arg) > sizeof(argvs) - 1) {
1023 args_too_longs:
1024 error("string too long");
1025 return NULL;
1026 }
Darren Tucker909d8582010-01-08 19:02:40 +11001027 if (terminated != NULL)
1028 *terminated = 1;
1029 if (lastquote != NULL)
1030 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001031 state = MA_START;
1032 i = j = 0;
1033 for (;;) {
Damien Miller07daed52012-10-31 08:57:55 +11001034 if ((size_t)argc >= sizeof(argv) / sizeof(*argv)){
Darren Tucker063018d2012-10-05 10:43:58 +10001035 error("Too many arguments.");
1036 return NULL;
1037 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001038 if (isspace(arg[i])) {
1039 if (state == MA_UNQUOTED) {
1040 /* Terminate current argument */
1041 argvs[j++] = '\0';
1042 argc++;
1043 state = MA_START;
1044 } else if (state != MA_START)
1045 argvs[j++] = arg[i];
1046 } else if (arg[i] == '"' || arg[i] == '\'') {
1047 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
1048 if (state == MA_START) {
1049 argv[argc] = argvs + j;
1050 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +11001051 if (lastquote != NULL)
1052 *lastquote = arg[i];
Damien Miller02e87802013-08-21 02:38:51 +10001053 } else if (state == MA_UNQUOTED)
Damien Miller1cbc2922007-10-26 14:27:45 +10001054 state = q;
1055 else if (state == q)
1056 state = MA_UNQUOTED;
1057 else
1058 argvs[j++] = arg[i];
1059 } else if (arg[i] == '\\') {
1060 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1061 quot = state == MA_SQUOTE ? '\'' : '"';
1062 /* Unescape quote we are in */
1063 /* XXX support \n and friends? */
1064 if (arg[i + 1] == quot) {
1065 i++;
1066 argvs[j++] = arg[i];
1067 } else if (arg[i + 1] == '?' ||
1068 arg[i + 1] == '[' || arg[i + 1] == '*') {
1069 /*
1070 * Special case for sftp: append
1071 * double-escaped glob sequence -
1072 * glob will undo one level of
1073 * escaping. NB. string can grow here.
1074 */
1075 if (j >= sizeof(argvs) - 5)
1076 goto args_too_longs;
1077 argvs[j++] = '\\';
1078 argvs[j++] = arg[i++];
1079 argvs[j++] = '\\';
1080 argvs[j++] = arg[i];
1081 } else {
1082 argvs[j++] = arg[i++];
1083 argvs[j++] = arg[i];
1084 }
1085 } else {
1086 if (state == MA_START) {
1087 argv[argc] = argvs + j;
1088 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001089 if (lastquote != NULL)
1090 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001091 }
1092 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1093 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1094 /*
1095 * Special case for sftp: append
1096 * escaped glob sequence -
1097 * glob will undo one level of
1098 * escaping.
1099 */
1100 argvs[j++] = arg[i++];
1101 argvs[j++] = arg[i];
1102 } else {
1103 /* Unescape everything */
1104 /* XXX support \n and friends? */
1105 i++;
1106 argvs[j++] = arg[i];
1107 }
1108 }
1109 } else if (arg[i] == '#') {
1110 if (state == MA_SQUOTE || state == MA_DQUOTE)
1111 argvs[j++] = arg[i];
1112 else
1113 goto string_done;
1114 } else if (arg[i] == '\0') {
1115 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001116 if (sloppy) {
1117 state = MA_UNQUOTED;
1118 if (terminated != NULL)
1119 *terminated = 0;
1120 goto string_done;
1121 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001122 error("Unterminated quoted argument");
1123 return NULL;
1124 }
1125 string_done:
1126 if (state == MA_UNQUOTED) {
1127 argvs[j++] = '\0';
1128 argc++;
1129 }
1130 break;
1131 } else {
1132 if (state == MA_START) {
1133 argv[argc] = argvs + j;
1134 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001135 if (lastquote != NULL)
1136 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001137 }
1138 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1139 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1140 /*
1141 * Special case for sftp: escape quoted
1142 * glob(3) wildcards. NB. string can grow
1143 * here.
1144 */
1145 if (j >= sizeof(argvs) - 3)
1146 goto args_too_longs;
1147 argvs[j++] = '\\';
1148 argvs[j++] = arg[i];
1149 } else
1150 argvs[j++] = arg[i];
1151 }
1152 i++;
1153 }
1154 *argcp = argc;
1155 return argv;
1156}
1157
Damien Miller20e1fab2004-02-18 14:30:55 +11001158static int
Damien Miller0d032412013-07-25 11:56:52 +10001159parse_args(const char **cpp, int *aflag, int *hflag, int *iflag, int *lflag,
1160 int *pflag, int *rflag, int *sflag, unsigned long *n_arg,
1161 char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001162{
1163 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001164 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001165 int base = 0;
1166 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001167 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001168
1169 /* Skip leading whitespace */
1170 cp = cp + strspn(cp, WHITESPACE);
1171
Damien Miller20e1fab2004-02-18 14:30:55 +11001172 /* Check for leading '-' (disable error processing) */
1173 *iflag = 0;
1174 if (*cp == '-') {
1175 *iflag = 1;
1176 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001177 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001178 }
1179
Darren Tucker70cc0922010-01-09 22:28:03 +11001180 /* Ignore blank lines and lines which begin with comment '#' char */
1181 if (*cp == '\0' || *cp == '#')
1182 return (0);
1183
Darren Tucker909d8582010-01-08 19:02:40 +11001184 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001185 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001186
Damien Miller1cbc2922007-10-26 14:27:45 +10001187 /* Figure out which command we have */
1188 for (i = 0; cmds[i].c != NULL; i++) {
Damien Millerd6d9fa02013-02-12 11:02:46 +11001189 if (argv[0] != NULL && strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001190 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001191 }
1192 cmdnum = cmds[i].n;
1193 cmd = cmds[i].c;
1194
1195 /* Special case */
1196 if (*cp == '!') {
1197 cp++;
1198 cmdnum = I_SHELL;
1199 } else if (cmdnum == -1) {
1200 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001201 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001202 }
1203
1204 /* Get arguments and parse flags */
Damien Miller0d032412013-07-25 11:56:52 +10001205 *aflag = *lflag = *pflag = *rflag = *hflag = *n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001206 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001207 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001208 switch (cmdnum) {
1209 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001210 case I_REGET:
Damien Miller20e1fab2004-02-18 14:30:55 +11001211 case I_PUT:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001212 if ((optidx = parse_getput_flags(cmd, argv, argc,
Damien Miller0d032412013-07-25 11:56:52 +10001213 aflag, pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001214 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001215 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001216 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001217 error("You must specify at least one path after a "
1218 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001219 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001220 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001221 *path1 = xstrdup(argv[optidx]);
1222 /* Get second pathname (optional) */
1223 if (argc - optidx > 1) {
1224 *path2 = xstrdup(argv[optidx + 1]);
1225 /* Destination is not globbed */
1226 undo_glob_escape(*path2);
1227 }
Damien Miller0d032412013-07-25 11:56:52 +10001228 if (*aflag && cmdnum == I_PUT) {
1229 /* XXX implement resume for uploads */
1230 error("Resume is not supported for uploads");
1231 return -1;
1232 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001233 break;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001234 case I_LINK:
1235 if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1)
1236 return -1;
Damien Millerc7dba122013-08-21 02:41:15 +10001237 goto parse_two_paths;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001238 case I_RENAME:
Damien Millerc7dba122013-08-21 02:41:15 +10001239 if ((optidx = parse_rename_flags(cmd, argv, argc, lflag)) == -1)
1240 return -1;
1241 goto parse_two_paths;
1242 case I_SYMLINK:
1243 parse_two_paths:
Damien Miller1cbc2922007-10-26 14:27:45 +10001244 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001245 error("You must specify two paths after a %s "
1246 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001247 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001248 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001249 *path1 = xstrdup(argv[optidx]);
1250 *path2 = xstrdup(argv[optidx + 1]);
1251 /* Paths are not globbed */
1252 undo_glob_escape(*path1);
1253 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001254 break;
1255 case I_RM:
1256 case I_MKDIR:
1257 case I_RMDIR:
1258 case I_CHDIR:
1259 case I_LCHDIR:
1260 case I_LMKDIR:
1261 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001262 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001263 error("You must specify a path after a %s command.",
1264 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001265 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001266 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001267 *path1 = xstrdup(argv[optidx]);
1268 /* Only "rm" globs */
1269 if (cmdnum != I_RM)
1270 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001271 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001272 case I_DF:
1273 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1274 iflag)) == -1)
1275 return -1;
1276 /* Default to current directory if no path specified */
1277 if (argc - optidx < 1)
1278 *path1 = NULL;
1279 else {
1280 *path1 = xstrdup(argv[optidx]);
1281 undo_glob_escape(*path1);
1282 }
1283 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001284 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001285 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001286 return(-1);
1287 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001288 if (argc - optidx > 0)
1289 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001290 break;
1291 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001292 /* Skip ls command and following whitespace */
1293 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001294 case I_SHELL:
1295 /* Uses the rest of the line */
1296 break;
1297 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001298 case I_CHMOD:
1299 base = 8;
1300 case I_CHOWN:
1301 case I_CHGRP:
1302 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001303 if (argc - optidx < 1)
1304 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001305 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001306 l = strtol(argv[optidx], &cp2, base);
1307 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1308 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1309 l < 0) {
1310 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001311 error("You must supply a numeric argument "
1312 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001313 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001314 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001315 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001316 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001317 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001318 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001319 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001320 error("You must specify a path after a %s command.",
1321 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001322 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001323 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001324 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001325 break;
1326 case I_QUIT:
1327 case I_PWD:
1328 case I_LPWD:
1329 case I_HELP:
1330 case I_VERSION:
1331 case I_PROGRESS:
1332 break;
1333 default:
1334 fatal("Command not implemented");
1335 }
1336
1337 *cpp = cp;
1338 return(cmdnum);
1339}
1340
1341static int
1342parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1343 int err_abort)
1344{
1345 char *path1, *path2, *tmp;
Damien Miller0d032412013-07-25 11:56:52 +10001346 int aflag = 0, hflag = 0, iflag = 0, lflag = 0, pflag = 0;
1347 int rflag = 0, sflag = 0;
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001348 int cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001349 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001350 Attrib a, *aa;
1351 char path_buf[MAXPATHLEN];
1352 int err = 0;
1353 glob_t g;
1354
1355 path1 = path2 = NULL;
Damien Miller0d032412013-07-25 11:56:52 +10001356 cmdnum = parse_args(&cmd, &aflag, &hflag, &iflag, &lflag, &pflag,
1357 &rflag, &sflag, &n_arg, &path1, &path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001358 if (iflag != 0)
1359 err_abort = 0;
1360
1361 memset(&g, 0, sizeof(g));
1362
1363 /* Perform command */
1364 switch (cmdnum) {
1365 case 0:
1366 /* Blank line */
1367 break;
1368 case -1:
1369 /* Unrecognized command */
1370 err = -1;
1371 break;
Damien Miller0d032412013-07-25 11:56:52 +10001372 case I_REGET:
1373 aflag = 1;
1374 /* FALLTHROUGH */
Damien Miller20e1fab2004-02-18 14:30:55 +11001375 case I_GET:
Damien Miller0d032412013-07-25 11:56:52 +10001376 err = process_get(conn, path1, path2, *pwd, pflag,
1377 rflag, aflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001378 break;
1379 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001380 err = process_put(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001381 break;
1382 case I_RENAME:
1383 path1 = make_absolute(path1, *pwd);
1384 path2 = make_absolute(path2, *pwd);
Damien Millerc7dba122013-08-21 02:41:15 +10001385 err = do_rename(conn, path1, path2, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001386 break;
1387 case I_SYMLINK:
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001388 sflag = 1;
1389 case I_LINK:
Damien Miller034f27a2013-08-21 02:40:44 +10001390 if (!sflag)
1391 path1 = make_absolute(path1, *pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001392 path2 = make_absolute(path2, *pwd);
Darren Tuckeraf1f9092010-12-05 09:02:47 +11001393 err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001394 break;
1395 case I_RM:
1396 path1 = make_absolute(path1, *pwd);
1397 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001398 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001399 if (!quiet)
1400 printf("Removing %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001401 err = do_rm(conn, g.gl_pathv[i]);
1402 if (err != 0 && err_abort)
1403 break;
1404 }
1405 break;
1406 case I_MKDIR:
1407 path1 = make_absolute(path1, *pwd);
1408 attrib_clear(&a);
1409 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1410 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001411 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001412 break;
1413 case I_RMDIR:
1414 path1 = make_absolute(path1, *pwd);
1415 err = do_rmdir(conn, path1);
1416 break;
1417 case I_CHDIR:
1418 path1 = make_absolute(path1, *pwd);
1419 if ((tmp = do_realpath(conn, path1)) == NULL) {
1420 err = 1;
1421 break;
1422 }
1423 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
Darren Tuckera627d422013-06-02 07:31:17 +10001424 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001425 err = 1;
1426 break;
1427 }
1428 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1429 error("Can't change directory: Can't check target");
Darren Tuckera627d422013-06-02 07:31:17 +10001430 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001431 err = 1;
1432 break;
1433 }
1434 if (!S_ISDIR(aa->perm)) {
1435 error("Can't change directory: \"%s\" is not "
1436 "a directory", tmp);
Darren Tuckera627d422013-06-02 07:31:17 +10001437 free(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +11001438 err = 1;
1439 break;
1440 }
Darren Tuckera627d422013-06-02 07:31:17 +10001441 free(*pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +11001442 *pwd = tmp;
1443 break;
1444 case I_LS:
1445 if (!path1) {
Damien Miller99ac4e92010-06-26 09:36:58 +10001446 do_ls_dir(conn, *pwd, *pwd, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001447 break;
1448 }
1449
1450 /* Strip pwd off beginning of non-absolute paths */
1451 tmp = NULL;
1452 if (*path1 != '/')
1453 tmp = *pwd;
1454
1455 path1 = make_absolute(path1, *pwd);
1456 err = do_globbed_ls(conn, path1, tmp, lflag);
1457 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001458 case I_DF:
1459 /* Default to current directory if no path specified */
1460 if (path1 == NULL)
1461 path1 = xstrdup(*pwd);
1462 path1 = make_absolute(path1, *pwd);
1463 err = do_df(conn, path1, hflag, iflag);
1464 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001465 case I_LCHDIR:
1466 if (chdir(path1) == -1) {
1467 error("Couldn't change local directory to "
1468 "\"%s\": %s", path1, strerror(errno));
1469 err = 1;
1470 }
1471 break;
1472 case I_LMKDIR:
1473 if (mkdir(path1, 0777) == -1) {
1474 error("Couldn't create local directory "
1475 "\"%s\": %s", path1, strerror(errno));
1476 err = 1;
1477 }
1478 break;
1479 case I_LLS:
1480 local_do_ls(cmd);
1481 break;
1482 case I_SHELL:
1483 local_do_shell(cmd);
1484 break;
1485 case I_LUMASK:
1486 umask(n_arg);
1487 printf("Local umask: %03lo\n", n_arg);
1488 break;
1489 case I_CHMOD:
1490 path1 = make_absolute(path1, *pwd);
1491 attrib_clear(&a);
1492 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1493 a.perm = n_arg;
1494 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001495 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller9303e652013-04-23 15:22:40 +10001496 if (!quiet)
1497 printf("Changing mode on %s\n", g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001498 err = do_setstat(conn, g.gl_pathv[i], &a);
1499 if (err != 0 && err_abort)
1500 break;
1501 }
1502 break;
1503 case I_CHOWN:
1504 case I_CHGRP:
1505 path1 = make_absolute(path1, *pwd);
1506 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001507 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001508 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001509 if (err_abort) {
1510 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001511 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001512 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001513 continue;
1514 }
1515 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1516 error("Can't get current ownership of "
1517 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001518 if (err_abort) {
1519 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001520 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001521 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001522 continue;
1523 }
1524 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1525 if (cmdnum == I_CHOWN) {
Damien Miller9303e652013-04-23 15:22:40 +10001526 if (!quiet)
1527 printf("Changing owner on %s\n",
1528 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001529 aa->uid = n_arg;
1530 } else {
Damien Miller9303e652013-04-23 15:22:40 +10001531 if (!quiet)
1532 printf("Changing group on %s\n",
1533 g.gl_pathv[i]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001534 aa->gid = n_arg;
1535 }
1536 err = do_setstat(conn, g.gl_pathv[i], aa);
1537 if (err != 0 && err_abort)
1538 break;
1539 }
1540 break;
1541 case I_PWD:
1542 printf("Remote working directory: %s\n", *pwd);
1543 break;
1544 case I_LPWD:
1545 if (!getcwd(path_buf, sizeof(path_buf))) {
1546 error("Couldn't get local cwd: %s", strerror(errno));
1547 err = -1;
1548 break;
1549 }
1550 printf("Local working directory: %s\n", path_buf);
1551 break;
1552 case I_QUIT:
1553 /* Processed below */
1554 break;
1555 case I_HELP:
1556 help();
1557 break;
1558 case I_VERSION:
1559 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1560 break;
1561 case I_PROGRESS:
1562 showprogress = !showprogress;
1563 if (showprogress)
1564 printf("Progress meter enabled\n");
1565 else
1566 printf("Progress meter disabled\n");
1567 break;
1568 default:
1569 fatal("%d is not implemented", cmdnum);
1570 }
1571
1572 if (g.gl_pathc)
1573 globfree(&g);
Darren Tuckera627d422013-06-02 07:31:17 +10001574 free(path1);
1575 free(path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001576
1577 /* If an unignored error occurs in batch mode we should abort. */
1578 if (err_abort && err != 0)
1579 return (-1);
1580 else if (cmdnum == I_QUIT)
1581 return (1);
1582
1583 return (0);
1584}
1585
Darren Tucker2d963d82004-11-07 20:04:10 +11001586#ifdef USE_LIBEDIT
1587static char *
1588prompt(EditLine *el)
1589{
1590 return ("sftp> ");
1591}
Darren Tucker2d963d82004-11-07 20:04:10 +11001592
Darren Tucker909d8582010-01-08 19:02:40 +11001593/* Display entries in 'list' after skipping the first 'len' chars */
1594static void
1595complete_display(char **list, u_int len)
1596{
1597 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1598 struct winsize ws;
1599 char *tmp;
1600
1601 /* Count entries for sort and find longest */
Damien Miller02e87802013-08-21 02:38:51 +10001602 for (y = 0; list[y]; y++)
Darren Tucker909d8582010-01-08 19:02:40 +11001603 m = MAX(m, strlen(list[y]));
1604
1605 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1606 width = ws.ws_col;
1607
1608 m = m > len ? m - len : 0;
1609 columns = width / (m + 2);
1610 columns = MAX(columns, 1);
1611 colspace = width / columns;
1612 colspace = MIN(colspace, width);
1613
1614 printf("\n");
1615 m = 1;
1616 for (y = 0; list[y]; y++) {
1617 llen = strlen(list[y]);
1618 tmp = llen > len ? list[y] + len : "";
1619 printf("%-*s", colspace, tmp);
1620 if (m >= columns) {
1621 printf("\n");
1622 m = 1;
1623 } else
1624 m++;
1625 }
1626 printf("\n");
1627}
1628
1629/*
1630 * Given a "list" of words that begin with a common prefix of "word",
1631 * attempt to find an autocompletion to extends "word" by the next
1632 * characters common to all entries in "list".
1633 */
1634static char *
1635complete_ambiguous(const char *word, char **list, size_t count)
1636{
1637 if (word == NULL)
1638 return NULL;
1639
1640 if (count > 0) {
1641 u_int y, matchlen = strlen(list[0]);
1642
1643 /* Find length of common stem */
1644 for (y = 1; list[y]; y++) {
1645 u_int x;
1646
Damien Miller02e87802013-08-21 02:38:51 +10001647 for (x = 0; x < matchlen; x++)
1648 if (list[0][x] != list[y][x])
Darren Tucker909d8582010-01-08 19:02:40 +11001649 break;
1650
1651 matchlen = x;
1652 }
1653
1654 if (matchlen > strlen(word)) {
1655 char *tmp = xstrdup(list[0]);
1656
Darren Tucker340d1682010-01-09 08:54:31 +11001657 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001658 return tmp;
1659 }
Damien Miller02e87802013-08-21 02:38:51 +10001660 }
Darren Tucker909d8582010-01-08 19:02:40 +11001661
1662 return xstrdup(word);
1663}
1664
1665/* Autocomplete a sftp command */
1666static int
1667complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1668 int terminated)
1669{
1670 u_int y, count = 0, cmdlen, tmplen;
1671 char *tmp, **list, argterm[3];
1672 const LineInfo *lf;
1673
1674 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1675
1676 /* No command specified: display all available commands */
1677 if (cmd == NULL) {
1678 for (y = 0; cmds[y].c; y++)
1679 list[count++] = xstrdup(cmds[y].c);
Damien Miller02e87802013-08-21 02:38:51 +10001680
Darren Tucker909d8582010-01-08 19:02:40 +11001681 list[count] = NULL;
1682 complete_display(list, 0);
1683
Damien Miller02e87802013-08-21 02:38:51 +10001684 for (y = 0; list[y] != NULL; y++)
1685 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001686 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001687 return count;
1688 }
1689
1690 /* Prepare subset of commands that start with "cmd" */
1691 cmdlen = strlen(cmd);
1692 for (y = 0; cmds[y].c; y++) {
Damien Miller02e87802013-08-21 02:38:51 +10001693 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
Darren Tucker909d8582010-01-08 19:02:40 +11001694 list[count++] = xstrdup(cmds[y].c);
1695 }
1696 list[count] = NULL;
1697
Damien Miller47d81152011-11-25 13:53:48 +11001698 if (count == 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10001699 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001700 return 0;
Damien Miller47d81152011-11-25 13:53:48 +11001701 }
Darren Tucker909d8582010-01-08 19:02:40 +11001702
1703 /* Complete ambigious command */
1704 tmp = complete_ambiguous(cmd, list, count);
1705 if (count > 1)
1706 complete_display(list, 0);
1707
Damien Miller02e87802013-08-21 02:38:51 +10001708 for (y = 0; list[y]; y++)
1709 free(list[y]);
Darren Tuckera627d422013-06-02 07:31:17 +10001710 free(list);
Darren Tucker909d8582010-01-08 19:02:40 +11001711
1712 if (tmp != NULL) {
1713 tmplen = strlen(tmp);
1714 cmdlen = strlen(cmd);
1715 /* If cmd may be extended then do so */
1716 if (tmplen > cmdlen)
1717 if (el_insertstr(el, tmp + cmdlen) == -1)
1718 fatal("el_insertstr failed.");
1719 lf = el_line(el);
1720 /* Terminate argument cleanly */
1721 if (count == 1) {
1722 y = 0;
1723 if (!terminated)
1724 argterm[y++] = quote;
1725 if (lastarg || *(lf->cursor) != ' ')
1726 argterm[y++] = ' ';
1727 argterm[y] = '\0';
1728 if (y > 0 && el_insertstr(el, argterm) == -1)
1729 fatal("el_insertstr failed.");
1730 }
Darren Tuckera627d422013-06-02 07:31:17 +10001731 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001732 }
1733
1734 return count;
1735}
1736
1737/*
1738 * Determine whether a particular sftp command's arguments (if any)
1739 * represent local or remote files.
1740 */
1741static int
1742complete_is_remote(char *cmd) {
1743 int i;
1744
1745 if (cmd == NULL)
1746 return -1;
1747
1748 for (i = 0; cmds[i].c; i++) {
Damien Miller02e87802013-08-21 02:38:51 +10001749 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
Darren Tucker909d8582010-01-08 19:02:40 +11001750 return cmds[i].t;
1751 }
1752
1753 return -1;
1754}
1755
1756/* Autocomplete a filename "file" */
1757static int
1758complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1759 char *file, int remote, int lastarg, char quote, int terminated)
1760{
1761 glob_t g;
Darren Tuckerea647212013-06-06 08:19:09 +10001762 char *tmp, *tmp2, ins[8];
Darren Tucker17146d32012-10-05 10:46:16 +10001763 u_int i, hadglob, pwdlen, len, tmplen, filelen, cesc, isesc, isabs;
Darren Tuckerea647212013-06-06 08:19:09 +10001764 int clen;
Darren Tucker909d8582010-01-08 19:02:40 +11001765 const LineInfo *lf;
Damien Miller02e87802013-08-21 02:38:51 +10001766
Darren Tucker909d8582010-01-08 19:02:40 +11001767 /* Glob from "file" location */
1768 if (file == NULL)
1769 tmp = xstrdup("*");
1770 else
1771 xasprintf(&tmp, "%s*", file);
1772
Darren Tucker191fcc62012-10-05 10:45:01 +10001773 /* Check if the path is absolute. */
1774 isabs = tmp[0] == '/';
1775
Darren Tucker909d8582010-01-08 19:02:40 +11001776 memset(&g, 0, sizeof(g));
1777 if (remote != LOCAL) {
1778 tmp = make_absolute(tmp, remote_path);
1779 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001780 } else
Darren Tucker909d8582010-01-08 19:02:40 +11001781 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
Damien Miller02e87802013-08-21 02:38:51 +10001782
Darren Tucker909d8582010-01-08 19:02:40 +11001783 /* Determine length of pwd so we can trim completion display */
1784 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1785 /* Terminate counting on first unescaped glob metacharacter */
1786 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1787 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1788 hadglob = 1;
1789 break;
1790 }
1791 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1792 tmplen++;
1793 if (tmp[tmplen] == '/')
1794 pwdlen = tmplen + 1; /* track last seen '/' */
1795 }
Darren Tuckera627d422013-06-02 07:31:17 +10001796 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001797
Damien Miller02e87802013-08-21 02:38:51 +10001798 if (g.gl_matchc == 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001799 goto out;
1800
1801 if (g.gl_matchc > 1)
1802 complete_display(g.gl_pathv, pwdlen);
1803
1804 tmp = NULL;
1805 /* Don't try to extend globs */
1806 if (file == NULL || hadglob)
1807 goto out;
1808
1809 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
Darren Tucker191fcc62012-10-05 10:45:01 +10001810 tmp = path_strip(tmp2, isabs ? NULL : remote_path);
Darren Tuckera627d422013-06-02 07:31:17 +10001811 free(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001812
1813 if (tmp == NULL)
1814 goto out;
1815
1816 tmplen = strlen(tmp);
1817 filelen = strlen(file);
1818
Darren Tucker17146d32012-10-05 10:46:16 +10001819 /* Count the number of escaped characters in the input string. */
1820 cesc = isesc = 0;
1821 for (i = 0; i < filelen; i++) {
1822 if (!isesc && file[i] == '\\' && i + 1 < filelen){
1823 isesc = 1;
1824 cesc++;
1825 } else
1826 isesc = 0;
1827 }
1828
1829 if (tmplen > (filelen - cesc)) {
1830 tmp2 = tmp + filelen - cesc;
Damien Miller02e87802013-08-21 02:38:51 +10001831 len = strlen(tmp2);
Darren Tucker909d8582010-01-08 19:02:40 +11001832 /* quote argument on way out */
Darren Tuckerea647212013-06-06 08:19:09 +10001833 for (i = 0; i < len; i += clen) {
1834 if ((clen = mblen(tmp2 + i, len - i)) < 0 ||
1835 (size_t)clen > sizeof(ins) - 2)
1836 fatal("invalid multibyte character");
Darren Tucker909d8582010-01-08 19:02:40 +11001837 ins[0] = '\\';
Darren Tuckerea647212013-06-06 08:19:09 +10001838 memcpy(ins + 1, tmp2 + i, clen);
1839 ins[clen + 1] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001840 switch (tmp2[i]) {
1841 case '\'':
1842 case '"':
1843 case '\\':
1844 case '\t':
Darren Tuckerd78739a2010-10-24 10:56:32 +11001845 case '[':
Darren Tucker909d8582010-01-08 19:02:40 +11001846 case ' ':
Darren Tucker17146d32012-10-05 10:46:16 +10001847 case '#':
1848 case '*':
Darren Tucker909d8582010-01-08 19:02:40 +11001849 if (quote == '\0' || tmp2[i] == quote) {
1850 if (el_insertstr(el, ins) == -1)
1851 fatal("el_insertstr "
1852 "failed.");
1853 break;
1854 }
1855 /* FALLTHROUGH */
1856 default:
1857 if (el_insertstr(el, ins + 1) == -1)
1858 fatal("el_insertstr failed.");
1859 break;
1860 }
1861 }
1862 }
1863
1864 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11001865 if (g.gl_matchc == 1) {
1866 i = 0;
1867 if (!terminated)
1868 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11001869 if (*(lf->cursor - 1) != '/' &&
1870 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11001871 ins[i++] = ' ';
1872 ins[i] = '\0';
1873 if (i > 0 && el_insertstr(el, ins) == -1)
1874 fatal("el_insertstr failed.");
1875 }
Darren Tuckera627d422013-06-02 07:31:17 +10001876 free(tmp);
Darren Tucker909d8582010-01-08 19:02:40 +11001877
1878 out:
1879 globfree(&g);
1880 return g.gl_matchc;
1881}
1882
1883/* tab-completion hook function, called via libedit */
1884static unsigned char
1885complete(EditLine *el, int ch)
1886{
Damien Miller02e87802013-08-21 02:38:51 +10001887 char **argv, *line, quote;
Damien Miller746d1a62013-07-18 16:13:02 +10001888 int argc, carg;
1889 u_int cursor, len, terminated, ret = CC_ERROR;
Darren Tucker909d8582010-01-08 19:02:40 +11001890 const LineInfo *lf;
1891 struct complete_ctx *complete_ctx;
1892
1893 lf = el_line(el);
1894 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1895 fatal("%s: el_get failed", __func__);
1896
1897 /* Figure out which argument the cursor points to */
1898 cursor = lf->cursor - lf->buffer;
1899 line = (char *)xmalloc(cursor + 1);
1900 memcpy(line, lf->buffer, cursor);
1901 line[cursor] = '\0';
1902 argv = makeargv(line, &carg, 1, &quote, &terminated);
Darren Tuckera627d422013-06-02 07:31:17 +10001903 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001904
1905 /* Get all the arguments on the line */
1906 len = lf->lastchar - lf->buffer;
1907 line = (char *)xmalloc(len + 1);
1908 memcpy(line, lf->buffer, len);
1909 line[len] = '\0';
1910 argv = makeargv(line, &argc, 1, NULL, NULL);
1911
1912 /* Ensure cursor is at EOL or a argument boundary */
1913 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1914 line[cursor] != '\n') {
Darren Tuckera627d422013-06-02 07:31:17 +10001915 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001916 return ret;
1917 }
1918
1919 if (carg == 0) {
1920 /* Show all available commands */
1921 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1922 ret = CC_REDISPLAY;
1923 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1924 /* Handle the command parsing */
1925 if (complete_cmd_parse(el, argv[0], argc == carg,
Damien Miller02e87802013-08-21 02:38:51 +10001926 quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001927 ret = CC_REDISPLAY;
1928 } else if (carg >= 1) {
1929 /* Handle file parsing */
1930 int remote = complete_is_remote(argv[0]);
1931 char *filematch = NULL;
1932
1933 if (carg > 1 && line[cursor-1] != ' ')
1934 filematch = argv[carg - 1];
1935
1936 if (remote != 0 &&
1937 complete_match(el, complete_ctx->conn,
1938 *complete_ctx->remote_pathp, filematch,
Damien Miller02e87802013-08-21 02:38:51 +10001939 remote, carg == argc, quote, terminated) != 0)
Darren Tucker909d8582010-01-08 19:02:40 +11001940 ret = CC_REDISPLAY;
1941 }
1942
Damien Miller02e87802013-08-21 02:38:51 +10001943 free(line);
Darren Tucker909d8582010-01-08 19:02:40 +11001944 return ret;
1945}
Darren Tuckere67f7db2010-01-08 19:50:02 +11001946#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11001947
Damien Miller20e1fab2004-02-18 14:30:55 +11001948int
Darren Tucker21063192010-01-08 17:10:36 +11001949interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001950{
Darren Tucker909d8582010-01-08 19:02:40 +11001951 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11001952 char *dir = NULL;
1953 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10001954 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11001955 EditLine *el = NULL;
1956#ifdef USE_LIBEDIT
1957 History *hl = NULL;
1958 HistEvent hev;
1959 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11001960 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11001961
1962 if (!batchmode && isatty(STDIN_FILENO)) {
1963 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1964 fatal("Couldn't initialise editline");
1965 if ((hl = history_init()) == NULL)
1966 fatal("Couldn't initialise editline history");
1967 history(hl, &hev, H_SETSIZE, 100);
1968 el_set(el, EL_HIST, history, hl);
1969
1970 el_set(el, EL_PROMPT, prompt);
1971 el_set(el, EL_EDITOR, "emacs");
1972 el_set(el, EL_TERMINAL, NULL);
1973 el_set(el, EL_SIGNAL, 1);
1974 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11001975
1976 /* Tab Completion */
Damien Miller02e87802013-08-21 02:38:51 +10001977 el_set(el, EL_ADDFN, "ftp-complete",
Darren Tuckerd78739a2010-10-24 10:56:32 +11001978 "Context sensitive argument completion", complete);
Darren Tucker909d8582010-01-08 19:02:40 +11001979 complete_ctx.conn = conn;
1980 complete_ctx.remote_pathp = &remote_path;
1981 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
1982 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11001983 }
1984#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001985
Darren Tucker909d8582010-01-08 19:02:40 +11001986 remote_path = do_realpath(conn, ".");
1987 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11001988 fatal("Need cwd");
1989
1990 if (file1 != NULL) {
1991 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11001992 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11001993
1994 if (remote_is_dir(conn, dir) && file2 == NULL) {
Damien Miller9303e652013-04-23 15:22:40 +10001995 if (!quiet)
1996 printf("Changing to: %s\n", dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11001997 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001998 if (parse_dispatch_command(conn, cmd,
1999 &remote_path, 1) != 0) {
Darren Tuckera627d422013-06-02 07:31:17 +10002000 free(dir);
2001 free(remote_path);
2002 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002003 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11002004 }
Damien Miller20e1fab2004-02-18 14:30:55 +11002005 } else {
Darren Tucker0af24052012-10-05 10:41:25 +10002006 /* XXX this is wrong wrt quoting */
Damien Miller0d032412013-07-25 11:56:52 +10002007 snprintf(cmd, sizeof cmd, "get%s %s%s%s",
2008 global_aflag ? " -a" : "", dir,
2009 file2 == NULL ? "" : " ",
2010 file2 == NULL ? "" : file2);
Darren Tucker909d8582010-01-08 19:02:40 +11002011 err = parse_dispatch_command(conn, cmd,
2012 &remote_path, 1);
Darren Tuckera627d422013-06-02 07:31:17 +10002013 free(dir);
2014 free(remote_path);
2015 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002016 return (err);
2017 }
Darren Tuckera627d422013-06-02 07:31:17 +10002018 free(dir);
Damien Miller20e1fab2004-02-18 14:30:55 +11002019 }
2020
Damien Miller37294fb2005-07-17 17:18:49 +10002021 setlinebuf(stdout);
2022 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11002023
Damien Miller0e2c1022005-08-12 22:16:22 +10002024 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11002025 err = 0;
2026 for (;;) {
2027 char *cp;
2028
Darren Tuckercdf547a2004-05-24 10:12:19 +10002029 signal(SIGINT, SIG_IGN);
2030
Darren Tucker2d963d82004-11-07 20:04:10 +11002031 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002032 if (interactive)
2033 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11002034 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002035 if (interactive)
2036 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11002037 break;
2038 }
Damien Miller0e2c1022005-08-12 22:16:22 +10002039 if (!interactive) { /* Echo command */
2040 printf("sftp> %s", cmd);
2041 if (strlen(cmd) > 0 &&
2042 cmd[strlen(cmd) - 1] != '\n')
2043 printf("\n");
2044 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002045 } else {
2046#ifdef USE_LIBEDIT
2047 const char *line;
2048 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11002049
Darren Tucker909d8582010-01-08 19:02:40 +11002050 if ((line = el_gets(el, &count)) == NULL ||
2051 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10002052 printf("\n");
2053 break;
2054 }
Darren Tucker2d963d82004-11-07 20:04:10 +11002055 history(hl, &hev, H_ENTER, line);
2056 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
2057 fprintf(stderr, "Error: input line too long\n");
2058 continue;
2059 }
2060#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11002061 }
2062
Damien Miller20e1fab2004-02-18 14:30:55 +11002063 cp = strrchr(cmd, '\n');
2064 if (cp)
2065 *cp = '\0';
2066
Darren Tuckercdf547a2004-05-24 10:12:19 +10002067 /* Handle user interrupts gracefully during commands */
2068 interrupted = 0;
2069 signal(SIGINT, cmd_interrupt);
2070
Darren Tucker909d8582010-01-08 19:02:40 +11002071 err = parse_dispatch_command(conn, cmd, &remote_path,
2072 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11002073 if (err != 0)
2074 break;
2075 }
Darren Tuckera627d422013-06-02 07:31:17 +10002076 free(remote_path);
2077 free(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11002078
Tim Rice027e8b12005-08-15 14:52:50 -07002079#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10002080 if (el != NULL)
2081 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07002082#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10002083
Damien Miller20e1fab2004-02-18 14:30:55 +11002084 /* err == 1 signifies normal "quit" exit */
2085 return (err >= 0 ? 0 : -1);
2086}
Damien Miller62d57f62003-01-10 21:43:24 +11002087
Ben Lindstrombba81212001-06-25 05:01:22 +00002088static void
Damien Millercc685c12003-06-04 22:51:38 +10002089connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11002090{
2091 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002092
Damien Miller33804262001-02-04 23:20:18 +11002093#ifdef USE_PIPES
2094 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002095
Damien Miller33804262001-02-04 23:20:18 +11002096 if ((pipe(pin) == -1) || (pipe(pout) == -1))
2097 fatal("pipe: %s", strerror(errno));
2098 *in = pin[0];
2099 *out = pout[1];
2100 c_in = pout[0];
2101 c_out = pin[1];
2102#else /* USE_PIPES */
2103 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002104
Damien Miller33804262001-02-04 23:20:18 +11002105 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2106 fatal("socketpair: %s", strerror(errno));
2107 *in = *out = inout[0];
2108 c_in = c_out = inout[1];
2109#endif /* USE_PIPES */
2110
Damien Millercc685c12003-06-04 22:51:38 +10002111 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002112 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002113 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002114 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2115 (dup2(c_out, STDOUT_FILENO) == -1)) {
2116 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002117 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002118 }
2119 close(*in);
2120 close(*out);
2121 close(c_in);
2122 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002123
2124 /*
2125 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002126 * ignore SIGINT if we want to gracefully abort commands,
2127 * otherwise the signal will make it to the ssh process and
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002128 * kill it too. Contrawise, since sftp sends SIGTERMs to the
2129 * underlying ssh, it must *not* ignore that signal.
Darren Tuckercdf547a2004-05-24 10:12:19 +10002130 */
2131 signal(SIGINT, SIG_IGN);
Darren Tuckerb8b17e92010-01-15 11:46:03 +11002132 signal(SIGTERM, SIG_DFL);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002133 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002134 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002135 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002136 }
2137
Damien Millercc685c12003-06-04 22:51:38 +10002138 signal(SIGTERM, killchild);
2139 signal(SIGINT, killchild);
2140 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11002141 close(c_in);
2142 close(c_out);
2143}
2144
Ben Lindstrombba81212001-06-25 05:01:22 +00002145static void
Damien Miller33804262001-02-04 23:20:18 +11002146usage(void)
2147{
Damien Miller025e01c2002-02-08 22:06:29 +11002148 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002149
Ben Lindstrom1e243242001-09-18 05:38:44 +00002150 fprintf(stderr,
Damien Millerc6895c52013-08-21 02:40:21 +10002151 "usage: %s [-1246aCpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002152 " [-D sftp_server_path] [-F ssh_config] "
Damien Miller56883e12010-09-24 22:15:39 +10002153 "[-i identity_file] [-l limit]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002154 " [-o ssh_option] [-P port] [-R num_requests] "
2155 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002156 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002157 " %s [user@]host[:file ...]\n"
2158 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002159 " %s -b batchfile [user@]host\n",
2160 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002161 exit(1);
2162}
2163
Kevin Stevesef4eea92001-02-05 12:42:17 +00002164int
Damien Miller33804262001-02-04 23:20:18 +11002165main(int argc, char **argv)
2166{
Damien Miller956f3fb2003-01-10 21:40:00 +11002167 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002168 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002169 int debug_level = 0, sshver = 2;
2170 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002171 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Damien Miller65e42f82010-09-24 22:15:11 +10002172 const char *errstr;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002173 LogLevel ll = SYSLOG_LEVEL_INFO;
2174 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002175 extern int optind;
2176 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002177 struct sftp_conn *conn;
2178 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2179 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller65e42f82010-09-24 22:15:11 +10002180 long long limit_kbps = 0;
Damien Miller33804262001-02-04 23:20:18 +11002181
Darren Tuckerce321d82005-10-03 18:11:24 +10002182 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2183 sanitise_stdfd();
Darren Tuckerea647212013-06-06 08:19:09 +10002184 setlocale(LC_CTYPE, "");
Darren Tuckerce321d82005-10-03 18:11:24 +10002185
Damien Miller59d3d5b2003-08-22 09:34:41 +10002186 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002187 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002188 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002189 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002190 addargs(&args, "-oForwardX11 no");
2191 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002192 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002193 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002194
Ben Lindstrom387c4722001-05-08 20:27:25 +00002195 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002196 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002197
Darren Tucker282b4022009-10-07 08:23:06 +11002198 while ((ch = getopt(argc, argv,
Damien Miller0d032412013-07-25 11:56:52 +10002199 "1246ahpqrvCc:D:i:l:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002200 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002201 /* Passed through to ssh(1) */
2202 case '4':
2203 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002204 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002205 addargs(&args, "-%c", ch);
2206 break;
2207 /* Passed through to ssh(1) with argument */
2208 case 'F':
2209 case 'c':
2210 case 'i':
2211 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002212 addargs(&args, "-%c", ch);
2213 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002214 break;
2215 case 'q':
Damien Miller9303e652013-04-23 15:22:40 +10002216 ll = SYSLOG_LEVEL_ERROR;
2217 quiet = 1;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002218 showprogress = 0;
2219 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002220 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002221 case 'P':
2222 addargs(&args, "-oPort %s", optarg);
2223 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002224 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002225 if (debug_level < 3) {
2226 addargs(&args, "-v");
2227 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2228 }
2229 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002230 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002231 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002232 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002233 if (sftp_server == NULL)
2234 sftp_server = _PATH_SFTP_SERVER;
2235 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002236 case '2':
2237 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002238 break;
Damien Miller0d032412013-07-25 11:56:52 +10002239 case 'a':
2240 global_aflag = 1;
2241 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002242 case 'B':
2243 copy_buffer_len = strtol(optarg, &cp, 10);
2244 if (copy_buffer_len == 0 || *cp != '\0')
2245 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002246 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002247 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002248 if (batchmode)
2249 fatal("Batch file already specified.");
2250
2251 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002252 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002253 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002254 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002255 showprogress = 0;
Damien Miller9303e652013-04-23 15:22:40 +10002256 quiet = batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002257 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002258 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002259 case 'p':
2260 global_pflag = 1;
2261 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002262 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002263 sftp_direct = optarg;
2264 break;
Damien Miller65e42f82010-09-24 22:15:11 +10002265 case 'l':
2266 limit_kbps = strtonum(optarg, 1, 100 * 1024 * 1024,
2267 &errstr);
2268 if (errstr != NULL)
2269 usage();
2270 limit_kbps *= 1024; /* kbps */
2271 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002272 case 'r':
2273 global_rflag = 1;
2274 break;
Damien Miller16a13332002-02-13 14:03:56 +11002275 case 'R':
2276 num_requests = strtol(optarg, &cp, 10);
2277 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002278 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002279 optarg);
2280 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002281 case 's':
2282 sftp_server = optarg;
2283 break;
2284 case 'S':
2285 ssh_program = optarg;
2286 replacearg(&args, 0, "%s", ssh_program);
2287 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002288 case 'h':
2289 default:
Damien Miller33804262001-02-04 23:20:18 +11002290 usage();
2291 }
2292 }
2293
Damien Millerc0f27d82004-03-08 23:12:19 +11002294 if (!isatty(STDERR_FILENO))
2295 showprogress = 0;
2296
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002297 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2298
Damien Millerd14ee1e2002-02-05 12:27:31 +11002299 if (sftp_direct == NULL) {
2300 if (optind == argc || argc > (optind + 2))
2301 usage();
Damien Miller33804262001-02-04 23:20:18 +11002302
Damien Millerd14ee1e2002-02-05 12:27:31 +11002303 userhost = xstrdup(argv[optind]);
2304 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002305
Ben Lindstromc276c122002-12-23 02:14:51 +00002306 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002307 host = userhost;
2308 else {
2309 *host++ = '\0';
2310 if (!userhost[0]) {
2311 fprintf(stderr, "Missing username\n");
2312 usage();
2313 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002314 addargs(&args, "-l");
2315 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002316 }
2317
Damien Millerec692032004-01-27 21:22:00 +11002318 if ((cp = colon(host)) != NULL) {
2319 *cp++ = '\0';
2320 file1 = cp;
2321 }
2322
Damien Millerd14ee1e2002-02-05 12:27:31 +11002323 host = cleanhostname(host);
2324 if (!*host) {
2325 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002326 usage();
2327 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002328
Damien Millerd14ee1e2002-02-05 12:27:31 +11002329 addargs(&args, "-oProtocol %d", sshver);
2330
2331 /* no subsystem if the server-spec contains a '/' */
2332 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2333 addargs(&args, "-s");
2334
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002335 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002336 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002337 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002338 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002339
Damien Millercc685c12003-06-04 22:51:38 +10002340 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002341 } else {
2342 args.list = NULL;
2343 addargs(&args, "sftp-server");
2344
Damien Millercc685c12003-06-04 22:51:38 +10002345 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002346 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002347 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002348
Damien Miller65e42f82010-09-24 22:15:11 +10002349 conn = do_init(in, out, copy_buffer_len, num_requests, limit_kbps);
Darren Tucker21063192010-01-08 17:10:36 +11002350 if (conn == NULL)
2351 fatal("Couldn't initialise connection to server");
2352
Damien Miller9303e652013-04-23 15:22:40 +10002353 if (!quiet) {
Darren Tucker21063192010-01-08 17:10:36 +11002354 if (sftp_direct == NULL)
2355 fprintf(stderr, "Connected to %s.\n", host);
2356 else
2357 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2358 }
2359
2360 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002361
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002362#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002363 shutdown(in, SHUT_RDWR);
2364 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002365#endif
2366
Damien Miller33804262001-02-04 23:20:18 +11002367 close(in);
2368 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002369 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002370 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002371
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002372 while (waitpid(sshpid, NULL, 0) == -1)
2373 if (errno != EINTR)
2374 fatal("Couldn't wait for ssh process: %s",
2375 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002376
Damien Miller956f3fb2003-01-10 21:40:00 +11002377 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002378}