blob: 4c1d553896742fbe7daac16c42e8bbccfb9d80b2 [file] [log] [blame]
Darren Tucker282b4022009-10-07 08:23:06 +11001/* $OpenBSD: sftp.c,v 1.109 2009/08/13 01:11:19 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 Tucker2d963d82004-11-07 20:04:10 +110038#ifdef USE_LIBEDIT
39#include <histedit.h>
40#else
41typedef void EditLine;
42#endif
Damien Miller6ff3cad2006-03-15 11:52:09 +110043#include <signal.h>
Damien Millere7a1e5c2006-08-05 11:34:19 +100044#include <stdlib.h>
Damien Millera7a73ee2006-08-05 11:37:59 +100045#include <stdio.h>
Damien Millere3476ed2006-07-24 14:13:33 +100046#include <string.h>
Damien Millere6b3b612006-07-24 14:01:23 +100047#include <unistd.h>
Damien Millerd7834352006-08-05 12:39:39 +100048#include <stdarg.h>
Damien Miller33804262001-02-04 23:20:18 +110049
Damien Millera7058ec2008-05-20 08:57:06 +100050#ifdef HAVE_UTIL_H
51# include <util.h>
52#endif
53
54#ifdef HAVE_LIBUTIL_H
55# include <libutil.h>
56#endif
57
Damien Miller33804262001-02-04 23:20:18 +110058#include "xmalloc.h"
59#include "log.h"
60#include "pathnames.h"
Ben Lindstrom4529b702001-05-03 23:39:53 +000061#include "misc.h"
Damien Miller33804262001-02-04 23:20:18 +110062
63#include "sftp.h"
Damien Millerd7834352006-08-05 12:39:39 +100064#include "buffer.h"
Damien Miller33804262001-02-04 23:20:18 +110065#include "sftp-common.h"
66#include "sftp-client.h"
Damien Millerd7d46bb2004-02-18 14:11:13 +110067
Damien Miller20e1fab2004-02-18 14:30:55 +110068/* File to read commands from */
69FILE* infile;
70
71/* Are we in batchfile mode? */
72int batchmode = 0;
73
74/* Size of buffer used when copying files */
75size_t copy_buffer_len = 32768;
76
77/* Number of concurrent outstanding requests */
Damien Miller7f980d12008-07-14 11:29:24 +100078size_t num_requests = 64;
Damien Miller20e1fab2004-02-18 14:30:55 +110079
80/* 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 Tuckercdf547a2004-05-24 10:12:19 +100086/* SIGINT received during command processing */
87volatile sig_atomic_t interrupted = 0;
88
Darren Tuckerb9123452004-06-22 13:06:45 +100089/* I wish qsort() took a separate ctx for the comparison function...*/
90int sort_flag;
91
Damien Miller20e1fab2004-02-18 14:30:55 +110092int remote_glob(struct sftp_conn *, const char *, int,
93 int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */
Damien Miller33804262001-02-04 23:20:18 +110094
Kevin Steves12888d12001-03-05 19:50:57 +000095extern char *__progname;
Kevin Steves12888d12001-03-05 19:50:57 +000096
Damien Miller20e1fab2004-02-18 14:30:55 +110097/* Separators for interactive commands */
98#define WHITESPACE " \t\r\n"
Damien Millerd7686fd2001-02-10 00:40:03 +110099
Darren Tuckerb9123452004-06-22 13:06:45 +1000100/* ls flags */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000101#define LS_LONG_VIEW 0x01 /* Full view ala ls -l */
102#define LS_SHORT_VIEW 0x02 /* Single row view ala ls -1 */
103#define LS_NUMERIC_VIEW 0x04 /* Long view with numeric uid/gid */
104#define LS_NAME_SORT 0x08 /* Sort by name (default) */
105#define LS_TIME_SORT 0x10 /* Sort by mtime */
106#define LS_SIZE_SORT 0x20 /* Sort by file size */
107#define LS_REVERSE_SORT 0x40 /* Reverse sort order */
Darren Tucker9a526452004-06-22 13:09:55 +1000108#define LS_SHOW_ALL 0x80 /* Don't skip filenames starting with '.' */
Darren Tuckerb9123452004-06-22 13:06:45 +1000109
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000110#define VIEW_FLAGS (LS_LONG_VIEW|LS_SHORT_VIEW|LS_NUMERIC_VIEW)
111#define SORT_FLAGS (LS_NAME_SORT|LS_TIME_SORT|LS_SIZE_SORT)
Damien Miller20e1fab2004-02-18 14:30:55 +1100112
113/* Commands for interactive mode */
114#define I_CHDIR 1
115#define I_CHGRP 2
116#define I_CHMOD 3
117#define I_CHOWN 4
Damien Millerd671e5a2008-05-19 14:53:33 +1000118#define I_DF 24
Damien Miller20e1fab2004-02-18 14:30:55 +1100119#define I_GET 5
120#define I_HELP 6
121#define I_LCHDIR 7
122#define I_LLS 8
123#define I_LMKDIR 9
124#define I_LPWD 10
125#define I_LS 11
126#define I_LUMASK 12
127#define I_MKDIR 13
128#define I_PUT 14
129#define I_PWD 15
130#define I_QUIT 16
131#define I_RENAME 17
132#define I_RM 18
133#define I_RMDIR 19
134#define I_SHELL 20
135#define I_SYMLINK 21
136#define I_VERSION 22
137#define I_PROGRESS 23
138
139struct CMD {
140 const char *c;
141 const int n;
142};
143
144static const struct CMD cmds[] = {
145 { "bye", I_QUIT },
146 { "cd", I_CHDIR },
147 { "chdir", I_CHDIR },
148 { "chgrp", I_CHGRP },
149 { "chmod", I_CHMOD },
150 { "chown", I_CHOWN },
Damien Millerd671e5a2008-05-19 14:53:33 +1000151 { "df", I_DF },
Damien Miller20e1fab2004-02-18 14:30:55 +1100152 { "dir", I_LS },
153 { "exit", I_QUIT },
154 { "get", I_GET },
155 { "mget", I_GET },
156 { "help", I_HELP },
157 { "lcd", I_LCHDIR },
158 { "lchdir", I_LCHDIR },
159 { "lls", I_LLS },
160 { "lmkdir", I_LMKDIR },
161 { "ln", I_SYMLINK },
162 { "lpwd", I_LPWD },
163 { "ls", I_LS },
164 { "lumask", I_LUMASK },
165 { "mkdir", I_MKDIR },
166 { "progress", I_PROGRESS },
167 { "put", I_PUT },
168 { "mput", I_PUT },
169 { "pwd", I_PWD },
170 { "quit", I_QUIT },
171 { "rename", I_RENAME },
172 { "rm", I_RM },
173 { "rmdir", I_RMDIR },
174 { "symlink", I_SYMLINK },
175 { "version", I_VERSION },
176 { "!", I_SHELL },
177 { "?", I_HELP },
178 { NULL, -1}
179};
180
181int interactive_loop(int fd_in, int fd_out, char *file1, char *file2);
182
Damien Millerb6c85fc2007-01-05 16:30:41 +1100183/* ARGSUSED */
Damien Miller20e1fab2004-02-18 14:30:55 +1100184static void
Darren Tuckercdf547a2004-05-24 10:12:19 +1000185killchild(int signo)
186{
Darren Tuckerba66df82005-01-24 21:57:40 +1100187 if (sshpid > 1) {
Darren Tuckercdf547a2004-05-24 10:12:19 +1000188 kill(sshpid, SIGTERM);
Darren Tuckerba66df82005-01-24 21:57:40 +1100189 waitpid(sshpid, NULL, 0);
190 }
Darren Tuckercdf547a2004-05-24 10:12:19 +1000191
192 _exit(1);
193}
194
Damien Millerb6c85fc2007-01-05 16:30:41 +1100195/* ARGSUSED */
Darren Tuckercdf547a2004-05-24 10:12:19 +1000196static void
197cmd_interrupt(int signo)
198{
199 const char msg[] = "\rInterrupt \n";
Darren Tuckere2f189a2004-12-06 22:45:53 +1100200 int olderrno = errno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000201
202 write(STDERR_FILENO, msg, sizeof(msg) - 1);
203 interrupted = 1;
Darren Tuckere2f189a2004-12-06 22:45:53 +1100204 errno = olderrno;
Darren Tuckercdf547a2004-05-24 10:12:19 +1000205}
206
207static void
Damien Miller20e1fab2004-02-18 14:30:55 +1100208help(void)
209{
Damien Miller62fd18a2009-01-28 16:14:09 +1100210 printf("Available commands:\n"
211 "bye Quit sftp\n"
212 "cd path Change remote directory to 'path'\n"
213 "chgrp grp path Change group of file 'path' to 'grp'\n"
214 "chmod mode path Change permissions of file 'path' to 'mode'\n"
215 "chown own path Change owner of file 'path' to 'own'\n"
216 "df [-hi] [path] Display statistics for current directory or\n"
217 " filesystem containing 'path'\n"
218 "exit Quit sftp\n"
219 "get [-P] remote-path [local-path] Download file\n"
220 "help Display this help text\n"
221 "lcd path Change local directory to 'path'\n"
222 "lls [ls-options [path]] Display local directory listing\n"
223 "lmkdir path Create local directory\n"
224 "ln oldpath newpath Symlink remote file\n"
225 "lpwd Print local working directory\n"
226 "ls [-1aflnrSt] [path] Display remote directory listing\n"
227 "lumask umask Set local umask to 'umask'\n"
228 "mkdir path Create remote directory\n"
229 "progress Toggle display of progress meter\n"
230 "put [-P] local-path [remote-path] Upload file\n"
231 "pwd Display remote working directory\n"
232 "quit Quit sftp\n"
233 "rename oldpath newpath Rename remote file\n"
234 "rm path Delete remote file\n"
235 "rmdir path Remove remote directory\n"
236 "symlink oldpath newpath Symlink remote file\n"
237 "version Show SFTP version\n"
238 "!command Execute 'command' in local shell\n"
239 "! Escape to local shell\n"
240 "? Synonym for help\n");
Damien Miller20e1fab2004-02-18 14:30:55 +1100241}
242
243static void
244local_do_shell(const char *args)
245{
246 int status;
247 char *shell;
248 pid_t pid;
249
250 if (!*args)
251 args = NULL;
252
253 if ((shell = getenv("SHELL")) == NULL)
254 shell = _PATH_BSHELL;
255
256 if ((pid = fork()) == -1)
257 fatal("Couldn't fork: %s", strerror(errno));
258
259 if (pid == 0) {
260 /* XXX: child has pipe fds to ssh subproc open - issue? */
261 if (args) {
262 debug3("Executing %s -c \"%s\"", shell, args);
263 execl(shell, shell, "-c", args, (char *)NULL);
264 } else {
265 debug3("Executing %s", shell);
266 execl(shell, shell, (char *)NULL);
267 }
268 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
269 strerror(errno));
270 _exit(1);
271 }
272 while (waitpid(pid, &status, 0) == -1)
273 if (errno != EINTR)
274 fatal("Couldn't wait for child: %s", strerror(errno));
275 if (!WIFEXITED(status))
Damien Miller55b04f12006-03-26 14:23:17 +1100276 error("Shell exited abnormally");
Damien Miller20e1fab2004-02-18 14:30:55 +1100277 else if (WEXITSTATUS(status))
278 error("Shell exited with status %d", WEXITSTATUS(status));
279}
280
281static void
282local_do_ls(const char *args)
283{
284 if (!args || !*args)
285 local_do_shell(_PATH_LS);
286 else {
287 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
288 char *buf = xmalloc(len);
289
290 /* XXX: quoting - rip quoting code from ftp? */
291 snprintf(buf, len, _PATH_LS " %s", args);
292 local_do_shell(buf);
293 xfree(buf);
294 }
295}
296
297/* Strip one path (usually the pwd) from the start of another */
298static char *
299path_strip(char *path, char *strip)
300{
301 size_t len;
302
303 if (strip == NULL)
304 return (xstrdup(path));
305
306 len = strlen(strip);
Darren Tuckere2f189a2004-12-06 22:45:53 +1100307 if (strncmp(path, strip, len) == 0) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100308 if (strip[len - 1] != '/' && path[len] == '/')
309 len++;
310 return (xstrdup(path + len));
311 }
312
313 return (xstrdup(path));
314}
315
316static char *
317path_append(char *p1, char *p2)
318{
319 char *ret;
Damien Miller3ca8b772007-01-05 16:24:47 +1100320 size_t len = strlen(p1) + strlen(p2) + 2;
Damien Miller20e1fab2004-02-18 14:30:55 +1100321
322 ret = xmalloc(len);
323 strlcpy(ret, p1, len);
Damien Miller3ca8b772007-01-05 16:24:47 +1100324 if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/')
Damien Miller20e1fab2004-02-18 14:30:55 +1100325 strlcat(ret, "/", len);
326 strlcat(ret, p2, len);
327
328 return(ret);
329}
330
331static char *
332make_absolute(char *p, char *pwd)
333{
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000334 char *abs_str;
Damien Miller20e1fab2004-02-18 14:30:55 +1100335
336 /* Derelativise */
337 if (p && p[0] != '/') {
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000338 abs_str = path_append(pwd, p);
Damien Miller20e1fab2004-02-18 14:30:55 +1100339 xfree(p);
Darren Tucker3f9fdc72004-06-22 12:56:01 +1000340 return(abs_str);
Damien Miller20e1fab2004-02-18 14:30:55 +1100341 } else
342 return(p);
343}
344
345static int
346infer_path(const char *p, char **ifp)
347{
348 char *cp;
349
350 cp = strrchr(p, '/');
351 if (cp == NULL) {
352 *ifp = xstrdup(p);
353 return(0);
354 }
355
356 if (!cp[1]) {
357 error("Invalid path");
358 return(-1);
359 }
360
361 *ifp = xstrdup(cp + 1);
362 return(0);
363}
364
365static int
Damien Miller1cbc2922007-10-26 14:27:45 +1000366parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100367{
Damien Millerf184bcf2008-06-29 22:45:13 +1000368 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000369 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100370
Damien Miller1cbc2922007-10-26 14:27:45 +1000371 optind = optreset = 1;
372 opterr = 0;
373
374 *pflag = 0;
375 while ((ch = getopt(argc, argv, "Pp")) != -1) {
376 switch (ch) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100377 case 'p':
378 case 'P':
379 *pflag = 1;
380 break;
381 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000382 error("%s: Invalid flag -%c", cmd, optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000383 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100384 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100385 }
386
Damien Miller1cbc2922007-10-26 14:27:45 +1000387 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100388}
389
390static int
Damien Miller1cbc2922007-10-26 14:27:45 +1000391parse_ls_flags(char **argv, int argc, int *lflag)
Damien Miller20e1fab2004-02-18 14:30:55 +1100392{
Damien Millerf184bcf2008-06-29 22:45:13 +1000393 extern int opterr, optind, optopt, optreset;
Damien Miller1cbc2922007-10-26 14:27:45 +1000394 int ch;
Damien Miller20e1fab2004-02-18 14:30:55 +1100395
Damien Miller1cbc2922007-10-26 14:27:45 +1000396 optind = optreset = 1;
397 opterr = 0;
398
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000399 *lflag = LS_NAME_SORT;
Damien Miller1cbc2922007-10-26 14:27:45 +1000400 while ((ch = getopt(argc, argv, "1Saflnrt")) != -1) {
401 switch (ch) {
402 case '1':
403 *lflag &= ~VIEW_FLAGS;
404 *lflag |= LS_SHORT_VIEW;
405 break;
406 case 'S':
407 *lflag &= ~SORT_FLAGS;
408 *lflag |= LS_SIZE_SORT;
409 break;
410 case 'a':
411 *lflag |= LS_SHOW_ALL;
412 break;
413 case 'f':
414 *lflag &= ~SORT_FLAGS;
415 break;
416 case 'l':
417 *lflag &= ~VIEW_FLAGS;
418 *lflag |= LS_LONG_VIEW;
419 break;
420 case 'n':
421 *lflag &= ~VIEW_FLAGS;
422 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
423 break;
424 case 'r':
425 *lflag |= LS_REVERSE_SORT;
426 break;
427 case 't':
428 *lflag &= ~SORT_FLAGS;
429 *lflag |= LS_TIME_SORT;
430 break;
431 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000432 error("ls: Invalid flag -%c", optopt);
Damien Miller1cbc2922007-10-26 14:27:45 +1000433 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100434 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100435 }
436
Damien Miller1cbc2922007-10-26 14:27:45 +1000437 return optind;
Damien Miller20e1fab2004-02-18 14:30:55 +1100438}
439
440static int
Damien Millerd671e5a2008-05-19 14:53:33 +1000441parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
442{
Damien Millerf184bcf2008-06-29 22:45:13 +1000443 extern int opterr, optind, optopt, optreset;
Damien Millerd671e5a2008-05-19 14:53:33 +1000444 int ch;
445
446 optind = optreset = 1;
447 opterr = 0;
448
449 *hflag = *iflag = 0;
450 while ((ch = getopt(argc, argv, "hi")) != -1) {
451 switch (ch) {
452 case 'h':
453 *hflag = 1;
454 break;
455 case 'i':
456 *iflag = 1;
457 break;
458 default:
Damien Millerf184bcf2008-06-29 22:45:13 +1000459 error("%s: Invalid flag -%c", cmd, optopt);
Damien Millerd671e5a2008-05-19 14:53:33 +1000460 return -1;
461 }
462 }
463
464 return optind;
465}
466
467static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100468is_dir(char *path)
469{
470 struct stat sb;
471
472 /* XXX: report errors? */
473 if (stat(path, &sb) == -1)
474 return(0);
475
Darren Tucker1e80e402006-09-21 12:59:33 +1000476 return(S_ISDIR(sb.st_mode));
Damien Miller20e1fab2004-02-18 14:30:55 +1100477}
478
479static int
Damien Miller20e1fab2004-02-18 14:30:55 +1100480remote_is_dir(struct sftp_conn *conn, char *path)
481{
482 Attrib *a;
483
484 /* XXX: report errors? */
485 if ((a = do_stat(conn, path, 1)) == NULL)
486 return(0);
487 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
488 return(0);
Darren Tucker1e80e402006-09-21 12:59:33 +1000489 return(S_ISDIR(a->perm));
Damien Miller20e1fab2004-02-18 14:30:55 +1100490}
491
492static int
493process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
494{
495 char *abs_src = NULL;
496 char *abs_dst = NULL;
497 char *tmp;
498 glob_t g;
499 int err = 0;
500 int i;
501
502 abs_src = xstrdup(src);
503 abs_src = make_absolute(abs_src, pwd);
504
505 memset(&g, 0, sizeof(g));
506 debug3("Looking up %s", abs_src);
507 if (remote_glob(conn, abs_src, 0, NULL, &g)) {
508 error("File \"%s\" not found.", abs_src);
509 err = -1;
510 goto out;
511 }
512
513 /* If multiple matches, dst must be a directory or unspecified */
514 if (g.gl_matchc > 1 && dst && !is_dir(dst)) {
515 error("Multiple files match, but \"%s\" is not a directory",
516 dst);
517 err = -1;
518 goto out;
519 }
520
Darren Tuckercdf547a2004-05-24 10:12:19 +1000521 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100522 if (infer_path(g.gl_pathv[i], &tmp)) {
523 err = -1;
524 goto out;
525 }
526
527 if (g.gl_matchc == 1 && dst) {
528 /* If directory specified, append filename */
Damien Miller40b59852006-06-13 13:00:25 +1000529 xfree(tmp);
Damien Miller20e1fab2004-02-18 14:30:55 +1100530 if (is_dir(dst)) {
531 if (infer_path(g.gl_pathv[0], &tmp)) {
532 err = 1;
533 goto out;
534 }
535 abs_dst = path_append(dst, tmp);
536 xfree(tmp);
537 } else
538 abs_dst = xstrdup(dst);
539 } else if (dst) {
540 abs_dst = path_append(dst, tmp);
541 xfree(tmp);
542 } else
543 abs_dst = tmp;
544
545 printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
546 if (do_download(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
547 err = -1;
548 xfree(abs_dst);
549 abs_dst = NULL;
550 }
551
552out:
553 xfree(abs_src);
Damien Miller20e1fab2004-02-18 14:30:55 +1100554 globfree(&g);
555 return(err);
556}
557
558static int
559process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
560{
561 char *tmp_dst = NULL;
562 char *abs_dst = NULL;
563 char *tmp;
564 glob_t g;
565 int err = 0;
566 int i;
Damien Milleraec5cf82008-02-10 22:26:24 +1100567 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100568
569 if (dst) {
570 tmp_dst = xstrdup(dst);
571 tmp_dst = make_absolute(tmp_dst, pwd);
572 }
573
574 memset(&g, 0, sizeof(g));
575 debug3("Looking up %s", src);
Damien Milleraec5cf82008-02-10 22:26:24 +1100576 if (glob(src, GLOB_NOCHECK, NULL, &g)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100577 error("File \"%s\" not found.", src);
578 err = -1;
579 goto out;
580 }
581
582 /* If multiple matches, dst may be directory or unspecified */
583 if (g.gl_matchc > 1 && tmp_dst && !remote_is_dir(conn, tmp_dst)) {
584 error("Multiple files match, but \"%s\" is not a directory",
585 tmp_dst);
586 err = -1;
587 goto out;
588 }
589
Darren Tuckercdf547a2004-05-24 10:12:19 +1000590 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Milleraec5cf82008-02-10 22:26:24 +1100591 if (stat(g.gl_pathv[i], &sb) == -1) {
592 err = -1;
593 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
594 continue;
595 }
596
597 if (!S_ISREG(sb.st_mode)) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100598 error("skipping non-regular file %s",
599 g.gl_pathv[i]);
600 continue;
601 }
602 if (infer_path(g.gl_pathv[i], &tmp)) {
603 err = -1;
604 goto out;
605 }
606
607 if (g.gl_matchc == 1 && tmp_dst) {
608 /* If directory specified, append filename */
609 if (remote_is_dir(conn, tmp_dst)) {
610 if (infer_path(g.gl_pathv[0], &tmp)) {
611 err = 1;
612 goto out;
613 }
614 abs_dst = path_append(tmp_dst, tmp);
615 xfree(tmp);
616 } else
617 abs_dst = xstrdup(tmp_dst);
618
619 } else if (tmp_dst) {
620 abs_dst = path_append(tmp_dst, tmp);
621 xfree(tmp);
622 } else
623 abs_dst = make_absolute(tmp, pwd);
624
625 printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
626 if (do_upload(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
627 err = -1;
628 }
629
630out:
631 if (abs_dst)
632 xfree(abs_dst);
633 if (tmp_dst)
634 xfree(tmp_dst);
635 globfree(&g);
636 return(err);
637}
638
639static int
640sdirent_comp(const void *aa, const void *bb)
641{
642 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
643 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000644 int rmul = sort_flag & LS_REVERSE_SORT ? -1 : 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100645
Darren Tuckerb9123452004-06-22 13:06:45 +1000646#define NCMP(a,b) (a == b ? 0 : (a < b ? 1 : -1))
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000647 if (sort_flag & LS_NAME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000648 return (rmul * strcmp(a->filename, b->filename));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000649 else if (sort_flag & LS_TIME_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000650 return (rmul * NCMP(a->a.mtime, b->a.mtime));
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000651 else if (sort_flag & LS_SIZE_SORT)
Darren Tuckerb9123452004-06-22 13:06:45 +1000652 return (rmul * NCMP(a->a.size, b->a.size));
653
654 fatal("Unknown ls sort type");
Damien Miller20e1fab2004-02-18 14:30:55 +1100655}
656
657/* sftp ls.1 replacement for directories */
658static int
659do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
660{
Damien Millereccb9de2005-06-17 12:59:34 +1000661 int n;
662 u_int c = 1, colspace = 0, columns = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +1100663 SFTP_DIRENT **d;
664
665 if ((n = do_readdir(conn, path, &d)) != 0)
666 return (n);
667
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000668 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000669 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100670 struct winsize ws;
671 char *tmp;
672
673 /* Count entries for sort and find longest filename */
Darren Tucker9a526452004-06-22 13:09:55 +1000674 for (n = 0; d[n] != NULL; n++) {
675 if (d[n]->filename[0] != '.' || (lflag & LS_SHOW_ALL))
676 m = MAX(m, strlen(d[n]->filename));
677 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100678
679 /* Add any subpath that also needs to be counted */
680 tmp = path_strip(path, strip_path);
681 m += strlen(tmp);
682 xfree(tmp);
683
684 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
685 width = ws.ws_col;
686
687 columns = width / (m + 2);
688 columns = MAX(columns, 1);
689 colspace = width / columns;
690 colspace = MIN(colspace, width);
691 }
692
Darren Tuckerb9123452004-06-22 13:06:45 +1000693 if (lflag & SORT_FLAGS) {
Damien Miller653b93b2005-11-05 15:15:23 +1100694 for (n = 0; d[n] != NULL; n++)
695 ; /* count entries */
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000696 sort_flag = lflag & (SORT_FLAGS|LS_REVERSE_SORT);
Darren Tuckerb9123452004-06-22 13:06:45 +1000697 qsort(d, n, sizeof(*d), sdirent_comp);
698 }
Damien Miller20e1fab2004-02-18 14:30:55 +1100699
Darren Tuckercdf547a2004-05-24 10:12:19 +1000700 for (n = 0; d[n] != NULL && !interrupted; n++) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100701 char *tmp, *fname;
702
Darren Tucker9a526452004-06-22 13:09:55 +1000703 if (d[n]->filename[0] == '.' && !(lflag & LS_SHOW_ALL))
704 continue;
705
Damien Miller20e1fab2004-02-18 14:30:55 +1100706 tmp = path_append(path, d[n]->filename);
707 fname = path_strip(tmp, strip_path);
708 xfree(tmp);
709
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000710 if (lflag & LS_LONG_VIEW) {
711 if (lflag & LS_NUMERIC_VIEW) {
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000712 char *lname;
713 struct stat sb;
Damien Miller20e1fab2004-02-18 14:30:55 +1100714
Darren Tuckerb215c5d2004-06-22 12:30:53 +1000715 memset(&sb, 0, sizeof(sb));
716 attrib_to_stat(&d[n]->a, &sb);
717 lname = ls_file(fname, &sb, 1);
718 printf("%s\n", lname);
719 xfree(lname);
720 } else
721 printf("%s\n", d[n]->longname);
Damien Miller20e1fab2004-02-18 14:30:55 +1100722 } else {
723 printf("%-*s", colspace, fname);
724 if (c >= columns) {
725 printf("\n");
726 c = 1;
727 } else
728 c++;
729 }
730
731 xfree(fname);
732 }
733
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000734 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100735 printf("\n");
736
737 free_sftp_dirents(d);
738 return (0);
739}
740
741/* sftp ls.1 replacement which handles path globs */
742static int
743do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
744 int lflag)
745{
746 glob_t g;
Damien Millereccb9de2005-06-17 12:59:34 +1000747 u_int i, c = 1, colspace = 0, columns = 1;
Darren Tucker596dcfa2004-12-11 13:37:22 +1100748 Attrib *a = NULL;
Damien Miller20e1fab2004-02-18 14:30:55 +1100749
750 memset(&g, 0, sizeof(g));
751
752 if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE,
Darren Tucker596dcfa2004-12-11 13:37:22 +1100753 NULL, &g) || (g.gl_pathc && !g.gl_matchc)) {
754 if (g.gl_pathc)
755 globfree(&g);
Damien Miller20e1fab2004-02-18 14:30:55 +1100756 error("Can't ls: \"%s\" not found", path);
757 return (-1);
758 }
759
Darren Tuckercdf547a2004-05-24 10:12:19 +1000760 if (interrupted)
761 goto out;
762
Damien Miller20e1fab2004-02-18 14:30:55 +1100763 /*
Darren Tucker596dcfa2004-12-11 13:37:22 +1100764 * If the glob returns a single match and it is a directory,
765 * then just list its contents.
Damien Miller20e1fab2004-02-18 14:30:55 +1100766 */
Darren Tucker596dcfa2004-12-11 13:37:22 +1100767 if (g.gl_matchc == 1) {
768 if ((a = do_lstat(conn, g.gl_pathv[0], 1)) == NULL) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100769 globfree(&g);
770 return (-1);
771 }
772 if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
773 S_ISDIR(a->perm)) {
Darren Tucker596dcfa2004-12-11 13:37:22 +1100774 int err;
775
776 err = do_ls_dir(conn, g.gl_pathv[0], strip_path, lflag);
Damien Miller20e1fab2004-02-18 14:30:55 +1100777 globfree(&g);
Darren Tucker596dcfa2004-12-11 13:37:22 +1100778 return (err);
Damien Miller20e1fab2004-02-18 14:30:55 +1100779 }
780 }
781
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000782 if (!(lflag & LS_SHORT_VIEW)) {
Damien Millereccb9de2005-06-17 12:59:34 +1000783 u_int m = 0, width = 80;
Damien Miller20e1fab2004-02-18 14:30:55 +1100784 struct winsize ws;
785
786 /* Count entries for sort and find longest filename */
787 for (i = 0; g.gl_pathv[i]; i++)
788 m = MAX(m, strlen(g.gl_pathv[i]));
789
790 if (ioctl(fileno(stdin), TIOCGWINSZ, &ws) != -1)
791 width = ws.ws_col;
792
793 columns = width / (m + 2);
794 columns = MAX(columns, 1);
795 colspace = width / columns;
796 }
797
Darren Tucker596dcfa2004-12-11 13:37:22 +1100798 for (i = 0; g.gl_pathv[i] && !interrupted; i++, a = NULL) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100799 char *fname;
800
801 fname = path_strip(g.gl_pathv[i], strip_path);
802
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000803 if (lflag & LS_LONG_VIEW) {
Damien Miller20e1fab2004-02-18 14:30:55 +1100804 char *lname;
805 struct stat sb;
806
807 /*
808 * XXX: this is slow - 1 roundtrip per path
809 * A solution to this is to fork glob() and
810 * build a sftp specific version which keeps the
811 * attribs (which currently get thrown away)
812 * that the server returns as well as the filenames.
813 */
814 memset(&sb, 0, sizeof(sb));
Darren Tucker596dcfa2004-12-11 13:37:22 +1100815 if (a == NULL)
816 a = do_lstat(conn, g.gl_pathv[i], 1);
Damien Miller20e1fab2004-02-18 14:30:55 +1100817 if (a != NULL)
818 attrib_to_stat(a, &sb);
819 lname = ls_file(fname, &sb, 1);
820 printf("%s\n", lname);
821 xfree(lname);
822 } else {
823 printf("%-*s", colspace, fname);
824 if (c >= columns) {
825 printf("\n");
826 c = 1;
827 } else
828 c++;
829 }
830 xfree(fname);
831 }
832
Darren Tuckera4e9ffa2004-06-22 13:07:58 +1000833 if (!(lflag & LS_LONG_VIEW) && (c != 1))
Damien Miller20e1fab2004-02-18 14:30:55 +1100834 printf("\n");
835
Darren Tuckercdf547a2004-05-24 10:12:19 +1000836 out:
Damien Miller20e1fab2004-02-18 14:30:55 +1100837 if (g.gl_pathc)
838 globfree(&g);
839
840 return (0);
841}
842
Damien Millerd671e5a2008-05-19 14:53:33 +1000843static int
844do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
845{
Darren Tucker7b598892008-06-09 22:49:36 +1000846 struct sftp_statvfs st;
Damien Millerd671e5a2008-05-19 14:53:33 +1000847 char s_used[FMT_SCALED_STRSIZE];
848 char s_avail[FMT_SCALED_STRSIZE];
849 char s_root[FMT_SCALED_STRSIZE];
850 char s_total[FMT_SCALED_STRSIZE];
851
852 if (do_statvfs(conn, path, &st, 1) == -1)
853 return -1;
854 if (iflag) {
855 printf(" Inodes Used Avail "
856 "(root) %%Capacity\n");
857 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
858 (unsigned long long)st.f_files,
859 (unsigned long long)(st.f_files - st.f_ffree),
860 (unsigned long long)st.f_favail,
861 (unsigned long long)st.f_ffree,
862 (unsigned long long)(100 * (st.f_files - st.f_ffree) /
863 st.f_files));
864 } else if (hflag) {
865 strlcpy(s_used, "error", sizeof(s_used));
866 strlcpy(s_avail, "error", sizeof(s_avail));
867 strlcpy(s_root, "error", sizeof(s_root));
868 strlcpy(s_total, "error", sizeof(s_total));
869 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
870 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
871 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
872 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
873 printf(" Size Used Avail (root) %%Capacity\n");
874 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
875 s_total, s_used, s_avail, s_root,
876 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
877 st.f_blocks));
878 } else {
879 printf(" Size Used Avail "
880 "(root) %%Capacity\n");
881 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
882 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
883 (unsigned long long)(st.f_frsize *
884 (st.f_blocks - st.f_bfree) / 1024),
885 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
886 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
887 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
888 st.f_blocks));
889 }
890 return 0;
891}
892
Damien Miller1cbc2922007-10-26 14:27:45 +1000893/*
894 * Undo escaping of glob sequences in place. Used to undo extra escaping
895 * applied in makeargv() when the string is destined for a function that
896 * does not glob it.
897 */
898static void
899undo_glob_escape(char *s)
900{
901 size_t i, j;
902
903 for (i = j = 0;;) {
904 if (s[i] == '\0') {
905 s[j] = '\0';
906 return;
907 }
908 if (s[i] != '\\') {
909 s[j++] = s[i++];
910 continue;
911 }
912 /* s[i] == '\\' */
913 ++i;
914 switch (s[i]) {
915 case '?':
916 case '[':
917 case '*':
918 case '\\':
919 s[j++] = s[i++];
920 break;
921 case '\0':
922 s[j++] = '\\';
923 s[j] = '\0';
924 return;
925 default:
926 s[j++] = '\\';
927 s[j++] = s[i++];
928 break;
929 }
930 }
931}
932
933/*
934 * Split a string into an argument vector using sh(1)-style quoting,
935 * comment and escaping rules, but with some tweaks to handle glob(3)
936 * wildcards.
937 * Returns NULL on error or a NULL-terminated array of arguments.
938 */
939#define MAXARGS 128
940#define MAXARGLEN 8192
941static char **
942makeargv(const char *arg, int *argcp)
943{
944 int argc, quot;
945 size_t i, j;
946 static char argvs[MAXARGLEN];
947 static char *argv[MAXARGS + 1];
948 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
949
950 *argcp = argc = 0;
951 if (strlen(arg) > sizeof(argvs) - 1) {
952 args_too_longs:
953 error("string too long");
954 return NULL;
955 }
956 state = MA_START;
957 i = j = 0;
958 for (;;) {
959 if (isspace(arg[i])) {
960 if (state == MA_UNQUOTED) {
961 /* Terminate current argument */
962 argvs[j++] = '\0';
963 argc++;
964 state = MA_START;
965 } else if (state != MA_START)
966 argvs[j++] = arg[i];
967 } else if (arg[i] == '"' || arg[i] == '\'') {
968 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
969 if (state == MA_START) {
970 argv[argc] = argvs + j;
971 state = q;
972 } else if (state == MA_UNQUOTED)
973 state = q;
974 else if (state == q)
975 state = MA_UNQUOTED;
976 else
977 argvs[j++] = arg[i];
978 } else if (arg[i] == '\\') {
979 if (state == MA_SQUOTE || state == MA_DQUOTE) {
980 quot = state == MA_SQUOTE ? '\'' : '"';
981 /* Unescape quote we are in */
982 /* XXX support \n and friends? */
983 if (arg[i + 1] == quot) {
984 i++;
985 argvs[j++] = arg[i];
986 } else if (arg[i + 1] == '?' ||
987 arg[i + 1] == '[' || arg[i + 1] == '*') {
988 /*
989 * Special case for sftp: append
990 * double-escaped glob sequence -
991 * glob will undo one level of
992 * escaping. NB. string can grow here.
993 */
994 if (j >= sizeof(argvs) - 5)
995 goto args_too_longs;
996 argvs[j++] = '\\';
997 argvs[j++] = arg[i++];
998 argvs[j++] = '\\';
999 argvs[j++] = arg[i];
1000 } else {
1001 argvs[j++] = arg[i++];
1002 argvs[j++] = arg[i];
1003 }
1004 } else {
1005 if (state == MA_START) {
1006 argv[argc] = argvs + j;
1007 state = MA_UNQUOTED;
1008 }
1009 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1010 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1011 /*
1012 * Special case for sftp: append
1013 * escaped glob sequence -
1014 * glob will undo one level of
1015 * escaping.
1016 */
1017 argvs[j++] = arg[i++];
1018 argvs[j++] = arg[i];
1019 } else {
1020 /* Unescape everything */
1021 /* XXX support \n and friends? */
1022 i++;
1023 argvs[j++] = arg[i];
1024 }
1025 }
1026 } else if (arg[i] == '#') {
1027 if (state == MA_SQUOTE || state == MA_DQUOTE)
1028 argvs[j++] = arg[i];
1029 else
1030 goto string_done;
1031 } else if (arg[i] == '\0') {
1032 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1033 error("Unterminated quoted argument");
1034 return NULL;
1035 }
1036 string_done:
1037 if (state == MA_UNQUOTED) {
1038 argvs[j++] = '\0';
1039 argc++;
1040 }
1041 break;
1042 } else {
1043 if (state == MA_START) {
1044 argv[argc] = argvs + j;
1045 state = MA_UNQUOTED;
1046 }
1047 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1048 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1049 /*
1050 * Special case for sftp: escape quoted
1051 * glob(3) wildcards. NB. string can grow
1052 * here.
1053 */
1054 if (j >= sizeof(argvs) - 3)
1055 goto args_too_longs;
1056 argvs[j++] = '\\';
1057 argvs[j++] = arg[i];
1058 } else
1059 argvs[j++] = arg[i];
1060 }
1061 i++;
1062 }
1063 *argcp = argc;
1064 return argv;
1065}
1066
Damien Miller20e1fab2004-02-18 14:30:55 +11001067static int
Damien Millerd671e5a2008-05-19 14:53:33 +10001068parse_args(const char **cpp, int *pflag, int *lflag, int *iflag, int *hflag,
Damien Miller20e1fab2004-02-18 14:30:55 +11001069 unsigned long *n_arg, char **path1, char **path2)
1070{
1071 const char *cmd, *cp = *cpp;
Damien Miller1cbc2922007-10-26 14:27:45 +10001072 char *cp2, **argv;
Damien Miller20e1fab2004-02-18 14:30:55 +11001073 int base = 0;
1074 long l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001075 int i, cmdnum, optidx, argc;
Damien Miller20e1fab2004-02-18 14:30:55 +11001076
1077 /* Skip leading whitespace */
1078 cp = cp + strspn(cp, WHITESPACE);
1079
1080 /* Ignore blank lines and lines which begin with comment '#' char */
1081 if (*cp == '\0' || *cp == '#')
1082 return (0);
1083
1084 /* Check for leading '-' (disable error processing) */
1085 *iflag = 0;
1086 if (*cp == '-') {
1087 *iflag = 1;
1088 cp++;
1089 }
1090
Damien Miller1cbc2922007-10-26 14:27:45 +10001091 if ((argv = makeargv(cp, &argc)) == NULL)
1092 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001093
Damien Miller1cbc2922007-10-26 14:27:45 +10001094 /* Figure out which command we have */
1095 for (i = 0; cmds[i].c != NULL; i++) {
1096 if (strcasecmp(cmds[i].c, argv[0]) == 0)
Damien Miller20e1fab2004-02-18 14:30:55 +11001097 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001098 }
1099 cmdnum = cmds[i].n;
1100 cmd = cmds[i].c;
1101
1102 /* Special case */
1103 if (*cp == '!') {
1104 cp++;
1105 cmdnum = I_SHELL;
1106 } else if (cmdnum == -1) {
1107 error("Invalid command.");
Damien Miller1cbc2922007-10-26 14:27:45 +10001108 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001109 }
1110
1111 /* Get arguments and parse flags */
Damien Millerd671e5a2008-05-19 14:53:33 +10001112 *lflag = *pflag = *hflag = *n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001113 *path1 = *path2 = NULL;
Damien Miller1cbc2922007-10-26 14:27:45 +10001114 optidx = 1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001115 switch (cmdnum) {
1116 case I_GET:
1117 case I_PUT:
Damien Miller1cbc2922007-10-26 14:27:45 +10001118 if ((optidx = parse_getput_flags(cmd, argv, argc, pflag)) == -1)
1119 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001120 /* Get first pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001121 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001122 error("You must specify at least one path after a "
1123 "%s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001124 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001125 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001126 *path1 = xstrdup(argv[optidx]);
1127 /* Get second pathname (optional) */
1128 if (argc - optidx > 1) {
1129 *path2 = xstrdup(argv[optidx + 1]);
1130 /* Destination is not globbed */
1131 undo_glob_escape(*path2);
1132 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001133 break;
1134 case I_RENAME:
1135 case I_SYMLINK:
Damien Miller1cbc2922007-10-26 14:27:45 +10001136 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001137 error("You must specify two paths after a %s "
1138 "command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001139 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001140 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001141 *path1 = xstrdup(argv[optidx]);
1142 *path2 = xstrdup(argv[optidx + 1]);
1143 /* Paths are not globbed */
1144 undo_glob_escape(*path1);
1145 undo_glob_escape(*path2);
Damien Miller20e1fab2004-02-18 14:30:55 +11001146 break;
1147 case I_RM:
1148 case I_MKDIR:
1149 case I_RMDIR:
1150 case I_CHDIR:
1151 case I_LCHDIR:
1152 case I_LMKDIR:
1153 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001154 if (argc - optidx < 1) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001155 error("You must specify a path after a %s command.",
1156 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001157 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001158 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001159 *path1 = xstrdup(argv[optidx]);
1160 /* Only "rm" globs */
1161 if (cmdnum != I_RM)
1162 undo_glob_escape(*path1);
Damien Miller20e1fab2004-02-18 14:30:55 +11001163 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001164 case I_DF:
1165 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1166 iflag)) == -1)
1167 return -1;
1168 /* Default to current directory if no path specified */
1169 if (argc - optidx < 1)
1170 *path1 = NULL;
1171 else {
1172 *path1 = xstrdup(argv[optidx]);
1173 undo_glob_escape(*path1);
1174 }
1175 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001176 case I_LS:
Damien Miller1cbc2922007-10-26 14:27:45 +10001177 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
Damien Miller20e1fab2004-02-18 14:30:55 +11001178 return(-1);
1179 /* Path is optional */
Damien Miller1cbc2922007-10-26 14:27:45 +10001180 if (argc - optidx > 0)
1181 *path1 = xstrdup(argv[optidx]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001182 break;
1183 case I_LLS:
Darren Tucker88b976f2007-12-29 02:40:43 +11001184 /* Skip ls command and following whitespace */
1185 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
Damien Miller20e1fab2004-02-18 14:30:55 +11001186 case I_SHELL:
1187 /* Uses the rest of the line */
1188 break;
1189 case I_LUMASK:
Damien Miller20e1fab2004-02-18 14:30:55 +11001190 case I_CHMOD:
1191 base = 8;
1192 case I_CHOWN:
1193 case I_CHGRP:
1194 /* Get numeric arg (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001195 if (argc - optidx < 1)
1196 goto need_num_arg;
Damien Millere7658a52006-10-24 03:00:12 +10001197 errno = 0;
Damien Miller1cbc2922007-10-26 14:27:45 +10001198 l = strtol(argv[optidx], &cp2, base);
1199 if (cp2 == argv[optidx] || *cp2 != '\0' ||
1200 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1201 l < 0) {
1202 need_num_arg:
Damien Miller20e1fab2004-02-18 14:30:55 +11001203 error("You must supply a numeric argument "
1204 "to the %s command.", cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001205 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001206 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001207 *n_arg = l;
Damien Miller1cbc2922007-10-26 14:27:45 +10001208 if (cmdnum == I_LUMASK)
Damien Miller20e1fab2004-02-18 14:30:55 +11001209 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001210 /* Get pathname (mandatory) */
Damien Miller1cbc2922007-10-26 14:27:45 +10001211 if (argc - optidx < 2) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001212 error("You must specify a path after a %s command.",
1213 cmd);
Damien Miller1cbc2922007-10-26 14:27:45 +10001214 return -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001215 }
Damien Miller1cbc2922007-10-26 14:27:45 +10001216 *path1 = xstrdup(argv[optidx + 1]);
Damien Miller20e1fab2004-02-18 14:30:55 +11001217 break;
1218 case I_QUIT:
1219 case I_PWD:
1220 case I_LPWD:
1221 case I_HELP:
1222 case I_VERSION:
1223 case I_PROGRESS:
1224 break;
1225 default:
1226 fatal("Command not implemented");
1227 }
1228
1229 *cpp = cp;
1230 return(cmdnum);
1231}
1232
1233static int
1234parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1235 int err_abort)
1236{
1237 char *path1, *path2, *tmp;
Damien Millerfdd66fc2009-02-14 16:26:19 +11001238 int pflag = 0, lflag = 0, iflag = 0, hflag = 0, cmdnum, i;
1239 unsigned long n_arg = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001240 Attrib a, *aa;
1241 char path_buf[MAXPATHLEN];
1242 int err = 0;
1243 glob_t g;
1244
1245 path1 = path2 = NULL;
Damien Millerd671e5a2008-05-19 14:53:33 +10001246 cmdnum = parse_args(&cmd, &pflag, &lflag, &iflag, &hflag, &n_arg,
Damien Miller20e1fab2004-02-18 14:30:55 +11001247 &path1, &path2);
1248
1249 if (iflag != 0)
1250 err_abort = 0;
1251
1252 memset(&g, 0, sizeof(g));
1253
1254 /* Perform command */
1255 switch (cmdnum) {
1256 case 0:
1257 /* Blank line */
1258 break;
1259 case -1:
1260 /* Unrecognized command */
1261 err = -1;
1262 break;
1263 case I_GET:
1264 err = process_get(conn, path1, path2, *pwd, pflag);
1265 break;
1266 case I_PUT:
1267 err = process_put(conn, path1, path2, *pwd, pflag);
1268 break;
1269 case I_RENAME:
1270 path1 = make_absolute(path1, *pwd);
1271 path2 = make_absolute(path2, *pwd);
1272 err = do_rename(conn, path1, path2);
1273 break;
1274 case I_SYMLINK:
1275 path2 = make_absolute(path2, *pwd);
1276 err = do_symlink(conn, path1, path2);
1277 break;
1278 case I_RM:
1279 path1 = make_absolute(path1, *pwd);
1280 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001281 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001282 printf("Removing %s\n", g.gl_pathv[i]);
1283 err = do_rm(conn, g.gl_pathv[i]);
1284 if (err != 0 && err_abort)
1285 break;
1286 }
1287 break;
1288 case I_MKDIR:
1289 path1 = make_absolute(path1, *pwd);
1290 attrib_clear(&a);
1291 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1292 a.perm = 0777;
1293 err = do_mkdir(conn, path1, &a);
1294 break;
1295 case I_RMDIR:
1296 path1 = make_absolute(path1, *pwd);
1297 err = do_rmdir(conn, path1);
1298 break;
1299 case I_CHDIR:
1300 path1 = make_absolute(path1, *pwd);
1301 if ((tmp = do_realpath(conn, path1)) == NULL) {
1302 err = 1;
1303 break;
1304 }
1305 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
1306 xfree(tmp);
1307 err = 1;
1308 break;
1309 }
1310 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
1311 error("Can't change directory: Can't check target");
1312 xfree(tmp);
1313 err = 1;
1314 break;
1315 }
1316 if (!S_ISDIR(aa->perm)) {
1317 error("Can't change directory: \"%s\" is not "
1318 "a directory", tmp);
1319 xfree(tmp);
1320 err = 1;
1321 break;
1322 }
1323 xfree(*pwd);
1324 *pwd = tmp;
1325 break;
1326 case I_LS:
1327 if (!path1) {
1328 do_globbed_ls(conn, *pwd, *pwd, lflag);
1329 break;
1330 }
1331
1332 /* Strip pwd off beginning of non-absolute paths */
1333 tmp = NULL;
1334 if (*path1 != '/')
1335 tmp = *pwd;
1336
1337 path1 = make_absolute(path1, *pwd);
1338 err = do_globbed_ls(conn, path1, tmp, lflag);
1339 break;
Damien Millerd671e5a2008-05-19 14:53:33 +10001340 case I_DF:
1341 /* Default to current directory if no path specified */
1342 if (path1 == NULL)
1343 path1 = xstrdup(*pwd);
1344 path1 = make_absolute(path1, *pwd);
1345 err = do_df(conn, path1, hflag, iflag);
1346 break;
Damien Miller20e1fab2004-02-18 14:30:55 +11001347 case I_LCHDIR:
1348 if (chdir(path1) == -1) {
1349 error("Couldn't change local directory to "
1350 "\"%s\": %s", path1, strerror(errno));
1351 err = 1;
1352 }
1353 break;
1354 case I_LMKDIR:
1355 if (mkdir(path1, 0777) == -1) {
1356 error("Couldn't create local directory "
1357 "\"%s\": %s", path1, strerror(errno));
1358 err = 1;
1359 }
1360 break;
1361 case I_LLS:
1362 local_do_ls(cmd);
1363 break;
1364 case I_SHELL:
1365 local_do_shell(cmd);
1366 break;
1367 case I_LUMASK:
1368 umask(n_arg);
1369 printf("Local umask: %03lo\n", n_arg);
1370 break;
1371 case I_CHMOD:
1372 path1 = make_absolute(path1, *pwd);
1373 attrib_clear(&a);
1374 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
1375 a.perm = n_arg;
1376 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001377 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001378 printf("Changing mode on %s\n", g.gl_pathv[i]);
1379 err = do_setstat(conn, g.gl_pathv[i], &a);
1380 if (err != 0 && err_abort)
1381 break;
1382 }
1383 break;
1384 case I_CHOWN:
1385 case I_CHGRP:
1386 path1 = make_absolute(path1, *pwd);
1387 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001388 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
Damien Miller20e1fab2004-02-18 14:30:55 +11001389 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
Damien Miller1be2cc42008-12-09 14:11:49 +11001390 if (err_abort) {
1391 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001392 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001393 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001394 continue;
1395 }
1396 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
1397 error("Can't get current ownership of "
1398 "remote file \"%s\"", g.gl_pathv[i]);
Damien Miller1be2cc42008-12-09 14:11:49 +11001399 if (err_abort) {
1400 err = -1;
Damien Miller20e1fab2004-02-18 14:30:55 +11001401 break;
Damien Miller1be2cc42008-12-09 14:11:49 +11001402 } else
Damien Miller20e1fab2004-02-18 14:30:55 +11001403 continue;
1404 }
1405 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
1406 if (cmdnum == I_CHOWN) {
1407 printf("Changing owner on %s\n", g.gl_pathv[i]);
1408 aa->uid = n_arg;
1409 } else {
1410 printf("Changing group on %s\n", g.gl_pathv[i]);
1411 aa->gid = n_arg;
1412 }
1413 err = do_setstat(conn, g.gl_pathv[i], aa);
1414 if (err != 0 && err_abort)
1415 break;
1416 }
1417 break;
1418 case I_PWD:
1419 printf("Remote working directory: %s\n", *pwd);
1420 break;
1421 case I_LPWD:
1422 if (!getcwd(path_buf, sizeof(path_buf))) {
1423 error("Couldn't get local cwd: %s", strerror(errno));
1424 err = -1;
1425 break;
1426 }
1427 printf("Local working directory: %s\n", path_buf);
1428 break;
1429 case I_QUIT:
1430 /* Processed below */
1431 break;
1432 case I_HELP:
1433 help();
1434 break;
1435 case I_VERSION:
1436 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
1437 break;
1438 case I_PROGRESS:
1439 showprogress = !showprogress;
1440 if (showprogress)
1441 printf("Progress meter enabled\n");
1442 else
1443 printf("Progress meter disabled\n");
1444 break;
1445 default:
1446 fatal("%d is not implemented", cmdnum);
1447 }
1448
1449 if (g.gl_pathc)
1450 globfree(&g);
1451 if (path1)
1452 xfree(path1);
1453 if (path2)
1454 xfree(path2);
1455
1456 /* If an unignored error occurs in batch mode we should abort. */
1457 if (err_abort && err != 0)
1458 return (-1);
1459 else if (cmdnum == I_QUIT)
1460 return (1);
1461
1462 return (0);
1463}
1464
Darren Tucker2d963d82004-11-07 20:04:10 +11001465#ifdef USE_LIBEDIT
1466static char *
1467prompt(EditLine *el)
1468{
1469 return ("sftp> ");
1470}
1471#endif
1472
Damien Miller20e1fab2004-02-18 14:30:55 +11001473int
1474interactive_loop(int fd_in, int fd_out, char *file1, char *file2)
1475{
1476 char *pwd;
1477 char *dir = NULL;
1478 char cmd[2048];
1479 struct sftp_conn *conn;
Damien Miller0e2c1022005-08-12 22:16:22 +10001480 int err, interactive;
Darren Tucker2d963d82004-11-07 20:04:10 +11001481 EditLine *el = NULL;
1482#ifdef USE_LIBEDIT
1483 History *hl = NULL;
1484 HistEvent hev;
1485 extern char *__progname;
1486
1487 if (!batchmode && isatty(STDIN_FILENO)) {
1488 if ((el = el_init(__progname, stdin, stdout, stderr)) == NULL)
1489 fatal("Couldn't initialise editline");
1490 if ((hl = history_init()) == NULL)
1491 fatal("Couldn't initialise editline history");
1492 history(hl, &hev, H_SETSIZE, 100);
1493 el_set(el, EL_HIST, history, hl);
1494
1495 el_set(el, EL_PROMPT, prompt);
1496 el_set(el, EL_EDITOR, "emacs");
1497 el_set(el, EL_TERMINAL, NULL);
1498 el_set(el, EL_SIGNAL, 1);
1499 el_source(el, NULL);
1500 }
1501#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001502
1503 conn = do_init(fd_in, fd_out, copy_buffer_len, num_requests);
1504 if (conn == NULL)
1505 fatal("Couldn't initialise connection to server");
1506
1507 pwd = do_realpath(conn, ".");
1508 if (pwd == NULL)
1509 fatal("Need cwd");
1510
1511 if (file1 != NULL) {
1512 dir = xstrdup(file1);
1513 dir = make_absolute(dir, pwd);
1514
1515 if (remote_is_dir(conn, dir) && file2 == NULL) {
1516 printf("Changing to: %s\n", dir);
1517 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
Darren Tuckercd516ef2004-12-06 22:43:43 +11001518 if (parse_dispatch_command(conn, cmd, &pwd, 1) != 0) {
1519 xfree(dir);
1520 xfree(pwd);
Damien Millere0b90a62006-03-26 13:51:44 +11001521 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001522 return (-1);
Darren Tuckercd516ef2004-12-06 22:43:43 +11001523 }
Damien Miller20e1fab2004-02-18 14:30:55 +11001524 } else {
1525 if (file2 == NULL)
1526 snprintf(cmd, sizeof cmd, "get %s", dir);
1527 else
1528 snprintf(cmd, sizeof cmd, "get %s %s", dir,
1529 file2);
1530
1531 err = parse_dispatch_command(conn, cmd, &pwd, 1);
1532 xfree(dir);
1533 xfree(pwd);
Damien Millere0b90a62006-03-26 13:51:44 +11001534 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001535 return (err);
1536 }
1537 xfree(dir);
1538 }
1539
Darren Tucker93e7e8f2005-08-23 08:06:55 +10001540#if defined(HAVE_SETVBUF) && !defined(BROKEN_SETVBUF)
Damien Miller20e1fab2004-02-18 14:30:55 +11001541 setvbuf(stdout, NULL, _IOLBF, 0);
1542 setvbuf(infile, NULL, _IOLBF, 0);
1543#else
Damien Miller37294fb2005-07-17 17:18:49 +10001544 setlinebuf(stdout);
1545 setlinebuf(infile);
Damien Miller20e1fab2004-02-18 14:30:55 +11001546#endif
1547
Damien Miller0e2c1022005-08-12 22:16:22 +10001548 interactive = !batchmode && isatty(STDIN_FILENO);
Damien Miller20e1fab2004-02-18 14:30:55 +11001549 err = 0;
1550 for (;;) {
1551 char *cp;
1552
Darren Tuckercdf547a2004-05-24 10:12:19 +10001553 signal(SIGINT, SIG_IGN);
1554
Darren Tucker2d963d82004-11-07 20:04:10 +11001555 if (el == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001556 if (interactive)
1557 printf("sftp> ");
Darren Tucker2d963d82004-11-07 20:04:10 +11001558 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
Damien Miller0e2c1022005-08-12 22:16:22 +10001559 if (interactive)
1560 printf("\n");
Darren Tucker2d963d82004-11-07 20:04:10 +11001561 break;
1562 }
Damien Miller0e2c1022005-08-12 22:16:22 +10001563 if (!interactive) { /* Echo command */
1564 printf("sftp> %s", cmd);
1565 if (strlen(cmd) > 0 &&
1566 cmd[strlen(cmd) - 1] != '\n')
1567 printf("\n");
1568 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001569 } else {
1570#ifdef USE_LIBEDIT
1571 const char *line;
1572 int count = 0;
Damien Miller20e1fab2004-02-18 14:30:55 +11001573
Damien Miller0e2c1022005-08-12 22:16:22 +10001574 if ((line = el_gets(el, &count)) == NULL || count <= 0) {
1575 printf("\n");
1576 break;
1577 }
Darren Tucker2d963d82004-11-07 20:04:10 +11001578 history(hl, &hev, H_ENTER, line);
1579 if (strlcpy(cmd, line, sizeof(cmd)) >= sizeof(cmd)) {
1580 fprintf(stderr, "Error: input line too long\n");
1581 continue;
1582 }
1583#endif /* USE_LIBEDIT */
Damien Miller20e1fab2004-02-18 14:30:55 +11001584 }
1585
Damien Miller20e1fab2004-02-18 14:30:55 +11001586 cp = strrchr(cmd, '\n');
1587 if (cp)
1588 *cp = '\0';
1589
Darren Tuckercdf547a2004-05-24 10:12:19 +10001590 /* Handle user interrupts gracefully during commands */
1591 interrupted = 0;
1592 signal(SIGINT, cmd_interrupt);
1593
Damien Miller20e1fab2004-02-18 14:30:55 +11001594 err = parse_dispatch_command(conn, cmd, &pwd, batchmode);
1595 if (err != 0)
1596 break;
1597 }
1598 xfree(pwd);
Damien Millere0b90a62006-03-26 13:51:44 +11001599 xfree(conn);
Damien Miller20e1fab2004-02-18 14:30:55 +11001600
Tim Rice027e8b12005-08-15 14:52:50 -07001601#ifdef USE_LIBEDIT
Damien Miller0e2c1022005-08-12 22:16:22 +10001602 if (el != NULL)
1603 el_end(el);
Tim Rice027e8b12005-08-15 14:52:50 -07001604#endif /* USE_LIBEDIT */
Damien Miller0e2c1022005-08-12 22:16:22 +10001605
Damien Miller20e1fab2004-02-18 14:30:55 +11001606 /* err == 1 signifies normal "quit" exit */
1607 return (err >= 0 ? 0 : -1);
1608}
Damien Miller62d57f62003-01-10 21:43:24 +11001609
Ben Lindstrombba81212001-06-25 05:01:22 +00001610static void
Damien Millercc685c12003-06-04 22:51:38 +10001611connect_to_server(char *path, char **args, int *in, int *out)
Damien Miller33804262001-02-04 23:20:18 +11001612{
1613 int c_in, c_out;
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001614
Damien Miller33804262001-02-04 23:20:18 +11001615#ifdef USE_PIPES
1616 int pin[2], pout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001617
Damien Miller33804262001-02-04 23:20:18 +11001618 if ((pipe(pin) == -1) || (pipe(pout) == -1))
1619 fatal("pipe: %s", strerror(errno));
1620 *in = pin[0];
1621 *out = pout[1];
1622 c_in = pout[0];
1623 c_out = pin[1];
1624#else /* USE_PIPES */
1625 int inout[2];
Ben Lindstromb1f483f2002-06-23 21:27:18 +00001626
Damien Miller33804262001-02-04 23:20:18 +11001627 if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) == -1)
1628 fatal("socketpair: %s", strerror(errno));
1629 *in = *out = inout[0];
1630 c_in = c_out = inout[1];
1631#endif /* USE_PIPES */
1632
Damien Millercc685c12003-06-04 22:51:38 +10001633 if ((sshpid = fork()) == -1)
Damien Miller33804262001-02-04 23:20:18 +11001634 fatal("fork: %s", strerror(errno));
Damien Millercc685c12003-06-04 22:51:38 +10001635 else if (sshpid == 0) {
Damien Miller33804262001-02-04 23:20:18 +11001636 if ((dup2(c_in, STDIN_FILENO) == -1) ||
1637 (dup2(c_out, STDOUT_FILENO) == -1)) {
1638 fprintf(stderr, "dup2: %s\n", strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10001639 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11001640 }
1641 close(*in);
1642 close(*out);
1643 close(c_in);
1644 close(c_out);
Darren Tuckercdf547a2004-05-24 10:12:19 +10001645
1646 /*
1647 * The underlying ssh is in the same process group, so we must
Darren Tuckerfc959702004-07-17 16:12:08 +10001648 * ignore SIGINT if we want to gracefully abort commands,
1649 * otherwise the signal will make it to the ssh process and
Darren Tuckercdf547a2004-05-24 10:12:19 +10001650 * kill it too
1651 */
1652 signal(SIGINT, SIG_IGN);
Darren Tuckerbd12f172004-06-18 16:23:43 +10001653 execvp(path, args);
Damien Millerd14ee1e2002-02-05 12:27:31 +11001654 fprintf(stderr, "exec: %s: %s\n", path, strerror(errno));
Damien Miller350327c2004-06-15 10:24:13 +10001655 _exit(1);
Damien Miller33804262001-02-04 23:20:18 +11001656 }
1657
Damien Millercc685c12003-06-04 22:51:38 +10001658 signal(SIGTERM, killchild);
1659 signal(SIGINT, killchild);
1660 signal(SIGHUP, killchild);
Damien Miller33804262001-02-04 23:20:18 +11001661 close(c_in);
1662 close(c_out);
1663}
1664
Ben Lindstrombba81212001-06-25 05:01:22 +00001665static void
Damien Miller33804262001-02-04 23:20:18 +11001666usage(void)
1667{
Damien Miller025e01c2002-02-08 22:06:29 +11001668 extern char *__progname;
Ben Lindstrom6328ab32002-03-22 02:54:23 +00001669
Ben Lindstrom1e243242001-09-18 05:38:44 +00001670 fprintf(stderr,
Darren Tucker46bbbe32009-10-07 08:21:48 +11001671 "usage: %s [-1246Cqv] [-B buffer_size] [-b batchfile] [-c cipher]\n"
1672 " [-F ssh_config] [-i identify_file] [-o ssh_option]\n"
1673 " [-P sftp_server_path] [-R num_requests] [-S program]\n"
1674 " [-s subsystem | sftp_server] host\n"
Damien Miller7ebfad72008-12-09 14:12:33 +11001675 " %s [user@]host[:file ...]\n"
1676 " %s [user@]host[:dir[/]]\n"
Darren Tucker46bbbe32009-10-07 08:21:48 +11001677 " %s -b batchfile [user@]host\n",
1678 __progname, __progname, __progname, __progname);
Damien Miller33804262001-02-04 23:20:18 +11001679 exit(1);
1680}
1681
Kevin Stevesef4eea92001-02-05 12:42:17 +00001682int
Damien Miller33804262001-02-04 23:20:18 +11001683main(int argc, char **argv)
1684{
Damien Miller956f3fb2003-01-10 21:40:00 +11001685 int in, out, ch, err;
Damien Miller7cf17eb2004-06-15 10:28:56 +10001686 char *host, *userhost, *cp, *file2 = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00001687 int debug_level = 0, sshver = 2;
1688 char *file1 = NULL, *sftp_server = NULL;
Damien Millerd14ee1e2002-02-05 12:27:31 +11001689 char *ssh_program = _PATH_SSH_PROGRAM, *sftp_direct = NULL;
Ben Lindstrom387c4722001-05-08 20:27:25 +00001690 LogLevel ll = SYSLOG_LEVEL_INFO;
1691 arglist args;
Damien Millerd7686fd2001-02-10 00:40:03 +11001692 extern int optind;
1693 extern char *optarg;
Damien Miller33804262001-02-04 23:20:18 +11001694
Darren Tuckerce321d82005-10-03 18:11:24 +10001695 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
1696 sanitise_stdfd();
1697
Damien Miller59d3d5b2003-08-22 09:34:41 +10001698 __progname = ssh_get_progname(argv[0]);
Damien Miller3eec6b72006-01-31 21:49:27 +11001699 memset(&args, '\0', sizeof(args));
Ben Lindstrom387c4722001-05-08 20:27:25 +00001700 args.list = NULL;
Damien Miller2b5a0de2006-03-31 23:10:31 +11001701 addargs(&args, "%s", ssh_program);
Ben Lindstrom387c4722001-05-08 20:27:25 +00001702 addargs(&args, "-oForwardX11 no");
1703 addargs(&args, "-oForwardAgent no");
Damien Millerd27b9472005-12-13 19:29:02 +11001704 addargs(&args, "-oPermitLocalCommand no");
Ben Lindstrom2b7a0e92001-09-20 00:57:55 +00001705 addargs(&args, "-oClearAllForwardings yes");
Damien Millere4f5a822004-01-21 14:11:05 +11001706
Ben Lindstrom387c4722001-05-08 20:27:25 +00001707 ll = SYSLOG_LEVEL_INFO;
Damien Millere4f5a822004-01-21 14:11:05 +11001708 infile = stdin;
Damien Millerd7686fd2001-02-10 00:40:03 +11001709
Darren Tucker282b4022009-10-07 08:23:06 +11001710 while ((ch = getopt(argc, argv,
1711 "1246hqvCc:D:i:o:s:S:b:B:F:P:R:")) != -1) {
Damien Millerd7686fd2001-02-10 00:40:03 +11001712 switch (ch) {
Darren Tucker46bbbe32009-10-07 08:21:48 +11001713 /* Passed through to ssh(1) */
1714 case '4':
1715 case '6':
Damien Millerd7686fd2001-02-10 00:40:03 +11001716 case 'C':
Darren Tucker46bbbe32009-10-07 08:21:48 +11001717 addargs(&args, "-%c", ch);
1718 break;
1719 /* Passed through to ssh(1) with argument */
1720 case 'F':
1721 case 'c':
1722 case 'i':
1723 case 'o':
1724 addargs(&args, "-%c%s", ch, optarg);
1725 break;
1726 case 'q':
1727 showprogress = 0;
1728 addargs(&args, "-%c", ch);
Damien Millerd7686fd2001-02-10 00:40:03 +11001729 break;
Darren Tucker282b4022009-10-07 08:23:06 +11001730 case 'P':
1731 addargs(&args, "-oPort %s", optarg);
1732 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11001733 case 'v':
Ben Lindstrom387c4722001-05-08 20:27:25 +00001734 if (debug_level < 3) {
1735 addargs(&args, "-v");
1736 ll = SYSLOG_LEVEL_DEBUG1 + debug_level;
1737 }
1738 debug_level++;
Damien Millerd7686fd2001-02-10 00:40:03 +11001739 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11001740 case '1':
Ben Lindstrom387c4722001-05-08 20:27:25 +00001741 sshver = 1;
Damien Millerd7686fd2001-02-10 00:40:03 +11001742 if (sftp_server == NULL)
1743 sftp_server = _PATH_SFTP_SERVER;
1744 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11001745 case '2':
1746 sshver = 2;
Damien Millerd7686fd2001-02-10 00:40:03 +11001747 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11001748 case 'B':
1749 copy_buffer_len = strtol(optarg, &cp, 10);
1750 if (copy_buffer_len == 0 || *cp != '\0')
1751 fatal("Invalid buffer size \"%s\"", optarg);
Damien Millerd7686fd2001-02-10 00:40:03 +11001752 break;
Ben Lindstrom562c26b2001-03-07 01:26:48 +00001753 case 'b':
Damien Miller44f75c12004-01-21 10:58:47 +11001754 if (batchmode)
1755 fatal("Batch file already specified.");
1756
1757 /* Allow "-" as stdin */
Darren Tuckerfc959702004-07-17 16:12:08 +10001758 if (strcmp(optarg, "-") != 0 &&
Damien Miller0dc1bef2005-07-17 17:22:45 +10001759 (infile = fopen(optarg, "r")) == NULL)
Damien Miller44f75c12004-01-21 10:58:47 +11001760 fatal("%s (%s).", strerror(errno), optarg);
Damien Miller62d57f62003-01-10 21:43:24 +11001761 showprogress = 0;
Damien Miller44f75c12004-01-21 10:58:47 +11001762 batchmode = 1;
Damien Miller64e8d442005-03-01 21:16:47 +11001763 addargs(&args, "-obatchmode yes");
Ben Lindstrom562c26b2001-03-07 01:26:48 +00001764 break;
Darren Tucker282b4022009-10-07 08:23:06 +11001765 case 'D':
Damien Millerd14ee1e2002-02-05 12:27:31 +11001766 sftp_direct = optarg;
1767 break;
Damien Miller16a13332002-02-13 14:03:56 +11001768 case 'R':
1769 num_requests = strtol(optarg, &cp, 10);
1770 if (num_requests == 0 || *cp != '\0')
Ben Lindstrom6328ab32002-03-22 02:54:23 +00001771 fatal("Invalid number of requests \"%s\"",
Damien Miller16a13332002-02-13 14:03:56 +11001772 optarg);
1773 break;
Darren Tucker46bbbe32009-10-07 08:21:48 +11001774 case 's':
1775 sftp_server = optarg;
1776 break;
1777 case 'S':
1778 ssh_program = optarg;
1779 replacearg(&args, 0, "%s", ssh_program);
1780 break;
Damien Millerd7686fd2001-02-10 00:40:03 +11001781 case 'h':
1782 default:
Damien Miller33804262001-02-04 23:20:18 +11001783 usage();
1784 }
1785 }
1786
Damien Millerc0f27d82004-03-08 23:12:19 +11001787 if (!isatty(STDERR_FILENO))
1788 showprogress = 0;
1789
Ben Lindstrom2f3d52a2002-04-02 21:06:18 +00001790 log_init(argv[0], ll, SYSLOG_FACILITY_USER, 1);
1791
Damien Millerd14ee1e2002-02-05 12:27:31 +11001792 if (sftp_direct == NULL) {
1793 if (optind == argc || argc > (optind + 2))
1794 usage();
Damien Miller33804262001-02-04 23:20:18 +11001795
Damien Millerd14ee1e2002-02-05 12:27:31 +11001796 userhost = xstrdup(argv[optind]);
1797 file2 = argv[optind+1];
Ben Lindstrom63667f62001-04-13 00:00:14 +00001798
Ben Lindstromc276c122002-12-23 02:14:51 +00001799 if ((host = strrchr(userhost, '@')) == NULL)
Damien Millerd14ee1e2002-02-05 12:27:31 +11001800 host = userhost;
1801 else {
1802 *host++ = '\0';
1803 if (!userhost[0]) {
1804 fprintf(stderr, "Missing username\n");
1805 usage();
1806 }
Damien Miller80163902007-01-05 16:30:16 +11001807 addargs(&args, "-l%s", userhost);
Damien Millerd14ee1e2002-02-05 12:27:31 +11001808 }
1809
Damien Millerec692032004-01-27 21:22:00 +11001810 if ((cp = colon(host)) != NULL) {
1811 *cp++ = '\0';
1812 file1 = cp;
1813 }
1814
Damien Millerd14ee1e2002-02-05 12:27:31 +11001815 host = cleanhostname(host);
1816 if (!*host) {
1817 fprintf(stderr, "Missing hostname\n");
Damien Miller33804262001-02-04 23:20:18 +11001818 usage();
1819 }
Damien Millerd14ee1e2002-02-05 12:27:31 +11001820
Damien Millerd14ee1e2002-02-05 12:27:31 +11001821 addargs(&args, "-oProtocol %d", sshver);
1822
1823 /* no subsystem if the server-spec contains a '/' */
1824 if (sftp_server == NULL || strchr(sftp_server, '/') == NULL)
1825 addargs(&args, "-s");
1826
1827 addargs(&args, "%s", host);
Ben Lindstrom6328ab32002-03-22 02:54:23 +00001828 addargs(&args, "%s", (sftp_server != NULL ?
Damien Millerd14ee1e2002-02-05 12:27:31 +11001829 sftp_server : "sftp"));
Damien Millerd14ee1e2002-02-05 12:27:31 +11001830
Damien Miller44f75c12004-01-21 10:58:47 +11001831 if (!batchmode)
1832 fprintf(stderr, "Connecting to %s...\n", host);
Damien Millercc685c12003-06-04 22:51:38 +10001833 connect_to_server(ssh_program, args.list, &in, &out);
Damien Millerd14ee1e2002-02-05 12:27:31 +11001834 } else {
1835 args.list = NULL;
1836 addargs(&args, "sftp-server");
1837
Damien Miller44f75c12004-01-21 10:58:47 +11001838 if (!batchmode)
1839 fprintf(stderr, "Attaching to %s...\n", sftp_direct);
Damien Millercc685c12003-06-04 22:51:38 +10001840 connect_to_server(sftp_direct, args.list, &in, &out);
Damien Miller33804262001-02-04 23:20:18 +11001841 }
Damien Miller3eec6b72006-01-31 21:49:27 +11001842 freeargs(&args);
Damien Miller33804262001-02-04 23:20:18 +11001843
Damien Miller956f3fb2003-01-10 21:40:00 +11001844 err = interactive_loop(in, out, file1, file2);
Damien Miller33804262001-02-04 23:20:18 +11001845
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00001846#if !defined(USE_PIPES)
Damien Miller37294fb2005-07-17 17:18:49 +10001847 shutdown(in, SHUT_RDWR);
1848 shutdown(out, SHUT_RDWR);
Ben Lindstrom10b9bf92001-02-26 20:04:45 +00001849#endif
1850
Damien Miller33804262001-02-04 23:20:18 +11001851 close(in);
1852 close(out);
Damien Miller44f75c12004-01-21 10:58:47 +11001853 if (batchmode)
Ben Lindstrom562c26b2001-03-07 01:26:48 +00001854 fclose(infile);
Damien Miller33804262001-02-04 23:20:18 +11001855
Ben Lindstrom47fd8112002-04-02 20:48:19 +00001856 while (waitpid(sshpid, NULL, 0) == -1)
1857 if (errno != EINTR)
1858 fatal("Couldn't wait for ssh process: %s",
1859 strerror(errno));
Damien Miller33804262001-02-04 23:20:18 +11001860
Damien Miller956f3fb2003-01-10 21:40:00 +11001861 exit(err == 0 ? 0 : 1);
Damien Miller33804262001-02-04 23:20:18 +11001862}