summaryrefslogtreecommitdiff
path: root/scp.c
diff options
context:
space:
mode:
authordjm@openbsd.org <djm@openbsd.org>2019-02-10 11:15:52 +0000
committerColin Watson <cjwatson@debian.org>2019-03-01 09:56:14 +0000
commit7a3fa37583d4abf128f7f4c6eb1e7ffc90115eab (patch)
treebe44e8b1f89b2b8880f25add29fb49ed1dde4eb3 /scp.c
parentd94226d4fcefbc398c5583e12b5d07ca33884ba4 (diff)
upstream: when checking that filenames sent by the server side
match what the client requested, be prepared to handle shell-style brace alternations, e.g. "{foo,bar}". "looks good to me" millert@ + in snaps for the last week courtesy deraadt@ OpenBSD-Commit-ID: 3b1ce7639b0b25b2248e3a30f561a548f6815f3e Origin: upstream, https://anongit.mindrot.org/openssh.git/commit/?id=3d896c157c722bc47adca51a58dca859225b5874 Bug-Debian: https://bugs.debian.org/923486 Last-Update: 2019-03-01 Patch-Name: scp-handle-braces.patch
Diffstat (limited to 'scp.c')
-rw-r--r--scp.c280
1 files changed, 269 insertions, 11 deletions
diff --git a/scp.c b/scp.c
index 035037bcc..3888baab0 100644
--- a/scp.c
+++ b/scp.c
@@ -635,6 +635,253 @@ parse_scp_uri(const char *uri, char **userp, char **hostp, int *portp,
635 return r; 635 return r;
636} 636}
637 637
638/* Appends a string to an array; returns 0 on success, -1 on alloc failure */
639static int
640append(char *cp, char ***ap, size_t *np)
641{
642 char **tmp;
643
644 if ((tmp = reallocarray(*ap, *np + 1, sizeof(*tmp))) == NULL)
645 return -1;
646 tmp[(*np)] = cp;
647 (*np)++;
648 *ap = tmp;
649 return 0;
650}
651
652/*
653 * Finds the start and end of the first brace pair in the pattern.
654 * returns 0 on success or -1 for invalid patterns.
655 */
656static int
657find_brace(const char *pattern, int *startp, int *endp)
658{
659 int i;
660 int in_bracket, brace_level;
661
662 *startp = *endp = -1;
663 in_bracket = brace_level = 0;
664 for (i = 0; i < INT_MAX && *endp < 0 && pattern[i] != '\0'; i++) {
665 switch (pattern[i]) {
666 case '\\':
667 /* skip next character */
668 if (pattern[i + 1] != '\0')
669 i++;
670 break;
671 case '[':
672 in_bracket = 1;
673 break;
674 case ']':
675 in_bracket = 0;
676 break;
677 case '{':
678 if (in_bracket)
679 break;
680 if (pattern[i + 1] == '}') {
681 /* Protect a single {}, for find(1), like csh */
682 i++; /* skip */
683 break;
684 }
685 if (*startp == -1)
686 *startp = i;
687 brace_level++;
688 break;
689 case '}':
690 if (in_bracket)
691 break;
692 if (*startp < 0) {
693 /* Unbalanced brace */
694 return -1;
695 }
696 if (--brace_level <= 0)
697 *endp = i;
698 break;
699 }
700 }
701 /* unbalanced brackets/braces */
702 if (*endp < 0 && (*startp >= 0 || in_bracket))
703 return -1;
704 return 0;
705}
706
707/*
708 * Assembles and records a successfully-expanded pattern, returns -1 on
709 * alloc failure.
710 */
711static int
712emit_expansion(const char *pattern, int brace_start, int brace_end,
713 int sel_start, int sel_end, char ***patternsp, size_t *npatternsp)
714{
715 char *cp;
716 int o = 0, tail_len = strlen(pattern + brace_end + 1);
717
718 if ((cp = malloc(brace_start + (sel_end - sel_start) +
719 tail_len + 1)) == NULL)
720 return -1;
721
722 /* Pattern before initial brace */
723 if (brace_start > 0) {
724 memcpy(cp, pattern, brace_start);
725 o = brace_start;
726 }
727 /* Current braced selection */
728 if (sel_end - sel_start > 0) {
729 memcpy(cp + o, pattern + sel_start,
730 sel_end - sel_start);
731 o += sel_end - sel_start;
732 }
733 /* Remainder of pattern after closing brace */
734 if (tail_len > 0) {
735 memcpy(cp + o, pattern + brace_end + 1, tail_len);
736 o += tail_len;
737 }
738 cp[o] = '\0';
739 if (append(cp, patternsp, npatternsp) != 0) {
740 free(cp);
741 return -1;
742 }
743 return 0;
744}
745
746/*
747 * Expand the first encountered brace in pattern, appending the expanded
748 * patterns it yielded to the *patternsp array.
749 *
750 * Returns 0 on success or -1 on allocation failure.
751 *
752 * Signals whether expansion was performed via *expanded and whether
753 * pattern was invalid via *invalid.
754 */
755static int
756brace_expand_one(const char *pattern, char ***patternsp, size_t *npatternsp,
757 int *expanded, int *invalid)
758{
759 int i;
760 int in_bracket, brace_start, brace_end, brace_level;
761 int sel_start, sel_end;
762
763 *invalid = *expanded = 0;
764
765 if (find_brace(pattern, &brace_start, &brace_end) != 0) {
766 *invalid = 1;
767 return 0;
768 } else if (brace_start == -1)
769 return 0;
770
771 in_bracket = brace_level = 0;
772 for (i = sel_start = brace_start + 1; i < brace_end; i++) {
773 switch (pattern[i]) {
774 case '{':
775 if (in_bracket)
776 break;
777 brace_level++;
778 break;
779 case '}':
780 if (in_bracket)
781 break;
782 brace_level--;
783 break;
784 case '[':
785 in_bracket = 1;
786 break;
787 case ']':
788 in_bracket = 0;
789 break;
790 case '\\':
791 if (i < brace_end - 1)
792 i++; /* skip */
793 break;
794 }
795 if (pattern[i] == ',' || i == brace_end - 1) {
796 if (in_bracket || brace_level > 0)
797 continue;
798 /* End of a selection, emit an expanded pattern */
799
800 /* Adjust end index for last selection */
801 sel_end = (i == brace_end - 1) ? brace_end : i;
802 if (emit_expansion(pattern, brace_start, brace_end,
803 sel_start, sel_end, patternsp, npatternsp) != 0)
804 return -1;
805 /* move on to the next selection */
806 sel_start = i + 1;
807 continue;
808 }
809 }
810 if (in_bracket || brace_level > 0) {
811 *invalid = 1;
812 return 0;
813 }
814 /* success */
815 *expanded = 1;
816 return 0;
817}
818
819/* Expand braces from pattern. Returns 0 on success, -1 on failure */
820static int
821brace_expand(const char *pattern, char ***patternsp, size_t *npatternsp)
822{
823 char *cp, *cp2, **active = NULL, **done = NULL;
824 size_t i, nactive = 0, ndone = 0;
825 int ret = -1, invalid = 0, expanded = 0;
826
827 *patternsp = NULL;
828 *npatternsp = 0;
829
830 /* Start the worklist with the original pattern */
831 if ((cp = strdup(pattern)) == NULL)
832 return -1;
833 if (append(cp, &active, &nactive) != 0) {
834 free(cp);
835 return -1;
836 }
837 while (nactive > 0) {
838 cp = active[nactive - 1];
839 nactive--;
840 if (brace_expand_one(cp, &active, &nactive,
841 &expanded, &invalid) == -1) {
842 free(cp);
843 goto fail;
844 }
845 if (invalid)
846 fatal("%s: invalid brace pattern \"%s\"", __func__, cp);
847 if (expanded) {
848 /*
849 * Current entry expanded to new entries on the
850 * active list; discard the progenitor pattern.
851 */
852 free(cp);
853 continue;
854 }
855 /*
856 * Pattern did not expand; append the finename component to
857 * the completed list
858 */
859 if ((cp2 = strrchr(cp, '/')) != NULL)
860 *cp2++ = '\0';
861 else
862 cp2 = cp;
863 if (append(xstrdup(cp2), &done, &ndone) != 0) {
864 free(cp);
865 goto fail;
866 }
867 free(cp);
868 }
869 /* success */
870 *patternsp = done;
871 *npatternsp = ndone;
872 done = NULL;
873 ndone = 0;
874 ret = 0;
875 fail:
876 for (i = 0; i < nactive; i++)
877 free(active[i]);
878 free(active);
879 for (i = 0; i < ndone; i++)
880 free(done[i]);
881 free(done);
882 return ret;
883}
884
638void 885void
639toremote(int argc, char **argv) 886toremote(int argc, char **argv)
640{ 887{
@@ -998,7 +1245,8 @@ sink(int argc, char **argv, const char *src)
998 unsigned long long ull; 1245 unsigned long long ull;
999 int setimes, targisdir, wrerrno = 0; 1246 int setimes, targisdir, wrerrno = 0;
1000 char ch, *cp, *np, *targ, *why, *vect[1], buf[2048], visbuf[2048]; 1247 char ch, *cp, *np, *targ, *why, *vect[1], buf[2048], visbuf[2048];
1001 char *src_copy = NULL, *restrict_pattern = NULL; 1248 char **patterns = NULL;
1249 size_t n, npatterns = 0;
1002 struct timeval tv[2]; 1250 struct timeval tv[2];
1003 1251
1004#define atime tv[0] 1252#define atime tv[0]
@@ -1028,16 +1276,13 @@ sink(int argc, char **argv, const char *src)
1028 * Prepare to try to restrict incoming filenames to match 1276 * Prepare to try to restrict incoming filenames to match
1029 * the requested destination file glob. 1277 * the requested destination file glob.
1030 */ 1278 */
1031 if ((src_copy = strdup(src)) == NULL) 1279 if (brace_expand(src, &patterns, &npatterns) != 0)
1032 fatal("strdup failed"); 1280 fatal("%s: could not expand pattern", __func__);
1033 if ((restrict_pattern = strrchr(src_copy, '/')) != NULL) {
1034 *restrict_pattern++ = '\0';
1035 }
1036 } 1281 }
1037 for (first = 1;; first = 0) { 1282 for (first = 1;; first = 0) {
1038 cp = buf; 1283 cp = buf;
1039 if (atomicio(read, remin, cp, 1) != 1) 1284 if (atomicio(read, remin, cp, 1) != 1)
1040 return; 1285 goto done;
1041 if (*cp++ == '\n') 1286 if (*cp++ == '\n')
1042 SCREWUP("unexpected <newline>"); 1287 SCREWUP("unexpected <newline>");
1043 do { 1288 do {
@@ -1063,7 +1308,7 @@ sink(int argc, char **argv, const char *src)
1063 } 1308 }
1064 if (buf[0] == 'E') { 1309 if (buf[0] == 'E') {
1065 (void) atomicio(vwrite, remout, "", 1); 1310 (void) atomicio(vwrite, remout, "", 1);
1066 return; 1311 goto done;
1067 } 1312 }
1068 if (ch == '\n') 1313 if (ch == '\n')
1069 *--cp = 0; 1314 *--cp = 0;
@@ -1138,9 +1383,14 @@ sink(int argc, char **argv, const char *src)
1138 run_err("error: unexpected filename: %s", cp); 1383 run_err("error: unexpected filename: %s", cp);
1139 exit(1); 1384 exit(1);
1140 } 1385 }
1141 if (restrict_pattern != NULL && 1386 if (npatterns > 0) {
1142 fnmatch(restrict_pattern, cp, 0) != 0) 1387 for (n = 0; n < npatterns; n++) {
1143 SCREWUP("filename does not match request"); 1388 if (fnmatch(patterns[n], cp, 0) == 0)
1389 break;
1390 }
1391 if (n >= npatterns)
1392 SCREWUP("filename does not match request");
1393 }
1144 if (targisdir) { 1394 if (targisdir) {
1145 static char *namebuf; 1395 static char *namebuf;
1146 static size_t cursize; 1396 static size_t cursize;
@@ -1299,7 +1549,15 @@ bad: run_err("%s: %s", np, strerror(errno));
1299 break; 1549 break;
1300 } 1550 }
1301 } 1551 }
1552done:
1553 for (n = 0; n < npatterns; n++)
1554 free(patterns[n]);
1555 free(patterns);
1556 return;
1302screwup: 1557screwup:
1558 for (n = 0; n < npatterns; n++)
1559 free(patterns[n]);
1560 free(patterns);
1303 run_err("protocol error: %s", why); 1561 run_err("protocol error: %s", why);
1304 exit(1); 1562 exit(1);
1305} 1563}