blob: 75b16b27ef254ffbfce83e6cced0651b736959af [file] [log] [blame]
Darren Tucker1b0dd172009-10-07 08:37:48 +11001/* $OpenBSD: sftp.c,v 1.111 2009/08/18 18:36:21 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 Tucker2d963d82004-11-07 20:04:10 +110041#ifdef USE_LIBEDIT
42#include <histedit.h>
43#else
44typedef void EditLine;
45#endif
Damien Miller6ff3cad2006-03-15 11:52:09 +110046#include <signal.h>
Damien Millere7a1e5c2006-08-05 11:34:19 +100047#include <stdlib.h>
Damien Millera7a73ee2006-08-05 11:37:59 +100048#include <stdio.h>
Damien Millere3476ed2006-07-24 14:13:33 +100049#include <string.h>
Damien Millere6b3b612006-07-24 14:01:23 +100050#include <unistd.h>
Damien Millerd7834352006-08-05 12:39:39 +100051#include <stdarg.h>
Damien Miller33804262001-02-04 23:20:18 +110052
Damien Millera7058ec2008-05-20 08:57:06 +100053#ifdef HAVE_UTIL_H
54# include <util.h>
55#endif
56
57#ifdef HAVE_LIBUTIL_H
58# include <libutil.h>
59#endif
60
Damien Miller33804262001-02-04 23:20:18 +110061#include "xmalloc.h"
62#include "log.h"
63#include "pathnames.h"
Ben Lindstrom4529b702001-05-03 23:39:53 +000064#include "misc.h"
Damien Miller33804262001-02-04 23:20:18 +110065
66#include "sftp.h"
Damien Millerd7834352006-08-05 12:39:39 +100067#include "buffer.h"
Damien Miller33804262001-02-04 23:20:18 +110068#include "sftp-common.h"
69#include "sftp-client.h"
Damien Millerd7d46bb2004-02-18 14:11:13 +110070
Damien Miller20e1fab2004-02-18 14:30:55 +110071/* File to read commands from */
72FILE* infile;
73
74/* Are we in batchfile mode? */
75int batchmode = 0;
76
77/* Size of buffer used when copying files */
78size_t copy_buffer_len = 32768;
79
80/* Number of concurrent outstanding requests */
Damien Miller7f980d12008-07-14 11:29:24 +100081size_t num_requests = 64;
Damien Miller20e1fab2004-02-18 14:30:55 +110082
83/* PID of ssh transport process */
84static pid_t sshpid = -1;
85
86/* This is set to 0 if the progressmeter is not desired. */
Damien Millerc0f27d82004-03-08 23:12:19 +110087int showprogress = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +110088
Darren Tucker1b0dd172009-10-07 08:37:48 +110089/* When this option is set, we always recursively download/upload directories */
90int global_rflag = 0;
91
92/* When this option is set, the file transfers will always preserve times */
93int global_pflag = 0;
94
Darren Tuckercdf547a2004-05-24 10:12:19 +100095/* SIGINT received during command processing */
96volatile sig_atomic_t interrupted = 0;
97
Darren Tuckerb9123452004-06-22 13:06:45 +100098/* I wish qsort() took a separate ctx for the comparison function...*/
99int sort_flag;
100
Damien Miller20e1fab2004-02-18 14:30:55 +1100101int remote_glob(struct sftp_conn *, const char *, int,
102 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
Damien Miller33804262001-02-04 23:20:18 +1100103
Kevin Steves12888d12001-03-05 19:50:57 +0000104extern char *__progname;
Kevin Steves12888d12001-03-05 19:50:57 +0000105
Damien Miller20e1fab2004-02-18 14:30:55 +1100106/* Separators for interactive commands */
107#define WHITESPACE " \t\r\n"
Damien Millerd7686fd2001-02-10 00:40:03 +1100108
Darren Tuckerb9123452004-06-22 13:06:45 +1000109/* ls flags */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000110#define LS_LONG_VIEW 0x01 /* Full view ala ls -l */
111#define LS_SHORT_VIEW 0x02 /* Single row view ala ls -1 */
112#define LS_NUMERIC_VIEW 0x04 /* Long view with numeric uid/gid */
113#define LS_NAME_SORT 0x08 /* Sort by name (default) */
114#define LS_TIME_SORT 0x10 /* Sort by mtime */
115#define LS_SIZE_SORT 0x20 /* Sort by file size */
116#define LS_REVERSE_SORT 0x40 /* Reverse sort order */
Darren Tucker9a526452004-06-22 13:09:55 +1000117#define LS_SHOW_ALL 0x80 /* Don't skip filenames starting with '.' */
Darren Tuckerb9123452004-06-22 13:06:45 +1000118
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000119#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW)
120#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
Damien Miller20e1fab2004-02-18 14:30:55 +1100121
122/* Commands for interactive mode */
123#define I_CHDIR 1
124#define I_CHGRP 2
125#define I_CHMOD 3
126#define I_CHOWN 4
Damien Millerd671e5a2008-05-19 14:53:33 +1000127#define I_DF 24
Damien Miller20e1fab2004-02-18 14:30:55 +1100128#define I_GET 5
129#define I_HELP 6
130#define I_LCHDIR 7
131#define I_LLS 8
132#define I_LMKDIR 9
133#define I_LPWD 10
134#define I_LS 11
135#define I_LUMASK 12
136#define I_MKDIR 13
137#define I_PUT 14
138#define I_PWD 15
139#define I_QUIT 16
140#define I_RENAME 17
141#define I_RM 18
142#define I_RMDIR 19
143#define I_SHELL 20
144#define I_SYMLINK 21
145#define I_VERSION 22
146#define I_PROGRESS 23
147
148struct CMD {
149 const char *c;
150 const int n;
151};
152
153static const struct CMD cmds[] = {
154 { "bye", I_QUIT },
155 { "cd", I_CHDIR },
156 { "chdir", I_CHDIR },
157 { "chgrp", I_CHGRP },
158 { "chmod", I_CHMOD },
159 { "chown", I_CHOWN },
Damien Millerd671e5a2008-05-19 14:53:33 +1000160 { "df", I_DF },
Damien Miller20e1fab2004-02-18 14:30:55 +1100161 { "dir", I_LS },
162 { "exit", I_QUIT },
163 { "get", I_GET },
164 { "mget", I_GET },
165 { "help", I_HELP },
166 { "lcd", I_LCHDIR },
167 { "lchdir", I_LCHDIR },
168 { "lls", I_LLS },
169 { "lmkdir", I_LMKDIR },
170 { "ln", I_SYMLINK },
171 { "lpwd", I_LPWD },
172 { "ls", I_LS },
173 { "lumask", I_LUMASK },
174 { "mkdir", I_MKDIR },
175 { "progress", I_PROGRESS },
176 { "put", I_PUT },
177 { "mput", I_PUT },
178 { "pwd", I_PWD },
179 { "quit", I_QUIT },
180 { "rename", I_RENAME },
181 { "rm", I_RM },
182 { "rmdir", I_RMDIR },
183 { "symlink", I_SYMLINK },
184 { "version", I_VERSION },
185 { "!", I_SHELL },
186 { "?", I_HELP },
187 { NULL, -1}
188};
189
190int interactive_loop(int fd_in, int fd_out, char *file1, char *file2);
191
Damien Millerb6c85fc2007-01-05 16:30:41 +1100192/* ARGSUSED */
Damien Miller20e1fab2004-02-18 14:30:55 +1100193static void
Darren Tuckercdf547a2004-05-24 10:12:19 +1000194killchild(int signo)
195{
Darren Tuckerba66df82005-01-24 21:57:40 +1100196 if (sshpid > 1) {
Darren Tuckercdf547a2004-05-24 10:12:19 +1000197 kill(sshpid, SIGTERM);
Darren Tuckerba66df82005-01-24 21:57:40 +1100198 waitpid(sshpid, NULL, 0);
199 }
Darren Tuckercdf547a2004-05-24 10:12:19 +1000200
201 _exit(1);
202}
203
Damien Millerb6c85fc2007-01-05 16:30:41 +1100204/* ARGSUSED */
Darren Tuckercdf547a2004-05-24 10:12:19 +1000205static void
206cmd_interrupt(int signo)
207{
208 const char msg[] = "\rInterrupt \n";
Darren Tuckere2f189a2004-12-06 22:45:53 +1100209 int olderrno = errno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000210
211 write(STDERR_FILENO, msg, sizeof(msg) - 1);
212 interrupted = 1;
Darren Tuckere2f189a2004-12-06 22:45:53 +1100213 errno = olderrno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000214}
215
216static void
Damien Miller20e1fab2004-02-18 14:30:55 +1100217help(void)
218{
Damien Miller62fd18a2009-01-28 16:14:09 +1100219 printf("Available commands:\n"
220 "bye Quit sftp\n"
221 "cd path Change remote directory to 'path'\n"
222 "chgrp grp path Change group of file 'path' to 'grp'\n"
223 "chmod mode path Change permissions of file 'path' to 'mode'\n"
224 "chown own path Change owner of file 'path' to 'own'\n"
225 "df [-hi] [path] Display statistics for current directory or\n"
226 " filesystem containing 'path'\n"
227 "exit Quit sftp\n"
Darren Tucker1b0dd172009-10-07 08:37:48 +1100228 "get [-Pr] remote-path [local-path] Download file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100229 "help Display this help text\n"
230 "lcd path Change local directory to 'path'\n"
231 "lls [ls-options [path]] Display local directory listing\n"
232 "lmkdir path Create local directory\n"
233 "ln oldpath newpath Symlink remote file\n"
234 "lpwd Print local working directory\n"
235 "ls [-1aflnrSt] [path] Display remote directory listing\n"
236 "lumask umask Set local umask to 'umask'\n"
237 "mkdir path Create remote directory\n"
238 "progress Toggle display of progress meter\n"
Darren Tucker1b0dd172009-10-07 08:37:48 +1100239 "put [-Pr] local-path [remote-path] Upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100240 "pwd Display remote working directory\n"
241 "quit Quit sftp\n"
242 "rename oldpath newpath Rename remote file\n"
243 "rm path Delete remote file\n"
244 "rmdir path Remove remote directory\n"
245 "symlink oldpath newpath Symlink remote file\n"
246 "version Show SFTP version\n"
247 "!command Execute 'command' in local shell\n"
248 "! Escape to local shell\n"
249 "? Synonym for help\n");
Damien Miller20e1fab2004-02-18 14:30:55 +1100250}
251
252static void
253local_do_shell(const char *args)
254{
255 int status;
256 char *shell;
257 pid_t pid;
258
259 if (!*args)
260 args = NULL;
261
262 if ((shell = getenv("SHELL")) == NULL)
263 shell = _PATH_BSHELL;
264
265 if ((pid = fork()) == -1)
266 fatal("Couldn't fork: %s", strerror(errno));
267
268 if (pid == 0) {
269 /* XXX: child has pipe fds to ssh subproc open - issue? */
270 if (args) {
271 debug3("Executing %s -c \"%s\"", shell, args);
272 execl(shell, shell, "-c", args, (char *)NULL);
273 } else {
274 debug3("Executing %s", shell);
275 execl(shell, shell, (char *)NULL);
276 }
277 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
278 strerror(errno));
279 _exit(1);
280 }
281 while (waitpid(pid, &status, 0) == -1)
282 if (errno != EINTR)
283 fatal("Couldn't wait for child: %s", strerror(errno));
284 if (!WIFEXITED(status))
Damien Miller55b04f12006-03-26 14:23:17 +1100285 error("Shell exited abnormally");
Damien Miller20e1fab2004-02-18 14:30:55 +1100286 else if (WEXITSTATUS(status))
287 error("Shell exited with status %d", WEXITSTATUS(status));
288}
289
290static void
291local_do_ls(const char *args)
292{
293 if (!args || !*args)
294 local_do_shell(_PATH_LS);
295 else {
296 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
297 char *buf = xmalloc(len);
298
299 /* XXX: quoting - rip quoting code from ftp? */
300 snprintf(buf, len, _PATH_LS " %s", args);
301 local_do_shell(buf);
302 xfree(buf);
303 }
304}
305
306/* Strip one path (usually the pwd) from the start of another */
307static char *
308path_strip(char *path, char *strip)
309{
310 size_t len;
311
312 if (strip == NULL)
313 return (xstrdup(path));
314
315 len = strlen(strip);
Darren Tuckere2f189a2004-12-06 22:45:53 +1100316 if (strncmp(path, strip, len) == 0) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100317 if (strip[len - 1] != '/' && path[len] == '/')
318 len++;
319 return (xstrdup(path + len));
320 }
321
322 return (xstrdup(path));
323}
324
325static char *
Damien Miller20e1fab2004-02-18 14:30:55 +1100326make_absolute(char *p, char *pwd)
327{
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000328 char *abs_str;
Damien Miller20e1fab2004-02-18 14:30:55 +1100329
330 /* Derelativise */
331 if (p && p[0] != '/') {
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000332 abs_str = path_append(pwd, p);
Damien Miller20e1fab2004-02-18 14:30:55 +1100333 xfree(p);
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000334 return(abs_str);
Damien Miller20e1fab2004-02-18 14:30:55 +1100335 } else
336 return(p);
337}
338
339static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100340parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag,
341 int *rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100342{
Damien Millerf184bcf2008-06-29 22:45:13 +1000343 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000344 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100345
Damien Miller1cbc2922007-10-26 14:27:45 +1000346 optind = optreset = 1;
347 opterr = 0;
348
Darren Tucker1b0dd172009-10-07 08:37:48 +1100349 *rflag = *pflag = 0;
350 while ((ch = getopt(argc, argv, "PpRr")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000351 switch (ch) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100352 case 'p':
353 case 'P':
354 *pflag = 1;
355 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100356 case 'r':
357 case 'R':
358 *rflag = 1;
359 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100360 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000361 error("%s: Invalid flag -%c", cmd, optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000362 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100363 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100364 }
365
Damien Miller1cbc2922007-10-26 14:27:45 +1000366 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100367}
368
369static int
Damien Miller1cbc2922007-10-26 14:27:45 +1000370parse_ls_flags(char **argv, int argc, int *lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100371{
Damien Millerf184bcf2008-06-29 22:45:13 +1000372 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000373 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100374
Damien Miller1cbc2922007-10-26 14:27:45 +1000375 optind = optreset = 1;
376 opterr = 0;
377
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000378 *lflag = LS_NAME_SORT;
Damien Miller1cbc2922007-10-26 14:27:45 +1000379 while ((ch = getopt(argc, argv, "1Saflnrt")) != -1) {
380 switch (ch) {
381 case '1':
382 *lflag &= ~VIEW_FLAGS;
383 *lflag |= LS_SHORT_VIEW;
384 break;
385 case 'S':
386 *lflag &= ~SORT_FLAGS;
387 *lflag |= LS_SIZE_SORT;
388 break;
389 case 'a':
390 *lflag |= LS_SHOW_ALL;
391 break;
392 case 'f':
393 *lflag &= ~SORT_FLAGS;
394 break;
395 case 'l':
396 *lflag &= ~VIEW_FLAGS;
397 *lflag |= LS_LONG_VIEW;
398 break;
399 case 'n':
400 *lflag &= ~VIEW_FLAGS;
401 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
402 break;
403 case 'r':
404 *lflag |= LS_REVERSE_SORT;
405 break;
406 case 't':
407 *lflag &= ~SORT_FLAGS;
408 *lflag |= LS_TIME_SORT;
409 break;
410 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000411 error("ls: Invalid flag -%c", optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000412 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100413 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100414 }
415
Damien Miller1cbc2922007-10-26 14:27:45 +1000416 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100417}
418
419static int
Damien Millerd671e5a2008-05-19 14:53:33 +1000420parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
421{
Damien Millerf184bcf2008-06-29 22:45:13 +1000422 extern int opterr, optind, optopt, optreset;
Damien Millerd671e5a2008-05-19 14:53:33 +1000423 int ch;
424
425 optind = optreset = 1;
426 opterr = 0;
427
428 *hflag = *iflag = 0;
429 while ((ch = getopt(argc, argv, "hi")) != -1) {
430 switch (ch) {
431 case 'h':
432 *hflag = 1;
433 break;
434 case 'i':
435 *iflag = 1;
436 break;
437 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000438 error("%s: Invalid flag -%c", cmd, optopt);
Damien Millerd671e5a2008-05-19 14:53:33 +1000439 return -1;
440 }
441 }
442
443 return optind;
444}
445
446static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100447is_dir(char *path)
448{
449 struct stat sb;
450
451 /* XXX: report errors? */
452 if (stat(path, &sb) == -1)
453 return(0);
454
Darren Tucker1e80e402006-09-21 12:59:33 +1000455 return(S_ISDIR(sb.st_mode));
Damien Miller20e1fab2004-02-18 14:30:55 +1100456}
457
458static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100459remote_is_dir(struct sftp_conn *conn, char *path)
460{
461 Attrib *a;
462
463 /* XXX: report errors? */
464 if ((a = do_stat(conn, path, 1)) == NULL)
465 return(0);
466 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
467 return(0);
Darren Tucker1e80e402006-09-21 12:59:33 +1000468 return(S_ISDIR(a->perm));
Damien Miller20e1fab2004-02-18 14:30:55 +1100469}
470
Darren Tucker1b0dd172009-10-07 08:37:48 +1100471/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
Damien Miller20e1fab2004-02-18 14:30:55 +1100472static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100473pathname_is_dir(char *pathname)
474{
475 size_t l = strlen(pathname);
476
477 return l > 0 && pathname[l - 1] == '/';
478}
479
480static int
481process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
482 int pflag, int rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100483{
484 char *abs_src = NULL;
485 char *abs_dst = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100486 glob_t g;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100487 char *filename, *tmp=NULL;
488 int i, err = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100489
490 abs_src = xstrdup(src);
491 abs_src = make_absolute(abs_src, pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +1100492 memset(&g, 0, sizeof(g));
Darren Tucker1b0dd172009-10-07 08:37:48 +1100493
Damien Miller20e1fab2004-02-18 14:30:55 +1100494 debug3("Looking up %s", abs_src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100495 if (remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100496 error("File \"%s\" not found.", abs_src);
497 err = -1;
498 goto out;
499 }
500
Darren Tucker1b0dd172009-10-07 08:37:48 +1100501 /*
502 * If multiple matches then dst must be a directory or
503 * unspecified.
504 */
505 if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
506 error("Multiple source paths, but destination "
507 "\"%s\" is not a directory", dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100508 err = -1;
509 goto out;
510 }
511
Darren Tuckercdf547a2004-05-24 10:12:19 +1000512 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100513 tmp = xstrdup(g.gl_pathv[i]);
514 if ((filename = basename(tmp)) == NULL) {
515 error("basename %s: %s", tmp, strerror(errno));
516 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100517 err = -1;
518 goto out;
519 }
520
521 if (g.gl_matchc == 1 && dst) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100522 if (is_dir(dst)) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100523 abs_dst = path_append(dst, filename);
524 } else {
Damien Miller20e1fab2004-02-18 14:30:55 +1100525 abs_dst = xstrdup(dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100526 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100527 } else if (dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100528 abs_dst = path_append(dst, filename);
529 } else {
530 abs_dst = xstrdup(filename);
531 }
532 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100533
534 printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100535 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
536 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
537 pflag || global_pflag, 1) == -1)
538 err = -1;
539 } else {
540 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
541 pflag || global_pflag) == -1)
542 err = -1;
543 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100544 xfree(abs_dst);
545 abs_dst = NULL;
546 }
547
548out:
549 xfree(abs_src);
Damien Miller20e1fab2004-02-18 14:30:55 +1100550 globfree(&g);
551 return(err);
552}
553
554static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100555process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
556 int pflag, int rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100557{
558 char *tmp_dst = NULL;
559 char *abs_dst = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100560 char *tmp = NULL, *filename = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100561 glob_t g;
562 int err = 0;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100563 int i, dst_is_dir = 1;
Damien Milleraec5cf82008-02-10 22:26:24 +1100564 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100565
566 if (dst) {
567 tmp_dst = xstrdup(dst);
568 tmp_dst = make_absolute(tmp_dst, pwd);
569 }
570
571 memset(&g, 0, sizeof(g));
572 debug3("Looking up %s", src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100573 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100574 error("File \"%s\" not found.", src);
575 err = -1;
576 goto out;
577 }
578
Darren Tucker1b0dd172009-10-07 08:37:48 +1100579 /* If we aren't fetching to pwd then stash this status for later */
580 if (tmp_dst != NULL)
581 dst_is_dir = remote_is_dir(conn, tmp_dst);
582
Damien Miller20e1fab2004-02-18 14:30:55 +1100583 /* If multiple matches, dst may be directory or unspecified */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100584 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
585 error("Multiple paths match, but destination "
586 "\"%s\" is not a directory", tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100587 err = -1;
588 goto out;
589 }
590
Darren Tuckercdf547a2004-05-24 10:12:19 +1000591 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Milleraec5cf82008-02-10 22:26:24 +1100592 if (stat(g.gl_pathv[i], &sb) == -1) {
593 err = -1;
594 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
595 continue;
596 }
Darren Tucker1b0dd172009-10-07 08:37:48 +1100597
598 tmp = xstrdup(g.gl_pathv[i]);
599 if ((filename = basename(tmp)) == NULL) {
600 error("basename %s: %s", tmp, strerror(errno));
601 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100602 err = -1;
603 goto out;
604 }
605
606 if (g.gl_matchc == 1 && tmp_dst) {
607 /* If directory specified, append filename */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100608 if (dst_is_dir)
609 abs_dst = path_append(tmp_dst, filename);
610 else
Damien Miller20e1fab2004-02-18 14:30:55 +1100611 abs_dst = xstrdup(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100612 } else if (tmp_dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100613 abs_dst = path_append(tmp_dst, filename);
614 } else {
615 abs_dst = make_absolute(xstrdup(filename), pwd);
616 }
617 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100618
619 printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100620 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
621 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
622 pflag || global_pflag, 1) == -1)
623 err = -1;
624 } else {
625 if (do_upload(conn, g.gl_pathv[i], abs_dst,
626 pflag || global_pflag) == -1)
627 err = -1;
628 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100629 }
630
631out:
632 if (abs_dst)
633 xfree(abs_dst);
634 if (tmp_dst)
635 xfree(tmp_dst);
636 globfree(&g);
637 return(err);
638}
639
640static int
641sdirent_comp(const void *aa, const void *bb)
642{
643 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
644 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000645 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100646
Darren Tuckerb9123452004-06-22 13:06:45 +1000647#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000648 if (sort_flag & LS_NAME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000649 return (rmul * strcmp(a->filename, b->filename));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000650 else if (sort_flag & LS_TIME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000651 return (rmul * NCMP(a->a.mtime, b->a.mtime));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000652 else if (sort_flag & LS_SIZE_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000653 return (rmul * NCMP(a->a.size, b->a.size));
654
655 fatal("Unknown ls sort type");
Damien Miller20e1fab2004-02-18 14:30:55 +1100656}
657
658/* sftp ls.1 replacement for directories */
659static int
660do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
661{
Damien Millereccb9de2005-06-17 12:59:34 +1000662 int n;
663 u_int c = 1, colspace = 0, columns = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100664 SFTP_DIRENT **d;
665
666 if ((n = do_readdir(conn, path, &d)) != 0)
667 return (n);
668
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000669 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000670 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100671 struct winsize ws;
672 char *tmp;
673
674 /* Count entries for sort and find longest filename */
Darren Tucker9a526452004-06-22 13:09:55 +1000675 for (n = 0; d[n] != NULL; n++) {
676 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
677 m = MAX(m, strlen(d[n]->filename));
678 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100679
680 /* Add any subpath that also needs to be counted */
681 tmp = path_strip(path, strip_path);
682 m += strlen(tmp);
683 xfree(tmp);
684
685 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
686 width = ws.ws_col;
687
688 columns = width / (m + 2);
689 columns = MAX(columns, 1);
690 colspace = width / columns;
691 colspace = MIN(colspace, width);
692 }
693
Darren Tuckerb9123452004-06-22 13:06:45 +1000694 if (lflag & SORT_FLAGS) {
Damien Miller653b93b2005-11-05 15:15:23 +1100695 for (n = 0; d[n] != NULL; n++)
696 ; /* count entries */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000697 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
Darren Tuckerb9123452004-06-22 13:06:45 +1000698 qsort(d, n, sizeof(*d), sdirent_comp);
699 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100700
Darren Tuckercdf547a2004-05-24 10:12:19 +1000701 for (n = 0; d[n] != NULL && !interrupted; n++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100702 char *tmp, *fname;
703
Darren Tucker9a526452004-06-22 13:09:55 +1000704 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
705 continue;
706
Damien Miller20e1fab2004-02-18 14:30:55 +1100707 tmp = path_append(path, d[n]->filename);
708 fname = path_strip(tmp, strip_path);
709 xfree(tmp);
710
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000711 if (lflag & LS_LONG_VIEW) {
712 if (lflag & LS_NUMERIC_VIEW) {
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000713 char *lname;
714 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100715
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000716 memset(&sb, 0, sizeof(sb));
717 attrib_to_stat(&d[n]->a, &sb);
718 lname = ls_file(fname, &sb, 1);
719 printf("%s\n", lname);
720 xfree(lname);
721 } else
722 printf("%s\n", d[n]->longname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100723 } else {
724 printf("%-*s", colspace, fname);
725 if (c >= columns) {
726 printf("\n");
727 c = 1;
728 } else
729 c++;
730 }
731
732 xfree(fname);
733 }
734
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000735 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100736 printf("\n");
737
738 free_sftp_dirents(d);
739 return (0);
740}
741
742/* sftp ls.1 replacement which handles path globs */
743static int
744do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
745 int lflag)
746{
747 glob_t g;
Damien Millereccb9de2005-06-17 12:59:34 +1000748 u_int i, c = 1, colspace = 0, columns = 1;
Darren Tucker596dcfa2004-12-11 13:37:22 +1100749 Attrib *a = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100750
751 memset(&g, 0, sizeof(g));
752
753 if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE,
Darren Tucker596dcfa2004-12-11 13:37:22 +1100754 NULL, &g) || (g.gl_pathc && !g.gl_matchc)) {
755 if (g.gl_pathc)
756 globfree(&g);
Damien Miller20e1fab2004-02-18 14:30:55 +1100757 error("Can't ls: \"%s\" not found", path);
758 return (-1);
759 }
760
Darren Tuckercdf547a2004-05-24 10:12:19 +1000761 if (interrupted)
762 goto out;
763
Damien Miller20e1fab2004-02-18 14:30:55 +1100764 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100765 * If the glob returns a single match and it is a directory,
766 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100767 */
Darren Tucker596dcfa2004-12-11 13:37:22 +1100768 if (g.gl_matchc == 1) {
769 if ((a = do_lstat(conn, g.gl_pathv[0], 1)) == NULL) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100770 globfree(&g);
771 return (-1);
772 }
773 if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
774 S_ISDIR(a->perm)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100775 int err;
776
777 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +1100778 globfree(&g);
Darren Tucker596dcfa2004-12-11 13:37:22 +1100779 return (err);
Damien Miller20e1fab2004-02-18 14:30:55 +1100780 }
781 }
782
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000783 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000784 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100785 struct winsize ws;
786
787 /* Count entries for sort and find longest filename */
788 for (i = 0; g.gl_pathv[i]; i++)
789 m = MAX(m, strlen(g.gl_pathv[i]));
790
791 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
792 width = ws.ws_col;
793
794 columns = width / (m + 2);
795 columns = MAX(columns, 1);
796 colspace = width / columns;
797 }
798
Darren Tucker596dcfa2004-12-11 13:37:22 +1100799 for (i = 0; g.gl_pathv[i] && !interrupted; i++, a = NULL) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100800 char *fname;
801
802 fname = path_strip(g.gl_pathv[i], strip_path);
803
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000804 if (lflag & LS_LONG_VIEW) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100805 char *lname;
806 struct stat sb;
807
808 /*
809 * XXX: this is slow - 1 roundtrip per path
810 * A solution to this is to fork glob() and
811 * build a sftp specific version which keeps the
812 * attribs (which currently get thrown away)
813 * that the server returns as well as the filenames.
814 */
815 memset(&sb, 0, sizeof(sb));
Darren Tucker596dcfa2004-12-11 13:37:22 +1100816 if (a == NULL)
817 a = do_lstat(conn, g.gl_pathv[i], 1);
Damien Miller20e1fab2004-02-18 14:30:55 +1100818 if (a != NULL)
819 attrib_to_stat(a, &sb);
820 lname = ls_file(fname, &sb, 1);
821 printf("%s\n", lname);
822 xfree(lname);
823 } else {
824 printf("%-*s", colspace, fname);
825 if (c >= columns) {
826 printf("\n");
827 c = 1;
828 } else
829 c++;
830 }
831 xfree(fname);
832 }
833
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000834 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100835 printf("\n");
836
Darren Tuckercdf547a2004-05-24 10:12:19 +1000837 out:
Damien Miller20e1fab2004-02-18 14:30:55 +1100838 if (g.gl_pathc)
839 globfree(&g);
840
841 return (0);
842}
843
Damien Millerd671e5a2008-05-19 14:53:33 +1000844static int
845do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
846{
Darren Tucker7b598892008-06-09 22:49:36 +1000847 struct sftp_statvfs st;
Damien Millerd671e5a2008-05-19 14:53:33 +1000848 char s_used[FMT_SCALED_STRSIZE];
849 char s_avail[FMT_SCALED_STRSIZE];
850 char s_root[FMT_SCALED_STRSIZE];
851 char s_total[FMT_SCALED_STRSIZE];
852
853 if (do_statvfs(conn, path, &st, 1) == -1)
854 return -1;
855 if (iflag) {
856 printf(" Inodes Used Avail "
857 "(root) %%Capacity\n");
858 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
859 (unsigned long long)st.f_files,
860 (unsigned long long)(st.f_files - st.f_ffree),
861 (unsigned long long)st.f_favail,
862 (unsigned long long)st.f_ffree,
863 (unsigned long long)(100 * (st.f_files - st.f_ffree) /
864 st.f_files));
865 } else if (hflag) {
866 strlcpy(s_used, "error", sizeof(s_used));
867 strlcpy(s_avail, "error", sizeof(s_avail));
868 strlcpy(s_root, "error", sizeof(s_root));
869 strlcpy(s_total, "error", sizeof(s_total));
870 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
871 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
872 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
873 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
874 printf(" Size Used Avail (root) %%Capacity\n");
875 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
876 s_total, s_used, s_avail, s_root,
877 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
878 st.f_blocks));
879 } else {
880 printf(" Size Used Avail "
881 "(root) %%Capacity\n");
882 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
883 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
884 (unsigned long long)(st.f_frsize *
885 (st.f_blocks - st.f_bfree) / 1024),
886 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
887 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
888 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
889 st.f_blocks));
890 }
891 return 0;
892}
893
Damien Miller1cbc2922007-10-26 14:27:45 +1000894/*
895 * Undo escaping of glob sequences in place. Used to undo extra escaping
896 * applied in makeargv() when the string is destined for a function that
897 * does not glob it.
898 */
899static void
900undo_glob_escape(char *s)
901{
902 size_t i, j;
903
904 for (i = j = 0;;) {
905 if (s[i] == '\0') {
906 s[j] = '\0';
907 return;
908 }
909 if (s[i] != '\\') {
910 s[j++] = s[i++];
911 continue;
912 }
913 /* s[i] == '\\' */
914 ++i;
915 switch (s[i]) {
916 case '?':
917 case '[':
918 case '*':
919 case '\\':
920 s[j++] = s[i++];
921 break;
922 case '\0':
923 s[j++] = '\\';
924 s[j] = '\0';
925 return;
926 default:
927 s[j++] = '\\';
928 s[j++] = s[i++];
929 break;
930 }
931 }
932}
933
934/*
935 * Split a string into an argument vector using sh(1)-style quoting,
936 * comment and escaping rules, but with some tweaks to handle glob(3)
937 * wildcards.
938 * Returns NULL on error or a NULL-terminated array of arguments.
939 */
940#define MAXARGS 128
941#define MAXARGLEN 8192
942static char **
943makeargv(const char *arg, int *argcp)
944{
945 int argc, quot;
946 size_t i, j;
947 static char argvs[MAXARGLEN];
948 static char *argv[MAXARGS + 1];
949 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
950
951 *argcp = argc = 0;
952 if (strlen(arg) > sizeof(argvs) - 1) {
953 args_too_longs:
954 error("string too long");
955 return NULL;
956 }
957 state = MA_START;
958 i = j = 0;
959 for (;;) {
960 if (isspace(arg[i])) {
961 if (state == MA_UNQUOTED) {
962 /* Terminate current argument */
963 argvs[j++] = '\0';
964 argc++;
965 state = MA_START;
966 } else if (state != MA_START)
967 argvs[j++] = arg[i];
968 } else if (arg[i] == '"' || arg[i] == '\'') {
969 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
970 if (state == MA_START) {
971 argv[argc] = argvs + j;
972 state = q;
973 } else if (state == MA_UNQUOTED)
974 state = q;
975 else if (state == q)
976 state = MA_UNQUOTED;
977 else
978 argvs[j++] = arg[i];
979 } else if (arg[i] == '\\') {
980 if (state == MA_SQUOTE || state == MA_DQUOTE) {
981 quot = state == MA_SQUOTE ? '\'' : '"';
982 /* Unescape quote we are in */
983 /* XXX support \n and friends? */
984 if (arg[i + 1] == quot) {
985 i++;
986 argvs[j++] = arg[i];
987 } else if (arg[i + 1] == '?' ||
988 arg[i + 1] == '[' || arg[i + 1] == '*') {
989 /*
990 * Special case for sftp: append
991 * double-escaped glob sequence -
992 * glob will undo one level of
993 * escaping. NB. string can grow here.
994 */
995 if (j >= sizeof(argvs) - 5)
996 goto args_too_longs;
997 argvs[j++] = '\\';
998 argvs[j++] = arg[i++];
999 argvs[j++] = '\\';
1000 argvs[j++] = arg[i];
1001 } else {
1002 argvs[j++] = arg[i++];
1003 argvs[j++] = arg[i];
1004 }
1005 } else {
1006 if (state == MA_START) {
1007 argv[argc] = argvs + j;
1008 state = MA_UNQUOTED;
1009 }
1010 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1011 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1012 /*
1013 * Special case for sftp: append
1014 * escaped glob sequence -
1015 * glob will undo one level of
1016 * escaping.
1017 */
1018 argvs[j++] = arg[i++];
1019 argvs[j++] = arg[i];
1020 } else {
1021 /* Unescape everything */
1022 /* XXX support \n and friends? */
1023 i++;
1024 argvs[j++] = arg[i];
1025 }
1026 }
1027 } else if (arg[i] == '#') {
1028 if (state == MA_SQUOTE || state == MA_DQUOTE)
1029 argvs[j++] = arg[i];
1030 else
1031 goto string_done;
1032 } else if (arg[i] == '\0') {
1033 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1034 error("Unterminated quoted argument");
1035 return NULL;
1036 }
1037 string_done:
1038 if (state == MA_UNQUOTED) {
1039 argvs[j++] = '\0';
1040 argc++;
1041 }
1042 break;
1043 } else {
1044 if (state == MA_START) {
1045 argv[argc] = argvs + j;
1046 state = MA_UNQUOTED;
1047 }
1048 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1049 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1050 /*
1051 * Special case for sftp: escape quoted
1052 * glob(3) wildcards. NB. string can grow
1053 * here.
1054 */
1055 if (j >= sizeof(argvs) - 3)
1056 goto args_too_longs;
1057 argvs[j++] = '\\';
1058 argvs[j++] = arg[i];
1059 } else
1060 argvs[j++] = arg[i];
1061 }
1062 i++;
1063 }
1064 *argcp = argc;
1065 return argv;
1066}
1067
Damien Miller20e1fab2004-02-18 14:30:55 +11001068static int
Darren Tucker1b0dd172009-10-07 08:37:48 +11001069parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag, int *hflag,
Damien Miller20e1fab2004-02-18 14:30:55 +11001070 unsigned long *n_arg, char **path1, char **path2)
1071{
1072 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001073 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001074 int base = 0;
1075 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001076 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001077
1078 /* Skip leading whitespace */
1079 cp = cp + strspn(cp, WHITESPACE);
1080
1081 /* Ignore blank lines and lines which begin with comment '#' char */
1082 if (*cp == '\0' || *cp == '#')
1083 return (0);
1084
1085 /* Check for leading '-' (disable error processing) */
1086 *iflag = 0;
1087 if (*cp == '-') {
1088 *iflag = 1;
1089 cp++;
1090 }
1091
Damien Miller1cbc2922007-10-26 14:27:45 +10001092 if ((argv = makeargv(cp, &argc)) == NULL)
1093 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001094
Damien Miller1cbc2922007-10-26 14:27:45 +10001095 /* Figure out which command we have */
1096 for (i = 0; cmds[i].c != NULL; i++) {
1097 if (strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001098 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001099 }
1100 cmdnum = cmds[i].n;
1101 cmd = cmds[i].c;
1102
1103 /* Special case */
1104 if (*cp == '!') {
1105 cp++;
1106 cmdnum = I_SHELL;
1107 } else if (cmdnum == -1) {
1108 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001109 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001110 }
1111
1112 /* Get arguments and parse flags */
Darren Tucker1b0dd172009-10-07 08:37:48 +11001113 *lflag = *pflag = *rflag = *hflag = *n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001114 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001115 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001116 switch (cmdnum) {
1117 case I_GET:
1118 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001119 if ((optidx = parse_getput_flags(cmd, argv, argc, pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001120 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001121 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001122 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001123 error("You must specify at least one path after a "
1124 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001125 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001126 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001127 *path1 = xstrdup(argv[optidx]);
1128 /* Get second pathname (optional) */
1129 if (argc - optidx > 1) {
1130 *path2 = xstrdup(argv[optidx + 1]);
1131 /* Destination is not globbed */
1132 undo_glob_escape(*path2);
1133 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001134 break;
1135 case I_RENAME:
1136 case I_SYMLINK:
Damien Miller1cbc2922007-10-26 14:27:45 +10001137 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001138 error("You must specify two paths after a %s "
1139 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001140 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001141 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001142 *path1 = xstrdup(argv[optidx]);
1143 *path2 = xstrdup(argv[optidx + 1]);
1144 /* Paths are not globbed */
1145 undo_glob_escape(*path1);
1146 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001147 break;
1148 case I_RM:
1149 case I_MKDIR:
1150 case I_RMDIR:
1151 case I_CHDIR:
1152 case I_LCHDIR:
1153 case I_LMKDIR:
1154 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001155 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001156 error("You must specify a path after a %s command.",
1157 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001158 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001159 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001160 *path1 = xstrdup(argv[optidx]);
1161 /* Only "rm" globs */
1162 if (cmdnum != I_RM)
1163 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001164 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001165 case I_DF:
1166 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1167 iflag)) == -1)
1168 return -1;
1169 /* Default to current directory if no path specified */
1170 if (argc - optidx < 1)
1171 *path1 = NULL;
1172 else {
1173 *path1 = xstrdup(argv[optidx]);
1174 undo_glob_escape(*path1);
1175 }
1176 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001177 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001178 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001179 return(-1);
1180 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001181 if (argc - optidx > 0)
1182 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001183 break;
1184 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001185 /* Skip ls command and following whitespace */
1186 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001187 case I_SHELL:
1188 /* Uses the rest of the line */
1189 break;
1190 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001191 case I_CHMOD:
1192 base = 8;
1193 case I_CHOWN:
1194 case I_CHGRP:
1195 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001196 if (argc - optidx < 1)
1197 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001198 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001199 l = strtol(argv[optidx], &cp2, base);
1200 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1201 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1202 l < 0) {
1203 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001204 error("You must supply a numeric argument "
1205 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001206 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001207 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001208 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001209 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001210 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001211 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001212 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001213 error("You must specify a path after a %s command.",
1214 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001215 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001216 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001217 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001218 break;
1219 case I_QUIT:
1220 case I_PWD:
1221 case I_LPWD:
1222 case I_HELP:
1223 case I_VERSION:
1224 case I_PROGRESS:
1225 break;
1226 default:
1227 fatal("Command not implemented");
1228 }
1229
1230 *cpp = cp;
1231 return(cmdnum);
1232}
1233
1234static int
1235parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1236 int err_abort)
1237{
1238 char *path1, *path2, *tmp;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001239 int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001240 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001241 Attrib a, *aa;
1242 char path_buf[MAXPATHLEN];
1243 int err = 0;
1244 glob_t g;
1245
1246 path1 = path2 = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001247 cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag, &n_arg,
Damien Miller20e1fab2004-02-18 14:30:55 +11001248 &path1, &path2);
1249
1250 if (iflag != 0)
1251 err_abort = 0;
1252
1253 memset(&g, 0, sizeof(g));
1254
1255 /* Perform command */
1256 switch (cmdnum) {
1257 case 0:
1258 /* Blank line */
1259 break;
1260 case -1:
1261 /* Unrecognized command */
1262 err = -1;
1263 break;
1264 case I_GET:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001265 err = process_get(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001266 break;
1267 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001268 err = process_put(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001269 break;
1270 case I_RENAME:
1271 path1 = make_absolute(path1, *pwd);
1272 path2 = make_absolute(path2, *pwd);
1273 err = do_rename(conn, path1, path2);
1274 break;
1275 case I_SYMLINK:
1276 path2 = make_absolute(path2, *pwd);
1277 err = do_symlink(conn, path1, path2);
1278 break;
1279 case I_RM:
1280 path1 = make_absolute(path1, *pwd);
1281 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001282 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001283 printf("Removing %s\n", g.gl_pathv[i]);
1284 err = do_rm(conn, g.gl_pathv[i]);
1285 if (err != 0 && err_abort)
1286 break;
1287 }
1288 break;
1289 case I_MKDIR:
1290 path1 = make_absolute(path1, *pwd);
1291 attrib_clear(&a);
1292 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1293 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001294 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001295 break;
1296 case I_RMDIR:
1297 path1 = make_absolute(path1, *pwd);
1298 err = do_rmdir(conn, path1);
1299 break;
1300 case I_CHDIR:
1301 path1 = make_absolute(path1, *pwd);
1302 if ((tmp = do_realpath(conn, path1)) == NULL) {
1303 err = 1;
1304 break;
1305 }
1306 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1307 xfree(tmp);
1308 err = 1;
1309 break;
1310 }
1311 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1312 error("Can't change directory: Can't check target");
1313 xfree(tmp);
1314 err = 1;
1315 break;
1316 }
1317 if (!S_ISDIR(aa->perm)) {
1318 error("Can't change directory: \"%s\" is not "
1319 "a directory", tmp);
1320 xfree(tmp);
1321 err = 1;
1322 break;
1323 }
1324 xfree(*pwd);
1325 *pwd = tmp;
1326 break;
1327 case I_LS:
1328 if (!path1) {
1329 do_globbed_ls(conn, *pwd, *pwd, lflag);
1330 break;
1331 }
1332
1333 /* Strip pwd off beginning of non-absolute paths */
1334 tmp = NULL;
1335 if (*path1 != '/')
1336 tmp = *pwd;
1337
1338 path1 = make_absolute(path1, *pwd);
1339 err = do_globbed_ls(conn, path1, tmp, lflag);
1340 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001341 case I_DF:
1342 /* Default to current directory if no path specified */
1343 if (path1 == NULL)
1344 path1 = xstrdup(*pwd);
1345 path1 = make_absolute(path1, *pwd);
1346 err = do_df(conn, path1, hflag, iflag);
1347 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001348 case I_LCHDIR:
1349 if (chdir(path1) == -1) {
1350 error("Couldn't change local directory to "
1351 "\"%s\": %s", path1, strerror(errno));
1352 err = 1;
1353 }
1354 break;
1355 case I_LMKDIR:
1356 if (mkdir(path1, 0777) == -1) {
1357 error("Couldn't create local directory "
1358 "\"%s\": %s", path1, strerror(errno));
1359 err = 1;
1360 }
1361 break;
1362 case I_LLS:
1363 local_do_ls(cmd);
1364 break;
1365 case I_SHELL:
1366 local_do_shell(cmd);
1367 break;
1368 case I_LUMASK:
1369 umask(n_arg);
1370 printf("Local umask: %03lo\n", n_arg);
1371 break;
1372 case I_CHMOD:
1373 path1 = make_absolute(path1, *pwd);
1374 attrib_clear(&a);
1375 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1376 a.perm = n_arg;
1377 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001378 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001379 printf("Changing mode on %s\n", g.gl_pathv[i]);
1380 err = do_setstat(conn, g.gl_pathv[i], &a);
1381 if (err != 0 && err_abort)
1382 break;
1383 }
1384 break;
1385 case I_CHOWN:
1386 case I_CHGRP:
1387 path1 = make_absolute(path1, *pwd);
1388 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001389 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001390 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001391 if (err_abort) {
1392 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001393 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001394 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001395 continue;
1396 }
1397 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1398 error("Can't get current ownership of "
1399 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001400 if (err_abort) {
1401 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001402 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001403 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001404 continue;
1405 }
1406 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1407 if (cmdnum == I_CHOWN) {
1408 printf("Changing owner on %s\n", g.gl_pathv[i]);
1409 aa->uid = n_arg;
1410 } else {
1411 printf("Changing group on %s\n", g.gl_pathv[i]);
1412 aa->gid = n_arg;
1413 }
1414 err = do_setstat(conn, g.gl_pathv[i], aa);
1415 if (err != 0 && err_abort)
1416 break;
1417 }
1418 break;
1419 case I_PWD:
1420 printf("Remote working directory: %s\n", *pwd);
1421 break;
1422 case I_LPWD:
1423 if (!getcwd(path_buf, sizeof(path_buf))) {
1424 error("Couldn't get local cwd: %s", strerror(errno));
1425 err = -1;
1426 break;
1427 }
1428 printf("Local working directory: %s\n", path_buf);
1429 break;
1430 case I_QUIT:
1431 /* Processed below */
1432 break;
1433 case I_HELP:
1434 help();
1435 break;
1436 case I_VERSION:
1437 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1438 break;
1439 case I_PROGRESS:
1440 showprogress = !showprogress;
1441 if (showprogress)
1442 printf("Progress meter enabled\n");
1443 else
1444 printf("Progress meter disabled\n");
1445 break;
1446 default:
1447 fatal("%d is not implemented", cmdnum);
1448 }
1449
1450 if (g.gl_pathc)
1451 globfree(&g);
1452 if (path1)
1453 xfree(path1);
1454 if (path2)
1455 xfree(path2);
1456
1457 /* If an unignored error occurs in batch mode we should abort. */
1458 if (err_abort && err != 0)
1459 return (-1);
1460 else if (cmdnum == I_QUIT)
1461 return (1);
1462
1463 return (0);
1464}
1465
Darren Tucker2d963d82004-11-07 20:04:10 +11001466#ifdef USE_LIBEDIT
1467static char *
1468prompt(EditLine *el)
1469{
1470 return ("sftp> ");
1471}
1472#endif
1473
Damien Miller20e1fab2004-02-18 14:30:55 +11001474int
1475interactive_loop(int fd_in, int fd_out, char *file1, char *file2)
1476{
1477 char *pwd;
1478 char *dir = NULL;
1479 char cmd[2048];
1480 struct sftp_conn *conn;
Damien Miller0e2c1022005-08-12 22:16:22 +10001481 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11001482 EditLine *el = NULL;
1483#ifdef USE_LIBEDIT
1484 History *hl = NULL;
1485 HistEvent hev;
1486 extern char *__progname;
1487
1488 if (!batchmode && isatty(STDIN_FILENO)) {
1489 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1490 fatal("Couldn't initialise editline");
1491 if ((hl = history_init()) == NULL)
1492 fatal("Couldn't initialise editline history");
1493 history(hl, &hev, H_SETSIZE, 100);
1494 el_set(el, EL_HIST, history, hl);
1495
1496 el_set(el, EL_PROMPT, prompt);
1497 el_set(el, EL_EDITOR, "emacs");
1498 el_set(el, EL_TERMINAL, NULL);
1499 el_set(el, EL_SIGNAL, 1);
1500 el_source(el, NULL);
1501 }
1502#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001503
1504 conn = do_init(fd_in, fd_out, copy_buffer_len, num_requests);
1505 if (conn == NULL)
1506 fatal("Couldn't initialise connection to server");
1507
1508 pwd = do_realpath(conn, ".");
1509 if (pwd == NULL)
1510 fatal("Need cwd");
1511
1512 if (file1 != NULL) {
1513 dir = xstrdup(file1);
1514 dir = make_absolute(dir, pwd);
1515
1516 if (remote_is_dir(conn, dir) && file2 == NULL) {
1517 printf("Changing to: %s\n", dir);
1518 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tuckercd516ef2004-12-06 22:43:43 +11001519 if (parse_dispatch_command(conn, cmd, &pwd, 1) != 0) {
1520 xfree(dir);
1521 xfree(pwd);
Damien Millere0b90a62006-03-26 13:51:44 +11001522 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001523 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11001524 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001525 } else {
1526 if (file2 == NULL)
1527 snprintf(cmd, sizeof cmd, "get %s", dir);
1528 else
1529 snprintf(cmd, sizeof cmd, "get %s %s", dir,
1530 file2);
1531
1532 err = parse_dispatch_command(conn, cmd, &pwd, 1);
1533 xfree(dir);
1534 xfree(pwd);
Damien Millere0b90a62006-03-26 13:51:44 +11001535 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001536 return (err);
1537 }
1538 xfree(dir);
1539 }
1540
Darren Tucker93e7e8f2005-08-23 08:06:55 +10001541#if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF)
Damien Miller20e1fab2004-02-18 14:30:55 +11001542 setvbuf(stdout, NULL, _IOLBF, 0);
1543 setvbuf(infile, NULL, _IOLBF, 0);
1544#else
Damien Miller37294fb2005-07-17 17:18:49 +10001545 setlinebuf(stdout);
1546 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11001547#endif
1548
Damien Miller0e2c1022005-08-12 22:16:22 +10001549 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11001550 err = 0;
1551 for (;;) {
1552 char *cp;
1553
Darren Tuckercdf547a2004-05-24 10:12:19 +10001554 signal(SIGINT, SIG_IGN);
1555
Darren Tucker2d963d82004-11-07 20:04:10 +11001556 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001557 if (interactive)
1558 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11001559 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001560 if (interactive)
1561 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11001562 break;
1563 }
Damien Miller0e2c1022005-08-12 22:16:22 +10001564 if (!interactive) { /* Echo command */
1565 printf("sftp> %s", cmd);
1566 if (strlen(cmd) > 0 &&
1567 cmd[strlen(cmd) - 1] != '\n')
1568 printf("\n");
1569 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001570 } else {
1571#ifdef USE_LIBEDIT
1572 const char *line;
1573 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001574
Damien Miller0e2c1022005-08-12 22:16:22 +10001575 if ((line = el_gets(el, &count)) == NULL || count <= 0) {
1576 printf("\n");
1577 break;
1578 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001579 history(hl, &hev, H_ENTER, line);
1580 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
1581 fprintf(stderr, "Error: input line too long\n");
1582 continue;
1583 }
1584#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001585 }
1586
Damien Miller20e1fab2004-02-18 14:30:55 +11001587 cp = strrchr(cmd, '\n');
1588 if (cp)
1589 *cp = '\0';
1590
Darren Tuckercdf547a2004-05-24 10:12:19 +10001591 /* Handle user interrupts gracefully during commands */
1592 interrupted = 0;
1593 signal(SIGINT, cmd_interrupt);
1594
Damien Miller20e1fab2004-02-18 14:30:55 +11001595 err = parse_dispatch_command(conn, cmd, &pwd, batchmode);
1596 if (err != 0)
1597 break;
1598 }
1599 xfree(pwd);
Damien Millere0b90a62006-03-26 13:51:44 +11001600 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001601
Tim Rice027e8b12005-08-15 14:52:50 -07001602#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10001603 if (el != NULL)
1604 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07001605#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10001606
Damien Miller20e1fab2004-02-18 14:30:55 +11001607 /* err == 1 signifies normal "quit" exit */
1608 return (err >= 0 ? 0 : -1);
1609}
Damien Miller62d57f62003-01-10 21:43:24 +11001610
Ben Lindstrombba81212001-06-25 05:01:22 +00001611static void
Damien Millercc685c12003-06-04 22:51:38 +10001612connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11001613{
1614 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001615
Damien Miller33804262001-02-04 23:20:18 +11001616#ifdef USE_PIPES
1617 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001618
Damien Miller33804262001-02-04 23:20:18 +11001619 if ((pipe(pin) == -1) || (pipe(pout) == -1))
1620 fatal("pipe: %s", strerror(errno));
1621 *in = pin[0];
1622 *out = pout[1];
1623 c_in = pout[0];
1624 c_out = pin[1];
1625#else /* USE_PIPES */
1626 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001627
Damien Miller33804262001-02-04 23:20:18 +11001628 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
1629 fatal("socketpair: %s", strerror(errno));
1630 *in = *out = inout[0];
1631 c_in = c_out = inout[1];
1632#endif /* USE_PIPES */
1633
Damien Millercc685c12003-06-04 22:51:38 +10001634 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11001635 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10001636 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11001637 if ((dup2(c_in, STDIN_FILENO) == -1) ||
1638 (dup2(c_out, STDOUT_FILENO) == -1)) {
1639 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10001640 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11001641 }
1642 close(*in);
1643 close(*out);
1644 close(c_in);
1645 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001646
1647 /*
1648 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10001649 * ignore SIGINT if we want to gracefully abort commands,
1650 * otherwise the signal will make it to the ssh process and
Darren Tuckercdf547a2004-05-24 10:12:19 +10001651 * kill it too
1652 */
1653 signal(SIGINT, SIG_IGN);
Darren Tuckerbd12f172004-06-18 16:23:43 +10001654 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11001655 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10001656 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11001657 }
1658
Damien Millercc685c12003-06-04 22:51:38 +10001659 signal(SIGTERM, killchild);
1660 signal(SIGINT, killchild);
1661 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11001662 close(c_in);
1663 close(c_out);
1664}
1665
Ben Lindstrombba81212001-06-25 05:01:22 +00001666static void
Damien Miller33804262001-02-04 23:20:18 +11001667usage(void)
1668{
Damien Miller025e01c2002-02-08 22:06:29 +11001669 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00001670
Ben Lindstrom1e243242001-09-18 05:38:44 +00001671 fprintf(stderr,
Darren Tucker1b0dd172009-10-07 08:37:48 +11001672 "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11001673 " [-D sftp_server_path] [-F ssh_config] "
1674 "[-i identity_file]\n"
1675 " [-o ssh_option] [-P port] [-R num_requests] "
1676 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11001677 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11001678 " %s [user@]host[:file ...]\n"
1679 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11001680 " %s -b batchfile [user@]host\n",
1681 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11001682 exit(1);
1683}
1684
Kevin Stevesef4eea92001-02-05 12:42:17 +00001685int
Damien Miller33804262001-02-04 23:20:18 +11001686main(int argc, char **argv)
1687{
Damien Miller956f3fb2003-01-10 21:40:00 +11001688 int in, out, ch, err;
Damien Miller7cf17eb2004-06-15 10:28:56 +10001689 char *host, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00001690 int debug_level = 0, sshver = 2;
1691 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11001692 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00001693 LogLevel ll = SYSLOG_LEVEL_INFO;
1694 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11001695 extern int optind;
1696 extern char *optarg;
Damien Miller33804262001-02-04 23:20:18 +11001697
Darren Tuckerce321d82005-10-03 18:11:24 +10001698 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
1699 sanitise_stdfd();
1700
Damien Miller59d3d5b2003-08-22 09:34:41 +10001701 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11001702 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00001703 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11001704 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00001705 addargs(&args, "-oForwardX11 no");
1706 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11001707 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00001708 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11001709
Ben Lindstrom387c4722001-05-08 20:27:25 +00001710 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11001711 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11001712
Darren Tucker282b4022009-10-07 08:23:06 +11001713 while ((ch = getopt(argc, argv,
Darren Tucker1b0dd172009-10-07 08:37:48 +11001714 "1246hqrvCc:D:i:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11001715 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11001716 /* Passed through to ssh(1) */
1717 case '4':
1718 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11001719 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11001720 addargs(&args, "-%c", ch);
1721 break;
1722 /* Passed through to ssh(1) with argument */
1723 case 'F':
1724 case 'c':
1725 case 'i':
1726 case 'o':
1727 addargs(&args, "-%c%s", ch, optarg);
1728 break;
1729 case 'q':
1730 showprogress = 0;
1731 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11001732 break;
Darren Tucker282b4022009-10-07 08:23:06 +11001733 case 'P':
1734 addargs(&args, "-oPort %s", optarg);
1735 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11001736 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00001737 if (debug_level < 3) {
1738 addargs(&args, "-v");
1739 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
1740 }
1741 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11001742 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11001743 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00001744 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11001745 if (sftp_server == NULL)
1746 sftp_server = _PATH_SFTP_SERVER;
1747 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11001748 case '2':
1749 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11001750 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11001751 case 'B':
1752 copy_buffer_len = strtol(optarg, &cp, 10);
1753 if (copy_buffer_len == 0 || *cp != '\0')
1754 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11001755 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00001756 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11001757 if (batchmode)
1758 fatal("Batch file already specified.");
1759
1760 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10001761 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10001762 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11001763 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11001764 showprogress = 0;
Damien Miller44f75c12004-01-21 10:58:47 +11001765 batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11001766 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00001767 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001768 case 'p':
1769 global_pflag = 1;
1770 break;
Darren Tucker282b4022009-10-07 08:23:06 +11001771 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11001772 sftp_direct = optarg;
1773 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001774 case 'r':
1775 global_rflag = 1;
1776 break;
Damien Miller16a13332002-02-13 14:03:56 +11001777 case 'R':
1778 num_requests = strtol(optarg, &cp, 10);
1779 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00001780 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11001781 optarg);
1782 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11001783 case 's':
1784 sftp_server = optarg;
1785 break;
1786 case 'S':
1787 ssh_program = optarg;
1788 replacearg(&args, 0, "%s", ssh_program);
1789 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11001790 case 'h':
1791 default:
Damien Miller33804262001-02-04 23:20:18 +11001792 usage();
1793 }
1794 }
1795
Damien Millerc0f27d82004-03-08 23:12:19 +11001796 if (!isatty(STDERR_FILENO))
1797 showprogress = 0;
1798
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00001799 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
1800
Damien Millerd14ee1e2002-02-05 12:27:31 +11001801 if (sftp_direct == NULL) {
1802 if (optind == argc || argc > (optind + 2))
1803 usage();
Damien Miller33804262001-02-04 23:20:18 +11001804
Damien Millerd14ee1e2002-02-05 12:27:31 +11001805 userhost = xstrdup(argv[optind]);
1806 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00001807
Ben Lindstromc276c122002-12-23 02:14:51 +00001808 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11001809 host = userhost;
1810 else {
1811 *host++ = '\0';
1812 if (!userhost[0]) {
1813 fprintf(stderr, "Missing username\n");
1814 usage();
1815 }
Damien Miller80163902007-01-05 16:30:16 +11001816 addargs(&args, "-l%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11001817 }
1818
Damien Millerec692032004-01-27 21:22:00 +11001819 if ((cp = colon(host)) != NULL) {
1820 *cp++ = '\0';
1821 file1 = cp;
1822 }
1823
Damien Millerd14ee1e2002-02-05 12:27:31 +11001824 host = cleanhostname(host);
1825 if (!*host) {
1826 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11001827 usage();
1828 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11001829
Damien Millerd14ee1e2002-02-05 12:27:31 +11001830 addargs(&args, "-oProtocol %d", sshver);
1831
1832 /* no subsystem if the server-spec contains a '/' */
1833 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
1834 addargs(&args, "-s");
1835
1836 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00001837 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11001838 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11001839
Damien Miller44f75c12004-01-21 10:58:47 +11001840 if (!batchmode)
1841 fprintf(stderr, "Connecting to %s...\n", host);
Damien Millercc685c12003-06-04 22:51:38 +10001842 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11001843 } else {
1844 args.list = NULL;
1845 addargs(&args, "sftp-server");
1846
Damien Miller44f75c12004-01-21 10:58:47 +11001847 if (!batchmode)
1848 fprintf(stderr, "Attaching to %s...\n", sftp_direct);
Damien Millercc685c12003-06-04 22:51:38 +10001849 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11001850 }
Damien Miller3eec6b72006-01-31 21:49:27 +11001851 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11001852
Damien Miller956f3fb2003-01-10 21:40:00 +11001853 err = interactive_loop(in, out, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11001854
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00001855#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10001856 shutdown(in, SHUT_RDWR);
1857 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00001858#endif
1859
Damien Miller33804262001-02-04 23:20:18 +11001860 close(in);
1861 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11001862 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00001863 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11001864
Ben Lindstrom47fd8112002-04-02 20:48:19 +00001865 while (waitpid(sshpid, NULL, 0) == -1)
1866 if (errno != EINTR)
1867 fatal("Couldn't wait for ssh process: %s",
1868 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11001869
Damien Miller956f3fb2003-01-10 21:40:00 +11001870 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11001871}