From aff9544b9583893ab3bd327f2927ef554f0a2926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Fri, 25 Jul 2008 22:38:42 +0200 Subject: [PATCH] =?UTF-8?q?Parsers=20for=20ipv4/ipv6=20f=C3=BCr=20conditio?= =?UTF-8?q?ns?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/condition.c | 199 ++++++++++++++++++++++----------------- src/condition.h | 23 ++--- src/condition_parsers.rl | 123 ++++++++++++++++++++++++ src/tests.c | 14 +++ src/wscript | 1 + 5 files changed, 262 insertions(+), 98 deletions(-) create mode 100644 src/condition_parsers.rl diff --git a/src/condition.c b/src/condition.c index 2a29c63..c31680f 100644 --- a/src/condition.c +++ b/src/condition.c @@ -3,12 +3,8 @@ static condition* condition_new(comp_operator_t op, condition_lvalue *lvalue); static condition* cond_new_string(comp_operator_t op, condition_lvalue *lvalue, GString *str); -static condition* cond_new_socket(comp_operator_t op, condition_lvalue *lvalue, GString *str); -static condition* condition_new_from_string(comp_operator_t op, condition_lvalue *lvalue, GString *str); static void condition_free(condition *c); -static gboolean condition_check_eval(server *srv, connection *con, condition *cond); - condition_lvalue* condition_lvalue_new(cond_lvalue_t type, GString *key) { condition_lvalue *lvalue = g_slice_new0(condition_lvalue); lvalue->type = type; @@ -38,92 +34,106 @@ static condition* condition_new(comp_operator_t op, condition_lvalue *lvalue) { return c; } +/* only EQ and NE */ static condition* cond_new_string(comp_operator_t op, condition_lvalue *lvalue, GString *str) { - condition *c = condition_new(op, lvalue); - switch (op) { - case CONFIG_COND_EQ: /** == */ - case CONFIG_COND_NE: /** != */ - c->rvalue.string = str; - break; - case CONFIG_COND_MATCH: /** =~ */ - case CONFIG_COND_NOMATCH: /** !~ */ -#ifdef HAVE_PCRE_H - /* TODO */ - condition_free(c); - return NULL; - break; -#else - condition_free(c); - return NULL; -#endif - case CONFIG_COND_GT: /** > */ - case CONFIG_COND_GE: /** >= */ - case CONFIG_COND_LT: /** < */ - case CONFIG_COND_LE: /** <= */ - condition_free(c); - return NULL; - } + condition *c; + c = condition_new(op, lvalue); c->rvalue.type = COND_VALUE_STRING; + c->rvalue.string = str; return c; } -static condition* cond_new_socket(comp_operator_t op, condition_lvalue *lvalue, GString *str) { - return cond_new_string(op, lvalue, str); - /* TODO: parse str as socket addr */ +#ifdef HAVE_PCRE_H +/* only MATCH and NOMATCH */ +static condition* cond_new_match(server *srv, comp_operator_t op, condition_lvalue *lvalue, GString *str) { + UNUSED(op); UNUSED(lvalue); UNUSED(str); + ERROR(srv, "%s", "pcre not supported for now"); + /* TODO */ + return NULL; } +#endif -static condition* condition_new_from_string(comp_operator_t op, condition_lvalue *lvalue, GString *str) { - switch (lvalue->type) { - case COMP_REQUEST_LOCALIP: - case COMP_REQUEST_REMOTEIP: - return cond_new_socket(op, lvalue, str); - case COMP_REQUEST_PATH: - case COMP_REQUEST_HOST: - case COMP_REQUEST_SCHEME: - case COMP_REQUEST_QUERY_STRING: - case COMP_REQUEST_METHOD: - case COMP_PHYSICAL_PATH: - case COMP_PHYSICAL_PATH_EXISTS: - case COMP_REQUEST_HEADER: - return cond_new_string(op, lvalue, str); - case COMP_PHYSICAL_SIZE: - case COMP_REQUEST_CONTENT_LENGTH: - // TODO: die with error - assert(NULL); - break; - } +/* only IP and NOTIP */ +static condition* cond_new_ip(server *srv, comp_operator_t op, condition_lvalue *lvalue, GString *str) { + UNUSED(op); UNUSED(lvalue); UNUSED(str); + ERROR(srv, "%s", "ip matching not supported for now"); + /* TODO: parse str as socket addr */ return NULL; } condition* condition_new_string(server *srv, comp_operator_t op, condition_lvalue *lvalue, GString *str) { - condition *c; - - if (NULL == (c = condition_new_from_string(op, lvalue, str))) { - ERROR(srv, "Condition creation failed: %s %s '%s' (perhaps you compiled without pcre?)", - cond_lvalue_to_string(lvalue->type), comp_op_to_string(op), - str->str); + switch (op) { + case CONFIG_COND_EQ: + case CONFIG_COND_NE: + return cond_new_string(op, lvalue, str); + case CONFIG_COND_MATCH: + case CONFIG_COND_NOMATCH: +#ifdef HAVE_PCRE_H + return cond_new_match(srv, op, lvalue, str); +#else + ERROR(srv, "compiled without pcre, cannot use '%s'", comp_op_to_string(op)); + return NULL; +#endif + case CONFIG_COND_IP: + case CONFIG_COND_NOTIP: + return cond_new_ip(srv, op, lvalue, str); + case CONFIG_COND_GT: + case CONFIG_COND_GE: + case CONFIG_COND_LT: + case CONFIG_COND_LE: + ERROR(srv, "Cannot compare strings with '%s'", comp_op_to_string(op)); return NULL; } - - return c; + ERROR(srv, "Condition creation failed: %s %s '%s' (perhaps you compiled without pcre?)", + cond_lvalue_to_string(lvalue->type), comp_op_to_string(op), + str->str); + return NULL; } +condition* condition_new_int(server *srv, comp_operator_t op, condition_lvalue *lvalue, gint64 i) { + condition *c; + switch (op) { + case CONFIG_COND_MATCH: + case CONFIG_COND_NOMATCH: + case CONFIG_COND_IP: + case CONFIG_COND_NOTIP: + ERROR(srv, "Cannot compare integers with '%s'", comp_op_to_string(op)); + return NULL; + case CONFIG_COND_EQ: + case CONFIG_COND_NE: + case CONFIG_COND_GT: + case CONFIG_COND_GE: + case CONFIG_COND_LT: + case CONFIG_COND_LE: + c = condition_new(op, lvalue); + c->rvalue.type = COND_VALUE_INT; + c->rvalue.i = i; + return c; + } + ERROR(srv, "Condition creation failed: %s %s %"G_GINT64_FORMAT" (perhaps you compiled without pcre?)", + cond_lvalue_to_string(lvalue->type), comp_op_to_string(op), + i); + return NULL; +} + + static void condition_free(condition *c) { switch (c->rvalue.type) { case COND_VALUE_INT: - case COND_VALUE_SOCKET_IPV4: - case COND_VALUE_SOCKET_IPV6: /* nothing to free */ break; case COND_VALUE_STRING: - if (c->op == CONFIG_COND_MATCH || c->op == CONFIG_COND_NOMATCH) { + g_string_free(c->rvalue.string, TRUE); + break; #ifdef HAVE_PCRE_H - if (c->rvalue.regex) pcre_free(c->rvalue.regex); - if (c->rvalue.regex_study) pcre_free(c->rvalue.regex_study); + case COND_VALUE_REGEXP + if (c->rvalue.regex) pcre_free(c->rvalue.regex); + if (c->rvalue.regex_study) pcre_free(c->rvalue.regex_study); #endif - } else { - g_string_free(c->rvalue.string, TRUE); - } + break; + case COND_VALUE_SOCKET_IPV4: + case COND_VALUE_SOCKET_IPV6: + /* nothing to free */ break; } g_slice_free(condition, c); @@ -145,13 +155,15 @@ void condition_release(server *srv, condition* c) { const char* comp_op_to_string(comp_operator_t op) { switch (op) { case CONFIG_COND_EQ: return "=="; - case CONFIG_COND_GE: return ">="; - case CONFIG_COND_GT: return ">"; - case CONFIG_COND_LE: return "<="; - case CONFIG_COND_LT: return "<"; - case CONFIG_COND_MATCH: return "=~"; case CONFIG_COND_NE: return "!="; + case CONFIG_COND_MATCH: return "=~"; case CONFIG_COND_NOMATCH: return "!~"; + case CONFIG_COND_IP: return "=/"; + case CONFIG_COND_NOTIP: return "!/"; + case CONFIG_COND_GT: return ">"; + case CONFIG_COND_GE: return ">="; + case CONFIG_COND_LT: return "<"; + case CONFIG_COND_LE: return "<="; } return ""; @@ -176,11 +188,7 @@ const char* cond_lvalue_to_string(cond_lvalue_t t) { return ""; } -gboolean condition_check(server *srv, connection *con, condition *cond) { - /* TODO: implement cache */ - return condition_check_eval(srv, con, cond); -} - +/* COND_VALUE_STRING and COND_VALUE_REGEXP only */ static gboolean condition_check_eval_string(server *srv, connection *con, condition *cond) { const char *value = NULL; GString *tmp = NULL; @@ -226,22 +234,31 @@ static gboolean condition_check_eval_string(server *srv, connection *con, condit } if (value) switch (cond->op) { - case CONFIG_COND_EQ: /** == */ + case CONFIG_COND_EQ: result = 0 == strcmp(value, cond->rvalue.string->str); break; - case CONFIG_COND_NE: /** != */ + case CONFIG_COND_NE: result = 0 != strcmp(value, cond->rvalue.string->str); break; - case CONFIG_COND_MATCH: /** =~ */ - case CONFIG_COND_NOMATCH: /** !~ */ + case CONFIG_COND_MATCH: + case CONFIG_COND_NOMATCH: +#ifdef HAVE_PCRE_H /* TODO: pcre */ + ERROR(srv, "%s", "regexp match not supported yet"); +#else + ERROR(srv, "compiled without pcre, cannot use '%s'", comp_op_to_string(cond->op)); +#endif break; + case CONFIG_COND_IP: + case CONFIG_COND_NOTIP: case CONFIG_COND_GE: case CONFIG_COND_GT: case CONFIG_COND_LE: case CONFIG_COND_LT: - assert(NULL); + ERROR(srv, "cannot compare string/regexp with '%s'", comp_op_to_string(cond->op)); break; + } else { + ERROR(srv, "couldn't get string value for '%s'", cond_lvalue_to_string(cond->lvalue->type)); } if (tmp) g_string_free(tmp, TRUE); @@ -279,9 +296,12 @@ static gboolean condition_check_eval_int(server *srv, connection *con, condition return (value >= cond->rvalue.i); case CONFIG_COND_MATCH: case CONFIG_COND_NOMATCH: - // TODO: die with error - assert(NULL); + case CONFIG_COND_IP: + case CONFIG_COND_NOTIP: + ERROR(srv, "cannot compare int with '%s'", comp_op_to_string(cond->op)); return FALSE; + } else { + ERROR(srv, "couldn't get int value for '%s'", cond_lvalue_to_string(cond->lvalue->type)); } return FALSE; @@ -308,14 +328,19 @@ static gboolean ipv6_in_ipv4_net(const unsigned char *target, guint32 match, gui } #endif -static gboolean condition_check_eval(server *srv, connection *con, condition *cond) { +gboolean condition_check(server *srv, connection *con, condition *cond) { switch (cond->rvalue.type) { case COND_VALUE_STRING: +#ifdef HAVE_PCRE_H + case COND_VALUE_REGEXP: +#endif return condition_check_eval_string(srv, con, cond); case COND_VALUE_INT: return condition_check_eval_int(srv, con, cond); + case COND_VALUE_SOCKET_IPV4: + case COND_VALUE_SOCKET_IPV6: /* TODO: implement checks */ - default: - return FALSE; + break; } + return FALSE; } diff --git a/src/condition.h b/src/condition.h index b7c3661..6b1aa39 100644 --- a/src/condition.h +++ b/src/condition.h @@ -20,10 +20,13 @@ typedef enum { CONFIG_COND_EQ, /** == */ CONFIG_COND_NE, /** != */ -/* only with strings (including socket name) */ +/* only usable with pcre */ CONFIG_COND_MATCH, /** =~ */ CONFIG_COND_NOMATCH, /** !~ */ + CONFIG_COND_IP, + CONFIG_COND_NOTIP, + /* only with int */ CONFIG_COND_GT, /** > */ CONFIG_COND_GE, /** >= */ @@ -67,10 +70,8 @@ typedef enum { #ifdef HAVE_PCRE_H COND_VALUE_REGEXP, #endif - COND_VALUE_SOCKET_IPV4 /** only match ip/netmask */ -#ifdef HAVE_IPV6 - ,COND_VALUE_SOCKET_IPV6 /** only match ip/netmask */ -#endif + COND_VALUE_SOCKET_IPV4, /** only match ip/netmask */ + COND_VALUE_SOCKET_IPV6 /** only match ip/netmask */ } cond_rvalue_t; struct condition_rvalue { @@ -88,13 +89,10 @@ struct condition_rvalue { guint32 addr; guint32 networkmask; } ipv4; -#ifdef HAVE_IPV6 struct { guint8 addr[16]; guint network; } ipv6; -#endif - sock_addr addr; }; #include "base.h" @@ -112,10 +110,8 @@ LI_API condition_lvalue* condition_lvalue_new(cond_lvalue_t type, GString *key); LI_API void condition_lvalue_acquire(condition_lvalue *lvalue); LI_API void condition_lvalue_release(condition_lvalue *lvalue); - - LI_API condition* condition_new_string(server *srv, comp_operator_t op, condition_lvalue *lvalue, GString *str); -LI_API condition* condition_new_int(server *srv, comp_operator_t op, condition_lvalue *lvalue, gint i); +LI_API condition* condition_new_int(server *srv, comp_operator_t op, condition_lvalue *lvalue, gint64 i); LI_API void condition_acquire(condition *c); LI_API void condition_release(server *srv, condition* c); @@ -125,4 +121,9 @@ LI_API const char* cond_lvalue_to_string(cond_lvalue_t t); LI_API gboolean condition_check(server *srv, connection *con, condition *cond); +/* parser */ +LI_API gboolean parse_ipv4(const char *str, guint32 *ip, guint32 *netmask); +LI_API gboolean parse_ipv6(const char *str, guint8 *ip, guint *network); +LI_API GString* ipv6_tostring(const guint8 ip[16]); + #endif diff --git a/src/condition_parsers.rl b/src/condition_parsers.rl new file mode 100644 index 0000000..2cf3369 --- /dev/null +++ b/src/condition_parsers.rl @@ -0,0 +1,123 @@ + +#include "settings.h" + +#include + +%%{ + machine ipv4_parser; + + action mark { mark = fpc; } + + decint = (digit+) >mark %{ tmpval = strtol(mark, NULL, 10); }; + octet = decint %{ + if (tmpval > 255) fbreak; + data[i++] = tmpval; + }; + ipv4_data = octet "." octet "." octet "." octet; + + ipv4 := ipv4_data (space|0) @{ res = TRUE; }; + ipv4_cidr := ipv4_data "/" decint (space|0) @{ + if (tmpval > 32) fbreak; + *netmask = htonl(~((1 << (32-tmpval)) - 1)); + res = TRUE; + fbreak; + }; + + write data; +}%% + +gboolean parse_ipv4(const char *str, guint32 *ip, guint32 *netmask) { + guint8 *data = (guint8*) ip; + const char *p = str, *mark; + gboolean res = FALSE; + int cs, tmpval, i = 0; + + %% write init nocs; + + cs = netmask ? ipv4_parser_en_ipv4_cidr : ipv4_parser_en_ipv4; + %% write exec noend; + + return res; +} + +%%{ + machine ipv6_parser; + + action mark { mark = fpc; } + + decint = (digit+) >mark %{ tmpval = strtol(mark, NULL, 10); }; + octet = decint %{ + if (tmpval > 255) fbreak; + data[i++] = tmpval; + }; + + hexint = (xdigit{1,4}) >mark %{ tmpval = strtol(mark, NULL, 16); }; + group = hexint %{ + if (tmpval > 0xffff) fbreak; + }; + pregroup = group %{ + if (prec >= 8) fbreak; + predata[prec++] = htons(tmpval); + }; + postgroup = group %{ + if (postc >= 8) fbreak; + postdata[postc++] = htons(tmpval); + }; + ipv4_data = octet "." octet "." octet "." octet; + pre_ipv4 = ipv4_data %{ + if (prec > 6) fbreak; + predata[prec++] = *(guint16*) (data); + predata[prec++] = *(guint16*) (data+2); + }; + post_ipv4 = ipv4_data %{ + if (postc > 6) fbreak; + postdata[postc++] = *(guint16*) (data); + postdata[postc++] = *(guint16*) (data+2); + }; + + ipv6_data = ((pregroup ":")+ | ":") ((":" @ { compressed = TRUE; } (postgroup ":")* (postgroup | post_ipv4)?) | (pregroup | pre_ipv4)); + + ipv6 := ipv6_data (space|0) @{ res = TRUE; }; + ipv6_cidr := ipv6_data "/" decint (space|0) @{ + if (tmpval > 128) fbreak; + *network = tmpval; + res = TRUE; + fbreak; + }; + + write data; +}%% + +gboolean parse_ipv6(const char *str, guint8 *ip, guint *network) { + guint8 data[4] = {0,0,0,0}; + guint16 *predata = (guint16*) ip, postdata[8]; + size_t prec = 0, postc = 0; + const char *p = str, *mark; + gboolean res = FALSE, compressed = FALSE; + int cs, tmpval, i = 0; + + %% write init nocs; + + cs = network ? ipv6_parser_en_ipv6_cidr : ipv6_parser_en_ipv6; + %% write exec noend; + + if (!res) return FALSE; + if (!compressed) return (prec == 8); + if (prec + postc > 7) return FALSE; + for ( ; prec < 8-postc; prec++) predata[prec] = 0; + for (postc = 0 ; prec < 8; prec++, postc++ ) predata[prec] = postdata[postc]; + + return TRUE; +} + +GString* ipv6_tostring(const guint8 ip[16]) { + guint16 *data = (guint16*) ip; + size_t i; + GString *s = g_string_sized_new(0); + + g_string_printf(s, "%" G_GINT16_MODIFIER "x", ntohs(data[0])); + for (i = 1; i < 8; i++) { + g_string_append_printf(s, ":%" G_GINT16_MODIFIER "x", ntohs(data[i])); + } + return s; +} diff --git a/src/tests.c b/src/tests.c index 2fdb09b..02fe168 100644 --- a/src/tests.c +++ b/src/tests.c @@ -37,6 +37,20 @@ int main() { GTimeVal start, end; gboolean result; + guint32 ip, netmask; + assert(parse_ipv4("10.0.3.8/24", &ip, &netmask)); + printf("parsed ip: %s\n", inet_ntoa(*(struct in_addr*) &ip)); + printf("parsed netmask: %s\n", inet_ntoa(*(struct in_addr*) &netmask)); + + guint8 ipv6[16]; + guint network; + GString *s; + assert(parse_ipv6("::ffff:192.168.0.1/80", ipv6, &network)); + s = ipv6_tostring(ipv6); + printf("parsed ipv6: %s/%u\n", s->str, network); + + return 0; + srv = server_new(); /* config parser test */ diff --git a/src/wscript b/src/wscript index f1c9322..a457160 100644 --- a/src/wscript +++ b/src/wscript @@ -12,6 +12,7 @@ common_source=''' chunk.c chunk_parser.c condition.c + condition_parsers.rl config_parser.rl http_headers.c http_request_parser.rl