blob: 2ce7cc1e1ef8099d9213fee88c0926a3be6229bf [file] [log] [blame]
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11001/* $OpenBSD: sftp.c,v 1.113 2009/11/22 13:18:00 halex 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
Darren Tucker21063192010-01-08 17:10:36 +110071#define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */
72#define DEFAULT_NUM_REQUESTS 64 /* # concurrent outstanding requests */
73
Damien Miller20e1fab2004-02-18 14:30:55 +110074/* File to read commands from */
75FILE* infile;
76
77/* Are we in batchfile mode? */
78int batchmode = 0;
79
Damien Miller20e1fab2004-02-18 14:30:55 +110080/* PID of ssh transport process */
81static pid_t sshpid = -1;
82
83/* This is set to 0 if the progressmeter is not desired. */
Damien Millerc0f27d82004-03-08 23:12:19 +110084int showprogress = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +110085
Darren Tucker1b0dd172009-10-07 08:37:48 +110086/* When this option is set, we always recursively download/upload directories */
87int global_rflag = 0;
88
89/* When this option is set, the file transfers will always preserve times */
90int global_pflag = 0;
91
Darren Tuckercdf547a2004-05-24 10:12:19 +100092/* SIGINT received during command processing */
93volatile sig_atomic_t interrupted = 0;
94
Darren Tuckerb9123452004-06-22 13:06:45 +100095/* I wish qsort() took a separate ctx for the comparison function...*/
96int sort_flag;
97
Damien Miller20e1fab2004-02-18 14:30:55 +110098int remote_glob(struct sftp_conn *, const char *, int,
99 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
Damien Miller33804262001-02-04 23:20:18 +1100100
Kevin Steves12888d12001-03-05 19:50:57 +0000101extern char *__progname;
Kevin Steves12888d12001-03-05 19:50:57 +0000102
Damien Miller20e1fab2004-02-18 14:30:55 +1100103/* Separators for interactive commands */
104#define WHITESPACE " \t\r\n"
Damien Millerd7686fd2001-02-10 00:40:03 +1100105
Darren Tuckerb9123452004-06-22 13:06:45 +1000106/* ls flags */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000107#define LS_LONG_VIEW 0x01 /* Full view ala ls -l */
108#define LS_SHORT_VIEW 0x02 /* Single row view ala ls -1 */
109#define LS_NUMERIC_VIEW 0x04 /* Long view with numeric uid/gid */
110#define LS_NAME_SORT 0x08 /* Sort by name (default) */
111#define LS_TIME_SORT 0x10 /* Sort by mtime */
112#define LS_SIZE_SORT 0x20 /* Sort by file size */
113#define LS_REVERSE_SORT 0x40 /* Reverse sort order */
Darren Tucker9a526452004-06-22 13:09:55 +1000114#define LS_SHOW_ALL 0x80 /* Don't skip filenames starting with '.' */
Darren Tuckerb9123452004-06-22 13:06:45 +1000115
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000116#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW)
117#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
Damien Miller20e1fab2004-02-18 14:30:55 +1100118
119/* Commands for interactive mode */
120#define I_CHDIR 1
121#define I_CHGRP 2
122#define I_CHMOD 3
123#define I_CHOWN 4
Damien Millerd671e5a2008-05-19 14:53:33 +1000124#define I_DF 24
Damien Miller20e1fab2004-02-18 14:30:55 +1100125#define I_GET 5
126#define I_HELP 6
127#define I_LCHDIR 7
128#define I_LLS 8
129#define I_LMKDIR 9
130#define I_LPWD 10
131#define I_LS 11
132#define I_LUMASK 12
133#define I_MKDIR 13
134#define I_PUT 14
135#define I_PWD 15
136#define I_QUIT 16
137#define I_RENAME 17
138#define I_RM 18
139#define I_RMDIR 19
140#define I_SHELL 20
141#define I_SYMLINK 21
142#define I_VERSION 22
143#define I_PROGRESS 23
144
145struct CMD {
146 const char *c;
147 const int n;
148};
149
150static const struct CMD cmds[] = {
151 { "bye", I_QUIT },
152 { "cd", I_CHDIR },
153 { "chdir", I_CHDIR },
154 { "chgrp", I_CHGRP },
155 { "chmod", I_CHMOD },
156 { "chown", I_CHOWN },
Damien Millerd671e5a2008-05-19 14:53:33 +1000157 { "df", I_DF },
Damien Miller20e1fab2004-02-18 14:30:55 +1100158 { "dir", I_LS },
159 { "exit", I_QUIT },
160 { "get", I_GET },
161 { "mget", I_GET },
162 { "help", I_HELP },
163 { "lcd", I_LCHDIR },
164 { "lchdir", I_LCHDIR },
165 { "lls", I_LLS },
166 { "lmkdir", I_LMKDIR },
167 { "ln", I_SYMLINK },
168 { "lpwd", I_LPWD },
169 { "ls", I_LS },
170 { "lumask", I_LUMASK },
171 { "mkdir", I_MKDIR },
172 { "progress", I_PROGRESS },
173 { "put", I_PUT },
174 { "mput", I_PUT },
175 { "pwd", I_PWD },
176 { "quit", I_QUIT },
177 { "rename", I_RENAME },
178 { "rm", I_RM },
179 { "rmdir", I_RMDIR },
180 { "symlink", I_SYMLINK },
181 { "version", I_VERSION },
182 { "!", I_SHELL },
183 { "?", I_HELP },
184 { NULL, -1}
185};
186
Darren Tucker21063192010-01-08 17:10:36 +1100187int interactive_loop(struct sftp_conn *, char *file1, char *file2);
Damien Miller20e1fab2004-02-18 14:30:55 +1100188
Damien Millerb6c85fc2007-01-05 16:30:41 +1100189/* ARGSUSED */
Damien Miller20e1fab2004-02-18 14:30:55 +1100190static void
Darren Tuckercdf547a2004-05-24 10:12:19 +1000191killchild(int signo)
192{
Darren Tuckerba66df82005-01-24 21:57:40 +1100193 if (sshpid > 1) {
Darren Tuckercdf547a2004-05-24 10:12:19 +1000194 kill(sshpid, SIGTERM);
Darren Tuckerba66df82005-01-24 21:57:40 +1100195 waitpid(sshpid, NULL, 0);
196 }
Darren Tuckercdf547a2004-05-24 10:12:19 +1000197
198 _exit(1);
199}
200
Damien Millerb6c85fc2007-01-05 16:30:41 +1100201/* ARGSUSED */
Darren Tuckercdf547a2004-05-24 10:12:19 +1000202static void
203cmd_interrupt(int signo)
204{
205 const char msg[] = "\rInterrupt \n";
Darren Tuckere2f189a2004-12-06 22:45:53 +1100206 int olderrno = errno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000207
208 write(STDERR_FILENO, msg, sizeof(msg) - 1);
209 interrupted = 1;
Darren Tuckere2f189a2004-12-06 22:45:53 +1100210 errno = olderrno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000211}
212
213static void
Damien Miller20e1fab2004-02-18 14:30:55 +1100214help(void)
215{
Damien Miller62fd18a2009-01-28 16:14:09 +1100216 printf("Available commands:\n"
217 "bye Quit sftp\n"
218 "cd path Change remote directory to 'path'\n"
219 "chgrp grp path Change group of file 'path' to 'grp'\n"
220 "chmod mode path Change permissions of file 'path' to 'mode'\n"
221 "chown own path Change owner of file 'path' to 'own'\n"
222 "df [-hi] [path] Display statistics for current directory or\n"
223 " filesystem containing 'path'\n"
224 "exit Quit sftp\n"
Darren Tucker1b0dd172009-10-07 08:37:48 +1100225 "get [-Pr] remote-path [local-path] Download file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100226 "help Display this help text\n"
227 "lcd path Change local directory to 'path'\n"
228 "lls [ls-options [path]] Display local directory listing\n"
229 "lmkdir path Create local directory\n"
230 "ln oldpath newpath Symlink remote file\n"
231 "lpwd Print local working directory\n"
232 "ls [-1aflnrSt] [path] Display remote directory listing\n"
233 "lumask umask Set local umask to 'umask'\n"
234 "mkdir path Create remote directory\n"
235 "progress Toggle display of progress meter\n"
Darren Tucker1b0dd172009-10-07 08:37:48 +1100236 "put [-Pr] local-path [remote-path] Upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100237 "pwd Display remote working directory\n"
238 "quit Quit sftp\n"
239 "rename oldpath newpath Rename remote file\n"
240 "rm path Delete remote file\n"
241 "rmdir path Remove remote directory\n"
242 "symlink oldpath newpath Symlink remote file\n"
243 "version Show SFTP version\n"
244 "!command Execute 'command' in local shell\n"
245 "! Escape to local shell\n"
246 "? Synonym for help\n");
Damien Miller20e1fab2004-02-18 14:30:55 +1100247}
248
249static void
250local_do_shell(const char *args)
251{
252 int status;
253 char *shell;
254 pid_t pid;
255
256 if (!*args)
257 args = NULL;
258
259 if ((shell = getenv("SHELL")) == NULL)
260 shell = _PATH_BSHELL;
261
262 if ((pid = fork()) == -1)
263 fatal("Couldn't fork: %s", strerror(errno));
264
265 if (pid == 0) {
266 /* XXX: child has pipe fds to ssh subproc open - issue? */
267 if (args) {
268 debug3("Executing %s -c \"%s\"", shell, args);
269 execl(shell, shell, "-c", args, (char *)NULL);
270 } else {
271 debug3("Executing %s", shell);
272 execl(shell, shell, (char *)NULL);
273 }
274 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
275 strerror(errno));
276 _exit(1);
277 }
278 while (waitpid(pid, &status, 0) == -1)
279 if (errno != EINTR)
280 fatal("Couldn't wait for child: %s", strerror(errno));
281 if (!WIFEXITED(status))
Damien Miller55b04f12006-03-26 14:23:17 +1100282 error("Shell exited abnormally");
Damien Miller20e1fab2004-02-18 14:30:55 +1100283 else if (WEXITSTATUS(status))
284 error("Shell exited with status %d", WEXITSTATUS(status));
285}
286
287static void
288local_do_ls(const char *args)
289{
290 if (!args || !*args)
291 local_do_shell(_PATH_LS);
292 else {
293 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
294 char *buf = xmalloc(len);
295
296 /* XXX: quoting - rip quoting code from ftp? */
297 snprintf(buf, len, _PATH_LS " %s", args);
298 local_do_shell(buf);
299 xfree(buf);
300 }
301}
302
303/* Strip one path (usually the pwd) from the start of another */
304static char *
305path_strip(char *path, char *strip)
306{
307 size_t len;
308
309 if (strip == NULL)
310 return (xstrdup(path));
311
312 len = strlen(strip);
Darren Tuckere2f189a2004-12-06 22:45:53 +1100313 if (strncmp(path, strip, len) == 0) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100314 if (strip[len - 1] != '/' && path[len] == '/')
315 len++;
316 return (xstrdup(path + len));
317 }
318
319 return (xstrdup(path));
320}
321
322static char *
Damien Miller20e1fab2004-02-18 14:30:55 +1100323make_absolute(char *p, char *pwd)
324{
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000325 char *abs_str;
Damien Miller20e1fab2004-02-18 14:30:55 +1100326
327 /* Derelativise */
328 if (p && p[0] != '/') {
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000329 abs_str = path_append(pwd, p);
Damien Miller20e1fab2004-02-18 14:30:55 +1100330 xfree(p);
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000331 return(abs_str);
Damien Miller20e1fab2004-02-18 14:30:55 +1100332 } else
333 return(p);
334}
335
336static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100337parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag,
338 int *rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100339{
Damien Millerf184bcf2008-06-29 22:45:13 +1000340 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000341 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100342
Damien Miller1cbc2922007-10-26 14:27:45 +1000343 optind = optreset = 1;
344 opterr = 0;
345
Darren Tucker1b0dd172009-10-07 08:37:48 +1100346 *rflag = *pflag = 0;
347 while ((ch = getopt(argc, argv, "PpRr")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000348 switch (ch) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100349 case 'p':
350 case 'P':
351 *pflag = 1;
352 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100353 case 'r':
354 case 'R':
355 *rflag = 1;
356 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100357 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000358 error("%s: Invalid flag -%c", cmd, optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000359 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100360 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100361 }
362
Damien Miller1cbc2922007-10-26 14:27:45 +1000363 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100364}
365
366static int
Damien Miller1cbc2922007-10-26 14:27:45 +1000367parse_ls_flags(char **argv, int argc, int *lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100368{
Damien Millerf184bcf2008-06-29 22:45:13 +1000369 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000370 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100371
Damien Miller1cbc2922007-10-26 14:27:45 +1000372 optind = optreset = 1;
373 opterr = 0;
374
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000375 *lflag = LS_NAME_SORT;
Damien Miller1cbc2922007-10-26 14:27:45 +1000376 while ((ch = getopt(argc, argv, "1Saflnrt")) != -1) {
377 switch (ch) {
378 case '1':
379 *lflag &= ~VIEW_FLAGS;
380 *lflag |= LS_SHORT_VIEW;
381 break;
382 case 'S':
383 *lflag &= ~SORT_FLAGS;
384 *lflag |= LS_SIZE_SORT;
385 break;
386 case 'a':
387 *lflag |= LS_SHOW_ALL;
388 break;
389 case 'f':
390 *lflag &= ~SORT_FLAGS;
391 break;
392 case 'l':
393 *lflag &= ~VIEW_FLAGS;
394 *lflag |= LS_LONG_VIEW;
395 break;
396 case 'n':
397 *lflag &= ~VIEW_FLAGS;
398 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
399 break;
400 case 'r':
401 *lflag |= LS_REVERSE_SORT;
402 break;
403 case 't':
404 *lflag &= ~SORT_FLAGS;
405 *lflag |= LS_TIME_SORT;
406 break;
407 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000408 error("ls: Invalid flag -%c", optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000409 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100410 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100411 }
412
Damien Miller1cbc2922007-10-26 14:27:45 +1000413 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100414}
415
416static int
Damien Millerd671e5a2008-05-19 14:53:33 +1000417parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
418{
Damien Millerf184bcf2008-06-29 22:45:13 +1000419 extern int opterr, optind, optopt, optreset;
Damien Millerd671e5a2008-05-19 14:53:33 +1000420 int ch;
421
422 optind = optreset = 1;
423 opterr = 0;
424
425 *hflag = *iflag = 0;
426 while ((ch = getopt(argc, argv, "hi")) != -1) {
427 switch (ch) {
428 case 'h':
429 *hflag = 1;
430 break;
431 case 'i':
432 *iflag = 1;
433 break;
434 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000435 error("%s: Invalid flag -%c", cmd, optopt);
Damien Millerd671e5a2008-05-19 14:53:33 +1000436 return -1;
437 }
438 }
439
440 return optind;
441}
442
443static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100444is_dir(char *path)
445{
446 struct stat sb;
447
448 /* XXX: report errors? */
449 if (stat(path, &sb) == -1)
450 return(0);
451
Darren Tucker1e80e402006-09-21 12:59:33 +1000452 return(S_ISDIR(sb.st_mode));
Damien Miller20e1fab2004-02-18 14:30:55 +1100453}
454
455static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100456remote_is_dir(struct sftp_conn *conn, char *path)
457{
458 Attrib *a;
459
460 /* XXX: report errors? */
461 if ((a = do_stat(conn, path, 1)) == NULL)
462 return(0);
463 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
464 return(0);
Darren Tucker1e80e402006-09-21 12:59:33 +1000465 return(S_ISDIR(a->perm));
Damien Miller20e1fab2004-02-18 14:30:55 +1100466}
467
Darren Tucker1b0dd172009-10-07 08:37:48 +1100468/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
Damien Miller20e1fab2004-02-18 14:30:55 +1100469static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100470pathname_is_dir(char *pathname)
471{
472 size_t l = strlen(pathname);
473
474 return l > 0 && pathname[l - 1] == '/';
475}
476
477static int
478process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
479 int pflag, int rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100480{
481 char *abs_src = NULL;
482 char *abs_dst = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100483 glob_t g;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100484 char *filename, *tmp=NULL;
485 int i, err = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100486
487 abs_src = xstrdup(src);
488 abs_src = make_absolute(abs_src, pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +1100489 memset(&g, 0, sizeof(g));
Darren Tucker1b0dd172009-10-07 08:37:48 +1100490
Damien Miller20e1fab2004-02-18 14:30:55 +1100491 debug3("Looking up %s", abs_src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100492 if (remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100493 error("File \"%s\" not found.", abs_src);
494 err = -1;
495 goto out;
496 }
497
Darren Tucker1b0dd172009-10-07 08:37:48 +1100498 /*
499 * If multiple matches then dst must be a directory or
500 * unspecified.
501 */
502 if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
503 error("Multiple source paths, but destination "
504 "\"%s\" is not a directory", dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100505 err = -1;
506 goto out;
507 }
508
Darren Tuckercdf547a2004-05-24 10:12:19 +1000509 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100510 tmp = xstrdup(g.gl_pathv[i]);
511 if ((filename = basename(tmp)) == NULL) {
512 error("basename %s: %s", tmp, strerror(errno));
513 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100514 err = -1;
515 goto out;
516 }
517
518 if (g.gl_matchc == 1 && dst) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100519 if (is_dir(dst)) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100520 abs_dst = path_append(dst, filename);
521 } else {
Damien Miller20e1fab2004-02-18 14:30:55 +1100522 abs_dst = xstrdup(dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100523 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100524 } else if (dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100525 abs_dst = path_append(dst, filename);
526 } else {
527 abs_dst = xstrdup(filename);
528 }
529 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100530
531 printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100532 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
533 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
534 pflag || global_pflag, 1) == -1)
535 err = -1;
536 } else {
537 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
538 pflag || global_pflag) == -1)
539 err = -1;
540 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100541 xfree(abs_dst);
542 abs_dst = NULL;
543 }
544
545out:
546 xfree(abs_src);
Damien Miller20e1fab2004-02-18 14:30:55 +1100547 globfree(&g);
548 return(err);
549}
550
551static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100552process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
553 int pflag, int rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100554{
555 char *tmp_dst = NULL;
556 char *abs_dst = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100557 char *tmp = NULL, *filename = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100558 glob_t g;
559 int err = 0;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100560 int i, dst_is_dir = 1;
Damien Milleraec5cf82008-02-10 22:26:24 +1100561 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100562
563 if (dst) {
564 tmp_dst = xstrdup(dst);
565 tmp_dst = make_absolute(tmp_dst, pwd);
566 }
567
568 memset(&g, 0, sizeof(g));
569 debug3("Looking up %s", src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100570 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100571 error("File \"%s\" not found.", src);
572 err = -1;
573 goto out;
574 }
575
Darren Tucker1b0dd172009-10-07 08:37:48 +1100576 /* If we aren't fetching to pwd then stash this status for later */
577 if (tmp_dst != NULL)
578 dst_is_dir = remote_is_dir(conn, tmp_dst);
579
Damien Miller20e1fab2004-02-18 14:30:55 +1100580 /* If multiple matches, dst may be directory or unspecified */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100581 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
582 error("Multiple paths match, but destination "
583 "\"%s\" is not a directory", tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100584 err = -1;
585 goto out;
586 }
587
Darren Tuckercdf547a2004-05-24 10:12:19 +1000588 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Milleraec5cf82008-02-10 22:26:24 +1100589 if (stat(g.gl_pathv[i], &sb) == -1) {
590 err = -1;
591 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
592 continue;
593 }
Darren Tucker1b0dd172009-10-07 08:37:48 +1100594
595 tmp = xstrdup(g.gl_pathv[i]);
596 if ((filename = basename(tmp)) == NULL) {
597 error("basename %s: %s", tmp, strerror(errno));
598 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100599 err = -1;
600 goto out;
601 }
602
603 if (g.gl_matchc == 1 && tmp_dst) {
604 /* If directory specified, append filename */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100605 if (dst_is_dir)
606 abs_dst = path_append(tmp_dst, filename);
607 else
Damien Miller20e1fab2004-02-18 14:30:55 +1100608 abs_dst = xstrdup(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100609 } else if (tmp_dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100610 abs_dst = path_append(tmp_dst, filename);
611 } else {
612 abs_dst = make_absolute(xstrdup(filename), pwd);
613 }
614 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100615
616 printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100617 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
618 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
619 pflag || global_pflag, 1) == -1)
620 err = -1;
621 } else {
622 if (do_upload(conn, g.gl_pathv[i], abs_dst,
623 pflag || global_pflag) == -1)
624 err = -1;
625 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100626 }
627
628out:
629 if (abs_dst)
630 xfree(abs_dst);
631 if (tmp_dst)
632 xfree(tmp_dst);
633 globfree(&g);
634 return(err);
635}
636
637static int
638sdirent_comp(const void *aa, const void *bb)
639{
640 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
641 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000642 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100643
Darren Tuckerb9123452004-06-22 13:06:45 +1000644#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000645 if (sort_flag & LS_NAME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000646 return (rmul * strcmp(a->filename, b->filename));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000647 else if (sort_flag & LS_TIME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000648 return (rmul * NCMP(a->a.mtime, b->a.mtime));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000649 else if (sort_flag & LS_SIZE_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000650 return (rmul * NCMP(a->a.size, b->a.size));
651
652 fatal("Unknown ls sort type");
Damien Miller20e1fab2004-02-18 14:30:55 +1100653}
654
655/* sftp ls.1 replacement for directories */
656static int
657do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
658{
Damien Millereccb9de2005-06-17 12:59:34 +1000659 int n;
660 u_int c = 1, colspace = 0, columns = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100661 SFTP_DIRENT **d;
662
663 if ((n = do_readdir(conn, path, &d)) != 0)
664 return (n);
665
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000666 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000667 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100668 struct winsize ws;
669 char *tmp;
670
671 /* Count entries for sort and find longest filename */
Darren Tucker9a526452004-06-22 13:09:55 +1000672 for (n = 0; d[n] != NULL; n++) {
673 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
674 m = MAX(m, strlen(d[n]->filename));
675 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100676
677 /* Add any subpath that also needs to be counted */
678 tmp = path_strip(path, strip_path);
679 m += strlen(tmp);
680 xfree(tmp);
681
682 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
683 width = ws.ws_col;
684
685 columns = width / (m + 2);
686 columns = MAX(columns, 1);
687 colspace = width / columns;
688 colspace = MIN(colspace, width);
689 }
690
Darren Tuckerb9123452004-06-22 13:06:45 +1000691 if (lflag & SORT_FLAGS) {
Damien Miller653b93b2005-11-05 15:15:23 +1100692 for (n = 0; d[n] != NULL; n++)
693 ; /* count entries */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000694 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
Darren Tuckerb9123452004-06-22 13:06:45 +1000695 qsort(d, n, sizeof(*d), sdirent_comp);
696 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100697
Darren Tuckercdf547a2004-05-24 10:12:19 +1000698 for (n = 0; d[n] != NULL && !interrupted; n++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100699 char *tmp, *fname;
700
Darren Tucker9a526452004-06-22 13:09:55 +1000701 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
702 continue;
703
Damien Miller20e1fab2004-02-18 14:30:55 +1100704 tmp = path_append(path, d[n]->filename);
705 fname = path_strip(tmp, strip_path);
706 xfree(tmp);
707
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000708 if (lflag & LS_LONG_VIEW) {
709 if (lflag & LS_NUMERIC_VIEW) {
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000710 char *lname;
711 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100712
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000713 memset(&sb, 0, sizeof(sb));
714 attrib_to_stat(&d[n]->a, &sb);
715 lname = ls_file(fname, &sb, 1);
716 printf("%s\n", lname);
717 xfree(lname);
718 } else
719 printf("%s\n", d[n]->longname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100720 } else {
721 printf("%-*s", colspace, fname);
722 if (c >= columns) {
723 printf("\n");
724 c = 1;
725 } else
726 c++;
727 }
728
729 xfree(fname);
730 }
731
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000732 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100733 printf("\n");
734
735 free_sftp_dirents(d);
736 return (0);
737}
738
739/* sftp ls.1 replacement which handles path globs */
740static int
741do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
742 int lflag)
743{
744 glob_t g;
Damien Millereccb9de2005-06-17 12:59:34 +1000745 u_int i, c = 1, colspace = 0, columns = 1;
Darren Tucker596dcfa2004-12-11 13:37:22 +1100746 Attrib *a = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100747
748 memset(&g, 0, sizeof(g));
749
750 if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE,
Darren Tucker596dcfa2004-12-11 13:37:22 +1100751 NULL, &g) || (g.gl_pathc && !g.gl_matchc)) {
752 if (g.gl_pathc)
753 globfree(&g);
Damien Miller20e1fab2004-02-18 14:30:55 +1100754 error("Can't ls: \"%s\" not found", path);
755 return (-1);
756 }
757
Darren Tuckercdf547a2004-05-24 10:12:19 +1000758 if (interrupted)
759 goto out;
760
Damien Miller20e1fab2004-02-18 14:30:55 +1100761 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100762 * If the glob returns a single match and it is a directory,
763 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100764 */
Darren Tucker596dcfa2004-12-11 13:37:22 +1100765 if (g.gl_matchc == 1) {
766 if ((a = do_lstat(conn, g.gl_pathv[0], 1)) == NULL) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100767 globfree(&g);
768 return (-1);
769 }
770 if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
771 S_ISDIR(a->perm)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100772 int err;
773
774 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +1100775 globfree(&g);
Darren Tucker596dcfa2004-12-11 13:37:22 +1100776 return (err);
Damien Miller20e1fab2004-02-18 14:30:55 +1100777 }
778 }
779
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000780 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000781 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100782 struct winsize ws;
783
784 /* Count entries for sort and find longest filename */
785 for (i = 0; g.gl_pathv[i]; i++)
786 m = MAX(m, strlen(g.gl_pathv[i]));
787
788 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
789 width = ws.ws_col;
790
791 columns = width / (m + 2);
792 columns = MAX(columns, 1);
793 colspace = width / columns;
794 }
795
Darren Tucker596dcfa2004-12-11 13:37:22 +1100796 for (i = 0; g.gl_pathv[i] && !interrupted; i++, a = NULL) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100797 char *fname;
798
799 fname = path_strip(g.gl_pathv[i], strip_path);
800
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000801 if (lflag & LS_LONG_VIEW) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100802 char *lname;
803 struct stat sb;
804
805 /*
806 * XXX: this is slow - 1 roundtrip per path
807 * A solution to this is to fork glob() and
808 * build a sftp specific version which keeps the
809 * attribs (which currently get thrown away)
810 * that the server returns as well as the filenames.
811 */
812 memset(&sb, 0, sizeof(sb));
Darren Tucker596dcfa2004-12-11 13:37:22 +1100813 if (a == NULL)
814 a = do_lstat(conn, g.gl_pathv[i], 1);
Damien Miller20e1fab2004-02-18 14:30:55 +1100815 if (a != NULL)
816 attrib_to_stat(a, &sb);
817 lname = ls_file(fname, &sb, 1);
818 printf("%s\n", lname);
819 xfree(lname);
820 } else {
821 printf("%-*s", colspace, fname);
822 if (c >= columns) {
823 printf("\n");
824 c = 1;
825 } else
826 c++;
827 }
828 xfree(fname);
829 }
830
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000831 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100832 printf("\n");
833
Darren Tuckercdf547a2004-05-24 10:12:19 +1000834 out:
Damien Miller20e1fab2004-02-18 14:30:55 +1100835 if (g.gl_pathc)
836 globfree(&g);
837
838 return (0);
839}
840
Damien Millerd671e5a2008-05-19 14:53:33 +1000841static int
842do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
843{
Darren Tucker7b598892008-06-09 22:49:36 +1000844 struct sftp_statvfs st;
Damien Millerd671e5a2008-05-19 14:53:33 +1000845 char s_used[FMT_SCALED_STRSIZE];
846 char s_avail[FMT_SCALED_STRSIZE];
847 char s_root[FMT_SCALED_STRSIZE];
848 char s_total[FMT_SCALED_STRSIZE];
849
850 if (do_statvfs(conn, path, &st, 1) == -1)
851 return -1;
852 if (iflag) {
853 printf(" Inodes Used Avail "
854 "(root) %%Capacity\n");
855 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
856 (unsigned long long)st.f_files,
857 (unsigned long long)(st.f_files - st.f_ffree),
858 (unsigned long long)st.f_favail,
859 (unsigned long long)st.f_ffree,
860 (unsigned long long)(100 * (st.f_files - st.f_ffree) /
861 st.f_files));
862 } else if (hflag) {
863 strlcpy(s_used, "error", sizeof(s_used));
864 strlcpy(s_avail, "error", sizeof(s_avail));
865 strlcpy(s_root, "error", sizeof(s_root));
866 strlcpy(s_total, "error", sizeof(s_total));
867 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
868 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
869 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
870 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
871 printf(" Size Used Avail (root) %%Capacity\n");
872 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
873 s_total, s_used, s_avail, s_root,
874 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
875 st.f_blocks));
876 } else {
877 printf(" Size Used Avail "
878 "(root) %%Capacity\n");
879 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
880 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
881 (unsigned long long)(st.f_frsize *
882 (st.f_blocks - st.f_bfree) / 1024),
883 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
884 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
885 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
886 st.f_blocks));
887 }
888 return 0;
889}
890
Damien Miller1cbc2922007-10-26 14:27:45 +1000891/*
892 * Undo escaping of glob sequences in place. Used to undo extra escaping
893 * applied in makeargv() when the string is destined for a function that
894 * does not glob it.
895 */
896static void
897undo_glob_escape(char *s)
898{
899 size_t i, j;
900
901 for (i = j = 0;;) {
902 if (s[i] == '\0') {
903 s[j] = '\0';
904 return;
905 }
906 if (s[i] != '\\') {
907 s[j++] = s[i++];
908 continue;
909 }
910 /* s[i] == '\\' */
911 ++i;
912 switch (s[i]) {
913 case '?':
914 case '[':
915 case '*':
916 case '\\':
917 s[j++] = s[i++];
918 break;
919 case '\0':
920 s[j++] = '\\';
921 s[j] = '\0';
922 return;
923 default:
924 s[j++] = '\\';
925 s[j++] = s[i++];
926 break;
927 }
928 }
929}
930
931/*
932 * Split a string into an argument vector using sh(1)-style quoting,
933 * comment and escaping rules, but with some tweaks to handle glob(3)
934 * wildcards.
935 * Returns NULL on error or a NULL-terminated array of arguments.
936 */
937#define MAXARGS 128
938#define MAXARGLEN 8192
939static char **
940makeargv(const char *arg, int *argcp)
941{
942 int argc, quot;
943 size_t i, j;
944 static char argvs[MAXARGLEN];
945 static char *argv[MAXARGS + 1];
946 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
947
948 *argcp = argc = 0;
949 if (strlen(arg) > sizeof(argvs) - 1) {
950 args_too_longs:
951 error("string too long");
952 return NULL;
953 }
954 state = MA_START;
955 i = j = 0;
956 for (;;) {
957 if (isspace(arg[i])) {
958 if (state == MA_UNQUOTED) {
959 /* Terminate current argument */
960 argvs[j++] = '\0';
961 argc++;
962 state = MA_START;
963 } else if (state != MA_START)
964 argvs[j++] = arg[i];
965 } else if (arg[i] == '"' || arg[i] == '\'') {
966 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
967 if (state == MA_START) {
968 argv[argc] = argvs + j;
969 state = q;
970 } else if (state == MA_UNQUOTED)
971 state = q;
972 else if (state == q)
973 state = MA_UNQUOTED;
974 else
975 argvs[j++] = arg[i];
976 } else if (arg[i] == '\\') {
977 if (state == MA_SQUOTE || state == MA_DQUOTE) {
978 quot = state == MA_SQUOTE ? '\'' : '"';
979 /* Unescape quote we are in */
980 /* XXX support \n and friends? */
981 if (arg[i + 1] == quot) {
982 i++;
983 argvs[j++] = arg[i];
984 } else if (arg[i + 1] == '?' ||
985 arg[i + 1] == '[' || arg[i + 1] == '*') {
986 /*
987 * Special case for sftp: append
988 * double-escaped glob sequence -
989 * glob will undo one level of
990 * escaping. NB. string can grow here.
991 */
992 if (j >= sizeof(argvs) - 5)
993 goto args_too_longs;
994 argvs[j++] = '\\';
995 argvs[j++] = arg[i++];
996 argvs[j++] = '\\';
997 argvs[j++] = arg[i];
998 } else {
999 argvs[j++] = arg[i++];
1000 argvs[j++] = arg[i];
1001 }
1002 } else {
1003 if (state == MA_START) {
1004 argv[argc] = argvs + j;
1005 state = MA_UNQUOTED;
1006 }
1007 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1008 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1009 /*
1010 * Special case for sftp: append
1011 * escaped glob sequence -
1012 * glob will undo one level of
1013 * escaping.
1014 */
1015 argvs[j++] = arg[i++];
1016 argvs[j++] = arg[i];
1017 } else {
1018 /* Unescape everything */
1019 /* XXX support \n and friends? */
1020 i++;
1021 argvs[j++] = arg[i];
1022 }
1023 }
1024 } else if (arg[i] == '#') {
1025 if (state == MA_SQUOTE || state == MA_DQUOTE)
1026 argvs[j++] = arg[i];
1027 else
1028 goto string_done;
1029 } else if (arg[i] == '\0') {
1030 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1031 error("Unterminated quoted argument");
1032 return NULL;
1033 }
1034 string_done:
1035 if (state == MA_UNQUOTED) {
1036 argvs[j++] = '\0';
1037 argc++;
1038 }
1039 break;
1040 } else {
1041 if (state == MA_START) {
1042 argv[argc] = argvs + j;
1043 state = MA_UNQUOTED;
1044 }
1045 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1046 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1047 /*
1048 * Special case for sftp: escape quoted
1049 * glob(3) wildcards. NB. string can grow
1050 * here.
1051 */
1052 if (j >= sizeof(argvs) - 3)
1053 goto args_too_longs;
1054 argvs[j++] = '\\';
1055 argvs[j++] = arg[i];
1056 } else
1057 argvs[j++] = arg[i];
1058 }
1059 i++;
1060 }
1061 *argcp = argc;
1062 return argv;
1063}
1064
Damien Miller20e1fab2004-02-18 14:30:55 +11001065static int
Darren Tucker1b0dd172009-10-07 08:37:48 +11001066parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag, int *hflag,
Damien Miller20e1fab2004-02-18 14:30:55 +11001067 unsigned long *n_arg, char **path1, char **path2)
1068{
1069 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001070 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001071 int base = 0;
1072 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001073 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001074
1075 /* Skip leading whitespace */
1076 cp = cp + strspn(cp, WHITESPACE);
1077
1078 /* Ignore blank lines and lines which begin with comment '#' char */
1079 if (*cp == '\0' || *cp == '#')
1080 return (0);
1081
1082 /* Check for leading '-' (disable error processing) */
1083 *iflag = 0;
1084 if (*cp == '-') {
1085 *iflag = 1;
1086 cp++;
1087 }
1088
Damien Miller1cbc2922007-10-26 14:27:45 +10001089 if ((argv = makeargv(cp, &argc)) == NULL)
1090 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001091
Damien Miller1cbc2922007-10-26 14:27:45 +10001092 /* Figure out which command we have */
1093 for (i = 0; cmds[i].c != NULL; i++) {
1094 if (strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001095 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001096 }
1097 cmdnum = cmds[i].n;
1098 cmd = cmds[i].c;
1099
1100 /* Special case */
1101 if (*cp == '!') {
1102 cp++;
1103 cmdnum = I_SHELL;
1104 } else if (cmdnum == -1) {
1105 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001106 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001107 }
1108
1109 /* Get arguments and parse flags */
Darren Tucker1b0dd172009-10-07 08:37:48 +11001110 *lflag = *pflag = *rflag = *hflag = *n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001111 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001112 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001113 switch (cmdnum) {
1114 case I_GET:
1115 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001116 if ((optidx = parse_getput_flags(cmd, argv, argc, pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001117 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001118 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001119 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001120 error("You must specify at least one path after a "
1121 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001122 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001123 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001124 *path1 = xstrdup(argv[optidx]);
1125 /* Get second pathname (optional) */
1126 if (argc - optidx > 1) {
1127 *path2 = xstrdup(argv[optidx + 1]);
1128 /* Destination is not globbed */
1129 undo_glob_escape(*path2);
1130 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001131 break;
1132 case I_RENAME:
1133 case I_SYMLINK:
Damien Miller1cbc2922007-10-26 14:27:45 +10001134 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001135 error("You must specify two paths after a %s "
1136 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001137 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001138 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001139 *path1 = xstrdup(argv[optidx]);
1140 *path2 = xstrdup(argv[optidx + 1]);
1141 /* Paths are not globbed */
1142 undo_glob_escape(*path1);
1143 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001144 break;
1145 case I_RM:
1146 case I_MKDIR:
1147 case I_RMDIR:
1148 case I_CHDIR:
1149 case I_LCHDIR:
1150 case I_LMKDIR:
1151 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001152 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001153 error("You must specify a path after a %s command.",
1154 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001155 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001156 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001157 *path1 = xstrdup(argv[optidx]);
1158 /* Only "rm" globs */
1159 if (cmdnum != I_RM)
1160 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001161 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001162 case I_DF:
1163 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1164 iflag)) == -1)
1165 return -1;
1166 /* Default to current directory if no path specified */
1167 if (argc - optidx < 1)
1168 *path1 = NULL;
1169 else {
1170 *path1 = xstrdup(argv[optidx]);
1171 undo_glob_escape(*path1);
1172 }
1173 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001174 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001175 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001176 return(-1);
1177 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001178 if (argc - optidx > 0)
1179 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001180 break;
1181 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001182 /* Skip ls command and following whitespace */
1183 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001184 case I_SHELL:
1185 /* Uses the rest of the line */
1186 break;
1187 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001188 case I_CHMOD:
1189 base = 8;
1190 case I_CHOWN:
1191 case I_CHGRP:
1192 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001193 if (argc - optidx < 1)
1194 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001195 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001196 l = strtol(argv[optidx], &cp2, base);
1197 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1198 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1199 l < 0) {
1200 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001201 error("You must supply a numeric argument "
1202 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001203 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001204 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001205 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001206 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001207 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001208 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001209 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001210 error("You must specify a path after a %s command.",
1211 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001212 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001213 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001214 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001215 break;
1216 case I_QUIT:
1217 case I_PWD:
1218 case I_LPWD:
1219 case I_HELP:
1220 case I_VERSION:
1221 case I_PROGRESS:
1222 break;
1223 default:
1224 fatal("Command not implemented");
1225 }
1226
1227 *cpp = cp;
1228 return(cmdnum);
1229}
1230
1231static int
1232parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1233 int err_abort)
1234{
1235 char *path1, *path2, *tmp;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001236 int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001237 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001238 Attrib a, *aa;
1239 char path_buf[MAXPATHLEN];
1240 int err = 0;
1241 glob_t g;
1242
1243 path1 = path2 = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001244 cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag, &n_arg,
Damien Miller20e1fab2004-02-18 14:30:55 +11001245 &path1, &path2);
1246
1247 if (iflag != 0)
1248 err_abort = 0;
1249
1250 memset(&g, 0, sizeof(g));
1251
1252 /* Perform command */
1253 switch (cmdnum) {
1254 case 0:
1255 /* Blank line */
1256 break;
1257 case -1:
1258 /* Unrecognized command */
1259 err = -1;
1260 break;
1261 case I_GET:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001262 err = process_get(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001263 break;
1264 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001265 err = process_put(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001266 break;
1267 case I_RENAME:
1268 path1 = make_absolute(path1, *pwd);
1269 path2 = make_absolute(path2, *pwd);
1270 err = do_rename(conn, path1, path2);
1271 break;
1272 case I_SYMLINK:
1273 path2 = make_absolute(path2, *pwd);
1274 err = do_symlink(conn, path1, path2);
1275 break;
1276 case I_RM:
1277 path1 = make_absolute(path1, *pwd);
1278 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001279 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001280 printf("Removing %s\n", g.gl_pathv[i]);
1281 err = do_rm(conn, g.gl_pathv[i]);
1282 if (err != 0 && err_abort)
1283 break;
1284 }
1285 break;
1286 case I_MKDIR:
1287 path1 = make_absolute(path1, *pwd);
1288 attrib_clear(&a);
1289 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1290 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001291 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001292 break;
1293 case I_RMDIR:
1294 path1 = make_absolute(path1, *pwd);
1295 err = do_rmdir(conn, path1);
1296 break;
1297 case I_CHDIR:
1298 path1 = make_absolute(path1, *pwd);
1299 if ((tmp = do_realpath(conn, path1)) == NULL) {
1300 err = 1;
1301 break;
1302 }
1303 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1304 xfree(tmp);
1305 err = 1;
1306 break;
1307 }
1308 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1309 error("Can't change directory: Can't check target");
1310 xfree(tmp);
1311 err = 1;
1312 break;
1313 }
1314 if (!S_ISDIR(aa->perm)) {
1315 error("Can't change directory: \"%s\" is not "
1316 "a directory", tmp);
1317 xfree(tmp);
1318 err = 1;
1319 break;
1320 }
1321 xfree(*pwd);
1322 *pwd = tmp;
1323 break;
1324 case I_LS:
1325 if (!path1) {
1326 do_globbed_ls(conn, *pwd, *pwd, lflag);
1327 break;
1328 }
1329
1330 /* Strip pwd off beginning of non-absolute paths */
1331 tmp = NULL;
1332 if (*path1 != '/')
1333 tmp = *pwd;
1334
1335 path1 = make_absolute(path1, *pwd);
1336 err = do_globbed_ls(conn, path1, tmp, lflag);
1337 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001338 case I_DF:
1339 /* Default to current directory if no path specified */
1340 if (path1 == NULL)
1341 path1 = xstrdup(*pwd);
1342 path1 = make_absolute(path1, *pwd);
1343 err = do_df(conn, path1, hflag, iflag);
1344 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001345 case I_LCHDIR:
1346 if (chdir(path1) == -1) {
1347 error("Couldn't change local directory to "
1348 "\"%s\": %s", path1, strerror(errno));
1349 err = 1;
1350 }
1351 break;
1352 case I_LMKDIR:
1353 if (mkdir(path1, 0777) == -1) {
1354 error("Couldn't create local directory "
1355 "\"%s\": %s", path1, strerror(errno));
1356 err = 1;
1357 }
1358 break;
1359 case I_LLS:
1360 local_do_ls(cmd);
1361 break;
1362 case I_SHELL:
1363 local_do_shell(cmd);
1364 break;
1365 case I_LUMASK:
1366 umask(n_arg);
1367 printf("Local umask: %03lo\n", n_arg);
1368 break;
1369 case I_CHMOD:
1370 path1 = make_absolute(path1, *pwd);
1371 attrib_clear(&a);
1372 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1373 a.perm = n_arg;
1374 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001375 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001376 printf("Changing mode on %s\n", g.gl_pathv[i]);
1377 err = do_setstat(conn, g.gl_pathv[i], &a);
1378 if (err != 0 && err_abort)
1379 break;
1380 }
1381 break;
1382 case I_CHOWN:
1383 case I_CHGRP:
1384 path1 = make_absolute(path1, *pwd);
1385 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001386 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001387 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001388 if (err_abort) {
1389 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001390 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001391 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001392 continue;
1393 }
1394 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1395 error("Can't get current ownership of "
1396 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001397 if (err_abort) {
1398 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001399 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001400 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001401 continue;
1402 }
1403 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1404 if (cmdnum == I_CHOWN) {
1405 printf("Changing owner on %s\n", g.gl_pathv[i]);
1406 aa->uid = n_arg;
1407 } else {
1408 printf("Changing group on %s\n", g.gl_pathv[i]);
1409 aa->gid = n_arg;
1410 }
1411 err = do_setstat(conn, g.gl_pathv[i], aa);
1412 if (err != 0 && err_abort)
1413 break;
1414 }
1415 break;
1416 case I_PWD:
1417 printf("Remote working directory: %s\n", *pwd);
1418 break;
1419 case I_LPWD:
1420 if (!getcwd(path_buf, sizeof(path_buf))) {
1421 error("Couldn't get local cwd: %s", strerror(errno));
1422 err = -1;
1423 break;
1424 }
1425 printf("Local working directory: %s\n", path_buf);
1426 break;
1427 case I_QUIT:
1428 /* Processed below */
1429 break;
1430 case I_HELP:
1431 help();
1432 break;
1433 case I_VERSION:
1434 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1435 break;
1436 case I_PROGRESS:
1437 showprogress = !showprogress;
1438 if (showprogress)
1439 printf("Progress meter enabled\n");
1440 else
1441 printf("Progress meter disabled\n");
1442 break;
1443 default:
1444 fatal("%d is not implemented", cmdnum);
1445 }
1446
1447 if (g.gl_pathc)
1448 globfree(&g);
1449 if (path1)
1450 xfree(path1);
1451 if (path2)
1452 xfree(path2);
1453
1454 /* If an unignored error occurs in batch mode we should abort. */
1455 if (err_abort && err != 0)
1456 return (-1);
1457 else if (cmdnum == I_QUIT)
1458 return (1);
1459
1460 return (0);
1461}
1462
Darren Tucker2d963d82004-11-07 20:04:10 +11001463#ifdef USE_LIBEDIT
1464static char *
1465prompt(EditLine *el)
1466{
1467 return ("sftp> ");
1468}
1469#endif
1470
Damien Miller20e1fab2004-02-18 14:30:55 +11001471int
Darren Tucker21063192010-01-08 17:10:36 +11001472interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001473{
1474 char *pwd;
1475 char *dir = NULL;
1476 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10001477 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11001478 EditLine *el = NULL;
1479#ifdef USE_LIBEDIT
1480 History *hl = NULL;
1481 HistEvent hev;
1482 extern char *__progname;
1483
1484 if (!batchmode && isatty(STDIN_FILENO)) {
1485 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1486 fatal("Couldn't initialise editline");
1487 if ((hl = history_init()) == NULL)
1488 fatal("Couldn't initialise editline history");
1489 history(hl, &hev, H_SETSIZE, 100);
1490 el_set(el, EL_HIST, history, hl);
1491
1492 el_set(el, EL_PROMPT, prompt);
1493 el_set(el, EL_EDITOR, "emacs");
1494 el_set(el, EL_TERMINAL, NULL);
1495 el_set(el, EL_SIGNAL, 1);
1496 el_source(el, NULL);
1497 }
1498#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001499
Damien Miller20e1fab2004-02-18 14:30:55 +11001500 pwd = do_realpath(conn, ".");
1501 if (pwd == NULL)
1502 fatal("Need cwd");
1503
1504 if (file1 != NULL) {
1505 dir = xstrdup(file1);
1506 dir = make_absolute(dir, pwd);
1507
1508 if (remote_is_dir(conn, dir) && file2 == NULL) {
1509 printf("Changing to: %s\n", dir);
1510 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tuckercd516ef2004-12-06 22:43:43 +11001511 if (parse_dispatch_command(conn, cmd, &pwd, 1) != 0) {
1512 xfree(dir);
1513 xfree(pwd);
Damien Millere0b90a62006-03-26 13:51:44 +11001514 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001515 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11001516 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001517 } else {
1518 if (file2 == NULL)
1519 snprintf(cmd, sizeof cmd, "get %s", dir);
1520 else
1521 snprintf(cmd, sizeof cmd, "get %s %s", dir,
1522 file2);
1523
1524 err = parse_dispatch_command(conn, cmd, &pwd, 1);
1525 xfree(dir);
1526 xfree(pwd);
Damien Millere0b90a62006-03-26 13:51:44 +11001527 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001528 return (err);
1529 }
1530 xfree(dir);
1531 }
1532
Darren Tucker93e7e8f2005-08-23 08:06:55 +10001533#if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF)
Damien Miller20e1fab2004-02-18 14:30:55 +11001534 setvbuf(stdout, NULL, _IOLBF, 0);
1535 setvbuf(infile, NULL, _IOLBF, 0);
1536#else
Damien Miller37294fb2005-07-17 17:18:49 +10001537 setlinebuf(stdout);
1538 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11001539#endif
1540
Damien Miller0e2c1022005-08-12 22:16:22 +10001541 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11001542 err = 0;
1543 for (;;) {
1544 char *cp;
1545
Darren Tuckercdf547a2004-05-24 10:12:19 +10001546 signal(SIGINT, SIG_IGN);
1547
Darren Tucker2d963d82004-11-07 20:04:10 +11001548 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001549 if (interactive)
1550 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11001551 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001552 if (interactive)
1553 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11001554 break;
1555 }
Damien Miller0e2c1022005-08-12 22:16:22 +10001556 if (!interactive) { /* Echo command */
1557 printf("sftp> %s", cmd);
1558 if (strlen(cmd) > 0 &&
1559 cmd[strlen(cmd) - 1] != '\n')
1560 printf("\n");
1561 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001562 } else {
1563#ifdef USE_LIBEDIT
1564 const char *line;
1565 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001566
Damien Miller0e2c1022005-08-12 22:16:22 +10001567 if ((line = el_gets(el, &count)) == NULL || count <= 0) {
1568 printf("\n");
1569 break;
1570 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001571 history(hl, &hev, H_ENTER, line);
1572 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
1573 fprintf(stderr, "Error: input line too long\n");
1574 continue;
1575 }
1576#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001577 }
1578
Damien Miller20e1fab2004-02-18 14:30:55 +11001579 cp = strrchr(cmd, '\n');
1580 if (cp)
1581 *cp = '\0';
1582
Darren Tuckercdf547a2004-05-24 10:12:19 +10001583 /* Handle user interrupts gracefully during commands */
1584 interrupted = 0;
1585 signal(SIGINT, cmd_interrupt);
1586
Damien Miller20e1fab2004-02-18 14:30:55 +11001587 err = parse_dispatch_command(conn, cmd, &pwd, batchmode);
1588 if (err != 0)
1589 break;
1590 }
1591 xfree(pwd);
Damien Millere0b90a62006-03-26 13:51:44 +11001592 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001593
Tim Rice027e8b12005-08-15 14:52:50 -07001594#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10001595 if (el != NULL)
1596 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07001597#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10001598
Damien Miller20e1fab2004-02-18 14:30:55 +11001599 /* err == 1 signifies normal "quit" exit */
1600 return (err >= 0 ? 0 : -1);
1601}
Damien Miller62d57f62003-01-10 21:43:24 +11001602
Ben Lindstrombba81212001-06-25 05:01:22 +00001603static void
Damien Millercc685c12003-06-04 22:51:38 +10001604connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11001605{
1606 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001607
Damien Miller33804262001-02-04 23:20:18 +11001608#ifdef USE_PIPES
1609 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001610
Damien Miller33804262001-02-04 23:20:18 +11001611 if ((pipe(pin) == -1) || (pipe(pout) == -1))
1612 fatal("pipe: %s", strerror(errno));
1613 *in = pin[0];
1614 *out = pout[1];
1615 c_in = pout[0];
1616 c_out = pin[1];
1617#else /* USE_PIPES */
1618 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001619
Damien Miller33804262001-02-04 23:20:18 +11001620 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
1621 fatal("socketpair: %s", strerror(errno));
1622 *in = *out = inout[0];
1623 c_in = c_out = inout[1];
1624#endif /* USE_PIPES */
1625
Damien Millercc685c12003-06-04 22:51:38 +10001626 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11001627 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10001628 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11001629 if ((dup2(c_in, STDIN_FILENO) == -1) ||
1630 (dup2(c_out, STDOUT_FILENO) == -1)) {
1631 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10001632 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11001633 }
1634 close(*in);
1635 close(*out);
1636 close(c_in);
1637 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001638
1639 /*
1640 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10001641 * ignore SIGINT if we want to gracefully abort commands,
1642 * otherwise the signal will make it to the ssh process and
Darren Tuckercdf547a2004-05-24 10:12:19 +10001643 * kill it too
1644 */
1645 signal(SIGINT, SIG_IGN);
Darren Tuckerbd12f172004-06-18 16:23:43 +10001646 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11001647 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10001648 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11001649 }
1650
Damien Millercc685c12003-06-04 22:51:38 +10001651 signal(SIGTERM, killchild);
1652 signal(SIGINT, killchild);
1653 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11001654 close(c_in);
1655 close(c_out);
1656}
1657
Ben Lindstrombba81212001-06-25 05:01:22 +00001658static void
Damien Miller33804262001-02-04 23:20:18 +11001659usage(void)
1660{
Damien Miller025e01c2002-02-08 22:06:29 +11001661 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00001662
Ben Lindstrom1e243242001-09-18 05:38:44 +00001663 fprintf(stderr,
Darren Tucker1b0dd172009-10-07 08:37:48 +11001664 "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11001665 " [-D sftp_server_path] [-F ssh_config] "
1666 "[-i identity_file]\n"
1667 " [-o ssh_option] [-P port] [-R num_requests] "
1668 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11001669 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11001670 " %s [user@]host[:file ...]\n"
1671 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11001672 " %s -b batchfile [user@]host\n",
1673 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11001674 exit(1);
1675}
1676
Kevin Stevesef4eea92001-02-05 12:42:17 +00001677int
Damien Miller33804262001-02-04 23:20:18 +11001678main(int argc, char **argv)
1679{
Damien Miller956f3fb2003-01-10 21:40:00 +11001680 int in, out, ch, err;
Damien Miller7cf17eb2004-06-15 10:28:56 +10001681 char *host, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00001682 int debug_level = 0, sshver = 2;
1683 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11001684 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00001685 LogLevel ll = SYSLOG_LEVEL_INFO;
1686 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11001687 extern int optind;
1688 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11001689 struct sftp_conn *conn;
1690 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
1691 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller33804262001-02-04 23:20:18 +11001692
Darren Tuckerce321d82005-10-03 18:11:24 +10001693 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
1694 sanitise_stdfd();
1695
Damien Miller59d3d5b2003-08-22 09:34:41 +10001696 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11001697 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00001698 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11001699 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00001700 addargs(&args, "-oForwardX11 no");
1701 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11001702 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00001703 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11001704
Ben Lindstrom387c4722001-05-08 20:27:25 +00001705 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11001706 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11001707
Darren Tucker282b4022009-10-07 08:23:06 +11001708 while ((ch = getopt(argc, argv,
Darren Tucker1b0dd172009-10-07 08:37:48 +11001709 "1246hqrvCc:D:i:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11001710 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11001711 /* Passed through to ssh(1) */
1712 case '4':
1713 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11001714 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11001715 addargs(&args, "-%c", ch);
1716 break;
1717 /* Passed through to ssh(1) with argument */
1718 case 'F':
1719 case 'c':
1720 case 'i':
1721 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11001722 addargs(&args, "-%c", ch);
1723 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11001724 break;
1725 case 'q':
1726 showprogress = 0;
1727 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11001728 break;
Darren Tucker282b4022009-10-07 08:23:06 +11001729 case 'P':
1730 addargs(&args, "-oPort %s", optarg);
1731 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11001732 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00001733 if (debug_level < 3) {
1734 addargs(&args, "-v");
1735 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
1736 }
1737 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11001738 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11001739 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00001740 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11001741 if (sftp_server == NULL)
1742 sftp_server = _PATH_SFTP_SERVER;
1743 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11001744 case '2':
1745 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11001746 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11001747 case 'B':
1748 copy_buffer_len = strtol(optarg, &cp, 10);
1749 if (copy_buffer_len == 0 || *cp != '\0')
1750 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11001751 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00001752 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11001753 if (batchmode)
1754 fatal("Batch file already specified.");
1755
1756 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10001757 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10001758 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11001759 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11001760 showprogress = 0;
Damien Miller44f75c12004-01-21 10:58:47 +11001761 batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11001762 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00001763 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001764 case 'p':
1765 global_pflag = 1;
1766 break;
Darren Tucker282b4022009-10-07 08:23:06 +11001767 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11001768 sftp_direct = optarg;
1769 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001770 case 'r':
1771 global_rflag = 1;
1772 break;
Damien Miller16a13332002-02-13 14:03:56 +11001773 case 'R':
1774 num_requests = strtol(optarg, &cp, 10);
1775 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00001776 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11001777 optarg);
1778 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11001779 case 's':
1780 sftp_server = optarg;
1781 break;
1782 case 'S':
1783 ssh_program = optarg;
1784 replacearg(&args, 0, "%s", ssh_program);
1785 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11001786 case 'h':
1787 default:
Damien Miller33804262001-02-04 23:20:18 +11001788 usage();
1789 }
1790 }
1791
Damien Millerc0f27d82004-03-08 23:12:19 +11001792 if (!isatty(STDERR_FILENO))
1793 showprogress = 0;
1794
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00001795 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
1796
Damien Millerd14ee1e2002-02-05 12:27:31 +11001797 if (sftp_direct == NULL) {
1798 if (optind == argc || argc > (optind + 2))
1799 usage();
Damien Miller33804262001-02-04 23:20:18 +11001800
Damien Millerd14ee1e2002-02-05 12:27:31 +11001801 userhost = xstrdup(argv[optind]);
1802 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00001803
Ben Lindstromc276c122002-12-23 02:14:51 +00001804 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11001805 host = userhost;
1806 else {
1807 *host++ = '\0';
1808 if (!userhost[0]) {
1809 fprintf(stderr, "Missing username\n");
1810 usage();
1811 }
Damien Miller80163902007-01-05 16:30:16 +11001812 addargs(&args, "-l%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11001813 }
1814
Damien Millerec692032004-01-27 21:22:00 +11001815 if ((cp = colon(host)) != NULL) {
1816 *cp++ = '\0';
1817 file1 = cp;
1818 }
1819
Damien Millerd14ee1e2002-02-05 12:27:31 +11001820 host = cleanhostname(host);
1821 if (!*host) {
1822 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11001823 usage();
1824 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11001825
Damien Millerd14ee1e2002-02-05 12:27:31 +11001826 addargs(&args, "-oProtocol %d", sshver);
1827
1828 /* no subsystem if the server-spec contains a '/' */
1829 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
1830 addargs(&args, "-s");
1831
1832 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00001833 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11001834 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11001835
Damien Millercc685c12003-06-04 22:51:38 +10001836 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11001837 } else {
1838 args.list = NULL;
1839 addargs(&args, "sftp-server");
1840
Damien Millercc685c12003-06-04 22:51:38 +10001841 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11001842 }
Damien Miller3eec6b72006-01-31 21:49:27 +11001843 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11001844
Darren Tucker21063192010-01-08 17:10:36 +11001845 conn = do_init(in, out, copy_buffer_len, num_requests);
1846 if (conn == NULL)
1847 fatal("Couldn't initialise connection to server");
1848
1849 if (!batchmode) {
1850 if (sftp_direct == NULL)
1851 fprintf(stderr, "Connected to %s.\n", host);
1852 else
1853 fprintf(stderr, "Attached to %s.\n", sftp_direct);
1854 }
1855
1856 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11001857
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00001858#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10001859 shutdown(in, SHUT_RDWR);
1860 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00001861#endif
1862
Damien Miller33804262001-02-04 23:20:18 +11001863 close(in);
1864 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11001865 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00001866 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11001867
Ben Lindstrom47fd8112002-04-02 20:48:19 +00001868 while (waitpid(sshpid, NULL, 0) == -1)
1869 if (errno != EINTR)
1870 fatal("Couldn't wait for ssh process: %s",
1871 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11001872
Damien Miller956f3fb2003-01-10 21:40:00 +11001873 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11001874}