mirror of
http://git.haproxy.org/git/haproxy.git
synced 2026-02-13 10:29:15 +02:00
This patch just renames functions, types and enums. No code was changed.
A significant number of files were touched, especially the ACL arrays,
so it is likely that some external patches will not apply anymore.
One important thing is that we had to split ACL_PAT_* into two groups :
- ACL_TEST_{PASS|MISS|FAIL}
- PAT_{MATCH|UNMATCH}
A future patch will enforce enums on all these places to avoid confusion.
988 lines
26 KiB
C
988 lines
26 KiB
C
/*
|
|
* Pattern management functions.
|
|
*
|
|
* Copyright 2000-2013 Willy Tarreau <w@1wt.eu>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the License, or (at your option) any later version.
|
|
*
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <stdio.h>
|
|
|
|
#include <common/config.h>
|
|
#include <common/standard.h>
|
|
|
|
#include <types/global.h>
|
|
#include <types/pattern.h>
|
|
|
|
#include <proto/pattern.h>
|
|
|
|
#include <ebsttree.h>
|
|
|
|
char *pat_match_names[PAT_MATCH_NUM] = {
|
|
[PAT_MATCH_FOUND] = "found",
|
|
[PAT_MATCH_BOOL] = "bool",
|
|
[PAT_MATCH_INT] = "int",
|
|
[PAT_MATCH_IP] = "ip",
|
|
[PAT_MATCH_BIN] = "bin",
|
|
[PAT_MATCH_LEN] = "len",
|
|
[PAT_MATCH_STR] = "str",
|
|
[PAT_MATCH_BEG] = "beg",
|
|
[PAT_MATCH_SUB] = "sub",
|
|
[PAT_MATCH_DIR] = "dir",
|
|
[PAT_MATCH_DOM] = "dom",
|
|
[PAT_MATCH_END] = "end",
|
|
[PAT_MATCH_REG] = "reg",
|
|
};
|
|
|
|
int (*pat_parse_fcts[PAT_MATCH_NUM])(const char **, struct pattern *, struct sample_storage *, int *, char **) = {
|
|
[PAT_MATCH_FOUND] = pat_parse_nothing,
|
|
[PAT_MATCH_BOOL] = pat_parse_nothing,
|
|
[PAT_MATCH_INT] = pat_parse_int,
|
|
[PAT_MATCH_IP] = pat_parse_ip,
|
|
[PAT_MATCH_BIN] = pat_parse_bin,
|
|
[PAT_MATCH_LEN] = pat_parse_int,
|
|
[PAT_MATCH_STR] = pat_parse_str,
|
|
[PAT_MATCH_BEG] = pat_parse_str,
|
|
[PAT_MATCH_SUB] = pat_parse_str,
|
|
[PAT_MATCH_DIR] = pat_parse_str,
|
|
[PAT_MATCH_DOM] = pat_parse_str,
|
|
[PAT_MATCH_END] = pat_parse_str,
|
|
[PAT_MATCH_REG] = pat_parse_reg,
|
|
};
|
|
|
|
int (*pat_match_fcts[PAT_MATCH_NUM])(struct sample *, struct pattern *) = {
|
|
[PAT_MATCH_FOUND] = NULL,
|
|
[PAT_MATCH_BOOL] = pat_match_nothing,
|
|
[PAT_MATCH_INT] = pat_match_int,
|
|
[PAT_MATCH_IP] = pat_match_ip,
|
|
[PAT_MATCH_BIN] = pat_match_bin,
|
|
[PAT_MATCH_LEN] = pat_match_len,
|
|
[PAT_MATCH_STR] = pat_match_str,
|
|
[PAT_MATCH_BEG] = pat_match_beg,
|
|
[PAT_MATCH_SUB] = pat_match_sub,
|
|
[PAT_MATCH_DIR] = pat_match_dir,
|
|
[PAT_MATCH_DOM] = pat_match_dom,
|
|
[PAT_MATCH_END] = pat_match_end,
|
|
[PAT_MATCH_REG] = pat_match_reg,
|
|
};
|
|
|
|
/*
|
|
* These functions are exported and may be used by any other component.
|
|
*/
|
|
|
|
/* ignore the current line */
|
|
int pat_parse_nothing(const char **text, struct pattern *pattern, struct sample_storage *smp, int *opaque, char **err)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
/* always return false */
|
|
int pat_match_nothing(struct sample *smp, struct pattern *pattern)
|
|
{
|
|
return PAT_NOMATCH;
|
|
}
|
|
|
|
|
|
/* NB: For two strings to be identical, it is required that their lengths match */
|
|
int pat_match_str(struct sample *smp, struct pattern *pattern)
|
|
{
|
|
int icase;
|
|
|
|
if (pattern->len != smp->data.str.len)
|
|
return PAT_NOMATCH;
|
|
|
|
icase = pattern->flags & PAT_F_IGNORE_CASE;
|
|
if ((icase && strncasecmp(pattern->ptr.str, smp->data.str.str, smp->data.str.len) == 0) ||
|
|
(!icase && strncmp(pattern->ptr.str, smp->data.str.str, smp->data.str.len) == 0))
|
|
return PAT_MATCH;
|
|
return PAT_NOMATCH;
|
|
}
|
|
|
|
/* NB: For two binaries buf to be identical, it is required that their lengths match */
|
|
int pat_match_bin(struct sample *smp, struct pattern *pattern)
|
|
{
|
|
if (pattern->len != smp->data.str.len)
|
|
return PAT_NOMATCH;
|
|
|
|
if (memcmp(pattern->ptr.str, smp->data.str.str, smp->data.str.len) == 0)
|
|
return PAT_MATCH;
|
|
return PAT_NOMATCH;
|
|
}
|
|
|
|
/* Lookup a string in the expression's pattern tree. The node is returned if it
|
|
* exists, otherwise NULL.
|
|
*/
|
|
static void *pat_lookup_str(struct sample *smp, struct pattern_expr *expr)
|
|
{
|
|
/* data are stored in a tree */
|
|
struct ebmb_node *node;
|
|
char prev;
|
|
|
|
/* we may have to force a trailing zero on the test pattern */
|
|
prev = smp->data.str.str[smp->data.str.len];
|
|
if (prev)
|
|
smp->data.str.str[smp->data.str.len] = '\0';
|
|
node = ebst_lookup(&expr->pattern_tree, smp->data.str.str);
|
|
if (prev)
|
|
smp->data.str.str[smp->data.str.len] = prev;
|
|
return node;
|
|
}
|
|
|
|
/* Executes a regex. It temporarily changes the data to add a trailing zero,
|
|
* and restores the previous character when leaving.
|
|
*/
|
|
int pat_match_reg(struct sample *smp, struct pattern *pattern)
|
|
{
|
|
if (regex_exec(pattern->ptr.reg, smp->data.str.str, smp->data.str.len) == 0)
|
|
return PAT_MATCH;
|
|
return PAT_NOMATCH;
|
|
}
|
|
|
|
/* Checks that the pattern matches the beginning of the tested string. */
|
|
int pat_match_beg(struct sample *smp, struct pattern *pattern)
|
|
{
|
|
int icase;
|
|
|
|
if (pattern->len > smp->data.str.len)
|
|
return PAT_NOMATCH;
|
|
|
|
icase = pattern->flags & PAT_F_IGNORE_CASE;
|
|
if ((icase && strncasecmp(pattern->ptr.str, smp->data.str.str, pattern->len) != 0) ||
|
|
(!icase && strncmp(pattern->ptr.str, smp->data.str.str, pattern->len) != 0))
|
|
return PAT_NOMATCH;
|
|
return PAT_MATCH;
|
|
}
|
|
|
|
/* Checks that the pattern matches the end of the tested string. */
|
|
int pat_match_end(struct sample *smp, struct pattern *pattern)
|
|
{
|
|
int icase;
|
|
|
|
if (pattern->len > smp->data.str.len)
|
|
return PAT_NOMATCH;
|
|
icase = pattern->flags & PAT_F_IGNORE_CASE;
|
|
if ((icase && strncasecmp(pattern->ptr.str, smp->data.str.str + smp->data.str.len - pattern->len, pattern->len) != 0) ||
|
|
(!icase && strncmp(pattern->ptr.str, smp->data.str.str + smp->data.str.len - pattern->len, pattern->len) != 0))
|
|
return PAT_NOMATCH;
|
|
return PAT_MATCH;
|
|
}
|
|
|
|
/* Checks that the pattern is included inside the tested string.
|
|
* NB: Suboptimal, should be rewritten using a Boyer-Moore method.
|
|
*/
|
|
int pat_match_sub(struct sample *smp, struct pattern *pattern)
|
|
{
|
|
int icase;
|
|
char *end;
|
|
char *c;
|
|
|
|
if (pattern->len > smp->data.str.len)
|
|
return PAT_NOMATCH;
|
|
|
|
end = smp->data.str.str + smp->data.str.len - pattern->len;
|
|
icase = pattern->flags & PAT_F_IGNORE_CASE;
|
|
if (icase) {
|
|
for (c = smp->data.str.str; c <= end; c++) {
|
|
if (tolower(*c) != tolower(*pattern->ptr.str))
|
|
continue;
|
|
if (strncasecmp(pattern->ptr.str, c, pattern->len) == 0)
|
|
return PAT_MATCH;
|
|
}
|
|
} else {
|
|
for (c = smp->data.str.str; c <= end; c++) {
|
|
if (*c != *pattern->ptr.str)
|
|
continue;
|
|
if (strncmp(pattern->ptr.str, c, pattern->len) == 0)
|
|
return PAT_MATCH;
|
|
}
|
|
}
|
|
return PAT_NOMATCH;
|
|
}
|
|
|
|
/* Background: Fast way to find a zero byte in a word
|
|
* http://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord
|
|
* hasZeroByte = (v - 0x01010101UL) & ~v & 0x80808080UL;
|
|
*
|
|
* To look for 4 different byte values, xor the word with those bytes and
|
|
* then check for zero bytes:
|
|
*
|
|
* v = (((unsigned char)c * 0x1010101U) ^ delimiter)
|
|
* where <delimiter> is the 4 byte values to look for (as an uint)
|
|
* and <c> is the character that is being tested
|
|
*/
|
|
static inline unsigned int is_delimiter(unsigned char c, unsigned int mask)
|
|
{
|
|
mask ^= (c * 0x01010101); /* propagate the char to all 4 bytes */
|
|
return (mask - 0x01010101) & ~mask & 0x80808080U;
|
|
}
|
|
|
|
static inline unsigned int make_4delim(unsigned char d1, unsigned char d2, unsigned char d3, unsigned char d4)
|
|
{
|
|
return d1 << 24 | d2 << 16 | d3 << 8 | d4;
|
|
}
|
|
|
|
/* This one is used by other real functions. It checks that the pattern is
|
|
* included inside the tested string, but enclosed between the specified
|
|
* delimiters or at the beginning or end of the string. The delimiters are
|
|
* provided as an unsigned int made by make_4delim() and match up to 4 different
|
|
* delimiters. Delimiters are stripped at the beginning and end of the pattern.
|
|
*/
|
|
static int match_word(struct sample *smp, struct pattern *pattern, unsigned int delimiters)
|
|
{
|
|
int may_match, icase;
|
|
char *c, *end;
|
|
char *ps;
|
|
int pl;
|
|
|
|
pl = pattern->len;
|
|
ps = pattern->ptr.str;
|
|
|
|
while (pl > 0 && is_delimiter(*ps, delimiters)) {
|
|
pl--;
|
|
ps++;
|
|
}
|
|
|
|
while (pl > 0 && is_delimiter(ps[pl - 1], delimiters))
|
|
pl--;
|
|
|
|
if (pl > smp->data.str.len)
|
|
return PAT_NOMATCH;
|
|
|
|
may_match = 1;
|
|
icase = pattern->flags & PAT_F_IGNORE_CASE;
|
|
end = smp->data.str.str + smp->data.str.len - pl;
|
|
for (c = smp->data.str.str; c <= end; c++) {
|
|
if (is_delimiter(*c, delimiters)) {
|
|
may_match = 1;
|
|
continue;
|
|
}
|
|
|
|
if (!may_match)
|
|
continue;
|
|
|
|
if (icase) {
|
|
if ((tolower(*c) == tolower(*ps)) &&
|
|
(strncasecmp(ps, c, pl) == 0) &&
|
|
(c == end || is_delimiter(c[pl], delimiters)))
|
|
return PAT_MATCH;
|
|
} else {
|
|
if ((*c == *ps) &&
|
|
(strncmp(ps, c, pl) == 0) &&
|
|
(c == end || is_delimiter(c[pl], delimiters)))
|
|
return PAT_MATCH;
|
|
}
|
|
may_match = 0;
|
|
}
|
|
return PAT_NOMATCH;
|
|
}
|
|
|
|
/* Checks that the pattern is included inside the tested string, but enclosed
|
|
* between the delimiters '?' or '/' or at the beginning or end of the string.
|
|
* Delimiters at the beginning or end of the pattern are ignored.
|
|
*/
|
|
int pat_match_dir(struct sample *smp, struct pattern *pattern)
|
|
{
|
|
return match_word(smp, pattern, make_4delim('/', '?', '?', '?'));
|
|
}
|
|
|
|
/* Checks that the pattern is included inside the tested string, but enclosed
|
|
* between the delmiters '/', '?', '.' or ":" or at the beginning or end of
|
|
* the string. Delimiters at the beginning or end of the pattern are ignored.
|
|
*/
|
|
int pat_match_dom(struct sample *smp, struct pattern *pattern)
|
|
{
|
|
return match_word(smp, pattern, make_4delim('/', '?', '.', ':'));
|
|
}
|
|
|
|
/* Checks that the integer in <test> is included between min and max */
|
|
int pat_match_int(struct sample *smp, struct pattern *pattern)
|
|
{
|
|
if ((!pattern->val.range.min_set || pattern->val.range.min <= smp->data.uint) &&
|
|
(!pattern->val.range.max_set || smp->data.uint <= pattern->val.range.max))
|
|
return PAT_MATCH;
|
|
return PAT_NOMATCH;
|
|
}
|
|
|
|
/* Checks that the length of the pattern in <test> is included between min and max */
|
|
int pat_match_len(struct sample *smp, struct pattern *pattern)
|
|
{
|
|
if ((!pattern->val.range.min_set || pattern->val.range.min <= smp->data.str.len) &&
|
|
(!pattern->val.range.max_set || smp->data.str.len <= pattern->val.range.max))
|
|
return PAT_MATCH;
|
|
return PAT_NOMATCH;
|
|
}
|
|
|
|
int pat_match_ip(struct sample *smp, struct pattern *pattern)
|
|
{
|
|
unsigned int v4; /* in network byte order */
|
|
struct in6_addr *v6;
|
|
int bits, pos;
|
|
struct in6_addr tmp6;
|
|
|
|
if (pattern->type == SMP_T_IPV4) {
|
|
if (smp->type == SMP_T_IPV4) {
|
|
v4 = smp->data.ipv4.s_addr;
|
|
}
|
|
else if (smp->type == SMP_T_IPV6) {
|
|
/* v4 match on a V6 sample. We want to check at least for
|
|
* the following forms :
|
|
* - ::ffff:ip:v4 (ipv4 mapped)
|
|
* - ::0000:ip:v4 (old ipv4 mapped)
|
|
* - 2002:ip:v4:: (6to4)
|
|
*/
|
|
if (*(uint32_t*)&smp->data.ipv6.s6_addr[0] == 0 &&
|
|
*(uint32_t*)&smp->data.ipv6.s6_addr[4] == 0 &&
|
|
(*(uint32_t*)&smp->data.ipv6.s6_addr[8] == 0 ||
|
|
*(uint32_t*)&smp->data.ipv6.s6_addr[8] == htonl(0xFFFF))) {
|
|
v4 = *(uint32_t*)&smp->data.ipv6.s6_addr[12];
|
|
}
|
|
else if (*(uint16_t*)&smp->data.ipv6.s6_addr[0] == htons(0x2002)) {
|
|
v4 = htonl((ntohs(*(uint16_t*)&smp->data.ipv6.s6_addr[2]) << 16) +
|
|
ntohs(*(uint16_t*)&smp->data.ipv6.s6_addr[4]));
|
|
}
|
|
else
|
|
return PAT_NOMATCH;
|
|
}
|
|
else
|
|
return PAT_NOMATCH;
|
|
|
|
if (((v4 ^ pattern->val.ipv4.addr.s_addr) & pattern->val.ipv4.mask.s_addr) == 0)
|
|
return PAT_MATCH;
|
|
else
|
|
return PAT_NOMATCH;
|
|
}
|
|
else if (pattern->type == SMP_T_IPV6) {
|
|
if (smp->type == SMP_T_IPV4) {
|
|
/* Convert the IPv4 sample address to IPv4 with the
|
|
* mapping method using the ::ffff: prefix.
|
|
*/
|
|
memset(&tmp6, 0, 10);
|
|
*(uint16_t*)&tmp6.s6_addr[10] = htons(0xffff);
|
|
*(uint32_t*)&tmp6.s6_addr[12] = smp->data.ipv4.s_addr;
|
|
v6 = &tmp6;
|
|
}
|
|
else if (smp->type == SMP_T_IPV6) {
|
|
v6 = &smp->data.ipv6;
|
|
}
|
|
else {
|
|
return PAT_NOMATCH;
|
|
}
|
|
|
|
bits = pattern->val.ipv6.mask;
|
|
for (pos = 0; bits > 0; pos += 4, bits -= 32) {
|
|
v4 = *(uint32_t*)&v6->s6_addr[pos] ^ *(uint32_t*)&pattern->val.ipv6.addr.s6_addr[pos];
|
|
if (bits < 32)
|
|
v4 &= htonl((~0U) << (32-bits));
|
|
if (v4)
|
|
return PAT_NOMATCH;
|
|
}
|
|
return PAT_MATCH;
|
|
}
|
|
return PAT_NOMATCH;
|
|
}
|
|
|
|
/* Lookup an IPv4 address in the expression's pattern tree using the longest
|
|
* match method. The node is returned if it exists, otherwise NULL.
|
|
*/
|
|
static void *pat_lookup_ip(struct sample *smp, struct pattern_expr *expr)
|
|
{
|
|
struct in_addr *s;
|
|
|
|
if (smp->type != SMP_T_IPV4)
|
|
return PAT_NOMATCH;
|
|
|
|
s = &smp->data.ipv4;
|
|
return ebmb_lookup_longest(&expr->pattern_tree, &s->s_addr);
|
|
}
|
|
|
|
/* Parse a string. It is allocated and duplicated. */
|
|
int pat_parse_str(const char **text, struct pattern *pattern, struct sample_storage *smp, int *opaque, char **err)
|
|
{
|
|
int len;
|
|
|
|
len = strlen(*text);
|
|
pattern->type = SMP_T_CSTR;
|
|
|
|
if (pattern->flags & PAT_F_TREE_OK) {
|
|
/* we're allowed to put the data in a tree whose root is pointed
|
|
* to by val.tree.
|
|
*/
|
|
struct pat_idx_elt *node;
|
|
|
|
node = calloc(1, sizeof(*node) + len + 1);
|
|
if (!node) {
|
|
memprintf(err, "out of memory while loading string pattern");
|
|
return 0;
|
|
}
|
|
node->smp = smp;
|
|
memcpy(node->node.key, *text, len + 1);
|
|
if (ebst_insert(pattern->val.tree, &node->node) != &node->node)
|
|
free(node); /* was a duplicate */
|
|
pattern->flags |= PAT_F_TREE; /* this pattern now contains a tree */
|
|
return 1;
|
|
}
|
|
|
|
pattern->ptr.str = strdup(*text);
|
|
pattern->smp = smp;
|
|
if (!pattern->ptr.str) {
|
|
memprintf(err, "out of memory while loading string pattern");
|
|
return 0;
|
|
}
|
|
pattern->len = len;
|
|
return 1;
|
|
}
|
|
|
|
/* Parse a binary written in hexa. It is allocated. */
|
|
int pat_parse_bin(const char **text, struct pattern *pattern, struct sample_storage *smp, int *opaque, char **err)
|
|
{
|
|
int len;
|
|
const char *p = *text;
|
|
int i,j;
|
|
|
|
len = strlen(p);
|
|
if (len%2) {
|
|
memprintf(err, "an even number of hex digit is expected");
|
|
return 0;
|
|
}
|
|
|
|
pattern->type = SMP_T_CBIN;
|
|
pattern->len = len >> 1;
|
|
pattern->ptr.str = malloc(pattern->len);
|
|
pattern->smp = smp;
|
|
if (!pattern->ptr.str) {
|
|
memprintf(err, "out of memory while loading string pattern");
|
|
return 0;
|
|
}
|
|
|
|
i = j = 0;
|
|
while (j < pattern->len) {
|
|
if (!ishex(p[i++]))
|
|
goto bad_input;
|
|
if (!ishex(p[i++]))
|
|
goto bad_input;
|
|
pattern->ptr.str[j++] = (hex2i(p[i-2]) << 4) + hex2i(p[i-1]);
|
|
}
|
|
return 1;
|
|
|
|
bad_input:
|
|
memprintf(err, "an hex digit is expected (found '%c')", p[i-1]);
|
|
free(pattern->ptr.str);
|
|
return 0;
|
|
}
|
|
|
|
/* Parse and concatenate all further strings into one. */
|
|
int
|
|
pat_parse_strcat(const char **text, struct pattern *pattern, struct sample_storage *smp, int *opaque, char **err)
|
|
{
|
|
|
|
int len = 0, i;
|
|
char *s;
|
|
|
|
for (i = 0; *text[i]; i++)
|
|
len += strlen(text[i])+1;
|
|
|
|
pattern->type = SMP_T_CSTR;
|
|
pattern->ptr.str = s = calloc(1, len);
|
|
pattern->smp = smp;
|
|
if (!pattern->ptr.str) {
|
|
memprintf(err, "out of memory while loading pattern");
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; *text[i]; i++)
|
|
s += sprintf(s, i?" %s":"%s", text[i]);
|
|
|
|
pattern->len = len;
|
|
|
|
return i;
|
|
}
|
|
|
|
/* Free data allocated by pat_parse_reg */
|
|
static void pat_free_reg(void *ptr)
|
|
{
|
|
regex_free(ptr);
|
|
}
|
|
|
|
/* Parse a regex. It is allocated. */
|
|
int pat_parse_reg(const char **text, struct pattern *pattern, struct sample_storage *smp, int *opaque, char **err)
|
|
{
|
|
regex *preg;
|
|
|
|
preg = calloc(1, sizeof(*preg));
|
|
|
|
if (!preg) {
|
|
memprintf(err, "out of memory while loading pattern");
|
|
return 0;
|
|
}
|
|
|
|
if (!regex_comp(*text, preg, !(pattern->flags & PAT_F_IGNORE_CASE), 0, err)) {
|
|
free(preg);
|
|
return 0;
|
|
}
|
|
|
|
pattern->ptr.reg = preg;
|
|
pattern->freeptrbuf = &pat_free_reg;
|
|
pattern->smp = smp;
|
|
return 1;
|
|
}
|
|
|
|
/* Parse a range of positive integers delimited by either ':' or '-'. If only
|
|
* one integer is read, it is set as both min and max. An operator may be
|
|
* specified as the prefix, among this list of 5 :
|
|
*
|
|
* 0:eq, 1:gt, 2:ge, 3:lt, 4:le
|
|
*
|
|
* The default operator is "eq". It supports range matching. Ranges are
|
|
* rejected for other operators. The operator may be changed at any time.
|
|
* The operator is stored in the 'opaque' argument.
|
|
*
|
|
* If err is non-NULL, an error message will be returned there on errors and
|
|
* the caller will have to free it.
|
|
*
|
|
*/
|
|
int pat_parse_int(const char **text, struct pattern *pattern, struct sample_storage *smp, int *opaque, char **err)
|
|
{
|
|
signed long long i;
|
|
unsigned int j, last, skip = 0;
|
|
const char *ptr = *text;
|
|
|
|
pattern->type = SMP_T_UINT;
|
|
pattern->smp = smp;
|
|
while (!isdigit((unsigned char)*ptr)) {
|
|
switch (get_std_op(ptr)) {
|
|
case STD_OP_EQ: *opaque = 0; break;
|
|
case STD_OP_GT: *opaque = 1; break;
|
|
case STD_OP_GE: *opaque = 2; break;
|
|
case STD_OP_LT: *opaque = 3; break;
|
|
case STD_OP_LE: *opaque = 4; break;
|
|
default:
|
|
memprintf(err, "'%s' is neither a number nor a supported operator", ptr);
|
|
return 0;
|
|
}
|
|
|
|
skip++;
|
|
ptr = text[skip];
|
|
}
|
|
|
|
last = i = 0;
|
|
while (1) {
|
|
j = *ptr++;
|
|
if ((j == '-' || j == ':') && !last) {
|
|
last++;
|
|
pattern->val.range.min = i;
|
|
i = 0;
|
|
continue;
|
|
}
|
|
j -= '0';
|
|
if (j > 9)
|
|
// also catches the terminating zero
|
|
break;
|
|
i *= 10;
|
|
i += j;
|
|
}
|
|
|
|
if (last && *opaque >= 1 && *opaque <= 4) {
|
|
/* having a range with a min or a max is absurd */
|
|
memprintf(err, "integer range '%s' specified with a comparison operator", text[skip]);
|
|
return 0;
|
|
}
|
|
|
|
if (!last)
|
|
pattern->val.range.min = i;
|
|
pattern->val.range.max = i;
|
|
|
|
switch (*opaque) {
|
|
case 0: /* eq */
|
|
pattern->val.range.min_set = 1;
|
|
pattern->val.range.max_set = 1;
|
|
break;
|
|
case 1: /* gt */
|
|
pattern->val.range.min++; /* gt = ge + 1 */
|
|
case 2: /* ge */
|
|
pattern->val.range.min_set = 1;
|
|
pattern->val.range.max_set = 0;
|
|
break;
|
|
case 3: /* lt */
|
|
pattern->val.range.max--; /* lt = le - 1 */
|
|
case 4: /* le */
|
|
pattern->val.range.min_set = 0;
|
|
pattern->val.range.max_set = 1;
|
|
break;
|
|
}
|
|
return skip + 1;
|
|
}
|
|
|
|
/* Parse a range of positive 2-component versions delimited by either ':' or
|
|
* '-'. The version consists in a major and a minor, both of which must be
|
|
* smaller than 65536, because internally they will be represented as a 32-bit
|
|
* integer.
|
|
* If only one version is read, it is set as both min and max. Just like for
|
|
* pure integers, an operator may be specified as the prefix, among this list
|
|
* of 5 :
|
|
*
|
|
* 0:eq, 1:gt, 2:ge, 3:lt, 4:le
|
|
*
|
|
* The default operator is "eq". It supports range matching. Ranges are
|
|
* rejected for other operators. The operator may be changed at any time.
|
|
* The operator is stored in the 'opaque' argument. This allows constructs
|
|
* such as the following one :
|
|
*
|
|
* acl obsolete_ssl ssl_req_proto lt 3
|
|
* acl unsupported_ssl ssl_req_proto gt 3.1
|
|
* acl valid_ssl ssl_req_proto 3.0-3.1
|
|
*
|
|
*/
|
|
int pat_parse_dotted_ver(const char **text, struct pattern *pattern, struct sample_storage *smp, int *opaque, char **err)
|
|
{
|
|
signed long long i;
|
|
unsigned int j, last, skip = 0;
|
|
const char *ptr = *text;
|
|
|
|
|
|
while (!isdigit((unsigned char)*ptr)) {
|
|
switch (get_std_op(ptr)) {
|
|
case STD_OP_EQ: *opaque = 0; break;
|
|
case STD_OP_GT: *opaque = 1; break;
|
|
case STD_OP_GE: *opaque = 2; break;
|
|
case STD_OP_LT: *opaque = 3; break;
|
|
case STD_OP_LE: *opaque = 4; break;
|
|
default:
|
|
memprintf(err, "'%s' is neither a number nor a supported operator", ptr);
|
|
return 0;
|
|
}
|
|
|
|
skip++;
|
|
ptr = text[skip];
|
|
}
|
|
|
|
last = i = 0;
|
|
while (1) {
|
|
j = *ptr++;
|
|
if (j == '.') {
|
|
/* minor part */
|
|
if (i >= 65536)
|
|
return 0;
|
|
i <<= 16;
|
|
continue;
|
|
}
|
|
if ((j == '-' || j == ':') && !last) {
|
|
last++;
|
|
if (i < 65536)
|
|
i <<= 16;
|
|
pattern->val.range.min = i;
|
|
i = 0;
|
|
continue;
|
|
}
|
|
j -= '0';
|
|
if (j > 9)
|
|
// also catches the terminating zero
|
|
break;
|
|
i = (i & 0xFFFF0000) + (i & 0xFFFF) * 10;
|
|
i += j;
|
|
}
|
|
|
|
/* if we only got a major version, let's shift it now */
|
|
if (i < 65536)
|
|
i <<= 16;
|
|
|
|
if (last && *opaque >= 1 && *opaque <= 4) {
|
|
/* having a range with a min or a max is absurd */
|
|
memprintf(err, "version range '%s' specified with a comparison operator", text[skip]);
|
|
return 0;
|
|
}
|
|
|
|
pattern->smp = smp;
|
|
|
|
if (!last)
|
|
pattern->val.range.min = i;
|
|
pattern->val.range.max = i;
|
|
|
|
switch (*opaque) {
|
|
case 0: /* eq */
|
|
pattern->val.range.min_set = 1;
|
|
pattern->val.range.max_set = 1;
|
|
break;
|
|
case 1: /* gt */
|
|
pattern->val.range.min++; /* gt = ge + 1 */
|
|
case 2: /* ge */
|
|
pattern->val.range.min_set = 1;
|
|
pattern->val.range.max_set = 0;
|
|
break;
|
|
case 3: /* lt */
|
|
pattern->val.range.max--; /* lt = le - 1 */
|
|
case 4: /* le */
|
|
pattern->val.range.min_set = 0;
|
|
pattern->val.range.max_set = 1;
|
|
break;
|
|
}
|
|
return skip + 1;
|
|
}
|
|
|
|
/* Parse an IP address and an optional mask in the form addr[/mask].
|
|
* The addr may either be an IPv4 address or a hostname. The mask
|
|
* may either be a dotted mask or a number of bits. Returns 1 if OK,
|
|
* otherwise 0. NOTE: IP address patterns are typed (IPV4/IPV6).
|
|
*/
|
|
int pat_parse_ip(const char **text, struct pattern *pattern, struct sample_storage *smp, int *opaque, char **err)
|
|
{
|
|
struct eb_root *tree = NULL;
|
|
if (pattern->flags & PAT_F_TREE_OK)
|
|
tree = pattern->val.tree;
|
|
|
|
if (str2net(*text, &pattern->val.ipv4.addr, &pattern->val.ipv4.mask)) {
|
|
unsigned int mask = ntohl(pattern->val.ipv4.mask.s_addr);
|
|
struct pat_idx_elt *node;
|
|
/* check if the mask is contiguous so that we can insert the
|
|
* network into the tree. A continuous mask has only ones on
|
|
* the left. This means that this mask + its lower bit added
|
|
* once again is null.
|
|
*/
|
|
pattern->type = SMP_T_IPV4;
|
|
if (mask + (mask & -mask) == 0 && tree) {
|
|
mask = mask ? 33 - flsnz(mask & -mask) : 0; /* equals cidr value */
|
|
/* FIXME: insert <addr>/<mask> into the tree here */
|
|
node = calloc(1, sizeof(*node) + 4); /* reserve 4 bytes for IPv4 address */
|
|
if (!node) {
|
|
memprintf(err, "out of memory while loading IPv4 pattern");
|
|
return 0;
|
|
}
|
|
node->smp = smp;
|
|
memcpy(node->node.key, &pattern->val.ipv4.addr, 4); /* network byte order */
|
|
node->node.node.pfx = mask;
|
|
if (ebmb_insert_prefix(tree, &node->node, 4) != &node->node)
|
|
free(node); /* was a duplicate */
|
|
pattern->flags |= PAT_F_TREE;
|
|
return 1;
|
|
}
|
|
return 1;
|
|
}
|
|
else if (str62net(*text, &pattern->val.ipv6.addr, &pattern->val.ipv6.mask)) {
|
|
/* no tree support right now */
|
|
pattern->type = SMP_T_IPV6;
|
|
return 1;
|
|
}
|
|
else {
|
|
memprintf(err, "'%s' is not a valid IPv4 or IPv6 address", *text);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* NB: does nothing if <pat> is NULL */
|
|
void pattern_free(struct pattern *pat)
|
|
{
|
|
if (!pat)
|
|
return;
|
|
|
|
if (pat->ptr.ptr) {
|
|
if (pat->freeptrbuf)
|
|
pat->freeptrbuf(pat->ptr.ptr);
|
|
|
|
free(pat->ptr.ptr);
|
|
}
|
|
|
|
free(pat);
|
|
}
|
|
|
|
void free_pattern_list(struct list *head)
|
|
{
|
|
struct pattern *pat, *tmp;
|
|
list_for_each_entry_safe(pat, tmp, head, list)
|
|
pattern_free(pat);
|
|
}
|
|
|
|
void free_pattern_tree(struct eb_root *root)
|
|
{
|
|
struct eb_node *node, *next;
|
|
node = eb_first(root);
|
|
while (node) {
|
|
next = eb_next(node);
|
|
eb_delete(node);
|
|
free(node);
|
|
node = next;
|
|
}
|
|
}
|
|
|
|
void pattern_prune_expr(struct pattern_expr *expr)
|
|
{
|
|
free_pattern_list(&expr->patterns);
|
|
free_pattern_tree(&expr->pattern_tree);
|
|
LIST_INIT(&expr->patterns);
|
|
}
|
|
|
|
void pattern_init_expr(struct pattern_expr *expr)
|
|
{
|
|
LIST_INIT(&expr->patterns);
|
|
expr->pattern_tree = EB_ROOT_UNIQUE;
|
|
}
|
|
|
|
/* return 1 if the process is ok
|
|
* return -1 if the parser fail. The err message is filled.
|
|
* return -2 if out of memory
|
|
*/
|
|
int pattern_register(struct pattern_expr *expr, char *text,
|
|
struct sample_storage *smp,
|
|
struct pattern **pattern,
|
|
int patflags, char **err)
|
|
{
|
|
const char *args[2];
|
|
int opaque = 0;
|
|
|
|
args[0] = text;
|
|
args[1] = "";
|
|
|
|
/* we keep the previous pattern along iterations as long as it's not used */
|
|
if (!*pattern)
|
|
*pattern = (struct pattern *)malloc(sizeof(**pattern));
|
|
if (!*pattern)
|
|
return -1;
|
|
|
|
memset(*pattern, 0, sizeof(**pattern));
|
|
(*pattern)->flags = patflags;
|
|
|
|
if (!((*pattern)->flags & PAT_F_IGNORE_CASE) &&
|
|
(expr->match == pat_match_str || expr->match == pat_match_ip)) {
|
|
/* we pre-set the data pointer to the tree's head so that functions
|
|
* which are able to insert in a tree know where to do that.
|
|
*/
|
|
(*pattern)->flags |= PAT_F_TREE_OK;
|
|
(*pattern)->val.tree = &expr->pattern_tree;
|
|
}
|
|
|
|
(*pattern)->type = SMP_TYPES; /* unspecified type by default */
|
|
if (!expr->parse(args, *pattern, smp, &opaque, err))
|
|
return -1;
|
|
|
|
/* if the parser did not feed the tree, let's chain the pattern to the list */
|
|
if (!((*pattern)->flags & PAT_F_TREE)) {
|
|
LIST_ADDQ(&expr->patterns, &(*pattern)->list);
|
|
*pattern = NULL; /* get a new one */
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Reads patterns from a file. If <err_msg> is non-NULL, an error message will
|
|
* be returned there on errors and the caller will have to free it.
|
|
*/
|
|
int pattern_read_from_file(struct pattern_expr *expr,
|
|
const char *filename, int patflags,
|
|
char **err)
|
|
{
|
|
FILE *file;
|
|
char *c;
|
|
char *arg;
|
|
struct pattern *pattern;
|
|
int ret = 0;
|
|
int line = 0;
|
|
int code;
|
|
|
|
file = fopen(filename, "r");
|
|
if (!file) {
|
|
memprintf(err, "failed to open pattern file <%s>", filename);
|
|
return 0;
|
|
}
|
|
|
|
/* now parse all patterns. The file may contain only one pattern per
|
|
* line. If the line contains spaces, they will be part of the pattern.
|
|
* The pattern stops at the first CR, LF or EOF encountered.
|
|
*/
|
|
pattern = NULL;
|
|
while (fgets(trash.str, trash.size, file) != NULL) {
|
|
line++;
|
|
c = trash.str;
|
|
|
|
/* ignore lines beginning with a dash */
|
|
if (*c == '#')
|
|
continue;
|
|
|
|
/* strip leading spaces and tabs */
|
|
while (*c == ' ' || *c == '\t')
|
|
c++;
|
|
|
|
|
|
arg = c;
|
|
while (*c && *c != '\n' && *c != '\r')
|
|
c++;
|
|
*c = 0;
|
|
|
|
/* empty lines are ignored too */
|
|
if (c == arg)
|
|
continue;
|
|
|
|
code = pattern_register(expr, arg, NULL, &pattern, patflags, err);
|
|
if (code == -2) {
|
|
memprintf(err, "out of memory when loading patterns from file <%s>", filename);
|
|
goto out_close;
|
|
}
|
|
else if (code < 0) {
|
|
memprintf(err, "%s when loading patterns from file <%s>", *err, filename);
|
|
goto out_free_pattern;
|
|
}
|
|
}
|
|
|
|
ret = 1; /* success */
|
|
|
|
out_free_pattern:
|
|
pattern_free(pattern);
|
|
out_close:
|
|
fclose(file);
|
|
return ret;
|
|
}
|
|
|
|
/* This function matches a sample <smp> against a set of patterns presented in
|
|
* pattern expression <expr>. Upon success, if <sample> is not NULL, it is fed
|
|
* with the pointer associated with the matching pattern. This function returns
|
|
* PAT_NOMATCH or PAT_MATCH.
|
|
*/
|
|
inline int pattern_exec_match(struct pattern_expr *expr, struct sample *smp,
|
|
struct sample_storage **sample)
|
|
{
|
|
int pat_res = PAT_NOMATCH;
|
|
struct pattern *pattern;
|
|
struct ebmb_node *node = NULL;
|
|
struct pat_idx_elt *elt;
|
|
|
|
if (expr->match == pat_match_nothing) {
|
|
if (smp->data.uint)
|
|
pat_res |= PAT_MATCH;
|
|
else
|
|
pat_res |= PAT_NOMATCH;
|
|
}
|
|
else if (!expr->match) {
|
|
/* just check for existence */
|
|
pat_res |= PAT_MATCH;
|
|
}
|
|
else {
|
|
if (!eb_is_empty(&expr->pattern_tree)) {
|
|
/* a tree is present, let's check what type it is */
|
|
if (expr->match == pat_match_str)
|
|
node = pat_lookup_str(smp, expr);
|
|
else if (expr->match == pat_match_ip)
|
|
node = pat_lookup_ip(smp, expr);
|
|
if (node) {
|
|
pat_res |= PAT_MATCH;
|
|
elt = ebmb_entry(node, struct pat_idx_elt, node);
|
|
if (sample)
|
|
*sample = elt->smp;
|
|
}
|
|
}
|
|
|
|
/* call the match() function for all tests on this value */
|
|
list_for_each_entry(pattern, &expr->patterns, list) {
|
|
if (pat_res == PAT_MATCH)
|
|
break;
|
|
pat_res |= expr->match(smp, pattern);
|
|
if (sample)
|
|
*sample = pattern->smp;
|
|
}
|
|
}
|
|
|
|
return pat_res;
|
|
}
|
|
|