summaryrefslogtreecommitdiff
path: root/sftp.c
diff options
context:
space:
mode:
authorColin Watson <cjwatson@debian.org>2008-07-22 19:45:18 +0000
committerColin Watson <cjwatson@debian.org>2008-07-22 19:45:18 +0000
commit137d76ba65883aa8143af1fcad83b57e7badef0c (patch)
treef426e804bb5248ceafedfab7bb78ae6e6752942c /sftp.c
parentdac7d049dad31f5f84d421d4eb628a7e13f977d7 (diff)
parentef94e5613d37bcbf880f21ee6094e4b1c7683a4c (diff)
* New upstream release (closes: #474301). Important changes not previously
backported to 4.7p1: - 4.9/4.9p1 (http://www.openssh.com/txt/release-4.9): + Added chroot(2) support for sshd(8), controlled by a new option "ChrootDirectory" (closes: #139047, LP: #24777). + Linked sftp-server(8) into sshd(8). The internal sftp server is used when the command "internal-sftp" is specified in a Subsystem or ForceCommand declaration. When used with ChrootDirectory, the internal sftp server requires no special configuration of files inside the chroot environment. + Added a protocol extension method "posix-rename@openssh.com" for sftp-server(8) to perform POSIX atomic rename() operations; sftp(1) prefers this if available (closes: #308561). + Removed the fixed limit of 100 file handles in sftp-server(8). + ssh(8) will now skip generation of SSH protocol 1 ephemeral server keys when in inetd mode and protocol 2 connections are negotiated. This speeds up protocol 2 connections to inetd-mode servers that also allow Protocol 1. + Accept the PermitRootLogin directive in a sshd_config(5) Match block. Allows for, e.g. permitting root only from the local network. + Reworked sftp(1) argument splitting and escaping to be more internally consistent (i.e. between sftp commands) and more consistent with sh(1). Please note that this will change the interpretation of some quoted strings, especially those with embedded backslash escape sequences. + Support "Banner=none" in sshd_config(5) to disable sending of a pre-login banner (e.g. in a Match block). + ssh(1) ProxyCommands are now executed with $SHELL rather than /bin/sh. + ssh(1)'s ConnectTimeout option is now applied to both the TCP connection and the SSH banner exchange (previously it just covered the TCP connection). This allows callers of ssh(1) to better detect and deal with stuck servers that accept a TCP connection but don't progress the protocol, and also makes ConnectTimeout useful for connections via a ProxyCommand. + scp(1) incorrectly reported "stalled" on slow copies (closes: #140828). + scp(1) date underflow for timestamps before epoch. + ssh(1) used the obsolete SIG DNS RRtype for host keys in DNS, instead of the current standard RRSIG. + Correctly drain ACKs when a sftp(1) upload write fails midway, avoids a fatal() exit from what should be a recoverable condition. + Fixed ssh-keygen(1) selective host key hashing (i.e. "ssh-keygen -HF hostname") to not include any IP address in the data to be hashed. + Make ssh(1) skip listening on the IPv6 wildcard address when a binding address of 0.0.0.0 is used against an old SSH server that does not support the RFC4254 syntax for wildcard bind addresses. + Enable IPV6_V6ONLY socket option on sshd(8) listen socket, as is already done for X11/TCP forwarding sockets (closes: #439661). + Fix FD leak that could hang a ssh(1) connection multiplexing master. + Make ssh(1) -q option documentation consistent with reality. + Fixed sshd(8) PAM support not calling pam_session_close(), or failing to call it with root privileges (closes: #372680). + Fix activation of OpenSSL engine support when requested in configure (LP: #119295). - 5.1/5.1p1 (http://www.openssh.com/txt/release-5.1): + Introduce experimental SSH Fingerprint ASCII Visualisation to ssh(1) and ssh-keygen(1). Visual fingerprint display is controlled by a new ssh_config(5) option "VisualHostKey". The intent is to render SSH host keys in a visual form that is amenable to easy recall and rejection of changed host keys. + sshd_config(5) now supports CIDR address/masklen matching in "Match address" blocks, with a fallback to classic wildcard matching. + sshd(8) now supports CIDR matching in ~/.ssh/authorized_keys from="..." restrictions, also with a fallback to classic wildcard matching. + Added an extended test mode (-T) to sshd(8) to request that it write its effective configuration to stdout and exit. Extended test mode also supports the specification of connection parameters (username, source address and hostname) to test the application of sshd_config(5) Match rules. + ssh(1) now prints the number of bytes transferred and the overall connection throughput for SSH protocol 2 sessions when in verbose mode (previously these statistics were displayed for protocol 1 connections only). + sftp-server(8) now supports extension methods statvfs@openssh.com and fstatvfs@openssh.com that implement statvfs(2)-like operations. + sftp(1) now has a "df" command to the sftp client that uses the statvfs@openssh.com to produce a df(1)-like display of filesystem space and inode utilisation (requires statvfs@openssh.com support on the server). + Added a MaxSessions option to sshd_config(5) to allow control of the number of multiplexed sessions supported over a single TCP connection. This allows increasing the number of allowed sessions above the previous default of 10, disabling connection multiplexing (MaxSessions=1) or disallowing login/shell/subsystem sessions entirely (MaxSessions=0). + Added a no-more-sessions@openssh.com global request extension that is sent from ssh(1) to sshd(8) when the client knows that it will never request another session (i.e. when session multiplexing is disabled). This allows a server to disallow further session requests and terminate the session in cases where the client has been hijacked. + ssh-keygen(1) now supports the use of the -l option in combination with -F to search for a host in ~/.ssh/known_hosts and display its fingerprint. + ssh-keyscan(1) now defaults to "rsa" (protocol 2) keys, instead of "rsa1". + Added an AllowAgentForwarding option to sshd_config(8) to control whether authentication agent forwarding is permitted. Note that this is a loose control, as a client may install their own unofficial forwarder. + ssh(1) and sshd(8): avoid unnecessary malloc/copy/free when receiving network data, resulting in a ~10% speedup. + ssh(1) and sshd(8) will now try additional addresses when connecting to a port forward destination whose DNS name resolves to more than one address. The previous behaviour was to try the only first address and give up if that failed. + ssh(1) and sshd(8) now support signalling that channels are half-closed for writing, through a channel protocol extension notification "eow@openssh.com". This allows propagation of closed file descriptors, so that commands such as "ssh -2 localhost od /bin/ls | true" do not send unnecessary data over the wire. + sshd(8): increased the default size of ssh protocol 1 ephemeral keys from 768 to 1024 bits. + When ssh(1) has been requested to fork after authentication ("ssh -f") with ExitOnForwardFailure enabled, delay the fork until after replies for any -R forwards have been seen. Allows for robust detection of -R forward failure when using -f. + "Match group" blocks in sshd_config(5) now support negation of groups. E.g. "Match group staff,!guests". + sftp(1) and sftp-server(8) now allow chmod-like operations to set set[ug]id/sticky bits. + The MaxAuthTries option is now permitted in sshd_config(5) match blocks. + Multiplexed ssh(1) sessions now support a subset of the ~ escapes that are available to a primary connection. + ssh(1) connection multiplexing will now fall back to creating a new connection in most error cases (closes: #352830). + Make ssh(1) deal more gracefully with channel requests that fail. Previously it would optimistically assume that requests would always succeed, which could cause hangs if they did not (e.g. when the server runs out of file descriptors). + ssh(1) now reports multiplexing errors via the multiplex slave's stderr where possible (subject to LogLevel in the mux master). + Prevent sshd(8) from erroneously applying public key restrictions leaned from ~/.ssh/authorized_keys to other authentication methods when public key authentication subsequently fails (LP: #161047). + Fixed an UMAC alignment problem that manifested on Itanium platforms.
Diffstat (limited to 'sftp.c')
-rw-r--r--sftp.c576
1 files changed, 395 insertions, 181 deletions
diff --git a/sftp.c b/sftp.c
index f0d5dd557..e1aa49d0f 100644
--- a/sftp.c
+++ b/sftp.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: sftp.c,v 1.96 2007/01/03 04:09:15 stevesk Exp $ */ 1/* $OpenBSD: sftp.c,v 1.103 2008/07/13 22:16:03 djm Exp $ */
2/* 2/*
3 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> 3 * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org>
4 * 4 *
@@ -25,7 +25,11 @@
25#include <sys/param.h> 25#include <sys/param.h>
26#include <sys/socket.h> 26#include <sys/socket.h>
27#include <sys/wait.h> 27#include <sys/wait.h>
28#ifdef HAVE_SYS_STATVFS_H
29#include <sys/statvfs.h>
30#endif
28 31
32#include <ctype.h>
29#include <errno.h> 33#include <errno.h>
30 34
31#ifdef HAVE_PATHS_H 35#ifdef HAVE_PATHS_H
@@ -43,6 +47,14 @@ typedef void EditLine;
43#include <unistd.h> 47#include <unistd.h>
44#include <stdarg.h> 48#include <stdarg.h>
45 49
50#ifdef HAVE_UTIL_H
51# include <util.h>
52#endif
53
54#ifdef HAVE_LIBUTIL_H
55# include <libutil.h>
56#endif
57
46#include "xmalloc.h" 58#include "xmalloc.h"
47#include "log.h" 59#include "log.h"
48#include "pathnames.h" 60#include "pathnames.h"
@@ -63,7 +75,7 @@ int batchmode = 0;
63size_t copy_buffer_len = 32768; 75size_t copy_buffer_len = 32768;
64 76
65/* Number of concurrent outstanding requests */ 77/* Number of concurrent outstanding requests */
66size_t num_requests = 16; 78size_t num_requests = 64;
67 79
68/* PID of ssh transport process */ 80/* PID of ssh transport process */
69static pid_t sshpid = -1; 81static pid_t sshpid = -1;
@@ -103,6 +115,7 @@ extern char *__progname;
103#define I_CHGRP 2 115#define I_CHGRP 2
104#define I_CHMOD 3 116#define I_CHMOD 3
105#define I_CHOWN 4 117#define I_CHOWN 4
118#define I_DF 24
106#define I_GET 5 119#define I_GET 5
107#define I_HELP 6 120#define I_HELP 6
108#define I_LCHDIR 7 121#define I_LCHDIR 7
@@ -135,6 +148,7 @@ static const struct CMD cmds[] = {
135 { "chgrp", I_CHGRP }, 148 { "chgrp", I_CHGRP },
136 { "chmod", I_CHMOD }, 149 { "chmod", I_CHMOD },
137 { "chown", I_CHOWN }, 150 { "chown", I_CHOWN },
151 { "df", I_DF },
138 { "dir", I_LS }, 152 { "dir", I_LS },
139 { "exit", I_QUIT }, 153 { "exit", I_QUIT },
140 { "get", I_GET }, 154 { "get", I_GET },
@@ -199,6 +213,8 @@ help(void)
199 printf("chgrp grp path Change group of file 'path' to 'grp'\n"); 213 printf("chgrp grp path Change group of file 'path' to 'grp'\n");
200 printf("chmod mode path Change permissions of file 'path' to 'mode'\n"); 214 printf("chmod mode path Change permissions of file 'path' to 'mode'\n");
201 printf("chown own path Change owner of file 'path' to 'own'\n"); 215 printf("chown own path Change owner of file 'path' to 'own'\n");
216 printf("df [path] Display statistics for current directory or\n");
217 printf(" filesystem containing 'path'\n");
202 printf("help Display this help text\n"); 218 printf("help Display this help text\n");
203 printf("get remote-path [local-path] Download file\n"); 219 printf("get remote-path [local-path] Download file\n");
204 printf("lls [ls-options [path]] Display local directory listing\n"); 220 printf("lls [ls-options [path]] Display local directory listing\n");
@@ -346,144 +362,105 @@ infer_path(const char *p, char **ifp)
346} 362}
347 363
348static int 364static int
349parse_getput_flags(const char **cpp, int *pflag) 365parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag)
350{ 366{
351 const char *cp = *cpp; 367 extern int opterr, optind, optopt, optreset;
368 int ch;
352 369
353 /* Check for flags */ 370 optind = optreset = 1;
354 if (cp[0] == '-' && cp[1] && strchr(WHITESPACE, cp[2])) { 371 opterr = 0;
355 switch (cp[1]) { 372
373 *pflag = 0;
374 while ((ch = getopt(argc, argv, "Pp")) != -1) {
375 switch (ch) {
356 case 'p': 376 case 'p':
357 case 'P': 377 case 'P':
358 *pflag = 1; 378 *pflag = 1;
359 break; 379 break;
360 default: 380 default:
361 error("Invalid flag -%c", cp[1]); 381 error("%s: Invalid flag -%c", cmd, optopt);
362 return(-1); 382 return -1;
363 } 383 }
364 cp += 2;
365 *cpp = cp + strspn(cp, WHITESPACE);
366 } 384 }
367 385
368 return(0); 386 return optind;
369} 387}
370 388
371static int 389static int
372parse_ls_flags(const char **cpp, int *lflag) 390parse_ls_flags(char **argv, int argc, int *lflag)
373{ 391{
374 const char *cp = *cpp; 392 extern int opterr, optind, optopt, optreset;
393 int ch;
375 394
376 /* Defaults */ 395 optind = optreset = 1;
377 *lflag = LS_NAME_SORT; 396 opterr = 0;
378 397
379 /* Check for flags */ 398 *lflag = LS_NAME_SORT;
380 if (cp++[0] == '-') { 399 while ((ch = getopt(argc, argv, "1Saflnrt")) != -1) {
381 for (; strchr(WHITESPACE, *cp) == NULL; cp++) { 400 switch (ch) {
382 switch (*cp) { 401 case '1':
383 case 'l': 402 *lflag &= ~VIEW_FLAGS;
384 *lflag &= ~VIEW_FLAGS; 403 *lflag |= LS_SHORT_VIEW;
385 *lflag |= LS_LONG_VIEW; 404 break;
386 break; 405 case 'S':
387 case '1': 406 *lflag &= ~SORT_FLAGS;
388 *lflag &= ~VIEW_FLAGS; 407 *lflag |= LS_SIZE_SORT;
389 *lflag |= LS_SHORT_VIEW; 408 break;
390 break; 409 case 'a':
391 case 'n': 410 *lflag |= LS_SHOW_ALL;
392 *lflag &= ~VIEW_FLAGS; 411 break;
393 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW; 412 case 'f':
394 break; 413 *lflag &= ~SORT_FLAGS;
395 case 'S': 414 break;
396 *lflag &= ~SORT_FLAGS; 415 case 'l':
397 *lflag |= LS_SIZE_SORT; 416 *lflag &= ~VIEW_FLAGS;
398 break; 417 *lflag |= LS_LONG_VIEW;
399 case 't': 418 break;
400 *lflag &= ~SORT_FLAGS; 419 case 'n':
401 *lflag |= LS_TIME_SORT; 420 *lflag &= ~VIEW_FLAGS;
402 break; 421 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
403 case 'r': 422 break;
404 *lflag |= LS_REVERSE_SORT; 423 case 'r':
405 break; 424 *lflag |= LS_REVERSE_SORT;
406 case 'f': 425 break;
407 *lflag &= ~SORT_FLAGS; 426 case 't':
408 break; 427 *lflag &= ~SORT_FLAGS;
409 case 'a': 428 *lflag |= LS_TIME_SORT;
410 *lflag |= LS_SHOW_ALL; 429 break;
411 break; 430 default:
412 default: 431 error("ls: Invalid flag -%c", optopt);
413 error("Invalid flag -%c", *cp); 432 return -1;
414 return(-1);
415 }
416 } 433 }
417 *cpp = cp + strspn(cp, WHITESPACE);
418 } 434 }
419 435
420 return(0); 436 return optind;
421} 437}
422 438
423static int 439static int
424get_pathname(const char **cpp, char **path) 440parse_df_flags(const char *cmd, char **argv, int argc, int *hflag, int *iflag)
425{ 441{
426 const char *cp = *cpp, *end; 442 extern int opterr, optind, optopt, optreset;
427 char quot; 443 int ch;
428 u_int i, j;
429
430 cp += strspn(cp, WHITESPACE);
431 if (!*cp) {
432 *cpp = cp;
433 *path = NULL;
434 return (0);
435 }
436
437 *path = xmalloc(strlen(cp) + 1);
438
439 /* Check for quoted filenames */
440 if (*cp == '\"' || *cp == '\'') {
441 quot = *cp++;
442 444
443 /* Search for terminating quote, unescape some chars */ 445 optind = optreset = 1;
444 for (i = j = 0; i <= strlen(cp); i++) { 446 opterr = 0;
445 if (cp[i] == quot) { /* Found quote */
446 i++;
447 (*path)[j] = '\0';
448 break;
449 }
450 if (cp[i] == '\0') { /* End of string */
451 error("Unterminated quote");
452 goto fail;
453 }
454 if (cp[i] == '\\') { /* Escaped characters */
455 i++;
456 if (cp[i] != '\'' && cp[i] != '\"' &&
457 cp[i] != '\\') {
458 error("Bad escaped character '\\%c'",
459 cp[i]);
460 goto fail;
461 }
462 }
463 (*path)[j++] = cp[i];
464 }
465 447
466 if (j == 0) { 448 *hflag = *iflag = 0;
467 error("Empty quotes"); 449 while ((ch = getopt(argc, argv, "hi")) != -1) {
468 goto fail; 450 switch (ch) {
451 case 'h':
452 *hflag = 1;
453 break;
454 case 'i':
455 *iflag = 1;
456 break;
457 default:
458 error("%s: Invalid flag -%c", cmd, optopt);
459 return -1;
469 } 460 }
470 *cpp = cp + i + strspn(cp + i, WHITESPACE);
471 } else {
472 /* Read to end of filename */
473 end = strpbrk(cp, WHITESPACE);
474 if (end == NULL)
475 end = strchr(cp, '\0');
476 *cpp = end + strspn(end, WHITESPACE);
477
478 memcpy(*path, cp, end - cp);
479 (*path)[end - cp] = '\0';
480 } 461 }
481 return (0);
482 462
483 fail: 463 return optind;
484 xfree(*path);
485 *path = NULL;
486 return (-1);
487} 464}
488 465
489static int 466static int
@@ -499,17 +476,6 @@ is_dir(char *path)
499} 476}
500 477
501static int 478static int
502is_reg(char *path)
503{
504 struct stat sb;
505
506 if (stat(path, &sb) == -1)
507 fatal("stat %s: %s", path, strerror(errno));
508
509 return(S_ISREG(sb.st_mode));
510}
511
512static int
513remote_is_dir(struct sftp_conn *conn, char *path) 479remote_is_dir(struct sftp_conn *conn, char *path)
514{ 480{
515 Attrib *a; 481 Attrib *a;
@@ -597,6 +563,7 @@ process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
597 glob_t g; 563 glob_t g;
598 int err = 0; 564 int err = 0;
599 int i; 565 int i;
566 struct stat sb;
600 567
601 if (dst) { 568 if (dst) {
602 tmp_dst = xstrdup(dst); 569 tmp_dst = xstrdup(dst);
@@ -605,7 +572,7 @@ process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
605 572
606 memset(&g, 0, sizeof(g)); 573 memset(&g, 0, sizeof(g));
607 debug3("Looking up %s", src); 574 debug3("Looking up %s", src);
608 if (glob(src, 0, NULL, &g)) { 575 if (glob(src, GLOB_NOCHECK, NULL, &g)) {
609 error("File \"%s\" not found.", src); 576 error("File \"%s\" not found.", src);
610 err = -1; 577 err = -1;
611 goto out; 578 goto out;
@@ -620,7 +587,13 @@ process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
620 } 587 }
621 588
622 for (i = 0; g.gl_pathv[i] && !interrupted; i++) { 589 for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
623 if (!is_reg(g.gl_pathv[i])) { 590 if (stat(g.gl_pathv[i], &sb) == -1) {
591 err = -1;
592 error("stat %s: %s", g.gl_pathv[i], strerror(errno));
593 continue;
594 }
595
596 if (!S_ISREG(sb.st_mode)) {
624 error("skipping non-regular file %s", 597 error("skipping non-regular file %s",
625 g.gl_pathv[i]); 598 g.gl_pathv[i]);
626 continue; 599 continue;
@@ -867,14 +840,238 @@ do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
867} 840}
868 841
869static int 842static int
870parse_args(const char **cpp, int *pflag, int *lflag, int *iflag, 843do_df(struct sftp_conn *conn, char *path, int hflag, int iflag)
844{
845 struct sftp_statvfs st;
846 char s_used[FMT_SCALED_STRSIZE];
847 char s_avail[FMT_SCALED_STRSIZE];
848 char s_root[FMT_SCALED_STRSIZE];
849 char s_total[FMT_SCALED_STRSIZE];
850
851 if (do_statvfs(conn, path, &st, 1) == -1)
852 return -1;
853 if (iflag) {
854 printf(" Inodes Used Avail "
855 "(root) %%Capacity\n");
856 printf("%11llu %11llu %11llu %11llu %3llu%%\n",
857 (unsigned long long)st.f_files,
858 (unsigned long long)(st.f_files - st.f_ffree),
859 (unsigned long long)st.f_favail,
860 (unsigned long long)st.f_ffree,
861 (unsigned long long)(100 * (st.f_files - st.f_ffree) /
862 st.f_files));
863 } else if (hflag) {
864 strlcpy(s_used, "error", sizeof(s_used));
865 strlcpy(s_avail, "error", sizeof(s_avail));
866 strlcpy(s_root, "error", sizeof(s_root));
867 strlcpy(s_total, "error", sizeof(s_total));
868 fmt_scaled((st.f_blocks - st.f_bfree) * st.f_frsize, s_used);
869 fmt_scaled(st.f_bavail * st.f_frsize, s_avail);
870 fmt_scaled(st.f_bfree * st.f_frsize, s_root);
871 fmt_scaled(st.f_blocks * st.f_frsize, s_total);
872 printf(" Size Used Avail (root) %%Capacity\n");
873 printf("%7sB %7sB %7sB %7sB %3llu%%\n",
874 s_total, s_used, s_avail, s_root,
875 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
876 st.f_blocks));
877 } else {
878 printf(" Size Used Avail "
879 "(root) %%Capacity\n");
880 printf("%12llu %12llu %12llu %12llu %3llu%%\n",
881 (unsigned long long)(st.f_frsize * st.f_blocks / 1024),
882 (unsigned long long)(st.f_frsize *
883 (st.f_blocks - st.f_bfree) / 1024),
884 (unsigned long long)(st.f_frsize * st.f_bavail / 1024),
885 (unsigned long long)(st.f_frsize * st.f_bfree / 1024),
886 (unsigned long long)(100 * (st.f_blocks - st.f_bfree) /
887 st.f_blocks));
888 }
889 return 0;
890}
891
892/*
893 * Undo escaping of glob sequences in place. Used to undo extra escaping
894 * applied in makeargv() when the string is destined for a function that
895 * does not glob it.
896 */
897static void
898undo_glob_escape(char *s)
899{
900 size_t i, j;
901
902 for (i = j = 0;;) {
903 if (s[i] == '\0') {
904 s[j] = '\0';
905 return;
906 }
907 if (s[i] != '\\') {
908 s[j++] = s[i++];
909 continue;
910 }
911 /* s[i] == '\\' */
912 ++i;
913 switch (s[i]) {
914 case '?':
915 case '[':
916 case '*':
917 case '\\':
918 s[j++] = s[i++];
919 break;
920 case '\0':
921 s[j++] = '\\';
922 s[j] = '\0';
923 return;
924 default:
925 s[j++] = '\\';
926 s[j++] = s[i++];
927 break;
928 }
929 }
930}
931
932/*
933 * Split a string into an argument vector using sh(1)-style quoting,
934 * comment and escaping rules, but with some tweaks to handle glob(3)
935 * wildcards.
936 * Returns NULL on error or a NULL-terminated array of arguments.
937 */
938#define MAXARGS 128
939#define MAXARGLEN 8192
940static char **
941makeargv(const char *arg, int *argcp)
942{
943 int argc, quot;
944 size_t i, j;
945 static char argvs[MAXARGLEN];
946 static char *argv[MAXARGS + 1];
947 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
948
949 *argcp = argc = 0;
950 if (strlen(arg) > sizeof(argvs) - 1) {
951 args_too_longs:
952 error("string too long");
953 return NULL;
954 }
955 state = MA_START;
956 i = j = 0;
957 for (;;) {
958 if (isspace(arg[i])) {
959 if (state == MA_UNQUOTED) {
960 /* Terminate current argument */
961 argvs[j++] = '\0';
962 argc++;
963 state = MA_START;
964 } else if (state != MA_START)
965 argvs[j++] = arg[i];
966 } else if (arg[i] == '"' || arg[i] == '\'') {
967 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
968 if (state == MA_START) {
969 argv[argc] = argvs + j;
970 state = q;
971 } else if (state == MA_UNQUOTED)
972 state = q;
973 else if (state == q)
974 state = MA_UNQUOTED;
975 else
976 argvs[j++] = arg[i];
977 } else if (arg[i] == '\\') {
978 if (state == MA_SQUOTE || state == MA_DQUOTE) {
979 quot = state == MA_SQUOTE ? '\'' : '"';
980 /* Unescape quote we are in */
981 /* XXX support \n and friends? */
982 if (arg[i + 1] == quot) {
983 i++;
984 argvs[j++] = arg[i];
985 } else if (arg[i + 1] == '?' ||
986 arg[i + 1] == '[' || arg[i + 1] == '*') {
987 /*
988 * Special case for sftp: append
989 * double-escaped glob sequence -
990 * glob will undo one level of
991 * escaping. NB. string can grow here.
992 */
993 if (j >= sizeof(argvs) - 5)
994 goto args_too_longs;
995 argvs[j++] = '\\';
996 argvs[j++] = arg[i++];
997 argvs[j++] = '\\';
998 argvs[j++] = arg[i];
999 } else {
1000 argvs[j++] = arg[i++];
1001 argvs[j++] = arg[i];
1002 }
1003 } else {
1004 if (state == MA_START) {
1005 argv[argc] = argvs + j;
1006 state = MA_UNQUOTED;
1007 }
1008 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
1009 arg[i + 1] == '*' || arg[i + 1] == '\\') {
1010 /*
1011 * Special case for sftp: append
1012 * escaped glob sequence -
1013 * glob will undo one level of
1014 * escaping.
1015 */
1016 argvs[j++] = arg[i++];
1017 argvs[j++] = arg[i];
1018 } else {
1019 /* Unescape everything */
1020 /* XXX support \n and friends? */
1021 i++;
1022 argvs[j++] = arg[i];
1023 }
1024 }
1025 } else if (arg[i] == '#') {
1026 if (state == MA_SQUOTE || state == MA_DQUOTE)
1027 argvs[j++] = arg[i];
1028 else
1029 goto string_done;
1030 } else if (arg[i] == '\0') {
1031 if (state == MA_SQUOTE || state == MA_DQUOTE) {
1032 error("Unterminated quoted argument");
1033 return NULL;
1034 }
1035 string_done:
1036 if (state == MA_UNQUOTED) {
1037 argvs[j++] = '\0';
1038 argc++;
1039 }
1040 break;
1041 } else {
1042 if (state == MA_START) {
1043 argv[argc] = argvs + j;
1044 state = MA_UNQUOTED;
1045 }
1046 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
1047 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
1048 /*
1049 * Special case for sftp: escape quoted
1050 * glob(3) wildcards. NB. string can grow
1051 * here.
1052 */
1053 if (j >= sizeof(argvs) - 3)
1054 goto args_too_longs;
1055 argvs[j++] = '\\';
1056 argvs[j++] = arg[i];
1057 } else
1058 argvs[j++] = arg[i];
1059 }
1060 i++;
1061 }
1062 *argcp = argc;
1063 return argv;
1064}
1065
1066static int
1067parse_args(const char **cpp, int *pflag, int *lflag, int *iflag, int *hflag,
871 unsigned long *n_arg, char **path1, char **path2) 1068 unsigned long *n_arg, char **path1, char **path2)
872{ 1069{
873 const char *cmd, *cp = *cpp; 1070 const char *cmd, *cp = *cpp;
874 char *cp2; 1071 char *cp2, **argv;
875 int base = 0; 1072 int base = 0;
876 long l; 1073 long l;
877 int i, cmdnum; 1074 int i, cmdnum, optidx, argc;
878 1075
879 /* Skip leading whitespace */ 1076 /* Skip leading whitespace */
880 cp = cp + strspn(cp, WHITESPACE); 1077 cp = cp + strspn(cp, WHITESPACE);
@@ -890,17 +1087,13 @@ parse_args(const char **cpp, int *pflag, int *lflag, int *iflag,
890 cp++; 1087 cp++;
891 } 1088 }
892 1089
1090 if ((argv = makeargv(cp, &argc)) == NULL)
1091 return -1;
1092
893 /* Figure out which command we have */ 1093 /* Figure out which command we have */
894 for (i = 0; cmds[i].c; i++) { 1094 for (i = 0; cmds[i].c != NULL; i++) {
895 int cmdlen = strlen(cmds[i].c); 1095 if (strcasecmp(cmds[i].c, argv[0]) == 0)
896
897 /* Check for command followed by whitespace */
898 if (!strncasecmp(cp, cmds[i].c, cmdlen) &&
899 strchr(WHITESPACE, cp[cmdlen])) {
900 cp += cmdlen;
901 cp = cp + strspn(cp, WHITESPACE);
902 break; 1096 break;
903 }
904 } 1097 }
905 cmdnum = cmds[i].n; 1098 cmdnum = cmds[i].n;
906 cmd = cmds[i].c; 1099 cmd = cmds[i].c;
@@ -911,40 +1104,44 @@ parse_args(const char **cpp, int *pflag, int *lflag, int *iflag,
911 cmdnum = I_SHELL; 1104 cmdnum = I_SHELL;
912 } else if (cmdnum == -1) { 1105 } else if (cmdnum == -1) {
913 error("Invalid command."); 1106 error("Invalid command.");
914 return (-1); 1107 return -1;
915 } 1108 }
916 1109
917 /* Get arguments and parse flags */ 1110 /* Get arguments and parse flags */
918 *lflag = *pflag = *n_arg = 0; 1111 *lflag = *pflag = *hflag = *n_arg = 0;
919 *path1 = *path2 = NULL; 1112 *path1 = *path2 = NULL;
1113 optidx = 1;
920 switch (cmdnum) { 1114 switch (cmdnum) {
921 case I_GET: 1115 case I_GET:
922 case I_PUT: 1116 case I_PUT:
923 if (parse_getput_flags(&cp, pflag)) 1117 if ((optidx = parse_getput_flags(cmd, argv, argc, pflag)) == -1)
924 return(-1); 1118 return -1;
925 /* Get first pathname (mandatory) */ 1119 /* Get first pathname (mandatory) */
926 if (get_pathname(&cp, path1)) 1120 if (argc - optidx < 1) {
927 return(-1);
928 if (*path1 == NULL) {
929 error("You must specify at least one path after a " 1121 error("You must specify at least one path after a "
930 "%s command.", cmd); 1122 "%s command.", cmd);
931 return(-1); 1123 return -1;
1124 }
1125 *path1 = xstrdup(argv[optidx]);
1126 /* Get second pathname (optional) */
1127 if (argc - optidx > 1) {
1128 *path2 = xstrdup(argv[optidx + 1]);
1129 /* Destination is not globbed */
1130 undo_glob_escape(*path2);
932 } 1131 }
933 /* Try to get second pathname (optional) */
934 if (get_pathname(&cp, path2))
935 return(-1);
936 break; 1132 break;
937 case I_RENAME: 1133 case I_RENAME:
938 case I_SYMLINK: 1134 case I_SYMLINK:
939 if (get_pathname(&cp, path1)) 1135 if (argc - optidx < 2) {
940 return(-1);
941 if (get_pathname(&cp, path2))
942 return(-1);
943 if (!*path1 || !*path2) {
944 error("You must specify two paths after a %s " 1136 error("You must specify two paths after a %s "
945 "command.", cmd); 1137 "command.", cmd);
946 return(-1); 1138 return -1;
947 } 1139 }
1140 *path1 = xstrdup(argv[optidx]);
1141 *path2 = xstrdup(argv[optidx + 1]);
1142 /* Paths are not globbed */
1143 undo_glob_escape(*path1);
1144 undo_glob_escape(*path2);
948 break; 1145 break;
949 case I_RM: 1146 case I_RM:
950 case I_MKDIR: 1147 case I_MKDIR:
@@ -953,59 +1150,69 @@ parse_args(const char **cpp, int *pflag, int *lflag, int *iflag,
953 case I_LCHDIR: 1150 case I_LCHDIR:
954 case I_LMKDIR: 1151 case I_LMKDIR:
955 /* Get pathname (mandatory) */ 1152 /* Get pathname (mandatory) */
956 if (get_pathname(&cp, path1)) 1153 if (argc - optidx < 1) {
957 return(-1);
958 if (*path1 == NULL) {
959 error("You must specify a path after a %s command.", 1154 error("You must specify a path after a %s command.",
960 cmd); 1155 cmd);
961 return(-1); 1156 return -1;
1157 }
1158 *path1 = xstrdup(argv[optidx]);
1159 /* Only "rm" globs */
1160 if (cmdnum != I_RM)
1161 undo_glob_escape(*path1);
1162 break;
1163 case I_DF:
1164 if ((optidx = parse_df_flags(cmd, argv, argc, hflag,
1165 iflag)) == -1)
1166 return -1;
1167 /* Default to current directory if no path specified */
1168 if (argc - optidx < 1)
1169 *path1 = NULL;
1170 else {
1171 *path1 = xstrdup(argv[optidx]);
1172 undo_glob_escape(*path1);
962 } 1173 }
963 break; 1174 break;
964 case I_LS: 1175 case I_LS:
965 if (parse_ls_flags(&cp, lflag)) 1176 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
966 return(-1); 1177 return(-1);
967 /* Path is optional */ 1178 /* Path is optional */
968 if (get_pathname(&cp, path1)) 1179 if (argc - optidx > 0)
969 return(-1); 1180 *path1 = xstrdup(argv[optidx]);
970 break; 1181 break;
971 case I_LLS: 1182 case I_LLS:
1183 /* Skip ls command and following whitespace */
1184 cp = cp + strlen(cmd) + strspn(cp, WHITESPACE);
972 case I_SHELL: 1185 case I_SHELL:
973 /* Uses the rest of the line */ 1186 /* Uses the rest of the line */
974 break; 1187 break;
975 case I_LUMASK: 1188 case I_LUMASK:
976 base = 8;
977 case I_CHMOD: 1189 case I_CHMOD:
978 base = 8; 1190 base = 8;
979 case I_CHOWN: 1191 case I_CHOWN:
980 case I_CHGRP: 1192 case I_CHGRP:
981 /* Get numeric arg (mandatory) */ 1193 /* Get numeric arg (mandatory) */
1194 if (argc - optidx < 1)
1195 goto need_num_arg;
982 errno = 0; 1196 errno = 0;
983 l = strtol(cp, &cp2, base); 1197 l = strtol(argv[optidx], &cp2, base);
984 if (cp2 == cp || ((l == LONG_MIN || l == LONG_MAX) && 1198 if (cp2 == argv[optidx] || *cp2 != '\0' ||
985 errno == ERANGE) || l < 0) { 1199 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1200 l < 0) {
1201 need_num_arg:
986 error("You must supply a numeric argument " 1202 error("You must supply a numeric argument "
987 "to the %s command.", cmd); 1203 "to the %s command.", cmd);
988 return(-1); 1204 return -1;
989 } 1205 }
990 cp = cp2;
991 *n_arg = l; 1206 *n_arg = l;
992 if (cmdnum == I_LUMASK && strchr(WHITESPACE, *cp)) 1207 if (cmdnum == I_LUMASK)
993 break; 1208 break;
994 if (cmdnum == I_LUMASK || !strchr(WHITESPACE, *cp)) {
995 error("You must supply a numeric argument "
996 "to the %s command.", cmd);
997 return(-1);
998 }
999 cp += strspn(cp, WHITESPACE);
1000
1001 /* Get pathname (mandatory) */ 1209 /* Get pathname (mandatory) */
1002 if (get_pathname(&cp, path1)) 1210 if (argc - optidx < 2) {
1003 return(-1);
1004 if (*path1 == NULL) {
1005 error("You must specify a path after a %s command.", 1211 error("You must specify a path after a %s command.",
1006 cmd); 1212 cmd);
1007 return(-1); 1213 return -1;
1008 } 1214 }
1215 *path1 = xstrdup(argv[optidx + 1]);
1009 break; 1216 break;
1010 case I_QUIT: 1217 case I_QUIT:
1011 case I_PWD: 1218 case I_PWD:
@@ -1027,7 +1234,7 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1027 int err_abort) 1234 int err_abort)
1028{ 1235{
1029 char *path1, *path2, *tmp; 1236 char *path1, *path2, *tmp;
1030 int pflag, lflag, iflag, cmdnum, i; 1237 int pflag, lflag, iflag, hflag, cmdnum, i;
1031 unsigned long n_arg; 1238 unsigned long n_arg;
1032 Attrib a, *aa; 1239 Attrib a, *aa;
1033 char path_buf[MAXPATHLEN]; 1240 char path_buf[MAXPATHLEN];
@@ -1035,7 +1242,7 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1035 glob_t g; 1242 glob_t g;
1036 1243
1037 path1 = path2 = NULL; 1244 path1 = path2 = NULL;
1038 cmdnum = parse_args(&cmd, &pflag, &lflag, &iflag, &n_arg, 1245 cmdnum = parse_args(&cmd, &pflag, &lflag, &iflag, &hflag, &n_arg,
1039 &path1, &path2); 1246 &path1, &path2);
1040 1247
1041 if (iflag != 0) 1248 if (iflag != 0)
@@ -1129,6 +1336,13 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
1129 path1 = make_absolute(path1, *pwd); 1336 path1 = make_absolute(path1, *pwd);
1130 err = do_globbed_ls(conn, path1, tmp, lflag); 1337 err = do_globbed_ls(conn, path1, tmp, lflag);
1131 break; 1338 break;
1339 case I_DF:
1340 /* Default to current directory if no path specified */
1341 if (path1 == NULL)
1342 path1 = xstrdup(*pwd);
1343 path1 = make_absolute(path1, *pwd);
1344 err = do_df(conn, path1, hflag, iflag);
1345 break;
1132 case I_LCHDIR: 1346 case I_LCHDIR:
1133 if (chdir(path1) == -1) { 1347 if (chdir(path1) == -1) {
1134 error("Couldn't change local directory to " 1348 error("Couldn't change local directory to "