summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog10
-rw-r--r--sftp.c449
2 files changed, 286 insertions, 173 deletions
diff --git a/ChangeLog b/ChangeLog
index d23839172..1f9f5fafc 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -41,6 +41,14 @@
41 [readconf.c] 41 [readconf.c]
42 make sure that both the local and remote port are correct when 42 make sure that both the local and remote port are correct when
43 parsing -L; Jan Pechanec (bz #1378) 43 parsing -L; Jan Pechanec (bz #1378)
44 - djm@cvs.openbsd.org 2007/10/24 03:30:02
45 [sftp.c]
46 rework argument splitting and parsing to cope correctly with common
47 shell escapes and make handling of escaped characters consistent
48 with sh(1) and between sftp commands (especially between ones that
49 glob their arguments and ones that don't).
50 parse command flags using getopt(3) rather than hand-rolled parsers.
51 ok dtucker@
44 52
4520070927 5320070927
46 - (dtucker) [configure.ac atomicio.c] Fall back to including <sys/poll.h> if 54 - (dtucker) [configure.ac atomicio.c] Fall back to including <sys/poll.h> if
@@ -3312,4 +3320,4 @@
3312 OpenServer 6 and add osr5bigcrypt support so when someone migrates 3320 OpenServer 6 and add osr5bigcrypt support so when someone migrates
3313 passwords between UnixWare and OpenServer they will still work. OK dtucker@ 3321 passwords between UnixWare and OpenServer they will still work. OK dtucker@
3314 3322
3315$Id: ChangeLog,v 1.4769 2007/10/26 04:27:22 djm Exp $ 3323$Id: ChangeLog,v 1.4770 2007/10/26 04:27:45 djm Exp $
diff --git a/sftp.c b/sftp.c
index f0d5dd557..e8113875e 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.97 2007/10/24 03:30:02 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 *
@@ -26,6 +26,7 @@
26#include <sys/socket.h> 26#include <sys/socket.h>
27#include <sys/wait.h> 27#include <sys/wait.h>
28 28
29#include <ctype.h>
29#include <errno.h> 30#include <errno.h>
30 31
31#ifdef HAVE_PATHS_H 32#ifdef HAVE_PATHS_H
@@ -346,144 +347,78 @@ infer_path(const char *p, char **ifp)
346} 347}
347 348
348static int 349static int
349parse_getput_flags(const char **cpp, int *pflag) 350parse_getput_flags(const char *cmd, char **argv, int argc, int *pflag)
350{ 351{
351 const char *cp = *cpp; 352 extern int optind, optreset, opterr;
353 int ch;
352 354
353 /* Check for flags */ 355 optind = optreset = 1;
354 if (cp[0] == '-' && cp[1] && strchr(WHITESPACE, cp[2])) { 356 opterr = 0;
355 switch (cp[1]) { 357
358 *pflag = 0;
359 while ((ch = getopt(argc, argv, "Pp")) != -1) {
360 switch (ch) {
356 case 'p': 361 case 'p':
357 case 'P': 362 case 'P':
358 *pflag = 1; 363 *pflag = 1;
359 break; 364 break;
360 default: 365 default:
361 error("Invalid flag -%c", cp[1]); 366 error("%s: Invalid flag -%c", cmd, ch);
362 return(-1); 367 return -1;
363 }
364 cp += 2;
365 *cpp = cp + strspn(cp, WHITESPACE);
366 }
367
368 return(0);
369}
370
371static int
372parse_ls_flags(const char **cpp, int *lflag)
373{
374 const char *cp = *cpp;
375
376 /* Defaults */
377 *lflag = LS_NAME_SORT;
378
379 /* Check for flags */
380 if (cp++[0] == '-') {
381 for (; strchr(WHITESPACE, *cp) == NULL; cp++) {
382 switch (*cp) {
383 case 'l':
384 *lflag &= ~VIEW_FLAGS;
385 *lflag |= LS_LONG_VIEW;
386 break;
387 case '1':
388 *lflag &= ~VIEW_FLAGS;
389 *lflag |= LS_SHORT_VIEW;
390 break;
391 case 'n':
392 *lflag &= ~VIEW_FLAGS;
393 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
394 break;
395 case 'S':
396 *lflag &= ~SORT_FLAGS;
397 *lflag |= LS_SIZE_SORT;
398 break;
399 case 't':
400 *lflag &= ~SORT_FLAGS;
401 *lflag |= LS_TIME_SORT;
402 break;
403 case 'r':
404 *lflag |= LS_REVERSE_SORT;
405 break;
406 case 'f':
407 *lflag &= ~SORT_FLAGS;
408 break;
409 case 'a':
410 *lflag |= LS_SHOW_ALL;
411 break;
412 default:
413 error("Invalid flag -%c", *cp);
414 return(-1);
415 }
416 } 368 }
417 *cpp = cp + strspn(cp, WHITESPACE);
418 } 369 }
419 370
420 return(0); 371 return optind;
421} 372}
422 373
423static int 374static int
424get_pathname(const char **cpp, char **path) 375parse_ls_flags(char **argv, int argc, int *lflag)
425{ 376{
426 const char *cp = *cpp, *end; 377 extern int optind, optreset, opterr;
427 char quot; 378 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 379
439 /* Check for quoted filenames */ 380 optind = optreset = 1;
440 if (*cp == '\"' || *cp == '\'') { 381 opterr = 0;
441 quot = *cp++;
442 382
443 /* Search for terminating quote, unescape some chars */ 383 *lflag = LS_NAME_SORT;
444 for (i = j = 0; i <= strlen(cp); i++) { 384 while ((ch = getopt(argc, argv, "1Saflnrt")) != -1) {
445 if (cp[i] == quot) { /* Found quote */ 385 switch (ch) {
446 i++; 386 case '1':
447 (*path)[j] = '\0'; 387 *lflag &= ~VIEW_FLAGS;
448 break; 388 *lflag |= LS_SHORT_VIEW;
449 } 389 break;
450 if (cp[i] == '\0') { /* End of string */ 390 case 'S':
451 error("Unterminated quote"); 391 *lflag &= ~SORT_FLAGS;
452 goto fail; 392 *lflag |= LS_SIZE_SORT;
453 } 393 break;
454 if (cp[i] == '\\') { /* Escaped characters */ 394 case 'a':
455 i++; 395 *lflag |= LS_SHOW_ALL;
456 if (cp[i] != '\'' && cp[i] != '\"' && 396 break;
457 cp[i] != '\\') { 397 case 'f':
458 error("Bad escaped character '\\%c'", 398 *lflag &= ~SORT_FLAGS;
459 cp[i]); 399 break;
460 goto fail; 400 case 'l':
461 } 401 *lflag &= ~VIEW_FLAGS;
462 } 402 *lflag |= LS_LONG_VIEW;
463 (*path)[j++] = cp[i]; 403 break;
464 } 404 case 'n':
465 405 *lflag &= ~VIEW_FLAGS;
466 if (j == 0) { 406 *lflag |= LS_NUMERIC_VIEW|LS_LONG_VIEW;
467 error("Empty quotes"); 407 break;
468 goto fail; 408 case 'r':
409 *lflag |= LS_REVERSE_SORT;
410 break;
411 case 't':
412 *lflag &= ~SORT_FLAGS;
413 *lflag |= LS_TIME_SORT;
414 break;
415 default:
416 error("ls: Invalid flag -%c", ch);
417 return -1;
469 } 418 }
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 } 419 }
481 return (0);
482 420
483 fail: 421 return optind;
484 xfree(*path);
485 *path = NULL;
486 return (-1);
487} 422}
488 423
489static int 424static int
@@ -866,15 +801,189 @@ do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
866 return (0); 801 return (0);
867} 802}
868 803
804/*
805 * Undo escaping of glob sequences in place. Used to undo extra escaping
806 * applied in makeargv() when the string is destined for a function that
807 * does not glob it.
808 */
809static void
810undo_glob_escape(char *s)
811{
812 size_t i, j;
813
814 for (i = j = 0;;) {
815 if (s[i] == '\0') {
816 s[j] = '\0';
817 return;
818 }
819 if (s[i] != '\\') {
820 s[j++] = s[i++];
821 continue;
822 }
823 /* s[i] == '\\' */
824 ++i;
825 switch (s[i]) {
826 case '?':
827 case '[':
828 case '*':
829 case '\\':
830 s[j++] = s[i++];
831 break;
832 case '\0':
833 s[j++] = '\\';
834 s[j] = '\0';
835 return;
836 default:
837 s[j++] = '\\';
838 s[j++] = s[i++];
839 break;
840 }
841 }
842}
843
844/*
845 * Split a string into an argument vector using sh(1)-style quoting,
846 * comment and escaping rules, but with some tweaks to handle glob(3)
847 * wildcards.
848 * Returns NULL on error or a NULL-terminated array of arguments.
849 */
850#define MAXARGS 128
851#define MAXARGLEN 8192
852static char **
853makeargv(const char *arg, int *argcp)
854{
855 int argc, quot;
856 size_t i, j;
857 static char argvs[MAXARGLEN];
858 static char *argv[MAXARGS + 1];
859 enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q;
860
861 *argcp = argc = 0;
862 if (strlen(arg) > sizeof(argvs) - 1) {
863 args_too_longs:
864 error("string too long");
865 return NULL;
866 }
867 state = MA_START;
868 i = j = 0;
869 for (;;) {
870 if (isspace(arg[i])) {
871 if (state == MA_UNQUOTED) {
872 /* Terminate current argument */
873 argvs[j++] = '\0';
874 argc++;
875 state = MA_START;
876 } else if (state != MA_START)
877 argvs[j++] = arg[i];
878 } else if (arg[i] == '"' || arg[i] == '\'') {
879 q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE;
880 if (state == MA_START) {
881 argv[argc] = argvs + j;
882 state = q;
883 } else if (state == MA_UNQUOTED)
884 state = q;
885 else if (state == q)
886 state = MA_UNQUOTED;
887 else
888 argvs[j++] = arg[i];
889 } else if (arg[i] == '\\') {
890 if (state == MA_SQUOTE || state == MA_DQUOTE) {
891 quot = state == MA_SQUOTE ? '\'' : '"';
892 /* Unescape quote we are in */
893 /* XXX support \n and friends? */
894 if (arg[i + 1] == quot) {
895 i++;
896 argvs[j++] = arg[i];
897 } else if (arg[i + 1] == '?' ||
898 arg[i + 1] == '[' || arg[i + 1] == '*') {
899 /*
900 * Special case for sftp: append
901 * double-escaped glob sequence -
902 * glob will undo one level of
903 * escaping. NB. string can grow here.
904 */
905 if (j >= sizeof(argvs) - 5)
906 goto args_too_longs;
907 argvs[j++] = '\\';
908 argvs[j++] = arg[i++];
909 argvs[j++] = '\\';
910 argvs[j++] = arg[i];
911 } else {
912 argvs[j++] = arg[i++];
913 argvs[j++] = arg[i];
914 }
915 } else {
916 if (state == MA_START) {
917 argv[argc] = argvs + j;
918 state = MA_UNQUOTED;
919 }
920 if (arg[i + 1] == '?' || arg[i + 1] == '[' ||
921 arg[i + 1] == '*' || arg[i + 1] == '\\') {
922 /*
923 * Special case for sftp: append
924 * escaped glob sequence -
925 * glob will undo one level of
926 * escaping.
927 */
928 argvs[j++] = arg[i++];
929 argvs[j++] = arg[i];
930 } else {
931 /* Unescape everything */
932 /* XXX support \n and friends? */
933 i++;
934 argvs[j++] = arg[i];
935 }
936 }
937 } else if (arg[i] == '#') {
938 if (state == MA_SQUOTE || state == MA_DQUOTE)
939 argvs[j++] = arg[i];
940 else
941 goto string_done;
942 } else if (arg[i] == '\0') {
943 if (state == MA_SQUOTE || state == MA_DQUOTE) {
944 error("Unterminated quoted argument");
945 return NULL;
946 }
947 string_done:
948 if (state == MA_UNQUOTED) {
949 argvs[j++] = '\0';
950 argc++;
951 }
952 break;
953 } else {
954 if (state == MA_START) {
955 argv[argc] = argvs + j;
956 state = MA_UNQUOTED;
957 }
958 if ((state == MA_SQUOTE || state == MA_DQUOTE) &&
959 (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) {
960 /*
961 * Special case for sftp: escape quoted
962 * glob(3) wildcards. NB. string can grow
963 * here.
964 */
965 if (j >= sizeof(argvs) - 3)
966 goto args_too_longs;
967 argvs[j++] = '\\';
968 argvs[j++] = arg[i];
969 } else
970 argvs[j++] = arg[i];
971 }
972 i++;
973 }
974 *argcp = argc;
975 return argv;
976}
977
869static int 978static int
870parse_args(const char **cpp, int *pflag, int *lflag, int *iflag, 979parse_args(const char **cpp, int *pflag, int *lflag, int *iflag,
871 unsigned long *n_arg, char **path1, char **path2) 980 unsigned long *n_arg, char **path1, char **path2)
872{ 981{
873 const char *cmd, *cp = *cpp; 982 const char *cmd, *cp = *cpp;
874 char *cp2; 983 char *cp2, **argv;
875 int base = 0; 984 int base = 0;
876 long l; 985 long l;
877 int i, cmdnum; 986 int i, cmdnum, optidx, argc;
878 987
879 /* Skip leading whitespace */ 988 /* Skip leading whitespace */
880 cp = cp + strspn(cp, WHITESPACE); 989 cp = cp + strspn(cp, WHITESPACE);
@@ -890,17 +999,13 @@ parse_args(const char **cpp, int *pflag, int *lflag, int *iflag,
890 cp++; 999 cp++;
891 } 1000 }
892 1001
1002 if ((argv = makeargv(cp, &argc)) == NULL)
1003 return -1;
1004
893 /* Figure out which command we have */ 1005 /* Figure out which command we have */
894 for (i = 0; cmds[i].c; i++) { 1006 for (i = 0; cmds[i].c != NULL; i++) {
895 int cmdlen = strlen(cmds[i].c); 1007 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; 1008 break;
903 }
904 } 1009 }
905 cmdnum = cmds[i].n; 1010 cmdnum = cmds[i].n;
906 cmd = cmds[i].c; 1011 cmd = cmds[i].c;
@@ -911,40 +1016,44 @@ parse_args(const char **cpp, int *pflag, int *lflag, int *iflag,
911 cmdnum = I_SHELL; 1016 cmdnum = I_SHELL;
912 } else if (cmdnum == -1) { 1017 } else if (cmdnum == -1) {
913 error("Invalid command."); 1018 error("Invalid command.");
914 return (-1); 1019 return -1;
915 } 1020 }
916 1021
917 /* Get arguments and parse flags */ 1022 /* Get arguments and parse flags */
918 *lflag = *pflag = *n_arg = 0; 1023 *lflag = *pflag = *n_arg = 0;
919 *path1 = *path2 = NULL; 1024 *path1 = *path2 = NULL;
1025 optidx = 1;
920 switch (cmdnum) { 1026 switch (cmdnum) {
921 case I_GET: 1027 case I_GET:
922 case I_PUT: 1028 case I_PUT:
923 if (parse_getput_flags(&cp, pflag)) 1029 if ((optidx = parse_getput_flags(cmd, argv, argc, pflag)) == -1)
924 return(-1); 1030 return -1;
925 /* Get first pathname (mandatory) */ 1031 /* Get first pathname (mandatory) */
926 if (get_pathname(&cp, path1)) 1032 if (argc - optidx < 1) {
927 return(-1);
928 if (*path1 == NULL) {
929 error("You must specify at least one path after a " 1033 error("You must specify at least one path after a "
930 "%s command.", cmd); 1034 "%s command.", cmd);
931 return(-1); 1035 return -1;
1036 }
1037 *path1 = xstrdup(argv[optidx]);
1038 /* Get second pathname (optional) */
1039 if (argc - optidx > 1) {
1040 *path2 = xstrdup(argv[optidx + 1]);
1041 /* Destination is not globbed */
1042 undo_glob_escape(*path2);
932 } 1043 }
933 /* Try to get second pathname (optional) */
934 if (get_pathname(&cp, path2))
935 return(-1);
936 break; 1044 break;
937 case I_RENAME: 1045 case I_RENAME:
938 case I_SYMLINK: 1046 case I_SYMLINK:
939 if (get_pathname(&cp, path1)) 1047 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 " 1048 error("You must specify two paths after a %s "
945 "command.", cmd); 1049 "command.", cmd);
946 return(-1); 1050 return -1;
947 } 1051 }
1052 *path1 = xstrdup(argv[optidx]);
1053 *path2 = xstrdup(argv[optidx + 1]);
1054 /* Paths are not globbed */
1055 undo_glob_escape(*path1);
1056 undo_glob_escape(*path2);
948 break; 1057 break;
949 case I_RM: 1058 case I_RM:
950 case I_MKDIR: 1059 case I_MKDIR:
@@ -953,59 +1062,55 @@ parse_args(const char **cpp, int *pflag, int *lflag, int *iflag,
953 case I_LCHDIR: 1062 case I_LCHDIR:
954 case I_LMKDIR: 1063 case I_LMKDIR:
955 /* Get pathname (mandatory) */ 1064 /* Get pathname (mandatory) */
956 if (get_pathname(&cp, path1)) 1065 if (argc - optidx < 1) {
957 return(-1);
958 if (*path1 == NULL) {
959 error("You must specify a path after a %s command.", 1066 error("You must specify a path after a %s command.",
960 cmd); 1067 cmd);
961 return(-1); 1068 return -1;
962 } 1069 }
1070 *path1 = xstrdup(argv[optidx]);
1071 /* Only "rm" globs */
1072 if (cmdnum != I_RM)
1073 undo_glob_escape(*path1);
963 break; 1074 break;
964 case I_LS: 1075 case I_LS:
965 if (parse_ls_flags(&cp, lflag)) 1076 if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1)
966 return(-1); 1077 return(-1);
967 /* Path is optional */ 1078 /* Path is optional */
968 if (get_pathname(&cp, path1)) 1079 if (argc - optidx > 0)
969 return(-1); 1080 *path1 = xstrdup(argv[optidx]);
970 break; 1081 break;
971 case I_LLS: 1082 case I_LLS:
972 case I_SHELL: 1083 case I_SHELL:
973 /* Uses the rest of the line */ 1084 /* Uses the rest of the line */
974 break; 1085 break;
975 case I_LUMASK: 1086 case I_LUMASK:
976 base = 8;
977 case I_CHMOD: 1087 case I_CHMOD:
978 base = 8; 1088 base = 8;
979 case I_CHOWN: 1089 case I_CHOWN:
980 case I_CHGRP: 1090 case I_CHGRP:
981 /* Get numeric arg (mandatory) */ 1091 /* Get numeric arg (mandatory) */
1092 if (argc - optidx < 1)
1093 goto need_num_arg;
982 errno = 0; 1094 errno = 0;
983 l = strtol(cp, &cp2, base); 1095 l = strtol(argv[optidx], &cp2, base);
984 if (cp2 == cp || ((l == LONG_MIN || l == LONG_MAX) && 1096 if (cp2 == argv[optidx] || *cp2 != '\0' ||
985 errno == ERANGE) || l < 0) { 1097 ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) ||
1098 l < 0) {
1099 need_num_arg:
986 error("You must supply a numeric argument " 1100 error("You must supply a numeric argument "
987 "to the %s command.", cmd); 1101 "to the %s command.", cmd);
988 return(-1); 1102 return -1;
989 } 1103 }
990 cp = cp2;
991 *n_arg = l; 1104 *n_arg = l;
992 if (cmdnum == I_LUMASK && strchr(WHITESPACE, *cp)) 1105 if (cmdnum == I_LUMASK)
993 break; 1106 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) */ 1107 /* Get pathname (mandatory) */
1002 if (get_pathname(&cp, path1)) 1108 if (argc - optidx < 2) {
1003 return(-1);
1004 if (*path1 == NULL) {
1005 error("You must specify a path after a %s command.", 1109 error("You must specify a path after a %s command.",
1006 cmd); 1110 cmd);
1007 return(-1); 1111 return -1;
1008 } 1112 }
1113 *path1 = xstrdup(argv[optidx + 1]);
1009 break; 1114 break;
1010 case I_QUIT: 1115 case I_QUIT:
1011 case I_PWD: 1116 case I_PWD: