From 3b70ebff7fa4e07f9959d258c28cf0382829315f Mon Sep 17 00:00:00 2001 From: Michael Poole Date: Mon, 12 Sep 2005 03:40:17 +0000 Subject: [PATCH] Fix IP mask parsing some more (hopefully for good). git-svn-id: file:///home/klmitch/undernet-ircu/undernet-ircu-svn/ircu2/trunk@1478 c9e4aea6-c8fd-4c43-8297-357d70d61c8c --- ChangeLog | 28 +++++ RELEASE.NOTES | 4 + include/ircd_string.h | 3 +- include/match.h | 2 - ircd/ircd_string.c | 214 +++++++++++++++++++++++-------------- ircd/m_burst.c | 6 +- ircd/match.c | 96 ----------------- ircd/test/ircd_in_addr_t.c | 61 +++++++++++ 8 files changed, 229 insertions(+), 185 deletions(-) diff --git a/ChangeLog b/ChangeLog index 5ba9007..11c1d56 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,31 @@ +2005-09-11 Michael Poole + + * RELEASE.NOTES: Mention the side benefits of this change. + + * include/ircd_string.h (ipmask_parse): Declare function here. + (ircd_aton): Becomes a special case of ipmask_parse(). + + * include/match.h (check_if_ipmask): Undeclare function. + (ipmask_parse): Remove function prototype from this file. + + * ircd/ircd_string.c (ircd_aton_ip4): Add nullable pbits parameter + to accept ipmask length. Rework to fill that in. + (ircd_aton): Rework into... + (ipmask_parse): this function, which knows how to fill in its own + pbits parameter. + + * ircd/m_burst.c (ms_burst): Rely on make_ban() to set the ban + flags correctly, to avoid call to check_if_ipmask(). + + * ircd/match.c (ipmask_parse_ipv4): Delete function. + (check_if_ipmask): Likewise. + (ipmask_parse): Delete this version in favor of ircd_string.c's. + + * ircd/test/ircd_in_addr_t.c (ipmask_test): New struct type. + (test_masks): New array of ipmask_test. + (test_ipmask): Function to run one of those tests. + (main): Call test_ipmask(). + 2005-09-11 Alex Badea * ircd/m_ping.c (ms_ping, mo_ping): misplaced chunk of code diff --git a/RELEASE.NOTES b/RELEASE.NOTES index abdcf2b..bde760c 100644 --- a/RELEASE.NOTES +++ b/RELEASE.NOTES @@ -85,6 +85,10 @@ except X, you could use SILENCE *!*@*,~X!cservice@undernet.org. The server will no longer kick "net riders" in keyed (+k) channels if both sides of the net join have the same key. +IP masks (as used in bans, G-lines, etc) are now parsed in a more +forgiving manner. 127.0.0.0/8, 127.* and 127/8 are all accepted and +mean the same thing. + Configuration Changes: As mentioned above, the configuration file format has changed diff --git a/include/ircd_string.h b/include/ircd_string.h index 7562135..df774c5 100644 --- a/include/ircd_string.h +++ b/include/ircd_string.h @@ -30,7 +30,8 @@ extern int token_vector(char* names, char token, char** vector, int size); extern const char* ircd_ntoa(const struct irc_in_addr* addr); extern const char* ircd_ntoa_r(char* buf, const struct irc_in_addr* addr); -extern int ircd_aton(struct irc_in_addr *addr, const char *str); +#define ircd_aton(ADDR, STR) ipmask_parse((STR), (ADDR), NULL) +extern int ipmask_parse(const char *in, struct irc_in_addr *mask, unsigned char *bits_ptr); extern char* host_from_uh(char* buf, const char* userhost, size_t len); extern char* ircd_strtok(char** save, char* str, char* fs); diff --git a/include/match.h b/include/match.h index 4af6747..a2d7542 100644 --- a/include/match.h +++ b/include/match.h @@ -29,8 +29,6 @@ extern int matchexec(const char *string, const char *cmask, int minlen); extern int matchdecomp(char *mask, const char *cmask); extern int mmexec(const char *wcm, int wminlen, const char *rcm, int rminlen); -extern int check_if_ipmask(const char *mask); -extern int ipmask_parse(const char *in, struct irc_in_addr *mask, unsigned char *bits_ptr); extern int ipmask_check(const struct irc_in_addr *addr, const struct irc_in_addr *mask, unsigned char bits); #endif /* INCLUDED_match_h */ diff --git a/ircd/ircd_string.c b/ircd/ircd_string.c index 1569349..bfdceb5 100644 --- a/ircd/ircd_string.c +++ b/ircd/ircd_string.c @@ -453,12 +453,13 @@ const char* ircd_ntoa_r(char* buf, const struct irc_in_addr* in) /** Attempt to parse an IPv4 address into a network-endian form. * @param[in] input Input string. * @param[out] output Network-endian representation of the address. + * @param[out] pbits Number of bits found in pbits. * @return Number of characters used from \a input, or 0 if the parse failed. */ static unsigned int -ircd_aton_ip4(const char *input, unsigned int *output) +ircd_aton_ip4(const char *input, unsigned int *output, unsigned char *pbits) { - unsigned int dots = 0, pos = 0, part = 0, ip = 0; + unsigned int dots = 0, pos = 0, part = 0, ip = 0, bits; /* Intentionally no support for bizarre IPv4 formats (plain * integers, octal or hex components) -- only vanilla dotted @@ -466,33 +467,60 @@ ircd_aton_ip4(const char *input, unsigned int *output) */ if (input[0] == '.') return 0; - while (1) { - if (IsDigit(input[pos])) { - part = part * 10 + input[pos++] - '0'; - if (part > 255) - return 0; - if ((dots == 3) && !IsDigit(input[pos])) { - *output = htonl(ip | part); - return pos; - } - } else if (input[pos] == '.') { - if (input[++pos] == '.') + bits = 32; + while (1) switch (input[pos]) { + case '\0': + if (dots < 3) + return 0; + out: + ip |= part << (24 - 8 * dots); + *output = htonl(ip); + if (pbits) + *pbits = bits; + return pos; + case '.': + if (input[++pos] == '.') + return 0; + ip |= part << (24 - 8 * dots++); + part = 0; + if (input[pos] == '*') { + while (input[++pos] == '*') ; + if (input[pos] != '\0') return 0; - ip |= part << (24 - 8 * dots++); - part = 0; - } else + if (pbits) + *pbits = dots * 8; + *output = htonl(ip); + return pos; + } + break; + case '/': + if (!pbits || !IsDigit(input[pos + 1])) + return 0; + for (bits = 0; IsDigit(input[++pos]); ) + bits = bits * 10 + input[pos] - '0'; + if (bits > 32) + return 0; + goto out; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + part = part * 10 + input[pos++] - '0'; + if (part > 255) return 0; + break; + default: + return 0; } } /** Parse a numeric IPv4 or IPv6 address into an irc_in_addr. - * @param[out] ip Receives parsed IP address. * @param[in] input Input buffer. + * @param[out] ip Receives parsed IP address. + * @param[out] pbits If non-NULL, receives number of bits specified in address mask. * @return Number of characters used from \a input, or 0 if the * address was unparseable or malformed. */ int -ircd_aton(struct irc_in_addr *ip, const char *input) +ipmask_parse(const char *input, struct irc_in_addr *ip, unsigned char *pbits) { char *colon; char *dot; @@ -520,80 +548,104 @@ ircd_aton(struct irc_in_addr *ip, const char *input) pos += 2; part_start = input + pos; } - while (ii < 8) { + while (ii < 8) switch (input[pos]) { unsigned char chval; - - switch (input[pos]) { - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - chval = input[pos] - '0'; - use_chval: - part = (part << 4) | chval; - if (part > 0xffff) + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + chval = input[pos] - '0'; + use_chval: + part = (part << 4) | chval; + if (part > 0xffff) + return 0; + pos++; + break; + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + chval = input[pos] - 'A' + 10; + goto use_chval; + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + chval = input[pos] - 'a' + 10; + goto use_chval; + case ':': + part_start = input + ++pos; + if (input[pos] == '.') + return 0; + ip->in6_16[ii++] = htons(part); + part = 0; + if (input[pos] == ':') { + if (colon < 8) return 0; + colon = ii; pos++; - break; - case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': - chval = input[pos] - 'A' + 10; - goto use_chval; - case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': - chval = input[pos] - 'a' + 10; - goto use_chval; - case ':': - part_start = input + ++pos; - if (input[pos] == '.') - return 0; - ip->in6_16[ii++] = htons(part); - part = 0; - if (input[pos] == ':') { - if (colon < 8) - return 0; - colon = ii; - pos++; - } - break; - case '.': { - uint32_t ip4; - unsigned int len; - len = ircd_aton_ip4(part_start, &ip4); - if (!len || (ii > 6)) - return 0; - ip->in6_16[ii++] = htons(ntohl(ip4) >> 16); - ip->in6_16[ii++] = htons(ntohl(ip4) & 65535); - if (colon < 8) { - unsigned int jj; - /* Shift stuff after "::" up and fill middle with zeros. */ - for (jj = 0; jj < ii - colon; jj++) - ip->in6_16[7 - jj] = ip->in6_16[ii - jj - 1]; - for (jj = 0; jj < 8 - ii; jj++) - ip->in6_16[colon + jj] = 0; - } - return part_start - input + len; - } - default: { - ip->in6_16[ii++] = htons(part); - if (colon < 8) { - unsigned int jj; - /* Shift stuff after "::" up and fill middle with zeros. */ - for (jj = 0; jj < ii - colon; jj++) - ip->in6_16[7 - jj] = ip->in6_16[ii - jj - 1]; - for (jj = 0; jj < 8 - ii; jj++) - ip->in6_16[colon + jj] = 0; - } - return pos; - } } + break; + case '.': { + uint32_t ip4; + unsigned int len; + len = ircd_aton_ip4(part_start, &ip4, pbits); + if (!len || (ii > 6)) + return 0; + ip->in6_16[ii++] = htons(ntohl(ip4) >> 16); + ip->in6_16[ii++] = htons(ntohl(ip4) & 65535); + if (pbits) + *pbits += 96; + pos = part_start + len - input; + goto finish; + } + case '/': + if (!pbits || !IsDigit(input[pos + 1])) + return 0; + ip->in6_16[ii++] = htons(part); + for (part = 0; IsDigit(input[++pos]); ) + part = part * 10 + input[pos] - '0'; + if (part > 128) + return 0; + *pbits = part; + goto finish; + case '*': + while (input[++pos] == '*') ; + if (input[pos] != '\0' || colon < 8) + return 0; + if (pbits) + *pbits = ii * 16; + return pos; + case '\0': + ip->in6_16[ii++] = htons(part); + if (colon == 8 && ii < 8) + return 0; + if (pbits) + *pbits = 128; + goto finish; + default: + return 0; + } + finish: + if (colon < 8) { + unsigned int jj; + /* Shift stuff after "::" up and fill middle with zeros. */ + for (jj = 0; jj < ii - colon; jj++) + ip->in6_16[7 - jj] = ip->in6_16[ii - jj - 1]; + for (jj = 0; jj < 8 - ii; jj++) + ip->in6_16[colon + jj] = 0; } return pos; } else if (dot) { unsigned int addr; - int len = ircd_aton_ip4(input, &addr); + int len = ircd_aton_ip4(input, &addr, pbits); if (len) { ip->in6_16[5] = htons(65535); ip->in6_16[6] = htons(ntohl(addr) >> 16); ip->in6_16[7] = htons(ntohl(addr) & 65535); - return len; + if (pbits) + *pbits += 96; } - } - return 0; /* parse failed */ + return len; + } else if (input[0] == '*') { + unsigned int pos = 0; + while (input[++pos] == '*') ; + if (input[pos] != '\0') + return 0; + if (pbits) + *pbits = 0; + return pos; + } else return 0; /* parse failed */ } diff --git a/ircd/m_burst.c b/ircd/m_burst.c index 50c59b6..5914af4 100644 --- a/ircd/m_burst.c +++ b/ircd/m_burst.c @@ -371,11 +371,7 @@ int ms_burst(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) newban = make_ban(ban); /* create new ban */ strcpy(newban->who, "*"); newban->when = TStime(); - - newban->flags = BAN_BURSTED; /* set flags */ - if ((ptr = strrchr(ban, '@')) && check_if_ipmask(ptr + 1)) - newban->flags |= BAN_IPMASK; - + newban->flags |= BAN_BURSTED; newban->next = 0; if (lp) lp->next = newban; /* link it in */ diff --git a/ircd/match.c b/ircd/match.c index 68f57d1..1715b53 100644 --- a/ircd/match.c +++ b/ircd/match.c @@ -839,102 +839,6 @@ int mmexec(const char *wcm, int wminlen, const char *rcm, int rminlen) return 1; /* Auch... something left out ? Fail */ } -#include -#include -#include -#include -#include - -/** Parse an input string as an IPv4 address. - * @param[in] in Text form of address. - * @param[out] out IPv4 address in network representation. - * @return Number of address bits specified by \a in. - */ -static int ipmask_parse_ipv4(const char *in, struct in_addr *out) -{ - int class; - int ad[4] = { 0 }; - int bits = 0; - - class = sscanf(in, "%d.%d.%d.%d/%d", &ad[0], &ad[1], &ad[2], &ad[3], &bits); - if (class != 5) - bits = class * 8; - out->s_addr = ntohl((ad[0] << 24) | (ad[1] << 16) | (ad[2] << 8) | ad[3]); - return bits; -} - -/** Test whether a string looks like it matches only IPv4 addresses. - * @param[in] mask Hostname matching mask. - * @return Non-zero if \a mask can only match IPv4 addresses, zero otherwise. - */ -int check_if_ipmask(const char *mask) -{ - int has_digit = 0; - const char *p; - - /* Given the bug that inspired this test, this may seem like a hasty - * kludge. It isn't: Wildcard characters should be matched from the - * start, as when the username is the "interesting" part of the ban. - * Likewise, we cannot simply reject masks interpreted as x/0 for - * all x. - */ - if (mask[0] == '.' || mask[0] == '/') - return 0; - for (p = mask; *p; ++p) - if (*p != '*' && *p != '.' && *p != '/') - { - if (!IsDigit(*p)) - return 0; - has_digit = -1; - } - - return has_digit; -} - -/** Try to parse an IPv4 or IPv6 address mask. - * @param[in] in Address matching mask. - * @param[out] mask Fixed bits of address mask. - * @param[out] bits_ptr If non-NULL, receives number of bits specified in address mask. - * @return Non-zero on successful parse; zero on error. - */ -int ipmask_parse(const char *in, struct irc_in_addr *mask, unsigned char *bits_ptr) -{ - struct in_addr ipv4; - char *p; - int bits = 0; - - if (check_if_ipmask(in)) { - bits = ipmask_parse_ipv4(in, &ipv4); - mask->in6_16[0] = mask->in6_16[1] = mask->in6_16[2] = 0; - mask->in6_16[3] = mask->in6_16[4] = 0; - mask->in6_16[5] = 0xffff; - memcpy(&mask->in6_16[6], &ipv4.s_addr, sizeof(ipv4.s_addr)); - bits += 96; - } else if (in[0] == '*' && in[1] == '\0') { - /* accept as a 0-bit mask */ - memset(&mask->in6_16, 0, sizeof(mask->in6_16)); - bits = 0; - } else { - if (!(p = strchr(in, '/'))) - bits = 128; - else - *p = 0; - if (!ircd_aton(mask, in)) { - if (p) - *p = '/'; - return 0; - } - if (p) { - bits = atoi(p + 1); - *p = '/'; - } - } - - if (bits_ptr) - *bits_ptr = bits; - return 1; -} - /** Test whether an address matches the most significant bits of a mask. * @param[in] addr Address to test. * @param[in] mask Address to test against. diff --git a/ircd/test/ircd_in_addr_t.c b/ircd/test/ircd_in_addr_t.c index 2797057..07f6cc9 100644 --- a/ircd/test/ircd_in_addr_t.c +++ b/ircd/test/ircd_in_addr_t.c @@ -95,13 +95,74 @@ test_address(struct address_test *addr) printf("Passed: %s (%s/%s)\n", addr->text, base64_v4, base64_v6); } +/** Structure to describe a test for IP mask parsing. */ +struct ipmask_test { + const char *text; /**< Textual IP mask to parse. */ + struct irc_in_addr expected; /**< Canonical form of address. */ + unsigned int is_ipmask : 1; /**< Parse as an ipmask? */ + unsigned int is_parseable : 1; /**< Is address parseable? */ + unsigned int exp_bits : 8; /**< Number of bits expected in netmask */ +}; + +/** Array of ipmasks to test with. */ +static struct ipmask_test test_masks[] = { + { "::", {{ 0, 0, 0, 0, 0, 0, 0, 0 }}, 1, 1, 128 }, + { "::/0", {{ 0, 0, 0, 0, 0, 0, 0, 0 }}, 1, 1, 0 }, + { "::10.0.0.0", {{ 0, 0, 0, 0, 0, 0, 0xa00, 0 }}, 1, 1, 128 }, + { "192.168/16", {{ 0, 0, 0, 0, 0, 0xffff, 0xc0a8, 0 }}, 1, 1, 112 }, + { "192.*", {{ 0, 0, 0, 0, 0, 0xffff, 0xc000, 0 }}, 1, 1, 104 }, + { "192.*/8", {{ 0, 0, 0, 0, 0, 0, 0, 0 }}, 1, 0, 0 }, + { "192*", {{ 0, 0, 0, 0, 0, 0, 0, 0 }}, 1, 0, 0 }, + { "192.168.0.0/16", {{ 0, 0, 0, 0, 0, 0, 0, 0 }}, 0, 0, 0 }, + { "ab.*", {{ 0, 0, 0, 0, 0, 0, 0, 0 }}, 1, 0, 0 }, + { "a*", {{ 0, 0, 0, 0, 0, 0, 0, 0 }}, 1, 0, 0 }, + { "*", {{ 0, 0, 0, 0, 0, 0, 0, 0 }}, 1, 1, 0 }, + { "a:b", {{ 0, 0, 0, 0, 0, 0, 0, 0 }}, 1, 0, 0 }, + { "a::*", {{ 0, 0, 0, 0, 0, 0, 0, 0 }}, 1, 0, 0 }, + { "a:*", {{ 0xa, 0, 0, 0, 0, 0, 0, 0 }}, 1, 1, 16 }, + { "a:/16", {{ 0xa, 0, 0, 0, 0, 0, 0, 0 }}, 1, 1, 16 }, + { 0 } +}; + +/** Perform tests for a single IP mask. + * @param[in] mask IP mask test structure. + */ +static void +test_ipmask(struct ipmask_test *mask) +{ + struct irc_in_addr parsed; + unsigned int len, ii; + unsigned char bits = 0; + + /* Convert expected address to network order. */ + for (ii = 0; ii < 8; ++ii) + mask->expected.in6_16[ii] = htons(mask->expected.in6_16[ii]); + /* Try to parse; make sure its parseability and netmask length are + * as expected. */ + len = ipmask_parse(mask->text, &parsed, mask->is_ipmask ? &bits : 0); + assert(!!len == mask->is_parseable); + if (!len) { + printf("X-Fail: %s\n", mask->text); + return; + } + if (mask->is_ipmask) + assert(bits == mask->exp_bits); + assert(!memcmp(&parsed, &mask->expected, sizeof(parsed))); + printf("Passed: %s (%s/%u)\n", mask->text, ircd_ntoa(&parsed), bits); +} + int main(int argc, char *argv[]) { unsigned int ii; + printf("Testing address parsing..\n"); for (ii = 0; test_addrs[ii].text; ++ii) test_address(&test_addrs[ii]); + printf("\nTesting ipmask parsing..\n"); + for (ii = 0; test_masks[ii].text; ++ii) + test_ipmask(&test_masks[ii]); + return 0; } -- 2.20.1