-
Notifications
You must be signed in to change notification settings - Fork 31
Expand file tree
/
Copy pathmatch.c
More file actions
628 lines (568 loc) · 18.8 KB
/
match.c
File metadata and controls
628 lines (568 loc) · 18.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
* All rights reserved
* Simple pattern matching, with '*' and '?' as wildcards.
*
* As far as I am concerned, the code I have written for this software
* can be used freely for any purpose. Any derived versions of this
* software must be clearly marked as such, and if the derived work is
* incompatible with the protocol description in the RFC file, it must be
* called by a name other than "ssh" or "Secure Shell".
*/
/*
* Copyright (c) 2000 Markus Friedl. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include <ctype.h>
#include <stdbool.h>
#include <sys/types.h>
#ifndef _WIN32
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#endif
/* for systems without IPv6 support matching should still work */
#ifndef INET6_ADDRSTRLEN
#define INET6_ADDRSTRLEN 46
#endif
#include "libssh/priv.h"
#define MAX_MATCH_RECURSION 16
/*
* Returns true if the given string matches the pattern (which may contain ?
* and * as wildcards), and zero if it does not match.
*/
static int match_pattern(const char *s, const char *pattern, size_t limit)
{
bool had_asterisk = false;
if (s == NULL || pattern == NULL || limit <= 0) {
return 0;
}
for (;;) {
/* If at end of pattern, accept if also at end of string. */
if (*pattern == '\0') {
return (*s == '\0');
}
/* Skip all the asterisks and adjacent question marks */
while (*pattern == '*' || (had_asterisk && *pattern == '?')) {
if (*pattern == '*') {
had_asterisk = true;
}
pattern++;
}
if (had_asterisk) {
/* If at end of pattern, accept immediately. */
if (!*pattern)
return 1;
/* If next character in pattern is known, optimize. */
if (*pattern != '?') {
/*
* Look instances of the next character in
* pattern, and try to match starting from
* those.
*/
for (; *s; s++)
if (*s == *pattern && match_pattern(s + 1, pattern + 1, limit - 1)) {
return 1;
}
/* Failed. */
return 0;
}
/*
* Move ahead one character at a time and try to
* match at each position.
*/
for (; *s; s++) {
if (match_pattern(s, pattern, limit - 1)) {
return 1;
}
}
/* Failed. */
return 0;
}
/*
* There must be at least one more character in the string.
* If we are at the end, fail.
*/
if (!*s) {
return 0;
}
/* Check if the next character of the string is acceptable. */
if (*pattern != '?' && *pattern != *s) {
return 0;
}
/* Move to the next character, both in string and in pattern. */
s++;
pattern++;
}
/* NOTREACHED */
return 0;
}
/*
* Tries to match the string against the comma-separated sequence of subpatterns
* (each possibly preceded by ! to indicate negation).
* Returns -1 if negation matches, 1 if there is a positive match, 0 if there is
* no match at all.
*/
int match_pattern_list(const char *string, const char *pattern,
size_t len, int dolower) {
char sub[1024];
int negated;
int got_positive;
size_t i, subi;
got_positive = 0;
for (i = 0; i < len;) {
/* Check if the subpattern is negated. */
if (pattern[i] == '!') {
negated = 1;
i++;
} else {
negated = 0;
}
/*
* Extract the subpattern up to a comma or end. Convert the
* subpattern to lowercase.
*/
for (subi = 0;
i < len && subi < sizeof(sub) - 1 && pattern[i] != ',';
subi++, i++) {
sub[subi] = dolower && isupper(pattern[i]) ?
(char)tolower(pattern[i]) : pattern[i];
}
/* If subpattern too long, return failure (no match). */
if (subi >= sizeof(sub) - 1) {
return 0;
}
/* If the subpattern was terminated by a comma, skip the comma. */
if (i < len && pattern[i] == ',') {
i++;
}
/* Null-terminate the subpattern. */
sub[subi] = '\0';
/* Try to match the subpattern against the string. */
if (match_pattern(string, sub, MAX_MATCH_RECURSION)) {
if (negated) {
return -1; /* Negative */
} else {
got_positive = 1; /* Positive */
}
}
}
/*
* Return success if got a positive match. If there was a negative
* match, we have already returned -1 and never get here.
*/
return got_positive;
}
/*
* Tries to match the host name (which must be in all lowercase) against the
* comma-separated sequence of subpatterns (each possibly preceded by ! to
* indicate negation).
* Returns -1 if negation matches, 1 if there is a positive match, 0 if there
* is no match at all.
*/
int match_hostname(const char *host, const char *pattern, unsigned int len) {
return match_pattern_list(host, pattern, len, 1);
}
#ifndef _WIN32
/**
* @brief Tries to match the host IPv6 address against a given network address
* with specified prefix length in CIDR notation.
*
* @param[in] host_addr The host address to verify.
*
* @param[in] net_addr The network id address against which the match is
* being verified
*
* @param[in] bits The prefix length
*
* @return 0 on a negative match.
* @return 1 on a positive match.
*/
static int
cidr_match_6(struct in6_addr *host_addr,
struct in6_addr *net_addr,
unsigned int bits)
{
const uint8_t *a = host_addr->s6_addr;
const uint8_t *b = net_addr->s6_addr;
unsigned int byte_whole, bits_left;
/* The number of a complete byte covered by the prefix */
byte_whole = bits / 8;
/*
* The number of bits remaining in the incomplete (last) byte
* covered by the prefix
*/
bits_left = bits % 8;
if (byte_whole) {
if (memcmp(a, b, byte_whole) != 0) {
return 0;
}
}
if (bits_left) {
if ((a[byte_whole] ^ b[byte_whole]) & (0xFFu << (8 - bits_left))) {
return 0;
}
}
return 1;
}
/**
* @brief Tries to match the host IPv4 address against a given network address
* with specified prefix length in CIDR notation.
*
* @param[in] host_addr The host address to verify.
*
* @param[in] net_addr The network id address against which the match is
* being verified
*
* @param[in] bits The prefix length
*
* @return 0 on a negative match.
* @return 1 on a positive match.
*/
static int
cidr_match_4(struct in_addr *host_addr,
struct in_addr *net_addr,
unsigned int bits)
{
if (bits == 0) {
/* C99 6.5.7 (3): u32 << 32 is undefined behaviour */
return 1;
}
return !((host_addr->s_addr ^ net_addr->s_addr) &
htonl((0xFFFFFFFFu << (32 - bits)) & 0xFFFFFFFFu));
}
/**
* @brief Checks if the mask length is valid according to the address family
* (IPv4 or IPv6).
*
* @param[in] family The address family (e.g. AF_INET or AF_INET6)
*
* @param[in] mask The subnet mask (prefix)
*
* @return true if the mask length does not exceed the maximum valid length
* according to the address family (IPv4 or IPv6).
* @return false if the mask length exceeds the maximum valid length
* or there is no match with IPv4 or IPv6 address family.
*/
static bool
masklen_valid(int family, unsigned int mask)
{
switch (family) {
case AF_INET:
return mask <= 32;
case AF_INET6:
return mask <= 128;
default:
return false;
}
}
/**
* @brief Extracts address family given a network address.
*
* @param[in] address The network address.
*
* @return The value of the address family if no errors.
* @return -1 in case of errors.
*/
static int
get_address_family(const char *address)
{
struct addrinfo hints, *ai = NULL;
int rc = -1, rv;
ZERO_STRUCT(hints);
if (address == NULL) {
SSH_LOG(SSH_LOG_TRACE, "Bad arguments");
goto out;
}
hints.ai_flags = AI_NUMERICHOST;
rv = getaddrinfo(address, NULL, &hints, &ai);
if (rv != 0) {
SSH_LOG(SSH_LOG_TRACE,
"Couldn't get address information - getaddrinfo() failed: %s",
gai_strerror(rv));
goto out;
}
rc = ai->ai_family;
freeaddrinfo(ai);
out:
return rc;
}
/**
* @brief Tries to match the host address against a CIDR list provided
* by the user. If the host address family is unknown, it can be derived by
* passing -1 as sa_family argument.
*
* It can be also used to validate a CIDR list when the passed address is NULL
* and sa_family is -1.
*
* @param[in] address The host address to verify (NULL to validate CIDR list).
*
* @param[in] addrlist The CIDR list against which the match is being verified.
* The CIDR list can contain both IPv4 and IPv6 addresses
* and has to be comma separated
* (',' only, space after comma not allowed).
*
* @param[in] sa_family The socket address family (e.g. AF_INET or AF_INET6,
* -1 to validate CIDR list or unknown address family).
*
* @usage To validate CIDR list: match_cidr_address_list(NULL, addrlist, -1).
* @usage To verify a match with unknown address family:
* match_cidr_address_list(address, addrlist, -1).
* @return 1 only on positive match.
* @return 0 on negative match or valid CIDR list.
* @return -1 on errors or invalid CIDR list.
*/
int
match_cidr_address_list(const char *address,
const char *addrlist,
int sa_family)
{
char *list = NULL, *cp = NULL, *a = NULL, *b = NULL, *sp = NULL;
char addr_buffer[64], addr[NI_MAXHOST];
struct in_addr try_addr, match_addr;
struct in6_addr try_addr6, match_addr6;
unsigned long mask_len;
size_t addr_len, tmp_len;
int rc = 0, r, ai_family;
ZERO_STRUCT(try_addr);
ZERO_STRUCT(try_addr6);
ZERO_STRUCT(match_addr);
ZERO_STRUCT(match_addr6);
if (sa_family != AF_INET && sa_family != AF_INET6 && sa_family != -1) {
SSH_LOG(SSH_LOG_TRACE,
"Invalid argument: sa_family %d is not valid",
sa_family);
return -1;
}
if (address != NULL) {
strncpy(addr, address, NI_MAXHOST - 1);
/* Remove interface in case of IPv6 address: addr%interface */
a = strchr(addr, '%');
if (a != NULL) {
*a = '\0';
}
/*
* If sa_family is set to -1 and address is not NULL then
* the socket address family should be derived
*/
if (sa_family == -1) {
r = get_address_family(addr);
if (r == -1) {
SSH_LOG(SSH_LOG_TRACE,
"Failed to derive address family for address "
"\"%.100s\"",
addr);
return -1;
}
sa_family = r;
}
/*
* Translate host address from dot notation to binary network format
* according to family type,
* i.e. IPv4 (store in in_addr) or IPv6 (store in in6_addr)
*/
if (sa_family == AF_INET) {
if (inet_pton(AF_INET, addr, &try_addr) == 0) {
SSH_LOG(SSH_LOG_TRACE,
"Couldn't parse IPv4 address \"%.100s\"",
addr);
return -1;
}
} else if (sa_family == AF_INET6) {
if (inet_pton(AF_INET6, addr, &try_addr6) == 0) {
SSH_LOG(SSH_LOG_TRACE,
"Couldn't parse IPv6 address \"%.100s\"",
addr);
return -1;
}
} else {
SSH_LOG(SSH_LOG_TRACE,
"Address family %d for address \"%.100s\" "
"is not recognized",
sa_family,
addr);
return -1;
}
}
b = list = strdup(addrlist);
if (b == NULL) {
return -1;
}
while ((cp = strsep(&list, ",")) != NULL) {
if (*cp == '\0') {
SSH_LOG(SSH_LOG_TRACE, "Empty entry in list \"%.100s\"", b);
rc = -1;
break;
}
/*
* Stop junk from reaching address translation. +3 for the "/prefix".
* INET6_ADDRSTRLEN is 46 and includes space for '\0' terminator. The
* maximum IPv6 address printable is the one that carries IPv4 too.
* E.g. ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255 is 46 chars
* long ('\0' included) and the maximum prefix length possible is 96.
* This explains why +3. All the other IPv6 addresses with maximum /127
* prefix length (39 + 4) are covered just by INET6_ADDRSTRLEN itself
*/
addr_len = strlen(cp);
if (addr_len > INET6_ADDRSTRLEN + 3) {
SSH_LOG(SSH_LOG_TRACE,
"List entry \"%.100s\" too long: %zu > %d (MAX ALLOWED)",
cp,
addr_len,
INET6_ADDRSTRLEN + 3);
rc = -1;
break;
}
#define VALID_CIDR_CHARS "0123456789abcdefABCDEF.:/"
tmp_len = strspn(cp, VALID_CIDR_CHARS);
if (tmp_len != addr_len) {
SSH_LOG(SSH_LOG_TRACE,
"List entry \"%.100s\" contains invalid characters "
"-> \"%c\" is an invalid character",
cp,
cp[tmp_len]);
rc = -1;
break;
}
#undef VALID_CIDR_CHARS
strncpy(addr_buffer, cp, sizeof(addr_buffer) - 1);
sp = strchr(addr_buffer, '/');
if (sp != NULL) {
*sp = '\0';
sp++;
mask_len = strtoul(sp, &cp, 10);
if (*sp < '0' || *sp > '9' || *cp != '\0') {
SSH_LOG(SSH_LOG_TRACE, "Error while parsing prefix: %s", sp);
rc = -1;
break;
}
if (mask_len > 128) {
SSH_LOG(SSH_LOG_TRACE,
"Invalid prefix: %lu exceeds the maximum allowed "
"(>128)",
mask_len);
rc = -1;
break;
}
} else {
SSH_LOG(SSH_LOG_TRACE,
"Missing prefix length for list entry \"%.100s\"",
addr_buffer);
rc = -1;
break;
}
ai_family = get_address_family(addr_buffer);
if (ai_family == -1) {
SSH_LOG(SSH_LOG_TRACE,
"Couldn't get address family for \"%.100s\"",
addr_buffer);
rc = -1;
break;
}
if (ai_family == AF_INET) {
if (inet_pton(AF_INET, addr_buffer, &match_addr) == 0) {
SSH_LOG(SSH_LOG_TRACE,
"Couldn't parse IPv4 address \"%.100s\"",
addr_buffer);
rc = -1;
break;
}
} else if (ai_family == AF_INET6) {
if (inet_pton(AF_INET6, addr_buffer, &match_addr6) == 0) {
SSH_LOG(SSH_LOG_TRACE,
"Couldn't parse IPv6 address \"%.100s\"",
addr_buffer);
rc = -1;
break;
}
} else {
SSH_LOG(SSH_LOG_TRACE,
"Address family %d for address \"%.100s\" "
"is not recognized",
ai_family,
addr_buffer);
rc = -1;
break;
}
if (masklen_valid(ai_family, mask_len) != true) {
SSH_LOG(SSH_LOG_TRACE,
"Invalid mask length %lu for list entry \"%.100s\"",
mask_len,
addr_buffer);
rc = -1;
break;
}
/* Verify match between host address and network address*/
if (((ai_family == AF_INET && sa_family == AF_INET) &&
cidr_match_4(&try_addr, &match_addr, mask_len)) ||
((ai_family == AF_INET6 && sa_family == AF_INET6) &&
cidr_match_6(&try_addr6, &match_addr6, mask_len))) {
rc = 1;
break;
}
}
SAFE_FREE(b);
return rc;
}
#endif /* _WIN32 */
/**
* @brief Tries to match an object against a comma separated group of objects
*
* The characters '*' and '?' are NOT considered wildcards and an object in the
* group preceded by a ! does NOT indicate negation. The characters '*', '?'
* and '!' are treated normally like other characters, only ',' (comma) is
* treated specially and is considered as a delimiter that separates objects in
* the group.
*
* @param[in] group Group of objects (comma separated) to match against.
*
* @param[in] object Object to match.
*
* @returns 1 if there is a match, 0 if there is no match at all.
*/
int match_group(const char *group, const char *object)
{
const char *a = NULL;
const char *z = NULL;
if (group == NULL || object == NULL) {
return 0;
}
z = group;
do {
a = strchr(z, ',');
if (a == NULL) {
if (strcmp(z, object) == 0) {
return 1;
}
return 0;
} else {
if (strncmp(z, object, a - z) == 0) {
return 1;
}
}
z = a + 1;
} while (1);
/* not reached */
return 0;
}