
These functions handle the matching and parsing of CIDR IPv4 and IPv6 addresses. Also save the binary IPv6 address for remote clients, now that we will be using it. --- include/h.h | 3 + src/Makefile.in | 1 + src/bitncmp.c | 60 ++++++++ src/inet_parse_cidr.c | 368 +++++++++++++++++++++++++++++++++++++++++++++++++ src/s_user.c | 2 + 5 files changed, 434 insertions(+), 0 deletions(-) create mode 100644 src/bitncmp.c create mode 100644 src/inet_parse_cidr.c diff --git a/include/h.h b/include/h.h index 36c8bd9..c434cef 100644 --- a/include/h.h +++ b/include/h.h @@ -388,4 +388,7 @@ int ssl_smart_shutdown(SSL *); #include "find.h" +int bitncmp(const void *l, const void *r, size_t n); +int inet_parse_cidr(int af, const char *src, void *dst, size_t size); + #endif /* H_H */ diff --git a/src/Makefile.in b/src/Makefile.in index e06a7f4..5a63328 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -23,6 +23,7 @@ SOURCES = blalloc.c bsd.c channel.c clientlist.c clones.c confparse.c \ probability.c res.c s_auth.c s_bsd.c s_conf.c s_debug.c s_err.c \ s_misc.c s_numeric.c s_serv.c s_user.c sbuf.c scache.c send.c \ struct.c support.c throttle.c userban.c whowas.c zlink.c ssl.c \ + bitncmp.c inet_parse_cidr.c \ $(ENGINE) $(CRYPTO) OBJECTS = $(SOURCES:.c=.o) version.o diff --git a/src/bitncmp.c b/src/bitncmp.c new file mode 100644 index 0000000..af674fb --- /dev/null +++ b/src/bitncmp.c @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2004, 2005, 2008 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 1996, 1999, 2001 Internet Software Consortium. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#if defined(LIBC_SCCS) && !defined(lint) +static const char rcsid[] = "$Id: bitncmp.c,v 1.5 2008/11/14 02:36:51 marka Exp $"; +#endif + +#include <sys/types.h> +#include <string.h> + +/* + * int + * bitncmp(l, r, n) + * compare bit masks l and r, for n bits. + * return: + * -1, 1, or 0 in the libc tradition. + * note: + * network byte order assumed. this means 192.5.5.240/28 has + * 0x11110000 in its fourth octet. + * author: + * Paul Vixie (ISC), June 1996 + */ +int +bitncmp(const void *l, const void *r, size_t n) { + unsigned int lb, rb; + size_t b; + int x; + + b = n / 8; + x = memcmp(l, r, b); + if (x || (n % 8) == 0) + return (x); + + lb = ((const unsigned char *)l)[b]; + rb = ((const unsigned char *)r)[b]; + for (b = n % 8; b > 0; b--) { + if ((lb & 0x80) != (rb & 0x80)) { + if (lb & 0x80) + return (1); + return (-1); + } + lb <<= 1; + rb <<= 1; + } + return (0); +} diff --git a/src/inet_parse_cidr.c b/src/inet_parse_cidr.c new file mode 100644 index 0000000..e985d74 --- /dev/null +++ b/src/inet_parse_cidr.c @@ -0,0 +1,368 @@ +/* + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1996,1999 by Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <errno.h> +#include <string.h> + +#define NS_IN6ADDRSZ 16 +#define NS_INT16SZ 2 +#define NS_INADDRSZ 4 + +#define IsDigit(c) ((c) >= '0' && (c) <= '9') + +static int inet_parse_cidr_ipv4(const char *src, unsigned char *dst, size_t size); +static int inet_parse_cidr_ipv6(const char *src, unsigned char *dst, size_t size); +int inet_parse_cidr(int af, const char *src, void *dst, size_t size); + +/* + * int inet_parse_cidr(af, src, dst, size) + * convert network address from presentation to network format. + * accepts inet_pton()'s input plus trailing "/CIDR". + * return: + * number of bits specified in the /CIDR prefix length, which can + * have defaults (like /32 for IPv4) or -1 if an error occurred. + * note: + * 192.5.5.1/28 has a nonzero host part, which means it isn't a network + * as called for by inet_cidr_pton() but it can be a host address with + * an included netmask. + * author: + * Paul Vixie (ISC), October 1998 + * + * Ned Crigler, March 2011 - functions imported from PostgreSQL with + * trailing .* wildcard support added. + */ +int +inet_parse_cidr(int af, const char *src, void *dst, size_t size) +{ + unsigned char *ucdst = (unsigned char *)dst; + + if (af == AF_INET) + return inet_parse_cidr_ipv4(src, ucdst, size); + else if (af == AF_INET6) + return inet_parse_cidr_ipv6(src, ucdst, size); + else + { + errno = EAFNOSUPPORT; + return -1; + } +} + +static int +inet_parse_cidr_ipv4(const char *src, unsigned char *dst, size_t size) +{ + const unsigned char *odst = dst; + int n, + ch, + tmp, + bits; + + if (size < NS_INADDRSZ) + goto emsgsize; + else + size = NS_INADDRSZ; + + /* Get the mantissa. */ + while (ch = *src++, IsDigit((unsigned char) ch)) + { + tmp = 0; + do + { + n = (int)ch - '0'; + tmp *= 10; + tmp += n; + if (tmp > 255) + goto enoent; + } while ((ch = *src++) != '\0' && IsDigit((unsigned char) ch)); + if (size-- == 0) + goto emsgsize; + *dst++ = (unsigned char) tmp; + if (ch == '\0' || ch == '/') + break; + if (ch != '.') + goto enoent; + + /* Handle a trailing .* wildcard. */ + if (src[0] == '*' && src[1] == '\0' && dst - odst < 4) + { + bits = (dst - odst) * 8; + + /* Extend address to four octets. */ + while (size-- > 0) + *dst++ = 0; + return bits; + } + } + + /* Get the prefix length if any. */ + bits = -1; + if (ch == '/' && IsDigit((unsigned char) src[0]) && dst > odst) + { + /* CIDR width specifier. Nothing can follow it. */ + ch = *src++; /* Skip over the /. */ + bits = 0; + do + { + n = (int)ch - '0'; + bits *= 10; + bits += n; + } while ((ch = *src++) != '\0' && IsDigit((unsigned char) ch)); + if (ch != '\0') + goto enoent; + if (bits > 32) + goto emsgsize; + } + + /* Firey death and destruction unless we prefetched EOS. */ + if (ch != '\0') + goto enoent; + + /* Prefix length can default to /32 only if all four octets spec'd. */ + if (bits == -1) + { + if (dst - odst == 4) + bits = 32; + else + goto enoent; + } + + /* If nothing was written to the destination, we found no address. */ + if (dst == odst) + goto enoent; + + /* If prefix length overspecifies mantissa, life is bad. */ + if ((bits / 8) > (dst - odst)) + goto enoent; + + /* Extend address to four octets. */ + while (size-- > 0) + *dst++ = 0; + + return bits; + +enoent: + errno = ENOENT; + return (-1); + +emsgsize: + errno = EMSGSIZE; + return (-1); +} + +static int +getbits(const char *src, int *bitsp) +{ + int n; + int val; + char ch; + + val = 0; + n = 0; + while ((ch = *src++) != '\0') + { + if (ch >= '0' && ch <= '9') + { + if (n++ != 0 && val == 0) /* no leading zeros */ + return (0); + val *= 10; + val += (int)ch - '0'; + if (val > 128) /* range */ + return (0); + continue; + } + return (0); + } + if (n == 0) + return (0); + *bitsp = val; + return (1); +} + +static int +getv4(const char *src, unsigned char *dst, int *bitsp) +{ + unsigned char *odst = dst; + int n; + unsigned int val; + char ch; + + val = 0; + n = 0; + while ((ch = *src++) != '\0') + { + if (IsDigit(ch)) + { + if (n++ != 0 && val == 0) /* no leading zeros */ + return (0); + val *= 10; + val += (unsigned int)ch - '0'; + if (val > 255) /* range */ + return (0); + continue; + } + if (ch == '.' || ch == '/') + { + if (dst - odst > 3) /* too many octets? */ + return (0); + *dst++ = (unsigned char)val; + if (ch == '/') + return (getbits(src, bitsp)); + val = 0; + n = 0; + continue; + } + return (0); + } + if (n == 0) + return (0); + if (dst - odst > 3) /* too many octets? */ + return (0); + *dst++ = (unsigned char)val; + return (1); +} + +static int +inet_parse_cidr_ipv6(const char *src, unsigned char *dst, size_t size) +{ + static const char xdigits_l[] = "0123456789abcdef", + xdigits_u[] = "0123456789ABCDEF"; + unsigned char tmp[NS_IN6ADDRSZ], + *tp, + *endp, + *colonp; + const char *xdigits, + *curtok; + int ch, + saw_xdigit; + unsigned int val; + int digits; + int bits; + + if (size < NS_IN6ADDRSZ) + goto emsgsize; + + memset((tp = tmp), '\0', NS_IN6ADDRSZ); + endp = tp + NS_IN6ADDRSZ; + colonp = NULL; + /* Leading :: requires some special handling. */ + if (*src == ':') + if (*++src != ':') + goto enoent; + curtok = src; + saw_xdigit = 0; + val = 0; + digits = 0; + bits = -1; + while ((ch = *src++) != '\0') + { + const char *pch; + + if ((pch = strchr((xdigits = xdigits_l), ch)) == NULL) + pch = strchr((xdigits = xdigits_u), ch); + if (pch != NULL) + { + val <<= 4; + val |= (unsigned int)(pch - xdigits); + if (++digits > 4) + goto enoent; + saw_xdigit = 1; + continue; + } + if (ch == ':') + { + curtok = src; + if (!saw_xdigit) + { + if (colonp) + goto enoent; + colonp = tp; + continue; + } + else if (*src == '\0') + goto enoent; + if (endp - tp < NS_INT16SZ) + goto emsgsize; + *tp++ = (unsigned char) (val >> 8) & 0xff; + *tp++ = (unsigned char) val & 0xff; + saw_xdigit = 0; + digits = 0; + val = 0; + continue; + } + if (ch == '.' && (endp - tp <= NS_INADDRSZ) && + getv4(curtok, tp, &bits) > 0) + { + tp += NS_INADDRSZ; + saw_xdigit = 0; + break; /* '\0' was seen by inet_pton4(). */ + } + if (ch == '/' && getbits(src, &bits) > 0) + break; + goto enoent; + } + if (saw_xdigit) + { + if (endp - tp < NS_INT16SZ) + goto enoent; + *tp++ = (unsigned char) (val >> 8) & 0xff; + *tp++ = (unsigned char) val & 0xff; + } + if (bits == -1) + bits = 128; + + endp = tmp + 16; + + if (colonp != NULL) + { + /* + * Since some memmove()'s erroneously fail to handle overlapping + * regions, we'll do the shift by hand. + */ + const int n = (int)(tp - colonp); + int i; + + if (tp == endp) + goto enoent; + for (i = 1; i <= n; i++) + { + endp[-i] = colonp[n - i]; + colonp[n - i] = 0; + } + tp = endp; + } + if (tp != endp) + goto enoent; + + /* + * Copy out the result. + */ + memcpy(dst, tmp, NS_IN6ADDRSZ); + + return (bits); + +enoent: + errno = ENOENT; + return (-1); + +emsgsize: + errno = EMSGSIZE; + return (-1); +} + diff --git a/src/s_user.c b/src/s_user.c index dd55f9b..35736fb 100644 --- a/src/s_user.c +++ b/src/s_user.c @@ -2161,6 +2161,8 @@ do_user(char *nick, aClient *cptr, aClient *sptr, char *username, char *host, else sptr->ip_family = 0; } + else if (inet_pton(AF_INET6, ip, &sptr->ip.ip6) == 1) + sptr->ip_family = AF_INET6; else { char *end; -- 1.7.4.1

This patch adds IPv4 and IPv6 CIDR support to a few areas using the new bitncmp and inet_parse_cidr functions. --- include/struct.h | 6 ++++- src/channel.c | 52 +++++++++++++++++++++++++++++++++++++----- src/s_bsd.c | 55 +++++++++++++++++++-------------------------- src/s_conf.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 137 insertions(+), 41 deletions(-) diff --git a/include/struct.h b/include/struct.h index 73d4a41..64a78e0 100644 --- a/include/struct.h +++ b/include/struct.h @@ -796,7 +796,11 @@ struct Listener { u_short port; char allow_string[HOSTLEN + 1]; /* allow from */ char vhost_string[HOSTLEN + 1]; /* bind to */ - struct in_addr allow_ip; /* allow from */ + int allow_cidr_bits; + struct + { + char ip[16]; + } allow_ip; /* allow from */ time_t lasttime; /* last time I accepted */ long sendK; /* counters, see below */ u_short sendB; diff --git a/src/channel.c b/src/channel.c index 4c2f61d..4f3bb07 100644 --- a/src/channel.c +++ b/src/channel.c @@ -207,6 +207,37 @@ static char *make_nick_user_host(char *nick, char *name, char *host) return (namebuf); } +/* Determine whether a client matches a CIDR banstr. */ +static int client_matches_cidrstr(aClient *cptr, char *banstr) +{ + char cidrbuf[NICKLEN + USERLEN + HOSTLEN + 6]; + char ipbuf[16]; + char *s; + int bits; + + if (!strchr(banstr, '/')) + return 0; + + s = strchr(banstr, '@'); + if (s) + s++; + else + return 0; + + bits = inet_parse_cidr(cptr->ip_family, s, ipbuf, sizeof(ipbuf)); + if (bits > 0 && bitncmp(&cptr->ip, ipbuf, bits) == 0) + { + /* Check the wildcards in the rest of the string. */ + snprintf(cidrbuf, sizeof(cidrbuf), "%s!%s@%s", + check_string(cptr->name), + check_string(cptr->user->username), + s); + if (match(banstr, cidrbuf) == 0) + return 1; + } + return 0; +} + #ifdef EXEMPT_LISTS /* Exempt list functions (+e) */ @@ -380,7 +411,8 @@ anInvite* is_invited(aClient* cptr, aChannel* chptr) for (invite = chptr->invite_list; invite; invite = invite->next) { - if (!match(invite->invstr, s) || !match(invite->invstr, s2)) + if (!match(invite->invstr, s) || !match(invite->invstr, s2) || + client_matches_cidrstr(cptr, invite->invstr)) break; } return invite; @@ -518,13 +550,15 @@ static int is_banned(aClient *cptr, aChannel *chptr, chanMember *cm) #ifdef EXEMPT_LISTS for (exempt = chptr->banexempt_list; exempt; exempt = exempt->next) - if (!match(exempt->banstr, s) || !match(exempt->banstr, s2)) + if (!match(exempt->banstr, s) || !match(exempt->banstr, s2) || + client_matches_cidrstr(cptr, exempt->banstr)) return 0; #endif for (ban = chptr->banlist; ban; ban = ban->next) if ((match(ban->banstr, s) == 0) || - (match(ban->banstr, s2) == 0)) + (match(ban->banstr, s2) == 0) || + client_matches_cidrstr(cptr, ban->banstr)) break; if (ban) @@ -575,14 +609,16 @@ aBan *nick_is_banned(aChannel *chptr, char *nick, aClient *cptr) for (exempt = chptr->banexempt_list; exempt; exempt = exempt->next) if (exempt->type == MTYP_FULL && ((match(exempt->banstr, s2) == 0) || - (match(exempt->banstr, s) == 0))) + (match(exempt->banstr, s) == 0) || + client_matches_cidrstr(cptr, exempt->banstr))) return NULL; #endif for (ban = chptr->banlist; ban; ban = ban->next) if (ban->type == MTYP_FULL && /* only check applicable bans */ ((match(ban->banstr, s2) == 0) || /* check host before IP */ - (match(ban->banstr, s) == 0))) + (match(ban->banstr, s) == 0) || + client_matches_cidrstr(cptr, ban->banstr))) break; return (ban); } @@ -614,7 +650,8 @@ void remove_matching_bans(aChannel *chptr, aClient *cptr, aClient *from) { bnext = ban->next; if((match(ban->banstr, targhost) == 0) || - (match(ban->banstr, targip) == 0)) + (match(ban->banstr, targip) == 0) || + client_matches_cidrstr(cptr, ban->banstr)) { if (strlen(parabuf) + strlen(ban->banstr) + 10 < (size_t) MODEBUFLEN) { @@ -699,7 +736,8 @@ void remove_matching_exempts(aChannel *chptr, aClient *cptr, aClient *from) { enext = ex->next; if((match(ex->banstr, targhost) == 0) || - (match(ex->banstr, targip) == 0)) + (match(ex->banstr, targip) == 0) || + client_matches_cidrstr(cptr, ex->banstr)) { if (strlen(parabuf) + strlen(ex->banstr) + 10 < (size_t) MODEBUFLEN) { diff --git a/src/s_bsd.c b/src/s_bsd.c index 27b307e..c568b6c 100644 --- a/src/s_bsd.c +++ b/src/s_bsd.c @@ -313,18 +313,19 @@ int add_listener(aPort *aport) len = sizeof(server.addr4); } - if(!BadPtr(aport->allow) && server.sa.sa_family == AF_INET) + if(!BadPtr(aport->allow)) { - int ad[4]; - char ipname[20]; - - ad[0] = ad[1] = ad[2] = ad[3] = 0; + int bits; strncpyzt(lstn.allow_string, aport->allow, sizeof(lstn.allow_string)); - sscanf(lstn.allow_string, "%d.%d.%d.%d", &ad[0], &ad[1], - &ad[2], &ad[3]); - ircsprintf(ipname, "%d.%d.%d.%d", ad[0], ad[1], ad[2], ad[3]); - lstn.allow_ip.s_addr = inet_addr(ipname); + + bits = inet_parse_cidr(server.sa.sa_family, + lstn.allow_string, &lstn.allow_ip, + sizeof(lstn.allow_ip)); + if (bits > 0) + lstn.allow_cidr_bits = bits; + else + lstn.allow_cidr_bits = -1; } if(lstn.port <= 0) /* stop stupidity cold */ @@ -1300,33 +1301,10 @@ aClient *add_connection(aListener *lptr, int fd) */ if (acptr->ip_family == AF_INET) { - char *s, *t; - get_sockhost(acptr, (char *) inetntoa((char *) &addr.addr4.sin_addr)); memcpy((char *) &acptr->ip.ip4, (char *) &addr.addr4.sin_addr, sizeof(struct in_addr)); acptr->port = ntohs(addr.addr4.sin_port); - - /* - * Check that this socket (client) is allowed to accept - * connections from this IP#. - */ - for (s = (char *) &lptr->allow_ip, t = (char *) &acptr->ip.ip4, len = 4; - len > 0; len--, s++, t++) - { - if (!*s) - continue; - if (*s != *t) - break; - } - if (len) - { - ircstp->is_ref++; - acptr->fd = -2; - free_client(acptr); - close(fd); - return NULL; - } } else if (acptr->ip_family == AF_INET6) { @@ -1336,6 +1314,19 @@ aClient *add_connection(aListener *lptr, int fd) acptr->port = ntohs(addr.addr6.sin6_port); } + /* + * Check that this socket (client) is allowed to accept + * connections from this IP#. + */ + if (lptr->allow_cidr_bits > 0 && + bitncmp(&acptr->ip, &lptr->allow_ip, lptr->allow_cidr_bits) != 0) + { + ircstp->is_ref++; + acptr->fd = -2; + free_client(acptr); + close(fd); + return NULL; + } lptr->ccount++; lptr->clients++; diff --git a/src/s_conf.c b/src/s_conf.c index 509109c..6d2c8be 100644 --- a/src/s_conf.c +++ b/src/s_conf.c @@ -395,9 +395,39 @@ find_oper(char *name, char *username, char *sockhost, char *hostip) for(i = 0; aoper->hosts[i]; i++) { - if(!mycmp(name, aoper->nick) && (!match(aoper->hosts[i], userhost) + if(!mycmp(name, aoper->nick) && (!match(aoper->hosts[i], userhost) || !match(aoper->hosts[i], userip))) return aoper; + if(strchr(aoper->hosts[i], '/')) + { + char cidrbuf[USERLEN + HOSTLEN + 3]; + char ipbuf1[16], ipbuf2[16]; + char *s; + int bits; + int family; + + s = strchr(aoper->hosts[i], '@'); + if (s) + s++; + else + s = aoper->hosts[i]; + + if (inet_pton(AF_INET, hostip, ipbuf1) == 1) + family = AF_INET; + else if (inet_pton(AF_INET6, hostip, ipbuf1) == 1) + family = AF_INET6; + else + family = 0; + + bits = inet_parse_cidr(family, s, ipbuf2, sizeof(ipbuf2)); + if (bits > 0 && bitncmp(ipbuf1, ipbuf2, bits) == 0) + { + /* Check the wildcards in the rest of the string. */ + ircsprintf(cidrbuf, "%s@%s", username, s); + if (match(aoper->hosts[i], cidrbuf) == 0) + return aoper; + } + } } } return NULL; @@ -526,11 +556,44 @@ attach_Iline(aClient *cptr, struct hostent *hp, char *sockhost) { if (!match(allow->ipmask, useriphost)) return (attach_iline(cptr, allow, iphost, 1)); + else if (strchr(allow->ipmask, '/')) + { + char cidrbuf[USERLEN + 1 + HOSTLEN + 1]; + char ipbuf[16]; + char *s; + int bits; + + s = strchr(allow->ipmask, '@'); + if (s) + s++; + else + continue; + + bits = inet_parse_cidr(cptr->ip_family, s, + ipbuf, sizeof(ipbuf)); + if (bits > 0 && bitncmp(&cptr->ip, ipbuf, bits) == 0) + { + /* Check the wildcards in the rest of the string. */ + ircsprintf(cidrbuf, "%s@%s", cptr->username, s); + if (match(allow->ipmask, cidrbuf) == 0) + return (attach_iline(cptr, allow, iphost, 1)); + } + } } else { if (!match(allow->ipmask, iphost)) return (attach_iline(cptr, allow, iphost, 0)); + else if (strchr(allow->ipmask, '/')) + { + char ipbuf[16]; + int bits; + + bits = inet_parse_cidr(cptr->ip_family, allow->ipmask, + ipbuf, sizeof(ipbuf)); + if (bits > 0 && bitncmp(&cptr->ip, ipbuf, bits) == 0) + return (attach_iline(cptr, allow, iphost, 1)); + } } } } -- 1.7.4.1

This patch adds IPv6 CIDR support using the new bitncmp and inet_parse_cidr functions. --- include/struct.h | 10 +++++-- src/m_rwho.c | 67 ++++++++++++++++++++++++++++++----------------------- src/m_who.c | 56 +++++++++++++++++++++++--------------------- 3 files changed, 74 insertions(+), 59 deletions(-) diff --git a/include/struct.h b/include/struct.h index 64a78e0..ee705cb 100644 --- a/include/struct.h +++ b/include/struct.h @@ -1439,8 +1439,12 @@ typedef struct SearchOptions char *ip; int class; int class_value; - unsigned int cidr4_ip; - unsigned int cidr4_mask; + int cidr_bits; + int cidr_family; + struct + { + char ip[16]; + } cidr_ip; int ts; int ts_value; aChannel *channel; @@ -1453,7 +1457,7 @@ typedef struct SearchOptions unsigned host_plus:1; unsigned gcos_plus:1; unsigned ip_plus:1; - unsigned cidr4_plus:1; + unsigned cidr_plus:1; unsigned chan_plus:1; unsigned serv_plus:1; unsigned away_plus:1; diff --git a/src/m_rwho.c b/src/m_rwho.c index 242dc04..64c79a0 100644 --- a/src/m_rwho.c +++ b/src/m_rwho.c @@ -33,7 +33,6 @@ #include "pcre.h" extern int user_modes[]; -extern unsigned int cidr_to_netmask(unsigned int); extern Link *find_channel_link(Link *, aChannel *); /* max capturing submatches to allow in all fields combined */ @@ -167,8 +166,12 @@ static struct { int (*host_func[2])(); /* host match function */ int umodes[2]; /* usermodes */ unsigned stype; /* services type */ - unsigned ip_mask[2]; /* IP netmask */ - unsigned ip_addr[2]; /* IP address */ + unsigned ip_family[2]; /* CIDR family to match */ + int ip_cidr_bits[2]; /* CIDR bits to match */ + struct + { + char ip[16]; + } ip_addr[2]; /* IP address */ char *ip_str[2]; /* IP string if CIDR is invalid */ ts_val ts[2]; /* signon timestamp */ int joined[2]; /* min/max joined chans */ @@ -439,27 +442,33 @@ static int rwho_parseopts(aClient *sptr, int parc, char *parv[]) rwho_synerr(sptr, "missing argument for match flag i"); return 0; } - if ((s = strchr(parv[arg], '/'))) - { - *s++ = 0; - i = strtol(s, &s, 10); - if (*s == 0 && 1 < i && i < 32) - rwho_opts.ip_mask[neg] = htonl(cidr_to_netmask(i)); - } - else - rwho_opts.ip_mask[neg] = ~0; - rwho_opts.ip_addr[neg] = inet_addr(parv[arg]); - if (!rwho_opts.ip_mask[neg] || - (rwho_opts.ip_mask[neg] != ~0 && - rwho_opts.ip_addr[neg] == 0xFFFFFFFF)) + if (strchr(parv[arg], '/')) { - rwho_synerr(sptr, "invalid CIDR IP for match flag i"); - return 0; + int bits; + + bits = inet_parse_cidr(AF_INET, parv[arg], + &rwho_opts.ip_addr[neg], + sizeof(struct in_addr)); + if (bits > 0) + rwho_opts.ip_family[neg] = AF_INET; + else + { + bits = inet_parse_cidr(AF_INET6, parv[arg], + &rwho_opts.ip_addr[neg], + sizeof(struct in6_addr)); + if (bits > 0) + rwho_opts.ip_family[neg] = AF_INET6; + } + if (bits > 0) + rwho_opts.ip_cidr_bits[neg] = bits; + else + { + rwho_synerr(sptr, "invalid CIDR IP for match flag i"); + return 0; + } } - if (rwho_opts.ip_addr[neg] == 0xFFFFFFFF) + else rwho_opts.ip_str[neg] = parv[arg]; - else - rwho_opts.ip_addr[neg] &= rwho_opts.ip_mask[neg]; rwho_opts.check[neg] |= RWM_IP; arg++; break; @@ -935,10 +944,10 @@ static int rwho_match(aClient *cptr, int *failcode, aClient **failclient) if (match(rwho_opts.ip_str[0], cptr->hostip)) return 0; } - else if (cptr->ip_family == AF_INET) + else if (cptr->ip_family == rwho_opts.ip_family[0]) { - if ((cptr->ip.ip4.s_addr & rwho_opts.ip_mask[0]) - != rwho_opts.ip_addr[0]) + if (bitncmp(&cptr->ip, &rwho_opts.ip_addr[0], + rwho_opts.ip_cidr_bits[0]) != 0) return 0; } else @@ -952,10 +961,10 @@ static int rwho_match(aClient *cptr, int *failcode, aClient **failclient) if (!match(rwho_opts.ip_str[1], cptr->hostip)) return 0; } - else if (cptr->ip_family == AF_INET) + else if (cptr->ip_family == rwho_opts.ip_family[1]) { - if ((cptr->ip.ip4.s_addr & rwho_opts.ip_mask[1]) - == rwho_opts.ip_addr[1]) + if (bitncmp(&cptr->ip, &rwho_opts.ip_addr[1], + rwho_opts.ip_cidr_bits[1]) == 0) return 0; } else diff --git a/src/m_who.c b/src/m_who.c index d64b7f5..703dca6 100644 --- a/src/m_who.c +++ b/src/m_who.c @@ -41,7 +41,6 @@ int chk_who(aClient *, int); extern int user_modes[]; extern Link *find_channel_link(Link *, aChannel *); -extern unsigned int cidr_to_netmask(unsigned int); int build_searchopts(aClient *sptr, int parc, char *parv[]) { @@ -317,33 +316,36 @@ int build_searchopts(aClient *sptr, int parc, char *parv[]) } else { - char *cpos; - - if((cpos = strchr(parv[args], '/'))) - { - char *err; - unsigned int maskval, ipval; - - *(cpos++) = '\0'; - - ipval = inet_addr(parv[args]); - maskval = strtol(cpos, &err, 10); - if(ipval == 0xFFFFFFFF || *err != '\0' || - maskval < 1 || maskval > 32) + if (strchr(parv[args], '/')) + { + int bits; + + bits = inet_parse_cidr(AF_INET, parv[args], + &wsopts.cidr_ip, + sizeof(wsopts.cidr_ip)); + if (bits > 0) + wsopts.cidr_family = AF_INET; + else + { + bits = inet_parse_cidr(AF_INET6, parv[args], + &wsopts.cidr_ip, + sizeof(wsopts.cidr_ip)); + if (bits > 0) + wsopts.cidr_family = AF_INET6; + } + if (bits > 0) + { + wsopts.cidr_bits = bits; + wsopts.cidr_plus = change; + } + else { sendto_one(sptr, getreply(ERR_WHOSYNTAX), me.name, sptr->name, "WHO", "who"); return 0; } - - maskval = htonl(cidr_to_netmask(maskval)); - ipval &= maskval; - - wsopts.cidr4_plus = change; - wsopts.cidr4_mask = maskval; - wsopts.cidr4_ip = ipval; - args++; - } + args++; + } else { wsopts.ip=parv[args]; @@ -470,7 +472,7 @@ int build_searchopts(aClient *sptr, int parc, char *parv[]) wsopts.serv_plus || wsopts.nick_plus || wsopts.user_plus || wsopts.ts_value || wsopts.client_type_plus || wsopts.ip_plus || - wsopts.chan_plus || wsopts.cidr4_mask)) + wsopts.chan_plus || wsopts.cidr_bits)) { if(parv[args]==NULL) { @@ -566,9 +568,9 @@ int chk_who(aClient *ac, int showall) (!wsopts.host_plus && !hchkfn(wsopts.host, ac->user->host))) return 0; - if(wsopts.cidr4_plus) - if(ac->ip_family != AF_INET || - (ac->ip.ip4.s_addr & wsopts.cidr4_mask) != wsopts.cidr4_ip) + if(wsopts.cidr_plus) + if(ac->ip_family != wsopts.cidr_family || + bitncmp(&ac->ip, &wsopts.cidr_ip, wsopts.cidr_bits) != 0) return 0; if(wsopts.ip_plus) -- 1.7.4.1

This patch adds IPv6 CIDR support using the new bitncmp and inet_parse_cidr functions. --- include/userban.h | 8 +- src/klines.c | 10 +- src/userban.c | 374 +++++++++++++++++++--------------------------------- 3 files changed, 148 insertions(+), 244 deletions(-) diff --git a/include/userban.h b/include/userban.h index faf057d..99812c0 100644 --- a/include/userban.h +++ b/include/userban.h @@ -50,8 +50,12 @@ struct userBan { char *u; /* username */ char *h; /* host or IP or GECOS or NICK */ - unsigned int cidr4ip; /* cidr4 IP */ - unsigned int cidr4mask; /* cidr4 mask */ + int cidr_bits; /* CIDR bits to match */ + int cidr_family; /* CIDR family to match */ + struct + { + char ip[16]; + } cidr_ip; /* CIDR IP to match */ char *reason; time_t timeset; /* time this ban was set */ diff --git a/src/klines.c b/src/klines.c index fb8ac6f..2c49e1b 100644 --- a/src/klines.c +++ b/src/klines.c @@ -322,9 +322,6 @@ ks_write(int f, char type, struct userBan *ub) char *host = ub->h; int len; - /* userban.c */ - unsigned int netmask_to_cidr(unsigned int); - if (ub->flags & UBAN_TEMPORARY) expiretime = ub->timeset + ub->duration; @@ -336,8 +333,11 @@ ks_write(int f, char type, struct userBan *ub) if (ub->flags & (UBAN_CIDR4|UBAN_CIDR4BIG)) { - host = inetntoa((char *)&ub->cidr4ip); - ircsprintf(cidr, "/%d", netmask_to_cidr(ntohl(ub->cidr4mask))); + if (ub->cidr_family == AF_INET) + host = inetntoa((char *)&ub->cidr_ip); + else if (ub->cidr_family == AF_INET6) + host = inet6ntoa((char *)&ub->cidr_ip); + ircsprintf(cidr, "/%d", ub->cidr_bits); } if (type == '+') diff --git a/src/userban.c b/src/userban.c index 5d30e71..8f2fc04 100644 --- a/src/userban.c +++ b/src/userban.c @@ -66,24 +66,6 @@ void simban_free(struct simBan *); unsigned int host_hash(char *n); unsigned int ip_hash(char *n); -unsigned int cidr_to_netmask(unsigned int cidr) -{ - if (cidr == 0) - return 0; - - return (0xFFFFFFFF - (1 << (32 - cidr)) + 1); -} - -unsigned int netmask_to_cidr(unsigned int mask) -{ - int tmp = 0; - - while (!(mask & (1 << tmp)) && tmp < 32) - tmp++; - - return (32 - tmp); -} - /* userban (akill/kline) functions */ void add_hostbased_userban(struct userBan *b) @@ -102,7 +84,7 @@ void add_hostbased_userban(struct userBan *b) if(b->flags & UBAN_CIDR4) { - unsigned char *s = (unsigned char *) &bl->ban->cidr4ip; + unsigned char *s = (unsigned char *) &bl->ban->cidr_ip; int a, b; a = (int) *s++; @@ -205,10 +187,11 @@ int user_match_ban(aClient *cptr, struct userBan *ban) if(ban->flags & (UBAN_CIDR4|UBAN_CIDR4BIG)) { - if(cptr->ip_family == AF_INET && - (cptr->ip.ip4.s_addr & ban->cidr4mask) == ban->cidr4ip) - return 1; - return 0; + if (cptr->ip_family == ban->cidr_family && + bitncmp(&cptr->ip, &ban->cidr_ip, ban->cidr_bits) == 0) + return 1; + else + return 0; } return 0; @@ -278,10 +261,14 @@ struct userBan *check_userbanned(aClient *cptr, unsigned int yflags, unsigned in if((!(bl->ban->flags & UBAN_WILDUSER)) && match(bl->ban->u, cptr->user->username)) continue; - if((cptr->ip.ip4.s_addr & bl->ban->cidr4mask) == bl->ban->cidr4ip) - return bl->ban; + if(cptr->ip_family == bl->ban->cidr_family && + bitncmp(&cptr->ip, &bl->ban->cidr_ip, bl->ban->cidr_bits) == 0) + return bl->ban; } + } + if(yflags & UBAN_CIDR4) + { LIST_FOREACH(bl, &CIDR4BIG_bans, lp) { if((bl->ban->flags & UBAN_TEMPORARY) && bl->ban->timeset + bl->ban->duration <= NOW) @@ -294,8 +281,9 @@ struct userBan *check_userbanned(aClient *cptr, unsigned int yflags, unsigned in if((!(bl->ban->flags & UBAN_WILDUSER)) && match(bl->ban->u, cptr->user->username)) continue; - if((cptr->ip.ip4.s_addr & bl->ban->cidr4mask) == bl->ban->cidr4ip) - return bl->ban; + if(cptr->ip_family == bl->ban->cidr_family && + bitncmp(&cptr->ip, &bl->ban->cidr_ip, bl->ban->cidr_bits) == 0) + return bl->ban; } } @@ -353,7 +341,10 @@ struct userBan *find_userban_exact(struct userBan *borig, unsigned int careflags if(!(borig->flags & UBAN_WILDUSER) && mycmp(borig->u, bl->ban->u)) continue; - if(!((borig->cidr4ip == bl->ban->cidr4ip) && (borig->cidr4mask == bl->ban->cidr4mask))) + if (!(borig->cidr_family == bl->ban->cidr_family && + memcmp(&borig->cidr_ip, &bl->ban->cidr_ip, + sizeof(borig->cidr_ip)) == 0 && + borig->cidr_bits == bl->ban->cidr_bits)) continue; return bl->ban; @@ -364,7 +355,7 @@ struct userBan *find_userban_exact(struct userBan *borig, unsigned int careflags if(borig->flags & UBAN_CIDR4) { - unsigned char *s = (unsigned char *) &borig->cidr4ip; + unsigned char *s = (unsigned char *) &borig->cidr_ip; int a, b; a = (int) *s++; @@ -377,8 +368,10 @@ struct userBan *find_userban_exact(struct userBan *borig, unsigned int careflags if(!(borig->flags & UBAN_WILDUSER) && mycmp(borig->u, bl->ban->u)) continue; - if(!((borig->cidr4ip == bl->ban->cidr4ip) && (borig->cidr4mask == bl->ban->cidr4mask))) - continue; + if (!(borig->cidr_family == bl->ban->cidr_family && + memcmp(&borig->cidr_ip, &bl->ban->cidr_ip, + sizeof(borig->cidr_ip)) == 0 && + borig->cidr_bits == bl->ban->cidr_bits)) return bl->ban; } @@ -531,7 +524,20 @@ static inline void report_list_match_flags(aClient *cptr, uBanEnt *bl, unsigned kset[2] = '\0'; if(ban->flags & (UBAN_CIDR4|UBAN_CIDR4BIG)) - snprintf(host, 128, "%s/%d", inetntoa((char *)&ban->cidr4ip), netmask_to_cidr(ntohl(ban->cidr4mask))); + { + if (ban->cidr_family == AF_INET) + { + snprintf(host, 128, "%s/%d", + inetntoa((char*)&ban->cidr_ip), + ban->cidr_bits); + } + else if (ban->cidr_family == AF_INET6) + { + snprintf(host, 128, "%s/%d", + inet6ntoa((char*)&ban->cidr_ip), + ban->cidr_bits); + } + } else strcpy(host, ban->h); @@ -644,7 +650,20 @@ char *get_userban_host(struct userBan *ban, char *buf, int buflen) *buf = '\0'; if(ban->flags & (UBAN_CIDR4|UBAN_CIDR4BIG)) - snprintf(buf, buflen, "%s/%d", inetntoa((char *)&ban->cidr4ip), netmask_to_cidr(ntohl(ban->cidr4mask))); + { + if (ban->cidr_family == AF_INET) + { + snprintf(buf, buflen, "%s/%d", + inetntoa((char*)&ban->cidr_ip), + ban->cidr_bits); + } + else if (ban->cidr_family == AF_INET6) + { + snprintf(buf, buflen, "%s/%d", + inet6ntoa((char*)&ban->cidr_ip), + ban->cidr_bits); + } + } else snprintf(buf, buflen, "%s", ban->h); @@ -654,227 +673,107 @@ char *get_userban_host(struct userBan *ban, char *buf, int buflen) /* * Fills in the following fields * of a userban structure, or returns NULL if invalid stuff is passed. - * - flags, u, h, cidr4ip, cidr4mask + * - flags, u, h, cidr_* */ -struct userBan *make_hostbased_ban(char *user, char *phost) +struct userBan *make_hostbased_ban(char *user, char *host) { - char host[512]; - unsigned int flags = 0, c4h = 0, c4m = 0; - int numcount, othercount, wildcount, dotcount, slashcount; - int coloncount; - char *tmp; + int cidr_family = 0; + struct + { + char buf[16]; + } cidr_ip; /* CIDR IP */ + int cidr_bits; /* CIDR bits */ + unsigned int flags = 0; struct userBan *b; + char *p; - strncpy(host, phost, 512); - - numcount = othercount = wildcount = dotcount = slashcount = 0; - coloncount = 0; + int has_colon, has_dot, has_ip4, has_wild, has_nonwild; - for(tmp = host; *tmp; tmp++) + /* check for an IP address with an optional CIDR or trailing .* */ + cidr_bits = inet_parse_cidr(AF_INET, host, &cidr_ip, + sizeof(struct in_addr)); + if (cidr_bits == 32) { - switch(*tmp) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - numcount++; - break; - - case '*': - case '?': - wildcount++; - break; - - case '.': - dotcount++; - break; - - case '/': - slashcount++; - break; - - case ':': - coloncount++; - break; - - default: - othercount++; - break; - } + flags = UBAN_IP; + goto success; } - - if(wildcount && !numcount && !othercount) + else if (cidr_bits > 0) { - if(!user || !*user || mycmp(user, "*") == 0) - return NULL; /* all wildcards? aagh! */ - - flags = (UBAN_HOST|UBAN_WILD); - - if(mycmp(host, "*.*") == 0 || mycmp(host, "*") == 0) - flags |= UBAN_WILDHOST; - - goto success; + cidr_family = AF_INET; + flags = (cidr_bits < 16) ? UBAN_CIDR4BIG : UBAN_CIDR4; + goto success; } - - /* IPv6 addresses. */ - if (coloncount != 0 && wildcount == 0) + else { - struct in6_addr tmp_addr; - - if (inet_pton(AF_INET6, host, &tmp_addr) != 1) - return NULL; + cidr_bits = inet_parse_cidr(AF_INET6, host, &cidr_ip, + sizeof(struct in6_addr)); + if (cidr_bits == 128) + { + flags = UBAN_IP; + goto success; + } + else if (cidr_bits > 0) + { + cidr_family = AF_INET6; + flags = UBAN_CIDR4BIG; + goto success; + } } - /* everything must have a dot or colon. never more than one slash. */ - if((dotcount == 0 && coloncount == 0) || slashcount > 1) - return NULL; - - /* wildcarded IP address? -- can we convert it to a CIDR? */ - if(wildcount && numcount && !othercount) + has_colon = has_dot = has_wild = has_nonwild = 0; + has_ip4 = 1; + for (p = host; *p != '\0'; p++) { - char octet[4][8]; - int i1, i2; - int gotwild; - - if(slashcount) - return NULL; /* slashes and wildcards? */ - - /* I see... more than 3 dots? */ - if(dotcount > 3) - return NULL; - - i1 = i2 = 0; - - /* separate this thing into dotcount octets. */ - for(tmp = host; *tmp; tmp++) - { - if(*tmp == '.') - { - octet[i1][i2] = '\0'; - i2 = 0; - i1++; - continue; - } - if(i2 < 6) - { - octet[i1][i2++] = *tmp; - } - } - octet[i1][i2] = '\0'; - - /* verify that each octet is all numbers or just a '*' */ - /* bans that match 123.123.123.1?? are still valid, just not convertable to a CIDR */ - - for(gotwild = i1 = 0; i1 <= dotcount; i1++) - { - if(strcmp(octet[i1], "*") == 0) - { - gotwild++; - continue; - } - - /* ban in the format of 1.2.*.4 */ - if(gotwild) - { - flags = (UBAN_IP|UBAN_WILD); - goto success; - } - - for(i2 = 0; octet[i1][i2]; i2++) - { - switch(octet[i1][i2]) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - break; - - default: - flags = (UBAN_IP|UBAN_WILD); - goto success; - } - } - } - - if(octet[0][0] == '*') - return NULL; /* the first octet is a wildcard? what the hell? */ - - if(octet[1][0] == '*') - { - sprintf(host, "%s.0.0.0/8", octet[0]); - goto cidrforce; - } - else if(dotcount >= 2 && octet[2][0] == '*') - { - sprintf(host, "%s.%s.0.0/16", octet[0], octet[1]); - goto cidrforce; - } - else if(dotcount >= 3 && octet[3][0] == '*') - { - sprintf(host, "%s.%s.%s.0/24", octet[0], octet[1], octet[2]); - goto cidrforce; - } - - return NULL; /* we should never get here. If we do, something is wrong. */ + if (*p == '/') + return NULL; + else if (*p == '.') + has_dot = 1; + else if (*p == '*' || *p == '?') + has_wild = 1; + else + { + has_nonwild = 1; + + if (*p == ':') + has_colon = 1; + if (!(*p >= '0' && *p <= '9')) + has_ip4 = 0; + } } - /* CIDR IP4 address? */ - if(!wildcount && numcount && !othercount && slashcount) + /* host is all wildcards? */ + if (has_wild && !has_nonwild) { - int sval; - char *sep, *err; - struct in_addr ia, na; - -cidrforce: - sep = strchr(host, '/'); /* guaranteed to be here because slashcount */ - *sep = '\0'; - sep++; - - if((ia.s_addr = inet_addr(host)) == 0xFFFFFFFF) /* invalid ip4 address! */ - return NULL; - - /* is there a problem with the / mask? */ - sval = strtol(sep, &err, 10); - if(*err != '\0') - return NULL; - - if(sval < 0 || sval > 32) - return NULL; + if(!user || !*user || mycmp(user, "*") == 0) + return NULL; - na.s_addr = htonl(cidr_to_netmask(sval)); - ia.s_addr &= na.s_addr; + flags = (UBAN_HOST | UBAN_WILD); - c4h = ia.s_addr; - c4m = na.s_addr; - - flags = (sval < 16) ? UBAN_CIDR4BIG : UBAN_CIDR4; - goto success; + if(mycmp(host, "*.*") == 0 || mycmp(host, "*") == 0) + flags |= UBAN_WILDHOST; } - if(slashcount) - return NULL; - - if(!othercount) + /* everything must have a dot or colon. */ + else if (!has_dot && !has_colon) + return NULL; + + /* an IPv6 address? */ + else if (has_colon) { - flags = (UBAN_IP | (wildcount ? UBAN_WILD : 0)); - goto success; + /* it must have a wildcard; non-wildcards are handled above. */ + if (!has_wild) + return NULL; + else + flags = (UBAN_IP | (has_wild ? UBAN_WILD : 0)); } - flags = (UBAN_HOST | (wildcount ? UBAN_WILD : 0)); + /* an IPv4 address? */ + else if (has_ip4) + flags = (UBAN_IP | (has_wild ? UBAN_WILD : 0)); + + /* or a hostname */ + else + flags = (UBAN_HOST | (has_wild ? UBAN_WILD : 0)); success: b = userban_alloc(); @@ -885,13 +784,14 @@ success: if(flags & (UBAN_CIDR4BIG|UBAN_CIDR4)) { - b->cidr4ip = c4h; - b->cidr4mask = c4m; - b->h = NULL; + b->cidr_family = cidr_family; + memcpy(&b->cidr_ip, &cidr_ip, sizeof(cidr_ip)); + b->cidr_bits = cidr_bits; + b->h = NULL; } else { - b->cidr4ip = b->cidr4mask = 0; + b->cidr_family = 0; b->h = (char *)MyMalloc(strlen(host) + 1); strcpy(b->h, host); } @@ -909,7 +809,7 @@ success: b->flags = flags; - return b; + return b; } /* simban (simple ban) functions */ -- 1.7.4.1
participants (1)
-
Ned T. Crigler