Line | Count | Source |
1 | | /* $OpenBSD: match.c,v 1.46 2026/05/31 04:19:16 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 | | * Copyright (c) 2026 Damien Miller. All rights reserved. |
17 | | * |
18 | | * Redistribution and use in source and binary forms, with or without |
19 | | * modification, are permitted provided that the following conditions |
20 | | * are met: |
21 | | * 1. Redistributions of source code must retain the above copyright |
22 | | * notice, this list of conditions and the following disclaimer. |
23 | | * 2. Redistributions in binary form must reproduce the above copyright |
24 | | * notice, this list of conditions and the following disclaimer in the |
25 | | * documentation and/or other materials provided with the distribution. |
26 | | * |
27 | | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
28 | | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
29 | | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
30 | | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
31 | | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
32 | | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
33 | | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
34 | | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
35 | | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
36 | | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
37 | | */ |
38 | | |
39 | | #include "includes.h" |
40 | | |
41 | | #include <sys/types.h> |
42 | | |
43 | | #include <ctype.h> |
44 | | #include <stdlib.h> |
45 | | #include <string.h> |
46 | | #include <stdarg.h> |
47 | | #include <stdio.h> |
48 | | |
49 | | #include "xmalloc.h" |
50 | | #include "match.h" |
51 | | #include "misc.h" |
52 | | |
53 | | /* |
54 | | * Computes the epsilon closure of an NFA set. |
55 | | * In our wildcard grammar, epsilon transitions only exist for '*' wildcards, |
56 | | * allowing us to transition from state i to i+1 without consuming input. |
57 | | * |
58 | | * This function modifies 'states' in place. |
59 | | */ |
60 | | static void |
61 | | epsilon_closure(char *states, const char *pattern, size_t M) |
62 | 0 | { |
63 | 0 | size_t i; |
64 | | |
65 | | /* only need a forward pass as there are no back jumps in our grammar */ |
66 | 0 | for (i = 0; i < M; i++) { |
67 | 0 | if (!states[i] || pattern[i] != '*') |
68 | 0 | continue; |
69 | | /* |
70 | | * State i is active, and pattern[i] is '*', so we can |
71 | | * epsilon-transition to i+1. |
72 | | */ |
73 | 0 | states[i + 1] = 1; |
74 | 0 | } |
75 | 0 | } |
76 | | |
77 | | /* |
78 | | * Returns true if the given string matches the pattern (which may contain ? |
79 | | * and * as wildcards), and zero if it does not match. Uses an NFA internally. |
80 | | */ |
81 | | int |
82 | | match_pattern(const char *s, const char *pattern) |
83 | 0 | { |
84 | 0 | size_t M; |
85 | 0 | size_t i; |
86 | 0 | char *states, *next_states, *tmp; |
87 | 0 | int active, matched = 0; |
88 | | |
89 | | /* trivial case: empty pattern vs empty input */ |
90 | 0 | if ((M = strlen(pattern)) == 0) |
91 | 0 | return *s == '\0'; |
92 | | |
93 | | /* A state for each pattern character, plus one final accepting state */ |
94 | 0 | states = xcalloc(M + 1, sizeof(*states)); |
95 | 0 | next_states = xcalloc(M + 1, sizeof(*next_states)); |
96 | | |
97 | | /* Initial state: state 0 is active */ |
98 | 0 | states[0] = 1; |
99 | | /* Other states might be reachable now if the pattern starts with '*' */ |
100 | 0 | epsilon_closure(states, pattern, M); |
101 | |
|
102 | 0 | for (; *s; s++) { |
103 | 0 | memset(next_states, 0, M + 1); |
104 | | |
105 | | /* Calculate the reachable next states given the input char */ |
106 | 0 | for (i = 0; i < M; i++) { |
107 | 0 | if (!states[i]) |
108 | 0 | continue; |
109 | 0 | if (pattern[i] == '*') { |
110 | | /* |
111 | | * '*' matches any character, so we can |
112 | | * stay in state i |
113 | | */ |
114 | 0 | next_states[i] = 1; |
115 | 0 | } else if (pattern[i] == '?' || pattern[i] == *s) { |
116 | | /* |
117 | | * '?' matches any character, or we have |
118 | | * a literal match. |
119 | | */ |
120 | 0 | next_states[i + 1] = 1; |
121 | 0 | } |
122 | 0 | } |
123 | | |
124 | | /* Expand the reachable next states with epsilon transitions */ |
125 | 0 | epsilon_closure(next_states, pattern, M); |
126 | | |
127 | | /* Swap states and next_states */ |
128 | 0 | tmp = states; |
129 | 0 | states = next_states; |
130 | 0 | next_states = tmp; |
131 | | |
132 | | /* Check if we have any active pattern states left */ |
133 | 0 | active = 0; |
134 | 0 | for (i = 0; i <= M; i++) { |
135 | 0 | if (states[i]) { |
136 | 0 | active = 1; |
137 | 0 | break; |
138 | 0 | } |
139 | 0 | } |
140 | 0 | if (!active) |
141 | 0 | goto out; /* No active states, fail early */ |
142 | 0 | } |
143 | | /* |
144 | | * We matched only if we ended up in the final, accepting state |
145 | | * after consuming all the input. |
146 | | */ |
147 | 0 | matched = states[M]; |
148 | 0 | out: |
149 | 0 | free(states); |
150 | 0 | free(next_states); |
151 | 0 | return matched; |
152 | 0 | } |
153 | | |
154 | | /* |
155 | | * Tries to match the string against the |
156 | | * comma-separated sequence of subpatterns (each possibly preceded by ! to |
157 | | * indicate negation). Returns -1 if negation matches, 1 if there is |
158 | | * a positive match, 0 if there is no match at all. |
159 | | */ |
160 | | int |
161 | | match_pattern_list(const char *string, const char *pattern, int dolower) |
162 | 0 | { |
163 | 0 | char sub[1024]; |
164 | 0 | int negated; |
165 | 0 | int got_positive; |
166 | 0 | u_int i, subi, len = strlen(pattern); |
167 | |
|
168 | 0 | got_positive = 0; |
169 | 0 | for (i = 0; i < len;) { |
170 | | /* Check if the subpattern is negated. */ |
171 | 0 | if (pattern[i] == '!') { |
172 | 0 | negated = 1; |
173 | 0 | i++; |
174 | 0 | } else |
175 | 0 | negated = 0; |
176 | | |
177 | | /* |
178 | | * Extract the subpattern up to a comma or end. Convert the |
179 | | * subpattern to lowercase. |
180 | | */ |
181 | 0 | for (subi = 0; |
182 | 0 | i < len && subi < sizeof(sub) - 1 && pattern[i] != ','; |
183 | 0 | subi++, i++) |
184 | 0 | sub[subi] = dolower && isupper((u_char)pattern[i]) ? |
185 | 0 | tolower((u_char)pattern[i]) : pattern[i]; |
186 | | /* If subpattern too long, return failure (no match). */ |
187 | 0 | if (subi >= sizeof(sub) - 1) |
188 | 0 | return 0; |
189 | | |
190 | | /* If the subpattern was terminated by a comma, then skip it. */ |
191 | 0 | if (i < len && pattern[i] == ',') |
192 | 0 | i++; |
193 | | |
194 | | /* Null-terminate the subpattern. */ |
195 | 0 | sub[subi] = '\0'; |
196 | | |
197 | | /* Try to match the subpattern against the string. */ |
198 | 0 | if (match_pattern(string, sub)) { |
199 | 0 | if (negated) |
200 | 0 | return -1; /* Negative */ |
201 | 0 | else |
202 | 0 | got_positive = 1; /* Positive */ |
203 | 0 | } |
204 | 0 | } |
205 | | |
206 | | /* |
207 | | * Return success if got a positive match. If there was a negative |
208 | | * match, we have already returned -1 and never get here. |
209 | | */ |
210 | 0 | return got_positive; |
211 | 0 | } |
212 | | |
213 | | /* Match a list representing users or groups. */ |
214 | | int |
215 | | match_usergroup_pattern_list(const char *string, const char *pattern) |
216 | 0 | { |
217 | | #ifdef HAVE_CYGWIN |
218 | | /* Windows usernames may be Unicode and are not case sensitive */ |
219 | | return cygwin_ug_match_pattern_list(string, pattern); |
220 | | #else |
221 | | /* Case sensitive match */ |
222 | 0 | return match_pattern_list(string, pattern, 0); |
223 | 0 | #endif |
224 | 0 | } |
225 | | |
226 | | /* |
227 | | * Tries to match the host name (which must be in all lowercase) against the |
228 | | * comma-separated sequence of subpatterns (each possibly preceded by ! to |
229 | | * indicate negation). Returns -1 if negation matches, 1 if there is |
230 | | * a positive match, 0 if there is no match at all. |
231 | | */ |
232 | | int |
233 | | match_hostname(const char *host, const char *pattern) |
234 | 0 | { |
235 | 0 | char *hostcopy = xstrdup(host); |
236 | 0 | int r; |
237 | |
|
238 | 0 | lowercase(hostcopy); |
239 | 0 | r = match_pattern_list(hostcopy, pattern, 1); |
240 | 0 | free(hostcopy); |
241 | 0 | return r; |
242 | 0 | } |
243 | | |
244 | | /* |
245 | | * returns 0 if we get a negative match for the hostname or the ip |
246 | | * or if we get no match at all. returns -1 on error, or 1 on |
247 | | * successful match. |
248 | | */ |
249 | | int |
250 | | match_host_and_ip(const char *host, const char *ipaddr, |
251 | | const char *patterns) |
252 | 0 | { |
253 | 0 | int mhost, mip; |
254 | |
|
255 | 0 | if ((mip = addr_match_list(ipaddr, patterns)) == -2) |
256 | 0 | return -1; /* error in ipaddr match */ |
257 | 0 | else if (host == NULL || ipaddr == NULL || mip == -1) |
258 | 0 | return 0; /* negative ip address match, or testing pattern */ |
259 | | |
260 | | /* negative hostname match */ |
261 | 0 | if ((mhost = match_hostname(host, patterns)) == -1) |
262 | 0 | return 0; |
263 | | /* no match at all */ |
264 | 0 | if (mhost == 0 && mip == 0) |
265 | 0 | return 0; |
266 | 0 | return 1; |
267 | 0 | } |
268 | | |
269 | | /* |
270 | | * Match user, user@host_or_ip, user@host_or_ip_list against pattern. |
271 | | * If user, host and ipaddr are all NULL then validate pattern/ |
272 | | * Returns -1 on invalid pattern, 0 on no match, 1 on match. |
273 | | */ |
274 | | int |
275 | | match_user(const char *user, const char *host, const char *ipaddr, |
276 | | const char *pattern) |
277 | 0 | { |
278 | 0 | char *p, *pat; |
279 | 0 | int ret; |
280 | | |
281 | | /* test mode */ |
282 | 0 | if (user == NULL && host == NULL && ipaddr == NULL) { |
283 | 0 | if ((p = strrchr(pattern, '@')) != NULL && |
284 | 0 | match_host_and_ip(NULL, NULL, p + 1) < 0) |
285 | 0 | return -1; |
286 | 0 | return 0; |
287 | 0 | } |
288 | | |
289 | 0 | if (user == NULL) |
290 | 0 | return 0; /* shouldn't happen */ |
291 | | |
292 | 0 | if (strrchr(pattern, '@') == NULL) |
293 | 0 | return match_pattern(user, pattern); |
294 | | |
295 | 0 | pat = xstrdup(pattern); |
296 | 0 | p = strrchr(pat, '@'); |
297 | 0 | *p++ = '\0'; |
298 | |
|
299 | 0 | if ((ret = match_pattern(user, pat)) == 1) |
300 | 0 | ret = match_host_and_ip(host, ipaddr, p); |
301 | 0 | free(pat); |
302 | |
|
303 | 0 | return ret; |
304 | 0 | } |
305 | | |
306 | | /* |
307 | | * Returns first item from client-list that is also supported by server-list, |
308 | | * caller must free the returned string. |
309 | | */ |
310 | 0 | #define MAX_PROP 40 |
311 | 0 | #define SEP "," |
312 | | char * |
313 | | match_list(const char *client, const char *server, u_int *next) |
314 | 0 | { |
315 | 0 | char *sproposals[MAX_PROP]; |
316 | 0 | char *c, *s, *p, *ret, *cp, *sp; |
317 | 0 | int i, j, nproposals; |
318 | |
|
319 | 0 | c = cp = xstrdup(client); |
320 | 0 | s = sp = xstrdup(server); |
321 | |
|
322 | 0 | for ((p = strsep(&sp, SEP)), i=0; p && *p != '\0'; |
323 | 0 | (p = strsep(&sp, SEP)), i++) { |
324 | 0 | if (i < MAX_PROP) |
325 | 0 | sproposals[i] = p; |
326 | 0 | else |
327 | 0 | break; |
328 | 0 | } |
329 | 0 | nproposals = i; |
330 | |
|
331 | 0 | for ((p = strsep(&cp, SEP)), i=0; p && *p != '\0'; |
332 | 0 | (p = strsep(&cp, SEP)), i++) { |
333 | 0 | for (j = 0; j < nproposals; j++) { |
334 | 0 | if (strcmp(p, sproposals[j]) == 0) { |
335 | 0 | ret = xstrdup(p); |
336 | 0 | if (next != NULL) |
337 | 0 | *next = (cp == NULL) ? |
338 | 0 | strlen(c) : (u_int)(cp - c); |
339 | 0 | free(c); |
340 | 0 | free(s); |
341 | 0 | return ret; |
342 | 0 | } |
343 | 0 | } |
344 | 0 | } |
345 | 0 | if (next != NULL) |
346 | 0 | *next = strlen(c); |
347 | 0 | free(c); |
348 | 0 | free(s); |
349 | 0 | return NULL; |
350 | 0 | } |
351 | | |
352 | | /* |
353 | | * Filter proposal using pattern-list filter. |
354 | | * "denylist" determines sense of filter: |
355 | | * non-zero indicates that items matching filter should be excluded. |
356 | | * zero indicates that only items matching filter should be included. |
357 | | * returns NULL on allocation error, otherwise caller must free result. |
358 | | */ |
359 | | static char * |
360 | | filter_list(const char *proposal, const char *filter, int denylist) |
361 | 0 | { |
362 | 0 | size_t len = strlen(proposal) + 1; |
363 | 0 | char *fix_prop = malloc(len); |
364 | 0 | char *orig_prop = strdup(proposal); |
365 | 0 | char *cp, *tmp; |
366 | 0 | int r; |
367 | |
|
368 | 0 | if (fix_prop == NULL || orig_prop == NULL) { |
369 | 0 | free(orig_prop); |
370 | 0 | free(fix_prop); |
371 | 0 | return NULL; |
372 | 0 | } |
373 | | |
374 | 0 | tmp = orig_prop; |
375 | 0 | *fix_prop = '\0'; |
376 | 0 | while ((cp = strsep(&tmp, ",")) != NULL) { |
377 | 0 | r = match_pattern_list(cp, filter, 0); |
378 | 0 | if ((denylist && r != 1) || (!denylist && r == 1)) { |
379 | 0 | if (*fix_prop != '\0') |
380 | 0 | strlcat(fix_prop, ",", len); |
381 | 0 | strlcat(fix_prop, cp, len); |
382 | 0 | } |
383 | 0 | } |
384 | 0 | free(orig_prop); |
385 | 0 | return fix_prop; |
386 | 0 | } |
387 | | |
388 | | /* |
389 | | * Filters a comma-separated list of strings, excluding any entry matching |
390 | | * the 'filter' pattern list. Caller must free returned string. |
391 | | */ |
392 | | char * |
393 | | match_filter_denylist(const char *proposal, const char *filter) |
394 | 0 | { |
395 | 0 | return filter_list(proposal, filter, 1); |
396 | 0 | } |
397 | | |
398 | | /* |
399 | | * Filters a comma-separated list of strings, including only entries matching |
400 | | * the 'filter' pattern list. Caller must free returned string. |
401 | | */ |
402 | | char * |
403 | | match_filter_allowlist(const char *proposal, const char *filter) |
404 | 0 | { |
405 | 0 | return filter_list(proposal, filter, 0); |
406 | 0 | } |