Line | Count | Source (jump to first uncovered line) |
1 | | /* $OpenBSD: match.c,v 1.44 2023/04/06 03:19:32 djm Exp $ */ |
2 | | /* |
3 | | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
4 | | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
5 | | * All rights reserved |
6 | | * Simple pattern matching, with '*' and '?' as wildcards. |
7 | | * |
8 | | * As far as I am concerned, the code I have written for this software |
9 | | * can be used freely for any purpose. Any derived versions of this |
10 | | * software must be clearly marked as such, and if the derived work is |
11 | | * incompatible with the protocol description in the RFC file, it must be |
12 | | * called by a name other than "ssh" or "Secure Shell". |
13 | | */ |
14 | | /* |
15 | | * Copyright (c) 2000 Markus Friedl. All rights reserved. |
16 | | * |
17 | | * Redistribution and use in source and binary forms, with or without |
18 | | * modification, are permitted provided that the following conditions |
19 | | * are met: |
20 | | * 1. Redistributions of source code must retain the above copyright |
21 | | * notice, this list of conditions and the following disclaimer. |
22 | | * 2. Redistributions in binary form must reproduce the above copyright |
23 | | * notice, this list of conditions and the following disclaimer in the |
24 | | * documentation and/or other materials provided with the distribution. |
25 | | * |
26 | | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
27 | | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
28 | | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
29 | | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
30 | | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
31 | | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
32 | | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
33 | | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
34 | | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
35 | | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
36 | | */ |
37 | | |
38 | | #include "includes.h" |
39 | | |
40 | | #include <sys/types.h> |
41 | | |
42 | | #include <ctype.h> |
43 | | #include <stdlib.h> |
44 | | #include <string.h> |
45 | | #include <stdarg.h> |
46 | | #include <stdio.h> |
47 | | |
48 | | #include "xmalloc.h" |
49 | | #include "match.h" |
50 | | #include "misc.h" |
51 | | |
52 | | /* |
53 | | * Returns true if the given string matches the pattern (which may contain ? |
54 | | * and * as wildcards), and zero if it does not match. |
55 | | */ |
56 | | int |
57 | | match_pattern(const char *s, const char *pattern) |
58 | 1.71M | { |
59 | 2.68M | for (;;) { |
60 | | /* If at end of pattern, accept if also at end of string. */ |
61 | 2.68M | if (!*pattern) |
62 | 0 | return !*s; |
63 | | |
64 | 2.68M | if (*pattern == '*') { |
65 | | /* Skip this and any consecutive asterisks. */ |
66 | 94.8k | while (*pattern == '*') |
67 | 47.4k | pattern++; |
68 | | |
69 | | /* If at end of pattern, accept immediately. */ |
70 | 47.4k | if (!*pattern) |
71 | 3.94k | return 1; |
72 | | |
73 | | /* If next character in pattern is known, optimize. */ |
74 | 43.4k | if (*pattern != '?' && *pattern != '*') { |
75 | | /* |
76 | | * Look instances of the next character in |
77 | | * pattern, and try to match starting from |
78 | | * those. |
79 | | */ |
80 | 3.59M | for (; *s; s++) |
81 | 3.55M | if (*s == *pattern && |
82 | 3.55M | match_pattern(s + 1, pattern + 1)) |
83 | 0 | return 1; |
84 | | /* Failed. */ |
85 | 43.4k | return 0; |
86 | 43.4k | } |
87 | | /* |
88 | | * Move ahead one character at a time and try to |
89 | | * match at each position. |
90 | | */ |
91 | 0 | for (; *s; s++) |
92 | 0 | if (match_pattern(s, pattern)) |
93 | 0 | return 1; |
94 | | /* Failed. */ |
95 | 0 | return 0; |
96 | 0 | } |
97 | | /* |
98 | | * There must be at least one more character in the string. |
99 | | * If we are at the end, fail. |
100 | | */ |
101 | 2.64M | if (!*s) |
102 | 44.3k | return 0; |
103 | | |
104 | | /* Check if the next character of the string is acceptable. */ |
105 | 2.59M | if (*pattern != '?' && *pattern != *s) |
106 | 1.61M | return 0; |
107 | | |
108 | | /* Move to the next character, both in string and in pattern. */ |
109 | 976k | s++; |
110 | 976k | pattern++; |
111 | 976k | } |
112 | | /* NOTREACHED */ |
113 | 1.71M | } |
114 | | |
115 | | /* |
116 | | * Tries to match the string against the |
117 | | * comma-separated sequence of subpatterns (each possibly preceded by ! to |
118 | | * indicate negation). Returns -1 if negation matches, 1 if there is |
119 | | * a positive match, 0 if there is no match at all. |
120 | | */ |
121 | | int |
122 | | match_pattern_list(const char *string, const char *pattern, int dolower) |
123 | 557k | { |
124 | 557k | char sub[1024]; |
125 | 557k | int negated; |
126 | 557k | int got_positive; |
127 | 557k | u_int i, subi, len = strlen(pattern); |
128 | | |
129 | 557k | got_positive = 0; |
130 | 2.02M | for (i = 0; i < len;) { |
131 | | /* Check if the subpattern is negated. */ |
132 | 1.46M | if (pattern[i] == '!') { |
133 | 0 | negated = 1; |
134 | 0 | i++; |
135 | 0 | } else |
136 | 1.46M | negated = 0; |
137 | | |
138 | | /* |
139 | | * Extract the subpattern up to a comma or end. Convert the |
140 | | * subpattern to lowercase. |
141 | | */ |
142 | 1.46M | for (subi = 0; |
143 | 20.9M | i < len && subi < sizeof(sub) - 1 && pattern[i] != ','; |
144 | 19.4M | subi++, i++) |
145 | 19.4M | sub[subi] = dolower && isupper((u_char)pattern[i]) ? |
146 | 19.4M | tolower((u_char)pattern[i]) : pattern[i]; |
147 | | /* If subpattern too long, return failure (no match). */ |
148 | 1.46M | if (subi >= sizeof(sub) - 1) |
149 | 0 | return 0; |
150 | | |
151 | | /* If the subpattern was terminated by a comma, then skip it. */ |
152 | 1.46M | if (i < len && pattern[i] == ',') |
153 | 907k | i++; |
154 | | |
155 | | /* Null-terminate the subpattern. */ |
156 | 1.46M | sub[subi] = '\0'; |
157 | | |
158 | | /* Try to match the subpattern against the string. */ |
159 | 1.46M | if (match_pattern(string, sub)) { |
160 | 3.94k | if (negated) |
161 | 0 | return -1; /* Negative */ |
162 | 3.94k | else |
163 | 3.94k | got_positive = 1; /* Positive */ |
164 | 3.94k | } |
165 | 1.46M | } |
166 | | |
167 | | /* |
168 | | * Return success if got a positive match. If there was a negative |
169 | | * match, we have already returned -1 and never get here. |
170 | | */ |
171 | 557k | return got_positive; |
172 | 557k | } |
173 | | |
174 | | /* Match a list representing users or groups. */ |
175 | | int |
176 | | match_usergroup_pattern_list(const char *string, const char *pattern) |
177 | 0 | { |
178 | | #ifdef HAVE_CYGWIN |
179 | | /* Windows usernames may be Unicode and are not case sensitive */ |
180 | | return cygwin_ug_match_pattern_list(string, pattern); |
181 | | #else |
182 | | /* Case sensitive match */ |
183 | 0 | return match_pattern_list(string, pattern, 0); |
184 | 0 | #endif |
185 | 0 | } |
186 | | |
187 | | /* |
188 | | * Tries to match the host name (which must be in all lowercase) against the |
189 | | * comma-separated sequence of subpatterns (each possibly preceded by ! to |
190 | | * indicate negation). Returns -1 if negation matches, 1 if there is |
191 | | * a positive match, 0 if there is no match at all. |
192 | | */ |
193 | | int |
194 | | match_hostname(const char *host, const char *pattern) |
195 | 0 | { |
196 | 0 | char *hostcopy = xstrdup(host); |
197 | 0 | int r; |
198 | |
|
199 | 0 | lowercase(hostcopy); |
200 | 0 | r = match_pattern_list(hostcopy, pattern, 1); |
201 | 0 | free(hostcopy); |
202 | 0 | return r; |
203 | 0 | } |
204 | | |
205 | | /* |
206 | | * returns 0 if we get a negative match for the hostname or the ip |
207 | | * or if we get no match at all. returns -1 on error, or 1 on |
208 | | * successful match. |
209 | | */ |
210 | | int |
211 | | match_host_and_ip(const char *host, const char *ipaddr, |
212 | | const char *patterns) |
213 | 0 | { |
214 | 0 | int mhost, mip; |
215 | |
|
216 | 0 | if ((mip = addr_match_list(ipaddr, patterns)) == -2) |
217 | 0 | return -1; /* error in ipaddr match */ |
218 | 0 | else if (host == NULL || ipaddr == NULL || mip == -1) |
219 | 0 | return 0; /* negative ip address match, or testing pattern */ |
220 | | |
221 | | /* negative hostname match */ |
222 | 0 | if ((mhost = match_hostname(host, patterns)) == -1) |
223 | 0 | return 0; |
224 | | /* no match at all */ |
225 | 0 | if (mhost == 0 && mip == 0) |
226 | 0 | return 0; |
227 | 0 | return 1; |
228 | 0 | } |
229 | | |
230 | | /* |
231 | | * Match user, user@host_or_ip, user@host_or_ip_list against pattern. |
232 | | * If user, host and ipaddr are all NULL then validate pattern/ |
233 | | * Returns -1 on invalid pattern, 0 on no match, 1 on match. |
234 | | */ |
235 | | int |
236 | | match_user(const char *user, const char *host, const char *ipaddr, |
237 | | const char *pattern) |
238 | 0 | { |
239 | 0 | char *p, *pat; |
240 | 0 | int ret; |
241 | | |
242 | | /* test mode */ |
243 | 0 | if (user == NULL && host == NULL && ipaddr == NULL) { |
244 | 0 | if ((p = strchr(pattern, '@')) != NULL && |
245 | 0 | match_host_and_ip(NULL, NULL, p + 1) < 0) |
246 | 0 | return -1; |
247 | 0 | return 0; |
248 | 0 | } |
249 | | |
250 | 0 | if (user == NULL) |
251 | 0 | return 0; /* shouldn't happen */ |
252 | | |
253 | 0 | if ((p = strchr(pattern, '@')) == NULL) |
254 | 0 | return match_pattern(user, pattern); |
255 | | |
256 | 0 | pat = xstrdup(pattern); |
257 | 0 | p = strchr(pat, '@'); |
258 | 0 | *p++ = '\0'; |
259 | |
|
260 | 0 | if ((ret = match_pattern(user, pat)) == 1) |
261 | 0 | ret = match_host_and_ip(host, ipaddr, p); |
262 | 0 | free(pat); |
263 | |
|
264 | 0 | return ret; |
265 | 0 | } |
266 | | |
267 | | /* |
268 | | * Returns first item from client-list that is also supported by server-list, |
269 | | * caller must free the returned string. |
270 | | */ |
271 | 299k | #define MAX_PROP 40 |
272 | 1.25M | #define SEP "," |
273 | | char * |
274 | | match_list(const char *client, const char *server, u_int *next) |
275 | 293k | { |
276 | 293k | char *sproposals[MAX_PROP]; |
277 | 293k | char *c, *s, *p, *ret, *cp, *sp; |
278 | 293k | int i, j, nproposals; |
279 | | |
280 | 293k | c = cp = xstrdup(client); |
281 | 293k | s = sp = xstrdup(server); |
282 | | |
283 | 593k | for ((p = strsep(&sp, SEP)), i=0; p && *p != '\0'; |
284 | 299k | (p = strsep(&sp, SEP)), i++) { |
285 | 299k | if (i < MAX_PROP) |
286 | 299k | sproposals[i] = p; |
287 | 3 | else |
288 | 3 | break; |
289 | 299k | } |
290 | 293k | nproposals = i; |
291 | | |
292 | 660k | for ((p = strsep(&cp, SEP)), i=0; p && *p != '\0'; |
293 | 368k | (p = strsep(&cp, SEP)), i++) { |
294 | 744k | for (j = 0; j < nproposals; j++) { |
295 | 377k | if (strcmp(p, sproposals[j]) == 0) { |
296 | 797 | ret = xstrdup(p); |
297 | 797 | if (next != NULL) |
298 | 0 | *next = (cp == NULL) ? |
299 | 0 | strlen(c) : (u_int)(cp - c); |
300 | 797 | free(c); |
301 | 797 | free(s); |
302 | 797 | return ret; |
303 | 797 | } |
304 | 377k | } |
305 | 368k | } |
306 | 292k | if (next != NULL) |
307 | 140k | *next = strlen(c); |
308 | 292k | free(c); |
309 | 292k | free(s); |
310 | 292k | return NULL; |
311 | 293k | } |
312 | | |
313 | | /* |
314 | | * Filter proposal using pattern-list filter. |
315 | | * "denylist" determines sense of filter: |
316 | | * non-zero indicates that items matching filter should be excluded. |
317 | | * zero indicates that only items matching filter should be included. |
318 | | * returns NULL on allocation error, otherwise caller must free result. |
319 | | */ |
320 | | static char * |
321 | | filter_list(const char *proposal, const char *filter, int denylist) |
322 | 0 | { |
323 | 0 | size_t len = strlen(proposal) + 1; |
324 | 0 | char *fix_prop = malloc(len); |
325 | 0 | char *orig_prop = strdup(proposal); |
326 | 0 | char *cp, *tmp; |
327 | 0 | int r; |
328 | |
|
329 | 0 | if (fix_prop == NULL || orig_prop == NULL) { |
330 | 0 | free(orig_prop); |
331 | 0 | free(fix_prop); |
332 | 0 | return NULL; |
333 | 0 | } |
334 | | |
335 | 0 | tmp = orig_prop; |
336 | 0 | *fix_prop = '\0'; |
337 | 0 | while ((cp = strsep(&tmp, ",")) != NULL) { |
338 | 0 | r = match_pattern_list(cp, filter, 0); |
339 | 0 | if ((denylist && r != 1) || (!denylist && r == 1)) { |
340 | 0 | if (*fix_prop != '\0') |
341 | 0 | strlcat(fix_prop, ",", len); |
342 | 0 | strlcat(fix_prop, cp, len); |
343 | 0 | } |
344 | 0 | } |
345 | 0 | free(orig_prop); |
346 | 0 | return fix_prop; |
347 | 0 | } |
348 | | |
349 | | /* |
350 | | * Filters a comma-separated list of strings, excluding any entry matching |
351 | | * the 'filter' pattern list. Caller must free returned string. |
352 | | */ |
353 | | char * |
354 | | match_filter_denylist(const char *proposal, const char *filter) |
355 | 0 | { |
356 | 0 | return filter_list(proposal, filter, 1); |
357 | 0 | } |
358 | | |
359 | | /* |
360 | | * Filters a comma-separated list of strings, including only entries matching |
361 | | * the 'filter' pattern list. Caller must free returned string. |
362 | | */ |
363 | | char * |
364 | | match_filter_allowlist(const char *proposal, const char *filter) |
365 | 0 | { |
366 | 0 | return filter_list(proposal, filter, 0); |
367 | 0 | } |