blob: 3bacb7d5128dee49592097a065b702d5c8b3d391 [file] [log] [blame]
Darren Tucker9c3ba072010-01-13 22:45:03 +11001/* $OpenBSD: sftp.c,v 1.120 2010/01/13 04:10:50 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
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
Darren Tucker909d8582010-01-08 19:02:40 +110098/* Context used for commandline completion */
99struct complete_ctx {
100 struct sftp_conn *conn;
101 char **remote_pathp;
102};
103
Damien Miller20e1fab2004-02-18 14:30:55 +1100104int remote_glob(struct sftp_conn *, const char *, int,
105 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
Damien Miller33804262001-02-04 23:20:18 +1100106
Kevin Steves12888d12001-03-05 19:50:57 +0000107extern char *__progname;
Kevin Steves12888d12001-03-05 19:50:57 +0000108
Damien Miller20e1fab2004-02-18 14:30:55 +1100109/* Separators for interactive commands */
110#define WHITESPACE " \t\r\n"
Damien Millerd7686fd2001-02-10 00:40:03 +1100111
Darren Tuckerb9123452004-06-22 13:06:45 +1000112/* ls flags */
Darren Tucker2901e2d2010-01-13 22:44:06 +1100113#define LS_LONG_VIEW 0x0001 /* Full view ala ls -l */
114#define LS_SHORT_VIEW 0x0002 /* Single row view ala ls -1 */
115#define LS_NUMERIC_VIEW 0x0004 /* Long view with numeric uid/gid */
116#define LS_NAME_SORT 0x0008 /* Sort by name (default) */
117#define LS_TIME_SORT 0x0010 /* Sort by mtime */
118#define LS_SIZE_SORT 0x0020 /* Sort by file size */
119#define LS_REVERSE_SORT 0x0040 /* Reverse sort order */
120#define LS_SHOW_ALL 0x0080 /* Don't skip filenames starting with '.' */
121#define LS_SI_UNITS 0x0100 /* Display sizes as K, M, G, etc. */
Darren Tuckerb9123452004-06-22 13:06:45 +1000122
Darren Tucker2901e2d2010-01-13 22:44:06 +1100123#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW|LS_SI_UNITS)
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000124#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
Damien Miller20e1fab2004-02-18 14:30:55 +1100125
126/* Commands for interactive mode */
127#define I_CHDIR 1
128#define I_CHGRP 2
129#define I_CHMOD 3
130#define I_CHOWN 4
Damien Millerd671e5a2008-05-19 14:53:33 +1000131#define I_DF 24
Damien Miller20e1fab2004-02-18 14:30:55 +1100132#define I_GET 5
133#define I_HELP 6
134#define I_LCHDIR 7
135#define I_LLS 8
136#define I_LMKDIR 9
137#define I_LPWD 10
138#define I_LS 11
139#define I_LUMASK 12
140#define I_MKDIR 13
141#define I_PUT 14
142#define I_PWD 15
143#define I_QUIT 16
144#define I_RENAME 17
145#define I_RM 18
146#define I_RMDIR 19
147#define I_SHELL 20
148#define I_SYMLINK 21
149#define I_VERSION 22
150#define I_PROGRESS 23
151
152struct CMD {
153 const char *c;
154 const int n;
Darren Tucker909d8582010-01-08 19:02:40 +1100155 const int t;
Damien Miller20e1fab2004-02-18 14:30:55 +1100156};
157
Darren Tucker909d8582010-01-08 19:02:40 +1100158/* Type of completion */
159#define NOARGS 0
160#define REMOTE 1
161#define LOCAL 2
162
Damien Miller20e1fab2004-02-18 14:30:55 +1100163static const struct CMD cmds[] = {
Darren Tucker909d8582010-01-08 19:02:40 +1100164 { "bye", I_QUIT, NOARGS },
165 { "cd", I_CHDIR, REMOTE },
166 { "chdir", I_CHDIR, REMOTE },
167 { "chgrp", I_CHGRP, REMOTE },
168 { "chmod", I_CHMOD, REMOTE },
169 { "chown", I_CHOWN, REMOTE },
170 { "df", I_DF, REMOTE },
171 { "dir", I_LS, REMOTE },
172 { "exit", I_QUIT, NOARGS },
173 { "get", I_GET, REMOTE },
174 { "help", I_HELP, NOARGS },
175 { "lcd", I_LCHDIR, LOCAL },
176 { "lchdir", I_LCHDIR, LOCAL },
177 { "lls", I_LLS, LOCAL },
178 { "lmkdir", I_LMKDIR, LOCAL },
179 { "ln", I_SYMLINK, REMOTE },
180 { "lpwd", I_LPWD, LOCAL },
181 { "ls", I_LS, REMOTE },
182 { "lumask", I_LUMASK, NOARGS },
183 { "mkdir", I_MKDIR, REMOTE },
184 { "progress", I_PROGRESS, NOARGS },
185 { "put", I_PUT, LOCAL },
186 { "pwd", I_PWD, REMOTE },
187 { "quit", I_QUIT, NOARGS },
188 { "rename", I_RENAME, REMOTE },
189 { "rm", I_RM, REMOTE },
190 { "rmdir", I_RMDIR, REMOTE },
191 { "symlink", I_SYMLINK, REMOTE },
192 { "version", I_VERSION, NOARGS },
193 { "!", I_SHELL, NOARGS },
194 { "?", I_HELP, NOARGS },
195 { NULL, -1, -1 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100196};
197
Darren Tucker21063192010-01-08 17:10:36 +1100198int interactive_loop(struct sftp_conn *, char *file1, char *file2);
Damien Miller20e1fab2004-02-18 14:30:55 +1100199
Damien Millerb6c85fc2007-01-05 16:30:41 +1100200/* ARGSUSED */
Damien Miller20e1fab2004-02-18 14:30:55 +1100201static void
Darren Tuckercdf547a2004-05-24 10:12:19 +1000202killchild(int signo)
203{
Darren Tuckerba66df82005-01-24 21:57:40 +1100204 if (sshpid > 1) {
Darren Tuckercdf547a2004-05-24 10:12:19 +1000205 kill(sshpid, SIGTERM);
Darren Tuckerba66df82005-01-24 21:57:40 +1100206 waitpid(sshpid, NULL, 0);
207 }
Darren Tuckercdf547a2004-05-24 10:12:19 +1000208
209 _exit(1);
210}
211
Damien Millerb6c85fc2007-01-05 16:30:41 +1100212/* ARGSUSED */
Darren Tuckercdf547a2004-05-24 10:12:19 +1000213static void
214cmd_interrupt(int signo)
215{
216 const char msg[] = "\rInterrupt \n";
Darren Tuckere2f189a2004-12-06 22:45:53 +1100217 int olderrno = errno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000218
219 write(STDERR_FILENO, msg, sizeof(msg) - 1);
220 interrupted = 1;
Darren Tuckere2f189a2004-12-06 22:45:53 +1100221 errno = olderrno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000222}
223
224static void
Damien Miller20e1fab2004-02-18 14:30:55 +1100225help(void)
226{
Damien Miller62fd18a2009-01-28 16:14:09 +1100227 printf("Available commands:\n"
228 "bye Quit sftp\n"
229 "cd path Change remote directory to 'path'\n"
230 "chgrp grp path Change group of file 'path' to 'grp'\n"
231 "chmod mode path Change permissions of file 'path' to 'mode'\n"
232 "chown own path Change owner of file 'path' to 'own'\n"
233 "df [-hi] [path] Display statistics for current directory or\n"
234 " filesystem containing 'path'\n"
235 "exit Quit sftp\n"
Darren Tucker1b0dd172009-10-07 08:37:48 +1100236 "get [-Pr] remote-path [local-path] Download file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100237 "help Display this help text\n"
238 "lcd path Change local directory to 'path'\n"
239 "lls [ls-options [path]] Display local directory listing\n"
240 "lmkdir path Create local directory\n"
241 "ln oldpath newpath Symlink remote file\n"
242 "lpwd Print local working directory\n"
243 "ls [-1aflnrSt] [path] Display remote directory listing\n"
244 "lumask umask Set local umask to 'umask'\n"
245 "mkdir path Create remote directory\n"
246 "progress Toggle display of progress meter\n"
Darren Tucker1b0dd172009-10-07 08:37:48 +1100247 "put [-Pr] local-path [remote-path] Upload file\n"
Damien Miller62fd18a2009-01-28 16:14:09 +1100248 "pwd Display remote working directory\n"
249 "quit Quit sftp\n"
250 "rename oldpath newpath Rename remote file\n"
251 "rm path Delete remote file\n"
252 "rmdir path Remove remote directory\n"
253 "symlink oldpath newpath Symlink remote file\n"
254 "version Show SFTP version\n"
255 "!command Execute 'command' in local shell\n"
256 "! Escape to local shell\n"
257 "? Synonym for help\n");
Damien Miller20e1fab2004-02-18 14:30:55 +1100258}
259
260static void
261local_do_shell(const char *args)
262{
263 int status;
264 char *shell;
265 pid_t pid;
266
267 if (!*args)
268 args = NULL;
269
270 if ((shell = getenv("SHELL")) == NULL)
271 shell = _PATH_BSHELL;
272
273 if ((pid = fork()) == -1)
274 fatal("Couldn't fork: %s", strerror(errno));
275
276 if (pid == 0) {
277 /* XXX: child has pipe fds to ssh subproc open - issue? */
278 if (args) {
279 debug3("Executing %s -c \"%s\"", shell, args);
280 execl(shell, shell, "-c", args, (char *)NULL);
281 } else {
282 debug3("Executing %s", shell);
283 execl(shell, shell, (char *)NULL);
284 }
285 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
286 strerror(errno));
287 _exit(1);
288 }
289 while (waitpid(pid, &status, 0) == -1)
290 if (errno != EINTR)
291 fatal("Couldn't wait for child: %s", strerror(errno));
292 if (!WIFEXITED(status))
Damien Miller55b04f12006-03-26 14:23:17 +1100293 error("Shell exited abnormally");
Damien Miller20e1fab2004-02-18 14:30:55 +1100294 else if (WEXITSTATUS(status))
295 error("Shell exited with status %d", WEXITSTATUS(status));
296}
297
298static void
299local_do_ls(const char *args)
300{
301 if (!args || !*args)
302 local_do_shell(_PATH_LS);
303 else {
304 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
305 char *buf = xmalloc(len);
306
307 /* XXX: quoting - rip quoting code from ftp? */
308 snprintf(buf, len, _PATH_LS " %s", args);
309 local_do_shell(buf);
310 xfree(buf);
311 }
312}
313
314/* Strip one path (usually the pwd) from the start of another */
315static char *
316path_strip(char *path, char *strip)
317{
318 size_t len;
319
320 if (strip == NULL)
321 return (xstrdup(path));
322
323 len = strlen(strip);
Darren Tuckere2f189a2004-12-06 22:45:53 +1100324 if (strncmp(path, strip, len) == 0) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100325 if (strip[len - 1] != '/' && path[len] == '/')
326 len++;
327 return (xstrdup(path + len));
328 }
329
330 return (xstrdup(path));
331}
332
333static char *
Damien Miller20e1fab2004-02-18 14:30:55 +1100334make_absolute(char *p, char *pwd)
335{
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000336 char *abs_str;
Damien Miller20e1fab2004-02-18 14:30:55 +1100337
338 /* Derelativise */
339 if (p && p[0] != '/') {
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000340 abs_str = path_append(pwd, p);
Damien Miller20e1fab2004-02-18 14:30:55 +1100341 xfree(p);
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000342 return(abs_str);
Damien Miller20e1fab2004-02-18 14:30:55 +1100343 } else
344 return(p);
345}
346
347static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100348parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag,
349 int *rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100350{
Damien Millerf184bcf2008-06-29 22:45:13 +1000351 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000352 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100353
Damien Miller1cbc2922007-10-26 14:27:45 +1000354 optind = optreset = 1;
355 opterr = 0;
356
Darren Tucker1b0dd172009-10-07 08:37:48 +1100357 *rflag = *pflag = 0;
358 while ((ch = getopt(argc, argv, "PpRr")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000359 switch (ch) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100360 case 'p':
361 case 'P':
362 *pflag = 1;
363 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100364 case 'r':
365 case 'R':
366 *rflag = 1;
367 break;
Damien Miller20e1fab2004-02-18 14:30:55 +1100368 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000369 error("%s: Invalid flag -%c", cmd, optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000370 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100371 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100372 }
373
Damien Miller1cbc2922007-10-26 14:27:45 +1000374 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100375}
376
377static int
Damien Miller1cbc2922007-10-26 14:27:45 +1000378parse_ls_flags(char **argv, int argc, int *lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100379{
Damien Millerf184bcf2008-06-29 22:45:13 +1000380 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000381 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100382
Damien Miller1cbc2922007-10-26 14:27:45 +1000383 optind = optreset = 1;
384 opterr = 0;
385
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000386 *lflag = LS_NAME_SORT;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100387 while ((ch = getopt(argc, argv, "1Safhlnrt")) != -1) {
Damien Miller1cbc2922007-10-26 14:27:45 +1000388 switch (ch) {
389 case '1':
390 *lflag &= ~VIEW_FLAGS;
391 *lflag |= LS_SHORT_VIEW;
392 break;
393 case 'S':
394 *lflag &= ~SORT_FLAGS;
395 *lflag |= LS_SIZE_SORT;
396 break;
397 case 'a':
398 *lflag |= LS_SHOW_ALL;
399 break;
400 case 'f':
401 *lflag &= ~SORT_FLAGS;
402 break;
Darren Tucker2901e2d2010-01-13 22:44:06 +1100403 case 'h':
404 *lflag |= LS_SI_UNITS;
405 break;
Damien Miller1cbc2922007-10-26 14:27:45 +1000406 case 'l':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100407 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000408 *lflag |= LS_LONG_VIEW;
409 break;
410 case 'n':
Darren Tucker2901e2d2010-01-13 22:44:06 +1100411 *lflag &= ~LS_SHORT_VIEW;
Damien Miller1cbc2922007-10-26 14:27:45 +1000412 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
413 break;
414 case 'r':
415 *lflag |= LS_REVERSE_SORT;
416 break;
417 case 't':
418 *lflag &= ~SORT_FLAGS;
419 *lflag |= LS_TIME_SORT;
420 break;
421 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000422 error("ls: Invalid flag -%c", optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000423 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100424 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100425 }
426
Damien Miller1cbc2922007-10-26 14:27:45 +1000427 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100428}
429
430static int
Damien Millerd671e5a2008-05-19 14:53:33 +1000431parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
432{
Damien Millerf184bcf2008-06-29 22:45:13 +1000433 extern int opterr, optind, optopt, optreset;
Damien Millerd671e5a2008-05-19 14:53:33 +1000434 int ch;
435
436 optind = optreset = 1;
437 opterr = 0;
438
439 *hflag = *iflag = 0;
440 while ((ch = getopt(argc, argv, "hi")) != -1) {
441 switch (ch) {
442 case 'h':
443 *hflag = 1;
444 break;
445 case 'i':
446 *iflag = 1;
447 break;
448 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000449 error("%s: Invalid flag -%c", cmd, optopt);
Damien Millerd671e5a2008-05-19 14:53:33 +1000450 return -1;
451 }
452 }
453
454 return optind;
455}
456
457static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100458is_dir(char *path)
459{
460 struct stat sb;
461
462 /* XXX: report errors? */
463 if (stat(path, &sb) == -1)
464 return(0);
465
Darren Tucker1e80e402006-09-21 12:59:33 +1000466 return(S_ISDIR(sb.st_mode));
Damien Miller20e1fab2004-02-18 14:30:55 +1100467}
468
469static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100470remote_is_dir(struct sftp_conn *conn, char *path)
471{
472 Attrib *a;
473
474 /* XXX: report errors? */
475 if ((a = do_stat(conn, path, 1)) == NULL)
476 return(0);
477 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
478 return(0);
Darren Tucker1e80e402006-09-21 12:59:33 +1000479 return(S_ISDIR(a->perm));
Damien Miller20e1fab2004-02-18 14:30:55 +1100480}
481
Darren Tucker1b0dd172009-10-07 08:37:48 +1100482/* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */
Damien Miller20e1fab2004-02-18 14:30:55 +1100483static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100484pathname_is_dir(char *pathname)
485{
486 size_t l = strlen(pathname);
487
488 return l > 0 && pathname[l - 1] == '/';
489}
490
491static int
492process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd,
493 int pflag, int rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100494{
495 char *abs_src = NULL;
496 char *abs_dst = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100497 glob_t g;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100498 char *filename, *tmp=NULL;
499 int i, err = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +1100500
501 abs_src = xstrdup(src);
502 abs_src = make_absolute(abs_src, pwd);
Damien Miller20e1fab2004-02-18 14:30:55 +1100503 memset(&g, 0, sizeof(g));
Darren Tucker1b0dd172009-10-07 08:37:48 +1100504
Damien Miller20e1fab2004-02-18 14:30:55 +1100505 debug3("Looking up %s", abs_src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100506 if (remote_glob(conn, abs_src, GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100507 error("File \"%s\" not found.", abs_src);
508 err = -1;
509 goto out;
510 }
511
Darren Tucker1b0dd172009-10-07 08:37:48 +1100512 /*
513 * If multiple matches then dst must be a directory or
514 * unspecified.
515 */
516 if (g.gl_matchc > 1 && dst != NULL && !is_dir(dst)) {
517 error("Multiple source paths, but destination "
518 "\"%s\" is not a directory", dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100519 err = -1;
520 goto out;
521 }
522
Darren Tuckercdf547a2004-05-24 10:12:19 +1000523 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100524 tmp = xstrdup(g.gl_pathv[i]);
525 if ((filename = basename(tmp)) == NULL) {
526 error("basename %s: %s", tmp, strerror(errno));
527 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100528 err = -1;
529 goto out;
530 }
531
532 if (g.gl_matchc == 1 && dst) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100533 if (is_dir(dst)) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100534 abs_dst = path_append(dst, filename);
535 } else {
Damien Miller20e1fab2004-02-18 14:30:55 +1100536 abs_dst = xstrdup(dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100537 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100538 } else if (dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100539 abs_dst = path_append(dst, filename);
540 } else {
541 abs_dst = xstrdup(filename);
542 }
543 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100544
545 printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100546 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
547 if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
548 pflag || global_pflag, 1) == -1)
549 err = -1;
550 } else {
551 if (do_download(conn, g.gl_pathv[i], abs_dst, NULL,
552 pflag || global_pflag) == -1)
553 err = -1;
554 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100555 xfree(abs_dst);
556 abs_dst = NULL;
557 }
558
559out:
560 xfree(abs_src);
Damien Miller20e1fab2004-02-18 14:30:55 +1100561 globfree(&g);
562 return(err);
563}
564
565static int
Darren Tucker1b0dd172009-10-07 08:37:48 +1100566process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd,
567 int pflag, int rflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100568{
569 char *tmp_dst = NULL;
570 char *abs_dst = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100571 char *tmp = NULL, *filename = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100572 glob_t g;
573 int err = 0;
Darren Tucker1b0dd172009-10-07 08:37:48 +1100574 int i, dst_is_dir = 1;
Damien Milleraec5cf82008-02-10 22:26:24 +1100575 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100576
577 if (dst) {
578 tmp_dst = xstrdup(dst);
579 tmp_dst = make_absolute(tmp_dst, pwd);
580 }
581
582 memset(&g, 0, sizeof(g));
583 debug3("Looking up %s", src);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100584 if (glob(src, GLOB_NOCHECK | GLOB_MARK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100585 error("File \"%s\" not found.", src);
586 err = -1;
587 goto out;
588 }
589
Darren Tucker1b0dd172009-10-07 08:37:48 +1100590 /* If we aren't fetching to pwd then stash this status for later */
591 if (tmp_dst != NULL)
592 dst_is_dir = remote_is_dir(conn, tmp_dst);
593
Damien Miller20e1fab2004-02-18 14:30:55 +1100594 /* If multiple matches, dst may be directory or unspecified */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100595 if (g.gl_matchc > 1 && tmp_dst && !dst_is_dir) {
596 error("Multiple paths match, but destination "
597 "\"%s\" is not a directory", tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100598 err = -1;
599 goto out;
600 }
601
Darren Tuckercdf547a2004-05-24 10:12:19 +1000602 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Milleraec5cf82008-02-10 22:26:24 +1100603 if (stat(g.gl_pathv[i], &sb) == -1) {
604 err = -1;
605 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
606 continue;
607 }
Darren Tucker1b0dd172009-10-07 08:37:48 +1100608
609 tmp = xstrdup(g.gl_pathv[i]);
610 if ((filename = basename(tmp)) == NULL) {
611 error("basename %s: %s", tmp, strerror(errno));
612 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100613 err = -1;
614 goto out;
615 }
616
617 if (g.gl_matchc == 1 && tmp_dst) {
618 /* If directory specified, append filename */
Darren Tucker1b0dd172009-10-07 08:37:48 +1100619 if (dst_is_dir)
620 abs_dst = path_append(tmp_dst, filename);
621 else
Damien Miller20e1fab2004-02-18 14:30:55 +1100622 abs_dst = xstrdup(tmp_dst);
Damien Miller20e1fab2004-02-18 14:30:55 +1100623 } else if (tmp_dst) {
Darren Tucker1b0dd172009-10-07 08:37:48 +1100624 abs_dst = path_append(tmp_dst, filename);
625 } else {
626 abs_dst = make_absolute(xstrdup(filename), pwd);
627 }
628 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100629
630 printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
Darren Tucker1b0dd172009-10-07 08:37:48 +1100631 if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
632 if (upload_dir(conn, g.gl_pathv[i], abs_dst,
633 pflag || global_pflag, 1) == -1)
634 err = -1;
635 } else {
636 if (do_upload(conn, g.gl_pathv[i], abs_dst,
637 pflag || global_pflag) == -1)
638 err = -1;
639 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100640 }
641
642out:
643 if (abs_dst)
644 xfree(abs_dst);
645 if (tmp_dst)
646 xfree(tmp_dst);
647 globfree(&g);
648 return(err);
649}
650
651static int
652sdirent_comp(const void *aa, const void *bb)
653{
654 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
655 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000656 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100657
Darren Tuckerb9123452004-06-22 13:06:45 +1000658#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000659 if (sort_flag & LS_NAME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000660 return (rmul * strcmp(a->filename, b->filename));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000661 else if (sort_flag & LS_TIME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000662 return (rmul * NCMP(a->a.mtime, b->a.mtime));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000663 else if (sort_flag & LS_SIZE_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000664 return (rmul * NCMP(a->a.size, b->a.size));
665
666 fatal("Unknown ls sort type");
Damien Miller20e1fab2004-02-18 14:30:55 +1100667}
668
669/* sftp ls.1 replacement for directories */
670static int
671do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
672{
Damien Millereccb9de2005-06-17 12:59:34 +1000673 int n;
674 u_int c = 1, colspace = 0, columns = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100675 SFTP_DIRENT **d;
676
677 if ((n = do_readdir(conn, path, &d)) != 0)
678 return (n);
679
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000680 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000681 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100682 struct winsize ws;
683 char *tmp;
684
685 /* Count entries for sort and find longest filename */
Darren Tucker9a526452004-06-22 13:09:55 +1000686 for (n = 0; d[n] != NULL; n++) {
687 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
688 m = MAX(m, strlen(d[n]->filename));
689 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100690
691 /* Add any subpath that also needs to be counted */
692 tmp = path_strip(path, strip_path);
693 m += strlen(tmp);
694 xfree(tmp);
695
696 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
697 width = ws.ws_col;
698
699 columns = width / (m + 2);
700 columns = MAX(columns, 1);
701 colspace = width / columns;
702 colspace = MIN(colspace, width);
703 }
704
Darren Tuckerb9123452004-06-22 13:06:45 +1000705 if (lflag & SORT_FLAGS) {
Damien Miller653b93b2005-11-05 15:15:23 +1100706 for (n = 0; d[n] != NULL; n++)
707 ; /* count entries */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000708 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
Darren Tuckerb9123452004-06-22 13:06:45 +1000709 qsort(d, n, sizeof(*d), sdirent_comp);
710 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100711
Darren Tuckercdf547a2004-05-24 10:12:19 +1000712 for (n = 0; d[n] != NULL && !interrupted; n++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100713 char *tmp, *fname;
714
Darren Tucker9a526452004-06-22 13:09:55 +1000715 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
716 continue;
717
Damien Miller20e1fab2004-02-18 14:30:55 +1100718 tmp = path_append(path, d[n]->filename);
719 fname = path_strip(tmp, strip_path);
720 xfree(tmp);
721
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000722 if (lflag & LS_LONG_VIEW) {
Darren Tucker2901e2d2010-01-13 22:44:06 +1100723 if (lflag & (LS_NUMERIC_VIEW|LS_SI_UNITS)) {
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000724 char *lname;
725 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100726
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000727 memset(&sb, 0, sizeof(sb));
728 attrib_to_stat(&d[n]->a, &sb);
Darren Tucker2901e2d2010-01-13 22:44:06 +1100729 lname = ls_file(fname, &sb, 1,
730 (lflag & LS_SI_UNITS));
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000731 printf("%s\n", lname);
732 xfree(lname);
733 } else
734 printf("%s\n", d[n]->longname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100735 } else {
736 printf("%-*s", colspace, fname);
737 if (c >= columns) {
738 printf("\n");
739 c = 1;
740 } else
741 c++;
742 }
743
744 xfree(fname);
745 }
746
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000747 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100748 printf("\n");
749
750 free_sftp_dirents(d);
751 return (0);
752}
753
754/* sftp ls.1 replacement which handles path globs */
755static int
756do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
757 int lflag)
758{
759 glob_t g;
Damien Millereccb9de2005-06-17 12:59:34 +1000760 u_int i, c = 1, colspace = 0, columns = 1;
Darren Tucker596dcfa2004-12-11 13:37:22 +1100761 Attrib *a = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100762
763 memset(&g, 0, sizeof(g));
764
765 if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE,
Darren Tucker596dcfa2004-12-11 13:37:22 +1100766 NULL, &g) || (g.gl_pathc && !g.gl_matchc)) {
767 if (g.gl_pathc)
768 globfree(&g);
Damien Miller20e1fab2004-02-18 14:30:55 +1100769 error("Can't ls: \"%s\" not found", path);
770 return (-1);
771 }
772
Darren Tuckercdf547a2004-05-24 10:12:19 +1000773 if (interrupted)
774 goto out;
775
Damien Miller20e1fab2004-02-18 14:30:55 +1100776 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100777 * If the glob returns a single match and it is a directory,
778 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100779 */
Darren Tucker596dcfa2004-12-11 13:37:22 +1100780 if (g.gl_matchc == 1) {
781 if ((a = do_lstat(conn, g.gl_pathv[0], 1)) == NULL) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100782 globfree(&g);
783 return (-1);
784 }
785 if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
786 S_ISDIR(a->perm)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100787 int err;
788
789 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +1100790 globfree(&g);
Darren Tucker596dcfa2004-12-11 13:37:22 +1100791 return (err);
Damien Miller20e1fab2004-02-18 14:30:55 +1100792 }
793 }
794
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000795 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000796 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100797 struct winsize ws;
798
799 /* Count entries for sort and find longest filename */
800 for (i = 0; g.gl_pathv[i]; i++)
801 m = MAX(m, strlen(g.gl_pathv[i]));
802
803 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
804 width = ws.ws_col;
805
806 columns = width / (m + 2);
807 columns = MAX(columns, 1);
808 colspace = width / columns;
809 }
810
Darren Tucker596dcfa2004-12-11 13:37:22 +1100811 for (i = 0; g.gl_pathv[i] && !interrupted; i++, a = NULL) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100812 char *fname;
813
814 fname = path_strip(g.gl_pathv[i], strip_path);
815
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000816 if (lflag & LS_LONG_VIEW) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100817 char *lname;
818 struct stat sb;
819
820 /*
821 * XXX: this is slow - 1 roundtrip per path
822 * A solution to this is to fork glob() and
823 * build a sftp specific version which keeps the
824 * attribs (which currently get thrown away)
825 * that the server returns as well as the filenames.
826 */
827 memset(&sb, 0, sizeof(sb));
Darren Tucker596dcfa2004-12-11 13:37:22 +1100828 if (a == NULL)
829 a = do_lstat(conn, g.gl_pathv[i], 1);
Damien Miller20e1fab2004-02-18 14:30:55 +1100830 if (a != NULL)
831 attrib_to_stat(a, &sb);
Darren Tucker2901e2d2010-01-13 22:44:06 +1100832 lname = ls_file(fname, &sb, 1, (lflag & LS_SI_UNITS));
Damien Miller20e1fab2004-02-18 14:30:55 +1100833 printf("%s\n", lname);
834 xfree(lname);
835 } else {
836 printf("%-*s", colspace, fname);
837 if (c >= columns) {
838 printf("\n");
839 c = 1;
840 } else
841 c++;
842 }
843 xfree(fname);
844 }
845
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000846 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100847 printf("\n");
848
Darren Tuckercdf547a2004-05-24 10:12:19 +1000849 out:
Damien Miller20e1fab2004-02-18 14:30:55 +1100850 if (g.gl_pathc)
851 globfree(&g);
852
853 return (0);
854}
855
Damien Millerd671e5a2008-05-19 14:53:33 +1000856static int
857do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
858{
Darren Tucker7b598892008-06-09 22:49:36 +1000859 struct sftp_statvfs st;
Damien Millerd671e5a2008-05-19 14:53:33 +1000860 char s_used[FMT_SCALED_STRSIZE];
861 char s_avail[FMT_SCALED_STRSIZE];
862 char s_root[FMT_SCALED_STRSIZE];
863 char s_total[FMT_SCALED_STRSIZE];
Darren Tuckerb5082e92010-01-08 18:51:47 +1100864 unsigned long long ffree;
Damien Millerd671e5a2008-05-19 14:53:33 +1000865
866 if (do_statvfs(conn, path, &st, 1) == -1)
867 return -1;
868 if (iflag) {
Darren Tuckerb5082e92010-01-08 18:51:47 +1100869 ffree = st.f_files ? (100 * (st.f_files - st.f_ffree) / st.f_files) : 0;
Damien Millerd671e5a2008-05-19 14:53:33 +1000870 printf(" Inodes Used Avail "
871 "(root) %%Capacity\n");
872 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
873 (unsigned long long)st.f_files,
874 (unsigned long long)(st.f_files - st.f_ffree),
875 (unsigned long long)st.f_favail,
Darren Tuckerb5082e92010-01-08 18:51:47 +1100876 (unsigned long long)st.f_ffree, ffree);
Damien Millerd671e5a2008-05-19 14:53:33 +1000877 } else if (hflag) {
878 strlcpy(s_used, "error", sizeof(s_used));
879 strlcpy(s_avail, "error", sizeof(s_avail));
880 strlcpy(s_root, "error", sizeof(s_root));
881 strlcpy(s_total, "error", sizeof(s_total));
882 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
883 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
884 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
885 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
886 printf(" Size Used Avail (root) %%Capacity\n");
887 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
888 s_total, s_used, s_avail, s_root,
889 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
890 st.f_blocks));
891 } else {
892 printf(" Size Used Avail "
893 "(root) %%Capacity\n");
894 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
895 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
896 (unsigned long long)(st.f_frsize *
897 (st.f_blocks - st.f_bfree) / 1024),
898 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
899 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
900 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
901 st.f_blocks));
902 }
903 return 0;
904}
905
Damien Miller1cbc2922007-10-26 14:27:45 +1000906/*
907 * Undo escaping of glob sequences in place. Used to undo extra escaping
908 * applied in makeargv() when the string is destined for a function that
909 * does not glob it.
910 */
911static void
912undo_glob_escape(char *s)
913{
914 size_t i, j;
915
916 for (i = j = 0;;) {
917 if (s[i] == '\0') {
918 s[j] = '\0';
919 return;
920 }
921 if (s[i] != '\\') {
922 s[j++] = s[i++];
923 continue;
924 }
925 /* s[i] == '\\' */
926 ++i;
927 switch (s[i]) {
928 case '?':
929 case '[':
930 case '*':
931 case '\\':
932 s[j++] = s[i++];
933 break;
934 case '\0':
935 s[j++] = '\\';
936 s[j] = '\0';
937 return;
938 default:
939 s[j++] = '\\';
940 s[j++] = s[i++];
941 break;
942 }
943 }
944}
945
946/*
947 * Split a string into an argument vector using sh(1)-style quoting,
948 * comment and escaping rules, but with some tweaks to handle glob(3)
949 * wildcards.
Darren Tucker909d8582010-01-08 19:02:40 +1100950 * The "sloppy" flag allows for recovery from missing terminating quote, for
951 * use in parsing incomplete commandlines during tab autocompletion.
952 *
Damien Miller1cbc2922007-10-26 14:27:45 +1000953 * Returns NULL on error or a NULL-terminated array of arguments.
Darren Tucker909d8582010-01-08 19:02:40 +1100954 *
955 * If "lastquote" is not NULL, the quoting character used for the last
956 * argument is placed in *lastquote ("\0", "'" or "\"").
957 *
958 * If "terminated" is not NULL, *terminated will be set to 1 when the
959 * last argument's quote has been properly terminated or 0 otherwise.
960 * This parameter is only of use if "sloppy" is set.
Damien Miller1cbc2922007-10-26 14:27:45 +1000961 */
962#define MAXARGS 128
963#define MAXARGLEN 8192
964static char **
Darren Tucker909d8582010-01-08 19:02:40 +1100965makeargv(const char *arg, int *argcp, int sloppy, char *lastquote,
966 u_int *terminated)
Damien Miller1cbc2922007-10-26 14:27:45 +1000967{
968 int argc, quot;
969 size_t i, j;
970 static char argvs[MAXARGLEN];
971 static char *argv[MAXARGS + 1];
972 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
973
974 *argcp = argc = 0;
975 if (strlen(arg) > sizeof(argvs) - 1) {
976 args_too_longs:
977 error("string too long");
978 return NULL;
979 }
Darren Tucker909d8582010-01-08 19:02:40 +1100980 if (terminated != NULL)
981 *terminated = 1;
982 if (lastquote != NULL)
983 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +1000984 state = MA_START;
985 i = j = 0;
986 for (;;) {
987 if (isspace(arg[i])) {
988 if (state == MA_UNQUOTED) {
989 /* Terminate current argument */
990 argvs[j++] = '\0';
991 argc++;
992 state = MA_START;
993 } else if (state != MA_START)
994 argvs[j++] = arg[i];
995 } else if (arg[i] == '"' || arg[i] == '\'') {
996 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
997 if (state == MA_START) {
998 argv[argc] = argvs + j;
999 state = q;
Darren Tucker909d8582010-01-08 19:02:40 +11001000 if (lastquote != NULL)
1001 *lastquote = arg[i];
Damien Miller1cbc2922007-10-26 14:27:45 +10001002 } else if (state == MA_UNQUOTED)
1003 state = q;
1004 else if (state == q)
1005 state = MA_UNQUOTED;
1006 else
1007 argvs[j++] = arg[i];
1008 } else if (arg[i] == '\\') {
1009 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1010 quot = state == MA_SQUOTE ? '\'' : '"';
1011 /* Unescape quote we are in */
1012 /* XXX support \n and friends? */
1013 if (arg[i + 1] == quot) {
1014 i++;
1015 argvs[j++] = arg[i];
1016 } else if (arg[i + 1] == '?' ||
1017 arg[i + 1] == '[' || arg[i + 1] == '*') {
1018 /*
1019 * Special case for sftp: append
1020 * double-escaped glob sequence -
1021 * glob will undo one level of
1022 * escaping. NB. string can grow here.
1023 */
1024 if (j >= sizeof(argvs) - 5)
1025 goto args_too_longs;
1026 argvs[j++] = '\\';
1027 argvs[j++] = arg[i++];
1028 argvs[j++] = '\\';
1029 argvs[j++] = arg[i];
1030 } else {
1031 argvs[j++] = arg[i++];
1032 argvs[j++] = arg[i];
1033 }
1034 } else {
1035 if (state == MA_START) {
1036 argv[argc] = argvs + j;
1037 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001038 if (lastquote != NULL)
1039 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001040 }
1041 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1042 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1043 /*
1044 * Special case for sftp: append
1045 * escaped glob sequence -
1046 * glob will undo one level of
1047 * escaping.
1048 */
1049 argvs[j++] = arg[i++];
1050 argvs[j++] = arg[i];
1051 } else {
1052 /* Unescape everything */
1053 /* XXX support \n and friends? */
1054 i++;
1055 argvs[j++] = arg[i];
1056 }
1057 }
1058 } else if (arg[i] == '#') {
1059 if (state == MA_SQUOTE || state == MA_DQUOTE)
1060 argvs[j++] = arg[i];
1061 else
1062 goto string_done;
1063 } else if (arg[i] == '\0') {
1064 if (state == MA_SQUOTE || state == MA_DQUOTE) {
Darren Tucker909d8582010-01-08 19:02:40 +11001065 if (sloppy) {
1066 state = MA_UNQUOTED;
1067 if (terminated != NULL)
1068 *terminated = 0;
1069 goto string_done;
1070 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001071 error("Unterminated quoted argument");
1072 return NULL;
1073 }
1074 string_done:
1075 if (state == MA_UNQUOTED) {
1076 argvs[j++] = '\0';
1077 argc++;
1078 }
1079 break;
1080 } else {
1081 if (state == MA_START) {
1082 argv[argc] = argvs + j;
1083 state = MA_UNQUOTED;
Darren Tucker909d8582010-01-08 19:02:40 +11001084 if (lastquote != NULL)
1085 *lastquote = '\0';
Damien Miller1cbc2922007-10-26 14:27:45 +10001086 }
1087 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1088 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1089 /*
1090 * Special case for sftp: escape quoted
1091 * glob(3) wildcards. NB. string can grow
1092 * here.
1093 */
1094 if (j >= sizeof(argvs) - 3)
1095 goto args_too_longs;
1096 argvs[j++] = '\\';
1097 argvs[j++] = arg[i];
1098 } else
1099 argvs[j++] = arg[i];
1100 }
1101 i++;
1102 }
1103 *argcp = argc;
1104 return argv;
1105}
1106
Damien Miller20e1fab2004-02-18 14:30:55 +11001107static int
Darren Tucker909d8582010-01-08 19:02:40 +11001108parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag,
1109 int *hflag, unsigned long *n_arg, char **path1, char **path2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001110{
1111 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001112 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001113 int base = 0;
1114 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001115 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001116
1117 /* Skip leading whitespace */
1118 cp = cp + strspn(cp, WHITESPACE);
1119
Damien Miller20e1fab2004-02-18 14:30:55 +11001120 /* Check for leading '-' (disable error processing) */
1121 *iflag = 0;
1122 if (*cp == '-') {
1123 *iflag = 1;
1124 cp++;
Darren Tucker70cc0922010-01-09 22:28:03 +11001125 cp = cp + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001126 }
1127
Darren Tucker70cc0922010-01-09 22:28:03 +11001128 /* Ignore blank lines and lines which begin with comment '#' char */
1129 if (*cp == '\0' || *cp == '#')
1130 return (0);
1131
Darren Tucker909d8582010-01-08 19:02:40 +11001132 if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL)
Damien Miller1cbc2922007-10-26 14:27:45 +10001133 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001134
Damien Miller1cbc2922007-10-26 14:27:45 +10001135 /* Figure out which command we have */
1136 for (i = 0; cmds[i].c != NULL; i++) {
1137 if (strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001138 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001139 }
1140 cmdnum = cmds[i].n;
1141 cmd = cmds[i].c;
1142
1143 /* Special case */
1144 if (*cp == '!') {
1145 cp++;
1146 cmdnum = I_SHELL;
1147 } else if (cmdnum == -1) {
1148 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001149 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001150 }
1151
1152 /* Get arguments and parse flags */
Darren Tucker1b0dd172009-10-07 08:37:48 +11001153 *lflag = *pflag = *rflag = *hflag = *n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001154 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001155 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001156 switch (cmdnum) {
1157 case I_GET:
1158 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001159 if ((optidx = parse_getput_flags(cmd, argv, argc, pflag, rflag)) == -1)
Damien Miller1cbc2922007-10-26 14:27:45 +10001160 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001161 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001162 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001163 error("You must specify at least one path after a "
1164 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001165 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001166 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001167 *path1 = xstrdup(argv[optidx]);
1168 /* Get second pathname (optional) */
1169 if (argc - optidx > 1) {
1170 *path2 = xstrdup(argv[optidx + 1]);
1171 /* Destination is not globbed */
1172 undo_glob_escape(*path2);
1173 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001174 break;
1175 case I_RENAME:
1176 case I_SYMLINK:
Damien Miller1cbc2922007-10-26 14:27:45 +10001177 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001178 error("You must specify two paths after a %s "
1179 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001180 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001181 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001182 *path1 = xstrdup(argv[optidx]);
1183 *path2 = xstrdup(argv[optidx + 1]);
1184 /* Paths are not globbed */
1185 undo_glob_escape(*path1);
1186 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001187 break;
1188 case I_RM:
1189 case I_MKDIR:
1190 case I_RMDIR:
1191 case I_CHDIR:
1192 case I_LCHDIR:
1193 case I_LMKDIR:
1194 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001195 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001196 error("You must specify a path after a %s command.",
1197 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001198 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001199 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001200 *path1 = xstrdup(argv[optidx]);
1201 /* Only "rm" globs */
1202 if (cmdnum != I_RM)
1203 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001204 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001205 case I_DF:
1206 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1207 iflag)) == -1)
1208 return -1;
1209 /* Default to current directory if no path specified */
1210 if (argc - optidx < 1)
1211 *path1 = NULL;
1212 else {
1213 *path1 = xstrdup(argv[optidx]);
1214 undo_glob_escape(*path1);
1215 }
1216 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001217 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001218 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001219 return(-1);
1220 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001221 if (argc - optidx > 0)
1222 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001223 break;
1224 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001225 /* Skip ls command and following whitespace */
1226 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001227 case I_SHELL:
1228 /* Uses the rest of the line */
1229 break;
1230 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001231 case I_CHMOD:
1232 base = 8;
1233 case I_CHOWN:
1234 case I_CHGRP:
1235 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001236 if (argc - optidx < 1)
1237 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001238 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001239 l = strtol(argv[optidx], &cp2, base);
1240 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1241 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1242 l < 0) {
1243 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001244 error("You must supply a numeric argument "
1245 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001246 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001247 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001248 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001249 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001250 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001251 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001252 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001253 error("You must specify a path after a %s command.",
1254 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001255 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001256 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001257 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001258 break;
1259 case I_QUIT:
1260 case I_PWD:
1261 case I_LPWD:
1262 case I_HELP:
1263 case I_VERSION:
1264 case I_PROGRESS:
1265 break;
1266 default:
1267 fatal("Command not implemented");
1268 }
1269
1270 *cpp = cp;
1271 return(cmdnum);
1272}
1273
1274static int
1275parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1276 int err_abort)
1277{
1278 char *path1, *path2, *tmp;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001279 int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, cmdnum, i;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001280 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001281 Attrib a, *aa;
1282 char path_buf[MAXPATHLEN];
1283 int err = 0;
1284 glob_t g;
1285
1286 path1 = path2 = NULL;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001287 cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag, &n_arg,
Damien Miller20e1fab2004-02-18 14:30:55 +11001288 &path1, &path2);
1289
1290 if (iflag != 0)
1291 err_abort = 0;
1292
1293 memset(&g, 0, sizeof(g));
1294
1295 /* Perform command */
1296 switch (cmdnum) {
1297 case 0:
1298 /* Blank line */
1299 break;
1300 case -1:
1301 /* Unrecognized command */
1302 err = -1;
1303 break;
1304 case I_GET:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001305 err = process_get(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001306 break;
1307 case I_PUT:
Darren Tucker1b0dd172009-10-07 08:37:48 +11001308 err = process_put(conn, path1, path2, *pwd, pflag, rflag);
Damien Miller20e1fab2004-02-18 14:30:55 +11001309 break;
1310 case I_RENAME:
1311 path1 = make_absolute(path1, *pwd);
1312 path2 = make_absolute(path2, *pwd);
1313 err = do_rename(conn, path1, path2);
1314 break;
1315 case I_SYMLINK:
1316 path2 = make_absolute(path2, *pwd);
1317 err = do_symlink(conn, path1, path2);
1318 break;
1319 case I_RM:
1320 path1 = make_absolute(path1, *pwd);
1321 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001322 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001323 printf("Removing %s\n", g.gl_pathv[i]);
1324 err = do_rm(conn, g.gl_pathv[i]);
1325 if (err != 0 && err_abort)
1326 break;
1327 }
1328 break;
1329 case I_MKDIR:
1330 path1 = make_absolute(path1, *pwd);
1331 attrib_clear(&a);
1332 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1333 a.perm = 0777;
Darren Tucker1b0dd172009-10-07 08:37:48 +11001334 err = do_mkdir(conn, path1, &a, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001335 break;
1336 case I_RMDIR:
1337 path1 = make_absolute(path1, *pwd);
1338 err = do_rmdir(conn, path1);
1339 break;
1340 case I_CHDIR:
1341 path1 = make_absolute(path1, *pwd);
1342 if ((tmp = do_realpath(conn, path1)) == NULL) {
1343 err = 1;
1344 break;
1345 }
1346 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1347 xfree(tmp);
1348 err = 1;
1349 break;
1350 }
1351 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1352 error("Can't change directory: Can't check target");
1353 xfree(tmp);
1354 err = 1;
1355 break;
1356 }
1357 if (!S_ISDIR(aa->perm)) {
1358 error("Can't change directory: \"%s\" is not "
1359 "a directory", tmp);
1360 xfree(tmp);
1361 err = 1;
1362 break;
1363 }
1364 xfree(*pwd);
1365 *pwd = tmp;
1366 break;
1367 case I_LS:
1368 if (!path1) {
1369 do_globbed_ls(conn, *pwd, *pwd, lflag);
1370 break;
1371 }
1372
1373 /* Strip pwd off beginning of non-absolute paths */
1374 tmp = NULL;
1375 if (*path1 != '/')
1376 tmp = *pwd;
1377
1378 path1 = make_absolute(path1, *pwd);
1379 err = do_globbed_ls(conn, path1, tmp, lflag);
1380 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001381 case I_DF:
1382 /* Default to current directory if no path specified */
1383 if (path1 == NULL)
1384 path1 = xstrdup(*pwd);
1385 path1 = make_absolute(path1, *pwd);
1386 err = do_df(conn, path1, hflag, iflag);
1387 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001388 case I_LCHDIR:
1389 if (chdir(path1) == -1) {
1390 error("Couldn't change local directory to "
1391 "\"%s\": %s", path1, strerror(errno));
1392 err = 1;
1393 }
1394 break;
1395 case I_LMKDIR:
1396 if (mkdir(path1, 0777) == -1) {
1397 error("Couldn't create local directory "
1398 "\"%s\": %s", path1, strerror(errno));
1399 err = 1;
1400 }
1401 break;
1402 case I_LLS:
1403 local_do_ls(cmd);
1404 break;
1405 case I_SHELL:
1406 local_do_shell(cmd);
1407 break;
1408 case I_LUMASK:
1409 umask(n_arg);
1410 printf("Local umask: %03lo\n", n_arg);
1411 break;
1412 case I_CHMOD:
1413 path1 = make_absolute(path1, *pwd);
1414 attrib_clear(&a);
1415 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1416 a.perm = n_arg;
1417 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001418 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001419 printf("Changing mode on %s\n", g.gl_pathv[i]);
1420 err = do_setstat(conn, g.gl_pathv[i], &a);
1421 if (err != 0 && err_abort)
1422 break;
1423 }
1424 break;
1425 case I_CHOWN:
1426 case I_CHGRP:
1427 path1 = make_absolute(path1, *pwd);
1428 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001429 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001430 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001431 if (err_abort) {
1432 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001433 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001434 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001435 continue;
1436 }
1437 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1438 error("Can't get current ownership of "
1439 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001440 if (err_abort) {
1441 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001442 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001443 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001444 continue;
1445 }
1446 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1447 if (cmdnum == I_CHOWN) {
1448 printf("Changing owner on %s\n", g.gl_pathv[i]);
1449 aa->uid = n_arg;
1450 } else {
1451 printf("Changing group on %s\n", g.gl_pathv[i]);
1452 aa->gid = n_arg;
1453 }
1454 err = do_setstat(conn, g.gl_pathv[i], aa);
1455 if (err != 0 && err_abort)
1456 break;
1457 }
1458 break;
1459 case I_PWD:
1460 printf("Remote working directory: %s\n", *pwd);
1461 break;
1462 case I_LPWD:
1463 if (!getcwd(path_buf, sizeof(path_buf))) {
1464 error("Couldn't get local cwd: %s", strerror(errno));
1465 err = -1;
1466 break;
1467 }
1468 printf("Local working directory: %s\n", path_buf);
1469 break;
1470 case I_QUIT:
1471 /* Processed below */
1472 break;
1473 case I_HELP:
1474 help();
1475 break;
1476 case I_VERSION:
1477 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1478 break;
1479 case I_PROGRESS:
1480 showprogress = !showprogress;
1481 if (showprogress)
1482 printf("Progress meter enabled\n");
1483 else
1484 printf("Progress meter disabled\n");
1485 break;
1486 default:
1487 fatal("%d is not implemented", cmdnum);
1488 }
1489
1490 if (g.gl_pathc)
1491 globfree(&g);
1492 if (path1)
1493 xfree(path1);
1494 if (path2)
1495 xfree(path2);
1496
1497 /* If an unignored error occurs in batch mode we should abort. */
1498 if (err_abort && err != 0)
1499 return (-1);
1500 else if (cmdnum == I_QUIT)
1501 return (1);
1502
1503 return (0);
1504}
1505
Darren Tucker2d963d82004-11-07 20:04:10 +11001506#ifdef USE_LIBEDIT
1507static char *
1508prompt(EditLine *el)
1509{
1510 return ("sftp> ");
1511}
Darren Tucker2d963d82004-11-07 20:04:10 +11001512
Darren Tucker909d8582010-01-08 19:02:40 +11001513/* Display entries in 'list' after skipping the first 'len' chars */
1514static void
1515complete_display(char **list, u_int len)
1516{
1517 u_int y, m = 0, width = 80, columns = 1, colspace = 0, llen;
1518 struct winsize ws;
1519 char *tmp;
1520
1521 /* Count entries for sort and find longest */
1522 for (y = 0; list[y]; y++)
1523 m = MAX(m, strlen(list[y]));
1524
1525 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
1526 width = ws.ws_col;
1527
1528 m = m > len ? m - len : 0;
1529 columns = width / (m + 2);
1530 columns = MAX(columns, 1);
1531 colspace = width / columns;
1532 colspace = MIN(colspace, width);
1533
1534 printf("\n");
1535 m = 1;
1536 for (y = 0; list[y]; y++) {
1537 llen = strlen(list[y]);
1538 tmp = llen > len ? list[y] + len : "";
1539 printf("%-*s", colspace, tmp);
1540 if (m >= columns) {
1541 printf("\n");
1542 m = 1;
1543 } else
1544 m++;
1545 }
1546 printf("\n");
1547}
1548
1549/*
1550 * Given a "list" of words that begin with a common prefix of "word",
1551 * attempt to find an autocompletion to extends "word" by the next
1552 * characters common to all entries in "list".
1553 */
1554static char *
1555complete_ambiguous(const char *word, char **list, size_t count)
1556{
1557 if (word == NULL)
1558 return NULL;
1559
1560 if (count > 0) {
1561 u_int y, matchlen = strlen(list[0]);
1562
1563 /* Find length of common stem */
1564 for (y = 1; list[y]; y++) {
1565 u_int x;
1566
1567 for (x = 0; x < matchlen; x++)
1568 if (list[0][x] != list[y][x])
1569 break;
1570
1571 matchlen = x;
1572 }
1573
1574 if (matchlen > strlen(word)) {
1575 char *tmp = xstrdup(list[0]);
1576
Darren Tucker340d1682010-01-09 08:54:31 +11001577 tmp[matchlen] = '\0';
Darren Tucker909d8582010-01-08 19:02:40 +11001578 return tmp;
1579 }
1580 }
1581
1582 return xstrdup(word);
1583}
1584
1585/* Autocomplete a sftp command */
1586static int
1587complete_cmd_parse(EditLine *el, char *cmd, int lastarg, char quote,
1588 int terminated)
1589{
1590 u_int y, count = 0, cmdlen, tmplen;
1591 char *tmp, **list, argterm[3];
1592 const LineInfo *lf;
1593
1594 list = xcalloc((sizeof(cmds) / sizeof(*cmds)) + 1, sizeof(char *));
1595
1596 /* No command specified: display all available commands */
1597 if (cmd == NULL) {
1598 for (y = 0; cmds[y].c; y++)
1599 list[count++] = xstrdup(cmds[y].c);
1600
1601 list[count] = NULL;
1602 complete_display(list, 0);
1603
1604 for (y = 0; list[y] != NULL; y++)
1605 xfree(list[y]);
1606 xfree(list);
1607 return count;
1608 }
1609
1610 /* Prepare subset of commands that start with "cmd" */
1611 cmdlen = strlen(cmd);
1612 for (y = 0; cmds[y].c; y++) {
1613 if (!strncasecmp(cmd, cmds[y].c, cmdlen))
1614 list[count++] = xstrdup(cmds[y].c);
1615 }
1616 list[count] = NULL;
1617
1618 if (count == 0)
1619 return 0;
1620
1621 /* Complete ambigious command */
1622 tmp = complete_ambiguous(cmd, list, count);
1623 if (count > 1)
1624 complete_display(list, 0);
1625
1626 for (y = 0; list[y]; y++)
1627 xfree(list[y]);
1628 xfree(list);
1629
1630 if (tmp != NULL) {
1631 tmplen = strlen(tmp);
1632 cmdlen = strlen(cmd);
1633 /* If cmd may be extended then do so */
1634 if (tmplen > cmdlen)
1635 if (el_insertstr(el, tmp + cmdlen) == -1)
1636 fatal("el_insertstr failed.");
1637 lf = el_line(el);
1638 /* Terminate argument cleanly */
1639 if (count == 1) {
1640 y = 0;
1641 if (!terminated)
1642 argterm[y++] = quote;
1643 if (lastarg || *(lf->cursor) != ' ')
1644 argterm[y++] = ' ';
1645 argterm[y] = '\0';
1646 if (y > 0 && el_insertstr(el, argterm) == -1)
1647 fatal("el_insertstr failed.");
1648 }
1649 xfree(tmp);
1650 }
1651
1652 return count;
1653}
1654
1655/*
1656 * Determine whether a particular sftp command's arguments (if any)
1657 * represent local or remote files.
1658 */
1659static int
1660complete_is_remote(char *cmd) {
1661 int i;
1662
1663 if (cmd == NULL)
1664 return -1;
1665
1666 for (i = 0; cmds[i].c; i++) {
1667 if (!strncasecmp(cmd, cmds[i].c, strlen(cmds[i].c)))
1668 return cmds[i].t;
1669 }
1670
1671 return -1;
1672}
1673
1674/* Autocomplete a filename "file" */
1675static int
1676complete_match(EditLine *el, struct sftp_conn *conn, char *remote_path,
1677 char *file, int remote, int lastarg, char quote, int terminated)
1678{
1679 glob_t g;
1680 char *tmp, *tmp2, ins[3];
1681 u_int i, hadglob, pwdlen, len, tmplen, filelen;
1682 const LineInfo *lf;
1683
1684 /* Glob from "file" location */
1685 if (file == NULL)
1686 tmp = xstrdup("*");
1687 else
1688 xasprintf(&tmp, "%s*", file);
1689
1690 memset(&g, 0, sizeof(g));
1691 if (remote != LOCAL) {
1692 tmp = make_absolute(tmp, remote_path);
1693 remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1694 } else
1695 glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
1696
1697 /* Determine length of pwd so we can trim completion display */
1698 for (hadglob = tmplen = pwdlen = 0; tmp[tmplen] != 0; tmplen++) {
1699 /* Terminate counting on first unescaped glob metacharacter */
1700 if (tmp[tmplen] == '*' || tmp[tmplen] == '?') {
1701 if (tmp[tmplen] != '*' || tmp[tmplen + 1] != '\0')
1702 hadglob = 1;
1703 break;
1704 }
1705 if (tmp[tmplen] == '\\' && tmp[tmplen + 1] != '\0')
1706 tmplen++;
1707 if (tmp[tmplen] == '/')
1708 pwdlen = tmplen + 1; /* track last seen '/' */
1709 }
1710 xfree(tmp);
1711
1712 if (g.gl_matchc == 0)
1713 goto out;
1714
1715 if (g.gl_matchc > 1)
1716 complete_display(g.gl_pathv, pwdlen);
1717
1718 tmp = NULL;
1719 /* Don't try to extend globs */
1720 if (file == NULL || hadglob)
1721 goto out;
1722
1723 tmp2 = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
1724 tmp = path_strip(tmp2, remote_path);
1725 xfree(tmp2);
1726
1727 if (tmp == NULL)
1728 goto out;
1729
1730 tmplen = strlen(tmp);
1731 filelen = strlen(file);
1732
1733 if (tmplen > filelen) {
1734 tmp2 = tmp + filelen;
1735 len = strlen(tmp2);
1736 /* quote argument on way out */
1737 for (i = 0; i < len; i++) {
1738 ins[0] = '\\';
1739 ins[1] = tmp2[i];
1740 ins[2] = '\0';
1741 switch (tmp2[i]) {
1742 case '\'':
1743 case '"':
1744 case '\\':
1745 case '\t':
1746 case ' ':
1747 if (quote == '\0' || tmp2[i] == quote) {
1748 if (el_insertstr(el, ins) == -1)
1749 fatal("el_insertstr "
1750 "failed.");
1751 break;
1752 }
1753 /* FALLTHROUGH */
1754 default:
1755 if (el_insertstr(el, ins + 1) == -1)
1756 fatal("el_insertstr failed.");
1757 break;
1758 }
1759 }
1760 }
1761
1762 lf = el_line(el);
Darren Tucker909d8582010-01-08 19:02:40 +11001763 if (g.gl_matchc == 1) {
1764 i = 0;
1765 if (!terminated)
1766 ins[i++] = quote;
Darren Tucker9c3ba072010-01-13 22:45:03 +11001767 if (*(lf->cursor - 1) != '/' &&
1768 (lastarg || *(lf->cursor) != ' '))
Darren Tucker909d8582010-01-08 19:02:40 +11001769 ins[i++] = ' ';
1770 ins[i] = '\0';
1771 if (i > 0 && el_insertstr(el, ins) == -1)
1772 fatal("el_insertstr failed.");
1773 }
1774 xfree(tmp);
1775
1776 out:
1777 globfree(&g);
1778 return g.gl_matchc;
1779}
1780
1781/* tab-completion hook function, called via libedit */
1782static unsigned char
1783complete(EditLine *el, int ch)
1784{
1785 char **argv, *line, quote;
1786 u_int argc, carg, cursor, len, terminated, ret = CC_ERROR;
1787 const LineInfo *lf;
1788 struct complete_ctx *complete_ctx;
1789
1790 lf = el_line(el);
1791 if (el_get(el, EL_CLIENTDATA, (void**)&complete_ctx) != 0)
1792 fatal("%s: el_get failed", __func__);
1793
1794 /* Figure out which argument the cursor points to */
1795 cursor = lf->cursor - lf->buffer;
1796 line = (char *)xmalloc(cursor + 1);
1797 memcpy(line, lf->buffer, cursor);
1798 line[cursor] = '\0';
1799 argv = makeargv(line, &carg, 1, &quote, &terminated);
1800 xfree(line);
1801
1802 /* Get all the arguments on the line */
1803 len = lf->lastchar - lf->buffer;
1804 line = (char *)xmalloc(len + 1);
1805 memcpy(line, lf->buffer, len);
1806 line[len] = '\0';
1807 argv = makeargv(line, &argc, 1, NULL, NULL);
1808
1809 /* Ensure cursor is at EOL or a argument boundary */
1810 if (line[cursor] != ' ' && line[cursor] != '\0' &&
1811 line[cursor] != '\n') {
1812 xfree(line);
1813 return ret;
1814 }
1815
1816 if (carg == 0) {
1817 /* Show all available commands */
1818 complete_cmd_parse(el, NULL, argc == carg, '\0', 1);
1819 ret = CC_REDISPLAY;
1820 } else if (carg == 1 && cursor > 0 && line[cursor - 1] != ' ') {
1821 /* Handle the command parsing */
1822 if (complete_cmd_parse(el, argv[0], argc == carg,
1823 quote, terminated) != 0)
1824 ret = CC_REDISPLAY;
1825 } else if (carg >= 1) {
1826 /* Handle file parsing */
1827 int remote = complete_is_remote(argv[0]);
1828 char *filematch = NULL;
1829
1830 if (carg > 1 && line[cursor-1] != ' ')
1831 filematch = argv[carg - 1];
1832
1833 if (remote != 0 &&
1834 complete_match(el, complete_ctx->conn,
1835 *complete_ctx->remote_pathp, filematch,
1836 remote, carg == argc, quote, terminated) != 0)
1837 ret = CC_REDISPLAY;
1838 }
1839
1840 xfree(line);
1841 return ret;
1842}
Darren Tuckere67f7db2010-01-08 19:50:02 +11001843#endif /* USE_LIBEDIT */
Darren Tucker909d8582010-01-08 19:02:40 +11001844
Damien Miller20e1fab2004-02-18 14:30:55 +11001845int
Darren Tucker21063192010-01-08 17:10:36 +11001846interactive_loop(struct sftp_conn *conn, char *file1, char *file2)
Damien Miller20e1fab2004-02-18 14:30:55 +11001847{
Darren Tucker909d8582010-01-08 19:02:40 +11001848 char *remote_path;
Damien Miller20e1fab2004-02-18 14:30:55 +11001849 char *dir = NULL;
1850 char cmd[2048];
Damien Miller0e2c1022005-08-12 22:16:22 +10001851 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11001852 EditLine *el = NULL;
1853#ifdef USE_LIBEDIT
1854 History *hl = NULL;
1855 HistEvent hev;
1856 extern char *__progname;
Darren Tucker909d8582010-01-08 19:02:40 +11001857 struct complete_ctx complete_ctx;
Darren Tucker2d963d82004-11-07 20:04:10 +11001858
1859 if (!batchmode && isatty(STDIN_FILENO)) {
1860 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1861 fatal("Couldn't initialise editline");
1862 if ((hl = history_init()) == NULL)
1863 fatal("Couldn't initialise editline history");
1864 history(hl, &hev, H_SETSIZE, 100);
1865 el_set(el, EL_HIST, history, hl);
1866
1867 el_set(el, EL_PROMPT, prompt);
1868 el_set(el, EL_EDITOR, "emacs");
1869 el_set(el, EL_TERMINAL, NULL);
1870 el_set(el, EL_SIGNAL, 1);
1871 el_source(el, NULL);
Darren Tucker909d8582010-01-08 19:02:40 +11001872
1873 /* Tab Completion */
1874 el_set(el, EL_ADDFN, "ftp-complete",
1875 "Context senstive argument completion", complete);
1876 complete_ctx.conn = conn;
1877 complete_ctx.remote_pathp = &remote_path;
1878 el_set(el, EL_CLIENTDATA, (void*)&complete_ctx);
1879 el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
Darren Tucker2d963d82004-11-07 20:04:10 +11001880 }
1881#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001882
Darren Tucker909d8582010-01-08 19:02:40 +11001883 remote_path = do_realpath(conn, ".");
1884 if (remote_path == NULL)
Damien Miller20e1fab2004-02-18 14:30:55 +11001885 fatal("Need cwd");
1886
1887 if (file1 != NULL) {
1888 dir = xstrdup(file1);
Darren Tucker909d8582010-01-08 19:02:40 +11001889 dir = make_absolute(dir, remote_path);
Damien Miller20e1fab2004-02-18 14:30:55 +11001890
1891 if (remote_is_dir(conn, dir) && file2 == NULL) {
1892 printf("Changing to: %s\n", dir);
1893 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001894 if (parse_dispatch_command(conn, cmd,
1895 &remote_path, 1) != 0) {
Darren Tuckercd516ef2004-12-06 22:43:43 +11001896 xfree(dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001897 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001898 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001899 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11001900 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001901 } else {
1902 if (file2 == NULL)
1903 snprintf(cmd, sizeof cmd, "get %s", dir);
1904 else
1905 snprintf(cmd, sizeof cmd, "get %s %s", dir,
1906 file2);
1907
Darren Tucker909d8582010-01-08 19:02:40 +11001908 err = parse_dispatch_command(conn, cmd,
1909 &remote_path, 1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001910 xfree(dir);
Darren Tucker909d8582010-01-08 19:02:40 +11001911 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001912 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001913 return (err);
1914 }
1915 xfree(dir);
1916 }
1917
Darren Tucker93e7e8f2005-08-23 08:06:55 +10001918#if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF)
Damien Miller20e1fab2004-02-18 14:30:55 +11001919 setvbuf(stdout, NULL, _IOLBF, 0);
1920 setvbuf(infile, NULL, _IOLBF, 0);
1921#else
Damien Miller37294fb2005-07-17 17:18:49 +10001922 setlinebuf(stdout);
1923 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11001924#endif
1925
Damien Miller0e2c1022005-08-12 22:16:22 +10001926 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11001927 err = 0;
1928 for (;;) {
1929 char *cp;
1930
Darren Tuckercdf547a2004-05-24 10:12:19 +10001931 signal(SIGINT, SIG_IGN);
1932
Darren Tucker2d963d82004-11-07 20:04:10 +11001933 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001934 if (interactive)
1935 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11001936 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001937 if (interactive)
1938 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11001939 break;
1940 }
Damien Miller0e2c1022005-08-12 22:16:22 +10001941 if (!interactive) { /* Echo command */
1942 printf("sftp> %s", cmd);
1943 if (strlen(cmd) > 0 &&
1944 cmd[strlen(cmd) - 1] != '\n')
1945 printf("\n");
1946 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001947 } else {
1948#ifdef USE_LIBEDIT
1949 const char *line;
1950 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001951
Darren Tucker909d8582010-01-08 19:02:40 +11001952 if ((line = el_gets(el, &count)) == NULL ||
1953 count <= 0) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001954 printf("\n");
1955 break;
1956 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001957 history(hl, &hev, H_ENTER, line);
1958 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
1959 fprintf(stderr, "Error: input line too long\n");
1960 continue;
1961 }
1962#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001963 }
1964
Damien Miller20e1fab2004-02-18 14:30:55 +11001965 cp = strrchr(cmd, '\n');
1966 if (cp)
1967 *cp = '\0';
1968
Darren Tuckercdf547a2004-05-24 10:12:19 +10001969 /* Handle user interrupts gracefully during commands */
1970 interrupted = 0;
1971 signal(SIGINT, cmd_interrupt);
1972
Darren Tucker909d8582010-01-08 19:02:40 +11001973 err = parse_dispatch_command(conn, cmd, &remote_path,
1974 batchmode);
Damien Miller20e1fab2004-02-18 14:30:55 +11001975 if (err != 0)
1976 break;
1977 }
Darren Tucker909d8582010-01-08 19:02:40 +11001978 xfree(remote_path);
Damien Millere0b90a62006-03-26 13:51:44 +11001979 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001980
Tim Rice027e8b12005-08-15 14:52:50 -07001981#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10001982 if (el != NULL)
1983 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07001984#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10001985
Damien Miller20e1fab2004-02-18 14:30:55 +11001986 /* err == 1 signifies normal "quit" exit */
1987 return (err >= 0 ? 0 : -1);
1988}
Damien Miller62d57f62003-01-10 21:43:24 +11001989
Ben Lindstrombba81212001-06-25 05:01:22 +00001990static void
Damien Millercc685c12003-06-04 22:51:38 +10001991connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11001992{
1993 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001994
Damien Miller33804262001-02-04 23:20:18 +11001995#ifdef USE_PIPES
1996 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001997
Damien Miller33804262001-02-04 23:20:18 +11001998 if ((pipe(pin) == -1) || (pipe(pout) == -1))
1999 fatal("pipe: %s", strerror(errno));
2000 *in = pin[0];
2001 *out = pout[1];
2002 c_in = pout[0];
2003 c_out = pin[1];
2004#else /* USE_PIPES */
2005 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00002006
Damien Miller33804262001-02-04 23:20:18 +11002007 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
2008 fatal("socketpair: %s", strerror(errno));
2009 *in = *out = inout[0];
2010 c_in = c_out = inout[1];
2011#endif /* USE_PIPES */
2012
Damien Millercc685c12003-06-04 22:51:38 +10002013 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11002014 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10002015 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11002016 if ((dup2(c_in, STDIN_FILENO) == -1) ||
2017 (dup2(c_out, STDOUT_FILENO) == -1)) {
2018 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002019 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002020 }
2021 close(*in);
2022 close(*out);
2023 close(c_in);
2024 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10002025
2026 /*
2027 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10002028 * ignore SIGINT if we want to gracefully abort commands,
2029 * otherwise the signal will make it to the ssh process and
Darren Tuckercdf547a2004-05-24 10:12:19 +10002030 * kill it too
2031 */
2032 signal(SIGINT, SIG_IGN);
Darren Tuckerbd12f172004-06-18 16:23:43 +10002033 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002034 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10002035 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11002036 }
2037
Damien Millercc685c12003-06-04 22:51:38 +10002038 signal(SIGTERM, killchild);
2039 signal(SIGINT, killchild);
2040 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11002041 close(c_in);
2042 close(c_out);
2043}
2044
Ben Lindstrombba81212001-06-25 05:01:22 +00002045static void
Damien Miller33804262001-02-04 23:20:18 +11002046usage(void)
2047{
Damien Miller025e01c2002-02-08 22:06:29 +11002048 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002049
Ben Lindstrom1e243242001-09-18 05:38:44 +00002050 fprintf(stderr,
Darren Tucker1b0dd172009-10-07 08:37:48 +11002051 "usage: %s [-1246Cpqrv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
Darren Tuckerc07138e2009-10-07 08:23:44 +11002052 " [-D sftp_server_path] [-F ssh_config] "
2053 "[-i identity_file]\n"
2054 " [-o ssh_option] [-P port] [-R num_requests] "
2055 "[-S program]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002056 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11002057 " %s [user@]host[:file ...]\n"
2058 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11002059 " %s -b batchfile [user@]host\n",
2060 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11002061 exit(1);
2062}
2063
Kevin Stevesef4eea92001-02-05 12:42:17 +00002064int
Damien Miller33804262001-02-04 23:20:18 +11002065main(int argc, char **argv)
2066{
Damien Miller956f3fb2003-01-10 21:40:00 +11002067 int in, out, ch, err;
Darren Tucker340d1682010-01-09 08:54:31 +11002068 char *host = NULL, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002069 int debug_level = 0, sshver = 2;
2070 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11002071 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00002072 LogLevel ll = SYSLOG_LEVEL_INFO;
2073 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11002074 extern int optind;
2075 extern char *optarg;
Darren Tucker21063192010-01-08 17:10:36 +11002076 struct sftp_conn *conn;
2077 size_t copy_buffer_len = DEFAULT_COPY_BUFLEN;
2078 size_t num_requests = DEFAULT_NUM_REQUESTS;
Damien Miller33804262001-02-04 23:20:18 +11002079
Darren Tuckerce321d82005-10-03 18:11:24 +10002080 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
2081 sanitise_stdfd();
2082
Damien Miller59d3d5b2003-08-22 09:34:41 +10002083 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11002084 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00002085 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11002086 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00002087 addargs(&args, "-oForwardX11 no");
2088 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11002089 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00002090 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11002091
Ben Lindstrom387c4722001-05-08 20:27:25 +00002092 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11002093 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11002094
Darren Tucker282b4022009-10-07 08:23:06 +11002095 while ((ch = getopt(argc, argv,
Darren Tucker1b0dd172009-10-07 08:37:48 +11002096 "1246hqrvCc:D:i:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11002097 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11002098 /* Passed through to ssh(1) */
2099 case '4':
2100 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11002101 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11002102 addargs(&args, "-%c", ch);
2103 break;
2104 /* Passed through to ssh(1) with argument */
2105 case 'F':
2106 case 'c':
2107 case 'i':
2108 case 'o':
Darren Tuckerc4dc4f52010-01-08 18:50:04 +11002109 addargs(&args, "-%c", ch);
2110 addargs(&args, "%s", optarg);
Darren Tucker46bbbe32009-10-07 08:21:48 +11002111 break;
2112 case 'q':
2113 showprogress = 0;
2114 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11002115 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002116 case 'P':
2117 addargs(&args, "-oPort %s", optarg);
2118 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002119 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002120 if (debug_level < 3) {
2121 addargs(&args, "-v");
2122 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
2123 }
2124 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11002125 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002126 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00002127 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11002128 if (sftp_server == NULL)
2129 sftp_server = _PATH_SFTP_SERVER;
2130 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002131 case '2':
2132 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11002133 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002134 case 'B':
2135 copy_buffer_len = strtol(optarg, &cp, 10);
2136 if (copy_buffer_len == 0 || *cp != '\0')
2137 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11002138 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002139 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11002140 if (batchmode)
2141 fatal("Batch file already specified.");
2142
2143 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10002144 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10002145 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11002146 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11002147 showprogress = 0;
Damien Miller44f75c12004-01-21 10:58:47 +11002148 batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11002149 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002150 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002151 case 'p':
2152 global_pflag = 1;
2153 break;
Darren Tucker282b4022009-10-07 08:23:06 +11002154 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11002155 sftp_direct = optarg;
2156 break;
Darren Tucker1b0dd172009-10-07 08:37:48 +11002157 case 'r':
2158 global_rflag = 1;
2159 break;
Damien Miller16a13332002-02-13 14:03:56 +11002160 case 'R':
2161 num_requests = strtol(optarg, &cp, 10);
2162 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002163 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11002164 optarg);
2165 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11002166 case 's':
2167 sftp_server = optarg;
2168 break;
2169 case 'S':
2170 ssh_program = optarg;
2171 replacearg(&args, 0, "%s", ssh_program);
2172 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11002173 case 'h':
2174 default:
Damien Miller33804262001-02-04 23:20:18 +11002175 usage();
2176 }
2177 }
2178
Damien Millerc0f27d82004-03-08 23:12:19 +11002179 if (!isatty(STDERR_FILENO))
2180 showprogress = 0;
2181
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00002182 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
2183
Damien Millerd14ee1e2002-02-05 12:27:31 +11002184 if (sftp_direct == NULL) {
2185 if (optind == argc || argc > (optind + 2))
2186 usage();
Damien Miller33804262001-02-04 23:20:18 +11002187
Damien Millerd14ee1e2002-02-05 12:27:31 +11002188 userhost = xstrdup(argv[optind]);
2189 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00002190
Ben Lindstromc276c122002-12-23 02:14:51 +00002191 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11002192 host = userhost;
2193 else {
2194 *host++ = '\0';
2195 if (!userhost[0]) {
2196 fprintf(stderr, "Missing username\n");
2197 usage();
2198 }
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002199 addargs(&args, "-l");
2200 addargs(&args, "%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002201 }
2202
Damien Millerec692032004-01-27 21:22:00 +11002203 if ((cp = colon(host)) != NULL) {
2204 *cp++ = '\0';
2205 file1 = cp;
2206 }
2207
Damien Millerd14ee1e2002-02-05 12:27:31 +11002208 host = cleanhostname(host);
2209 if (!*host) {
2210 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11002211 usage();
2212 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11002213
Damien Millerd14ee1e2002-02-05 12:27:31 +11002214 addargs(&args, "-oProtocol %d", sshver);
2215
2216 /* no subsystem if the server-spec contains a '/' */
2217 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
2218 addargs(&args, "-s");
2219
Darren Tuckerb8c884a2010-01-08 18:53:43 +11002220 addargs(&args, "--");
Damien Millerd14ee1e2002-02-05 12:27:31 +11002221 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00002222 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11002223 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11002224
Damien Millercc685c12003-06-04 22:51:38 +10002225 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11002226 } else {
2227 args.list = NULL;
2228 addargs(&args, "sftp-server");
2229
Damien Millercc685c12003-06-04 22:51:38 +10002230 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11002231 }
Damien Miller3eec6b72006-01-31 21:49:27 +11002232 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11002233
Darren Tucker21063192010-01-08 17:10:36 +11002234 conn = do_init(in, out, copy_buffer_len, num_requests);
2235 if (conn == NULL)
2236 fatal("Couldn't initialise connection to server");
2237
2238 if (!batchmode) {
2239 if (sftp_direct == NULL)
2240 fprintf(stderr, "Connected to %s.\n", host);
2241 else
2242 fprintf(stderr, "Attached to %s.\n", sftp_direct);
2243 }
2244
2245 err = interactive_loop(conn, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11002246
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002247#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10002248 shutdown(in, SHUT_RDWR);
2249 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00002250#endif
2251
Damien Miller33804262001-02-04 23:20:18 +11002252 close(in);
2253 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11002254 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00002255 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11002256
Ben Lindstrom47fd8112002-04-02 20:48:19 +00002257 while (waitpid(sshpid, NULL, 0) == -1)
2258 if (errno != EINTR)
2259 fatal("Couldn't wait for ssh process: %s",
2260 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11002261
Damien Miller956f3fb2003-01-10 21:40:00 +11002262 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11002263}