diff options
author | Colin Watson <cjwatson@debian.org> | 2019-03-01 09:57:53 +0000 |
---|---|---|
committer | Colin Watson <cjwatson@debian.org> | 2019-03-01 09:58:48 +0000 |
commit | bc8b39092a460d8e7e9d8d42d32f2c964d18fec3 (patch) | |
tree | 258baf7478e0e5737351c704c6313d5973cdd81f | |
parent | b85f89c2c0ee71d4eee9c5ce3753eb89bc5fc715 (diff) | |
parent | 7a3fa37583d4abf128f7f4c6eb1e7ffc90115eab (diff) |
Handle shell-style brace expansions in scp checks
-rw-r--r-- | debian/.git-dpm | 4 | ||||
-rw-r--r-- | debian/changelog | 8 | ||||
-rw-r--r-- | debian/patches/scp-handle-braces.patch | 353 | ||||
-rw-r--r-- | debian/patches/series | 1 | ||||
-rw-r--r-- | scp.c | 280 |
5 files changed, 633 insertions, 13 deletions
diff --git a/debian/.git-dpm b/debian/.git-dpm index 42208f2de..6e6c8addb 100644 --- a/debian/.git-dpm +++ b/debian/.git-dpm | |||
@@ -1,6 +1,6 @@ | |||
1 | # see git-dpm(1) from git-dpm package | 1 | # see git-dpm(1) from git-dpm package |
2 | d94226d4fcefbc398c5583e12b5d07ca33884ba4 | 2 | 7a3fa37583d4abf128f7f4c6eb1e7ffc90115eab |
3 | d94226d4fcefbc398c5583e12b5d07ca33884ba4 | 3 | 7a3fa37583d4abf128f7f4c6eb1e7ffc90115eab |
4 | 3d246f10429fc9a37b98eabef94fe8dc7c61002b | 4 | 3d246f10429fc9a37b98eabef94fe8dc7c61002b |
5 | 3d246f10429fc9a37b98eabef94fe8dc7c61002b | 5 | 3d246f10429fc9a37b98eabef94fe8dc7c61002b |
6 | openssh_7.9p1.orig.tar.gz | 6 | openssh_7.9p1.orig.tar.gz |
diff --git a/debian/changelog b/debian/changelog index 418d98d2f..614b4e9cf 100644 --- a/debian/changelog +++ b/debian/changelog | |||
@@ -1,3 +1,11 @@ | |||
1 | openssh (1:7.9p1-9) UNRELEASED; urgency=medium | ||
2 | |||
3 | * Apply upstream patch to make scp handle shell-style brace expansions | ||
4 | when checking that filenames sent by the server match what the client | ||
5 | requested (closes: #923486). | ||
6 | |||
7 | -- Colin Watson <cjwatson@debian.org> Fri, 01 Mar 2019 09:57:59 +0000 | ||
8 | |||
1 | openssh (1:7.9p1-8) unstable; urgency=medium | 9 | openssh (1:7.9p1-8) unstable; urgency=medium |
2 | 10 | ||
3 | [ Colin Watson ] | 11 | [ Colin Watson ] |
diff --git a/debian/patches/scp-handle-braces.patch b/debian/patches/scp-handle-braces.patch new file mode 100644 index 000000000..0cbdcfdc0 --- /dev/null +++ b/debian/patches/scp-handle-braces.patch | |||
@@ -0,0 +1,353 @@ | |||
1 | From 7a3fa37583d4abf128f7f4c6eb1e7ffc90115eab Mon Sep 17 00:00:00 2001 | ||
2 | From: "djm@openbsd.org" <djm@openbsd.org> | ||
3 | Date: Sun, 10 Feb 2019 11:15:52 +0000 | ||
4 | Subject: upstream: when checking that filenames sent by the server side | ||
5 | |||
6 | match what the client requested, be prepared to handle shell-style brace | ||
7 | alternations, e.g. "{foo,bar}". | ||
8 | |||
9 | "looks good to me" millert@ + in snaps for the last week courtesy | ||
10 | deraadt@ | ||
11 | |||
12 | OpenBSD-Commit-ID: 3b1ce7639b0b25b2248e3a30f561a548f6815f3e | ||
13 | |||
14 | Origin: upstream, https://anongit.mindrot.org/openssh.git/commit/?id=3d896c157c722bc47adca51a58dca859225b5874 | ||
15 | Bug-Debian: https://bugs.debian.org/923486 | ||
16 | Last-Update: 2019-03-01 | ||
17 | |||
18 | Patch-Name: scp-handle-braces.patch | ||
19 | --- | ||
20 | scp.c | 280 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--- | ||
21 | 1 file changed, 269 insertions(+), 11 deletions(-) | ||
22 | |||
23 | diff --git a/scp.c b/scp.c | ||
24 | index 035037bcc..3888baab0 100644 | ||
25 | --- a/scp.c | ||
26 | +++ b/scp.c | ||
27 | @@ -635,6 +635,253 @@ parse_scp_uri(const char *uri, char **userp, char **hostp, int *portp, | ||
28 | return r; | ||
29 | } | ||
30 | |||
31 | +/* Appends a string to an array; returns 0 on success, -1 on alloc failure */ | ||
32 | +static int | ||
33 | +append(char *cp, char ***ap, size_t *np) | ||
34 | +{ | ||
35 | + char **tmp; | ||
36 | + | ||
37 | + if ((tmp = reallocarray(*ap, *np + 1, sizeof(*tmp))) == NULL) | ||
38 | + return -1; | ||
39 | + tmp[(*np)] = cp; | ||
40 | + (*np)++; | ||
41 | + *ap = tmp; | ||
42 | + return 0; | ||
43 | +} | ||
44 | + | ||
45 | +/* | ||
46 | + * Finds the start and end of the first brace pair in the pattern. | ||
47 | + * returns 0 on success or -1 for invalid patterns. | ||
48 | + */ | ||
49 | +static int | ||
50 | +find_brace(const char *pattern, int *startp, int *endp) | ||
51 | +{ | ||
52 | + int i; | ||
53 | + int in_bracket, brace_level; | ||
54 | + | ||
55 | + *startp = *endp = -1; | ||
56 | + in_bracket = brace_level = 0; | ||
57 | + for (i = 0; i < INT_MAX && *endp < 0 && pattern[i] != '\0'; i++) { | ||
58 | + switch (pattern[i]) { | ||
59 | + case '\\': | ||
60 | + /* skip next character */ | ||
61 | + if (pattern[i + 1] != '\0') | ||
62 | + i++; | ||
63 | + break; | ||
64 | + case '[': | ||
65 | + in_bracket = 1; | ||
66 | + break; | ||
67 | + case ']': | ||
68 | + in_bracket = 0; | ||
69 | + break; | ||
70 | + case '{': | ||
71 | + if (in_bracket) | ||
72 | + break; | ||
73 | + if (pattern[i + 1] == '}') { | ||
74 | + /* Protect a single {}, for find(1), like csh */ | ||
75 | + i++; /* skip */ | ||
76 | + break; | ||
77 | + } | ||
78 | + if (*startp == -1) | ||
79 | + *startp = i; | ||
80 | + brace_level++; | ||
81 | + break; | ||
82 | + case '}': | ||
83 | + if (in_bracket) | ||
84 | + break; | ||
85 | + if (*startp < 0) { | ||
86 | + /* Unbalanced brace */ | ||
87 | + return -1; | ||
88 | + } | ||
89 | + if (--brace_level <= 0) | ||
90 | + *endp = i; | ||
91 | + break; | ||
92 | + } | ||
93 | + } | ||
94 | + /* unbalanced brackets/braces */ | ||
95 | + if (*endp < 0 && (*startp >= 0 || in_bracket)) | ||
96 | + return -1; | ||
97 | + return 0; | ||
98 | +} | ||
99 | + | ||
100 | +/* | ||
101 | + * Assembles and records a successfully-expanded pattern, returns -1 on | ||
102 | + * alloc failure. | ||
103 | + */ | ||
104 | +static int | ||
105 | +emit_expansion(const char *pattern, int brace_start, int brace_end, | ||
106 | + int sel_start, int sel_end, char ***patternsp, size_t *npatternsp) | ||
107 | +{ | ||
108 | + char *cp; | ||
109 | + int o = 0, tail_len = strlen(pattern + brace_end + 1); | ||
110 | + | ||
111 | + if ((cp = malloc(brace_start + (sel_end - sel_start) + | ||
112 | + tail_len + 1)) == NULL) | ||
113 | + return -1; | ||
114 | + | ||
115 | + /* Pattern before initial brace */ | ||
116 | + if (brace_start > 0) { | ||
117 | + memcpy(cp, pattern, brace_start); | ||
118 | + o = brace_start; | ||
119 | + } | ||
120 | + /* Current braced selection */ | ||
121 | + if (sel_end - sel_start > 0) { | ||
122 | + memcpy(cp + o, pattern + sel_start, | ||
123 | + sel_end - sel_start); | ||
124 | + o += sel_end - sel_start; | ||
125 | + } | ||
126 | + /* Remainder of pattern after closing brace */ | ||
127 | + if (tail_len > 0) { | ||
128 | + memcpy(cp + o, pattern + brace_end + 1, tail_len); | ||
129 | + o += tail_len; | ||
130 | + } | ||
131 | + cp[o] = '\0'; | ||
132 | + if (append(cp, patternsp, npatternsp) != 0) { | ||
133 | + free(cp); | ||
134 | + return -1; | ||
135 | + } | ||
136 | + return 0; | ||
137 | +} | ||
138 | + | ||
139 | +/* | ||
140 | + * Expand the first encountered brace in pattern, appending the expanded | ||
141 | + * patterns it yielded to the *patternsp array. | ||
142 | + * | ||
143 | + * Returns 0 on success or -1 on allocation failure. | ||
144 | + * | ||
145 | + * Signals whether expansion was performed via *expanded and whether | ||
146 | + * pattern was invalid via *invalid. | ||
147 | + */ | ||
148 | +static int | ||
149 | +brace_expand_one(const char *pattern, char ***patternsp, size_t *npatternsp, | ||
150 | + int *expanded, int *invalid) | ||
151 | +{ | ||
152 | + int i; | ||
153 | + int in_bracket, brace_start, brace_end, brace_level; | ||
154 | + int sel_start, sel_end; | ||
155 | + | ||
156 | + *invalid = *expanded = 0; | ||
157 | + | ||
158 | + if (find_brace(pattern, &brace_start, &brace_end) != 0) { | ||
159 | + *invalid = 1; | ||
160 | + return 0; | ||
161 | + } else if (brace_start == -1) | ||
162 | + return 0; | ||
163 | + | ||
164 | + in_bracket = brace_level = 0; | ||
165 | + for (i = sel_start = brace_start + 1; i < brace_end; i++) { | ||
166 | + switch (pattern[i]) { | ||
167 | + case '{': | ||
168 | + if (in_bracket) | ||
169 | + break; | ||
170 | + brace_level++; | ||
171 | + break; | ||
172 | + case '}': | ||
173 | + if (in_bracket) | ||
174 | + break; | ||
175 | + brace_level--; | ||
176 | + break; | ||
177 | + case '[': | ||
178 | + in_bracket = 1; | ||
179 | + break; | ||
180 | + case ']': | ||
181 | + in_bracket = 0; | ||
182 | + break; | ||
183 | + case '\\': | ||
184 | + if (i < brace_end - 1) | ||
185 | + i++; /* skip */ | ||
186 | + break; | ||
187 | + } | ||
188 | + if (pattern[i] == ',' || i == brace_end - 1) { | ||
189 | + if (in_bracket || brace_level > 0) | ||
190 | + continue; | ||
191 | + /* End of a selection, emit an expanded pattern */ | ||
192 | + | ||
193 | + /* Adjust end index for last selection */ | ||
194 | + sel_end = (i == brace_end - 1) ? brace_end : i; | ||
195 | + if (emit_expansion(pattern, brace_start, brace_end, | ||
196 | + sel_start, sel_end, patternsp, npatternsp) != 0) | ||
197 | + return -1; | ||
198 | + /* move on to the next selection */ | ||
199 | + sel_start = i + 1; | ||
200 | + continue; | ||
201 | + } | ||
202 | + } | ||
203 | + if (in_bracket || brace_level > 0) { | ||
204 | + *invalid = 1; | ||
205 | + return 0; | ||
206 | + } | ||
207 | + /* success */ | ||
208 | + *expanded = 1; | ||
209 | + return 0; | ||
210 | +} | ||
211 | + | ||
212 | +/* Expand braces from pattern. Returns 0 on success, -1 on failure */ | ||
213 | +static int | ||
214 | +brace_expand(const char *pattern, char ***patternsp, size_t *npatternsp) | ||
215 | +{ | ||
216 | + char *cp, *cp2, **active = NULL, **done = NULL; | ||
217 | + size_t i, nactive = 0, ndone = 0; | ||
218 | + int ret = -1, invalid = 0, expanded = 0; | ||
219 | + | ||
220 | + *patternsp = NULL; | ||
221 | + *npatternsp = 0; | ||
222 | + | ||
223 | + /* Start the worklist with the original pattern */ | ||
224 | + if ((cp = strdup(pattern)) == NULL) | ||
225 | + return -1; | ||
226 | + if (append(cp, &active, &nactive) != 0) { | ||
227 | + free(cp); | ||
228 | + return -1; | ||
229 | + } | ||
230 | + while (nactive > 0) { | ||
231 | + cp = active[nactive - 1]; | ||
232 | + nactive--; | ||
233 | + if (brace_expand_one(cp, &active, &nactive, | ||
234 | + &expanded, &invalid) == -1) { | ||
235 | + free(cp); | ||
236 | + goto fail; | ||
237 | + } | ||
238 | + if (invalid) | ||
239 | + fatal("%s: invalid brace pattern \"%s\"", __func__, cp); | ||
240 | + if (expanded) { | ||
241 | + /* | ||
242 | + * Current entry expanded to new entries on the | ||
243 | + * active list; discard the progenitor pattern. | ||
244 | + */ | ||
245 | + free(cp); | ||
246 | + continue; | ||
247 | + } | ||
248 | + /* | ||
249 | + * Pattern did not expand; append the finename component to | ||
250 | + * the completed list | ||
251 | + */ | ||
252 | + if ((cp2 = strrchr(cp, '/')) != NULL) | ||
253 | + *cp2++ = '\0'; | ||
254 | + else | ||
255 | + cp2 = cp; | ||
256 | + if (append(xstrdup(cp2), &done, &ndone) != 0) { | ||
257 | + free(cp); | ||
258 | + goto fail; | ||
259 | + } | ||
260 | + free(cp); | ||
261 | + } | ||
262 | + /* success */ | ||
263 | + *patternsp = done; | ||
264 | + *npatternsp = ndone; | ||
265 | + done = NULL; | ||
266 | + ndone = 0; | ||
267 | + ret = 0; | ||
268 | + fail: | ||
269 | + for (i = 0; i < nactive; i++) | ||
270 | + free(active[i]); | ||
271 | + free(active); | ||
272 | + for (i = 0; i < ndone; i++) | ||
273 | + free(done[i]); | ||
274 | + free(done); | ||
275 | + return ret; | ||
276 | +} | ||
277 | + | ||
278 | void | ||
279 | toremote(int argc, char **argv) | ||
280 | { | ||
281 | @@ -998,7 +1245,8 @@ sink(int argc, char **argv, const char *src) | ||
282 | unsigned long long ull; | ||
283 | int setimes, targisdir, wrerrno = 0; | ||
284 | char ch, *cp, *np, *targ, *why, *vect[1], buf[2048], visbuf[2048]; | ||
285 | - char *src_copy = NULL, *restrict_pattern = NULL; | ||
286 | + char **patterns = NULL; | ||
287 | + size_t n, npatterns = 0; | ||
288 | struct timeval tv[2]; | ||
289 | |||
290 | #define atime tv[0] | ||
291 | @@ -1028,16 +1276,13 @@ sink(int argc, char **argv, const char *src) | ||
292 | * Prepare to try to restrict incoming filenames to match | ||
293 | * the requested destination file glob. | ||
294 | */ | ||
295 | - if ((src_copy = strdup(src)) == NULL) | ||
296 | - fatal("strdup failed"); | ||
297 | - if ((restrict_pattern = strrchr(src_copy, '/')) != NULL) { | ||
298 | - *restrict_pattern++ = '\0'; | ||
299 | - } | ||
300 | + if (brace_expand(src, &patterns, &npatterns) != 0) | ||
301 | + fatal("%s: could not expand pattern", __func__); | ||
302 | } | ||
303 | for (first = 1;; first = 0) { | ||
304 | cp = buf; | ||
305 | if (atomicio(read, remin, cp, 1) != 1) | ||
306 | - return; | ||
307 | + goto done; | ||
308 | if (*cp++ == '\n') | ||
309 | SCREWUP("unexpected <newline>"); | ||
310 | do { | ||
311 | @@ -1063,7 +1308,7 @@ sink(int argc, char **argv, const char *src) | ||
312 | } | ||
313 | if (buf[0] == 'E') { | ||
314 | (void) atomicio(vwrite, remout, "", 1); | ||
315 | - return; | ||
316 | + goto done; | ||
317 | } | ||
318 | if (ch == '\n') | ||
319 | *--cp = 0; | ||
320 | @@ -1138,9 +1383,14 @@ sink(int argc, char **argv, const char *src) | ||
321 | run_err("error: unexpected filename: %s", cp); | ||
322 | exit(1); | ||
323 | } | ||
324 | - if (restrict_pattern != NULL && | ||
325 | - fnmatch(restrict_pattern, cp, 0) != 0) | ||
326 | - SCREWUP("filename does not match request"); | ||
327 | + if (npatterns > 0) { | ||
328 | + for (n = 0; n < npatterns; n++) { | ||
329 | + if (fnmatch(patterns[n], cp, 0) == 0) | ||
330 | + break; | ||
331 | + } | ||
332 | + if (n >= npatterns) | ||
333 | + SCREWUP("filename does not match request"); | ||
334 | + } | ||
335 | if (targisdir) { | ||
336 | static char *namebuf; | ||
337 | static size_t cursize; | ||
338 | @@ -1299,7 +1549,15 @@ bad: run_err("%s: %s", np, strerror(errno)); | ||
339 | break; | ||
340 | } | ||
341 | } | ||
342 | +done: | ||
343 | + for (n = 0; n < npatterns; n++) | ||
344 | + free(patterns[n]); | ||
345 | + free(patterns); | ||
346 | + return; | ||
347 | screwup: | ||
348 | + for (n = 0; n < npatterns; n++) | ||
349 | + free(patterns[n]); | ||
350 | + free(patterns); | ||
351 | run_err("protocol error: %s", why); | ||
352 | exit(1); | ||
353 | } | ||
diff --git a/debian/patches/series b/debian/patches/series index a95dccaa6..ff6011442 100644 --- a/debian/patches/series +++ b/debian/patches/series | |||
@@ -30,3 +30,4 @@ have-progressmeter-force-update-at-beginning-and-end-transfer.patch | |||
30 | check-filenames-in-scp-client.patch | 30 | check-filenames-in-scp-client.patch |
31 | fix-key-type-check.patch | 31 | fix-key-type-check.patch |
32 | request-rsa-sha2-cert-signatures.patch | 32 | request-rsa-sha2-cert-signatures.patch |
33 | scp-handle-braces.patch | ||
@@ -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 */ | ||
639 | static int | ||
640 | append(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 | */ | ||
656 | static int | ||
657 | find_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 | */ | ||
711 | static int | ||
712 | emit_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 | */ | ||
755 | static int | ||
756 | brace_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 */ | ||
820 | static int | ||
821 | brace_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 | |||
638 | void | 885 | void |
639 | toremote(int argc, char **argv) | 886 | toremote(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 | } |
1552 | done: | ||
1553 | for (n = 0; n < npatterns; n++) | ||
1554 | free(patterns[n]); | ||
1555 | free(patterns); | ||
1556 | return; | ||
1302 | screwup: | 1557 | screwup: |
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 | } |