+2005-09-11 Michael Poole <mdpoole@troilus.org>
+
+ * 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 <vamposdecampos@gmail.com>
* ircd/m_ping.c (ms_ping, mo_ping): misplaced chunk of code
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
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);
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 */
/** 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
*/
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;
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 */
}
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 */
return 1; /* Auch... something left out ? Fail */
}
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-
-/** 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.
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;
}