diff --git a/include/hostmask.h b/include/hostmask.h index 28b47d66..7b2ce435 100644 --- a/include/hostmask.h +++ b/include/hostmask.h @@ -27,12 +27,14 @@ #define INCLUDE_hostmask_h 1 enum { + HM_ERROR, HM_HOST, HM_IPV4, HM_IPV6, }; int parse_netmask(const char *, struct rb_sockaddr_storage *, int *); +int parse_netmask_strict(const char *, struct rb_sockaddr_storage *, int *); struct ConfItem *find_conf_by_address(const char *host, const char *sockhost, const char *orighost, struct sockaddr *, int, int, const char *, const char *); diff --git a/ircd/hostmask.c b/ircd/hostmask.c index 922a8d44..5266ba2d 100644 --- a/ircd/hostmask.c +++ b/ircd/hostmask.c @@ -36,18 +36,12 @@ static unsigned long hash_ipv6(struct sockaddr *, int); static unsigned long hash_ipv4(struct sockaddr *, int); -/* int parse_netmask(const char *, struct rb_sockaddr_storage *, int *); - * Input: A hostmask, or an IPV4/6 address. - * Output: An integer describing whether it is an IPV4, IPV6 address or a - * hostmask, an address(if it is an IP mask), - * a bitlength(if it is IP mask). - * Side effects: None - */ -int -parse_netmask(const char *text, struct rb_sockaddr_storage *naddr, int *nb) +static int +_parse_netmask(const char *text, struct rb_sockaddr_storage *naddr, int *nb, bool strict) { char *ip = LOCAL_COPY(text); char *ptr; + char *endp; struct rb_sockaddr_storage *addr, xaddr; int *b, xb; if(nb == NULL) @@ -70,11 +64,17 @@ parse_netmask(const char *text, struct rb_sockaddr_storage *naddr, int *nb) { *ptr = '\0'; ptr++; - *b = atoi(ptr); - if(*b > 128) - *b = 128; - else if(*b < 0) + long n = strtol(ptr, &endp, 10); + if (endp == ptr || n < 0) return HM_HOST; + if (n > 128 || *endp != '\0') + { + if (strict) + return HM_ERROR; + else + n = 128; + } + *b = n; } else *b = 128; if(rb_inet_pton_sock(ip, addr) > 0) @@ -88,11 +88,17 @@ parse_netmask(const char *text, struct rb_sockaddr_storage *naddr, int *nb) { *ptr = '\0'; ptr++; - *b = atoi(ptr); - if(*b > 32) - *b = 32; - else if(*b < 0) + long n = strtol(ptr, &endp, 10); + if (endp == ptr || n < 0) return HM_HOST; + if (n > 32 || *endp != '\0') + { + if (strict) + return HM_ERROR; + else + n = 32; + } + *b = n; } else *b = 32; if(rb_inet_pton_sock(ip, addr) > 0) @@ -103,6 +109,23 @@ parse_netmask(const char *text, struct rb_sockaddr_storage *naddr, int *nb) return HM_HOST; } +/* int parse_netmask(const char *, struct rb_sockaddr_storage *, int *); + * Input: A hostmask, or an IPV4/6 address. + * Output: An integer describing whether it is an IPV4, IPV6 address or a + * hostmask, an address(if it is an IP mask), + * a bitlength(if it is IP mask). + * Side effects: None + */ +int parse_netmask(const char *mask, struct rb_sockaddr_storage *addr, int *blen) +{ + return _parse_netmask(mask, addr, blen, false); +} + +int parse_netmask_strict(const char *mask, struct rb_sockaddr_storage *addr, int *blen) +{ + return _parse_netmask(mask, addr, blen, true); +} + /* Hashtable stuff...now external as its used in m_stats.c */ struct AddressRec *atable[ATABLE_SIZE]; diff --git a/ircd/newconf.c b/ircd/newconf.c index 9e200fd7..cff557fc 100644 --- a/ircd/newconf.c +++ b/ircd/newconf.c @@ -1515,8 +1515,9 @@ static void conf_set_exempt_ip(void *data) { struct ConfItem *yy_tmp; + int masktype = parse_netmask_strict(data, NULL, NULL); - if(parse_netmask(data, NULL, NULL) == HM_HOST) + if(masktype != HM_IPV4 && masktype != HM_IPV6) { conf_report_error("Ignoring exempt -- invalid exempt::ip."); return; diff --git a/modules/m_dline.c b/modules/m_dline.c index cc62d572..a0d9da33 100644 --- a/modules/m_dline.c +++ b/modules/m_dline.c @@ -99,7 +99,14 @@ mo_dline(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source /* would break the protocol */ if (*dlhost == ':') { - sendto_one_notice(source_p, ":Invalid D-Line"); + sendto_one_notice(source_p, ":Invalid D-Line [%s] - IP cannot start with :", dlhost); + return; + } + + int ty = parse_netmask_strict(dlhost, NULL, NULL); + if (ty != HM_IPV4 && ty != HM_IPV6) + { + sendto_one_notice(source_p, ":Invalid D-Line [%s] - doesn't look like IP[/cidr]", dlhost); return; } @@ -216,8 +223,8 @@ apply_dline(struct Client *source_p, const char *dlhost, int tdline_time, char * int t = AF_INET, ty, b; const char *creason; - ty = parse_netmask(dlhost, &daddr, &b); - if(ty == HM_HOST) + ty = parse_netmask_strict(dlhost, &daddr, &b); + if(ty != HM_IPV4 && ty != HM_IPV6) { sendto_one(source_p, ":%s NOTICE %s :Invalid D-Line", me.name, source_p->name); return; @@ -252,8 +259,9 @@ apply_dline(struct Client *source_p, const char *dlhost, int tdline_time, char * if((aconf = find_dline((struct sockaddr *) &daddr, t)) != NULL) { int bx; - parse_netmask(aconf->host, NULL, &bx); - if(b >= bx) + int masktype = parse_netmask_strict(aconf->host, NULL, &bx); + + if (masktype != HM_ERROR && b >= bx) { creason = aconf->passwd ? aconf->passwd : ""; if(IsConfExemptKline(aconf)) @@ -354,9 +362,11 @@ apply_undline(struct Client *source_p, const char *cidr) char buf[BUFSIZE]; struct ConfItem *aconf; - if(parse_netmask(cidr, NULL, NULL) == HM_HOST) + int masktype = parse_netmask(cidr, NULL, NULL); + + if(masktype != HM_IPV4 && masktype != HM_IPV6) { - sendto_one_notice(source_p, ":Invalid D-Line"); + sendto_one_notice(source_p, ":Invalid D-Line [%s] - doesn't look like IP[/cidr]", cidr); return; } diff --git a/modules/m_kline.c b/modules/m_kline.c index 14f6bbb5..0b1d0bf3 100644 --- a/modules/m_kline.c +++ b/modules/m_kline.c @@ -153,6 +153,14 @@ mo_kline(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source reason = LOCAL_COPY(parv[loc]); + if(parse_netmask_strict(host, NULL, NULL) == HM_ERROR) + { + sendto_one_notice(source_p, + ":[%s@%s] looks like an ill-formed IP K-line, refusing to set it", + user, host); + return; + } + if(target_server != NULL) { propagate_generic(source_p, "KLINE", target_server, CAP_KLN, @@ -700,15 +708,12 @@ already_placed_kline(struct Client *source_p, const char *luser, const char *lho if(aconf == NULL && ConfigFileEntry.non_redundant_klines) { bits = 0; - if((t = parse_netmask(lhost, &iphost, &bits)) != HM_HOST) - { - if(t == HM_IPV6) - t = AF_INET6; - else - t = AF_INET; - - piphost = &iphost; - } + t = parse_netmask_strict(lhost, &iphost, &bits); + piphost = &iphost; + if (t == HM_IPV4) + t = AF_INET; + else if (t == HM_IPV6) + t = AF_INET6; else piphost = NULL; diff --git a/tests/Makefile.am b/tests/Makefile.am index 2b06e5c6..f4754f63 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,6 +1,7 @@ check_PROGRAMS = runtests \ msgbuf_parse1 \ msgbuf_unparse1 \ + hostmask1 \ rb_dictionary1 \ rb_snprintf_append1 \ rb_snprintf_try_append1 \ @@ -24,6 +25,7 @@ tap_libtap_a_SOURCES = tap/basic.c tap/basic.h \ msgbuf_parse1_SOURCES = msgbuf_parse1.c msgbuf_unparse1_SOURCES = msgbuf_unparse1.c +hostmask1_SOURCES = hostmask1.c rb_dictionary1_SOURCES = rb_dictionary1.c rb_snprintf_append1_SOURCES = rb_snprintf_append1.c rb_snprintf_try_append1_SOURCES = rb_snprintf_try_append1.c diff --git a/tests/TESTS b/tests/TESTS index 61e51d74..31cc388c 100644 --- a/tests/TESTS +++ b/tests/TESTS @@ -1,5 +1,6 @@ msgbuf_parse1 msgbuf_unparse1 +hostmask1 rb_dictionary1 rb_snprintf_append1 rb_snprintf_try_append1 diff --git a/tests/hostmask1.c b/tests/hostmask1.c new file mode 100644 index 00000000..295c8652 --- /dev/null +++ b/tests/hostmask1.c @@ -0,0 +1,218 @@ +/* + * hostmask1.c: Test parse_netmask + * Copyright 2020 Ed Kellett + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ +#include +#include +#include +#include +#include "tap/basic.h" + +#include "stdinc.h" +#include "ircd_defs.h" +#include "client.h" +#include "hostmask.h" + +#define MSG "%s:%d (%s)", __FILE__, __LINE__, __FUNCTION__ + +struct Client me; + +static void plain_hostmask(void) +{ + int ty; + + ty = parse_netmask("foo.example", NULL, NULL); + is_int(HM_HOST, ty, MSG); + + ty = parse_netmask("*.example", NULL, NULL); + is_int(HM_HOST, ty, MSG); + + ty = parse_netmask("foo.examp?", NULL, NULL); + is_int(HM_HOST, ty, MSG); +} + +static void valid_ipv4(void) +{ + int ty, nb; + struct rb_sockaddr_storage addr; + char ip[1024]; + + ty = parse_netmask("10.20.30.40", &addr, &nb); + is_int(HM_IPV4, ty, MSG); + if (ty == HM_IPV4) + { + is_string("10.20.30.40", rb_inet_ntop_sock((struct sockaddr *)&addr, ip, sizeof ip), MSG); + is_int(32, nb, MSG); + } +} + +static void invalid_ipv4(void) +{ + int ty; + + ty = parse_netmask(".1", NULL, NULL); + is_int(HM_HOST, ty, MSG); + + ty = parse_netmask("10.20.30", NULL, NULL); + is_int(HM_HOST, ty, MSG); + + ty = parse_netmask("10.20.30.40.50", NULL, NULL); + is_int(HM_HOST, ty, MSG); +} + +static void valid_ipv4_cidr(void) +{ + int ty, nb; + struct rb_sockaddr_storage addr; + char ip[1024]; + + ty = parse_netmask("10.20.30.40/7", &addr, &nb); + is_int(HM_IPV4, ty, MSG); + if (ty == HM_IPV4) + { + is_string("10.20.30.40", rb_inet_ntop_sock((struct sockaddr *)&addr, ip, sizeof ip), MSG); + is_int(7, nb, MSG); + } +} + +static void invalid_ipv4_cidr(void) +{ + int ty, nb; + + ty = parse_netmask("10.20.30.40/aaa", NULL, NULL); + is_int(HM_HOST, ty, MSG); + + ty = parse_netmask("10.20.30.40/-1", NULL, NULL); + is_int(HM_HOST, ty, MSG); + + ty = parse_netmask("10.20.30.40/1000", NULL, &nb); + is_int(HM_IPV4, ty, MSG); + is_int(32, nb, MSG); + + ty = parse_netmask("10.20.30.40/1a", NULL, &nb); + is_int(HM_IPV4, ty, MSG); + is_int(32, nb, MSG); + + ty = parse_netmask_strict("10.20.30.40/1000", NULL, NULL); + is_int(HM_ERROR, ty, MSG); + + ty = parse_netmask_strict("10.20.30.40/1a", NULL, NULL); + is_int(HM_ERROR, ty, MSG); +} + +static void valid_ipv6(void) +{ + int ty, nb; + struct rb_sockaddr_storage addr; + char ip[1024]; + + ty = parse_netmask("1:2:3:4:5:6:7:8", &addr, &nb); + is_int(HM_IPV6, ty, MSG); + if (ty == HM_IPV6) + { + is_string("1:2:3:4:5:6:7:8", rb_inet_ntop_sock((struct sockaddr *)&addr, ip, sizeof ip), MSG); + is_int(128, nb, MSG); + } + + ty = parse_netmask("1:2::7:8", &addr, &nb); + is_int(HM_IPV6, ty, MSG); + if (ty == HM_IPV6) + { + is_string("1:2::7:8", rb_inet_ntop_sock((struct sockaddr *)&addr, ip, sizeof ip), MSG); + is_int(128, nb, MSG); + } +} + +static void invalid_ipv6(void) +{ + int ty; + + ty = parse_netmask(":1", NULL, NULL); + is_int(HM_HOST, ty, MSG); + + ty = parse_netmask("1:2:3:4:5", NULL, NULL); + is_int(HM_HOST, ty, MSG); + + ty = parse_netmask("1:2:3:4:5:6:7:8:9", NULL, NULL); + is_int(HM_HOST, ty, MSG); +} + +static void valid_ipv6_cidr(void) +{ + int ty, nb; + struct rb_sockaddr_storage addr; + char ip[1024]; + + ty = parse_netmask("1:2:3:4:5:6:7:8/96", &addr, &nb); + is_int(HM_IPV6, ty, MSG); + if (ty == HM_IPV6) + { + is_string("1:2:3:4:5:6:7:8", rb_inet_ntop_sock((struct sockaddr *)&addr, ip, sizeof ip), MSG); + is_int(96, nb, MSG); + } +} + +static void invalid_ipv6_cidr(void) +{ + int ty, nb; + + ty = parse_netmask("1:2::7:8/aaa", NULL, NULL); + is_int(HM_HOST, ty, MSG); + + ty = parse_netmask("1:2::7:8/-1", NULL, NULL); + is_int(HM_HOST, ty, MSG); + + ty = parse_netmask("1:2::7:8/1000", NULL, &nb); + is_int(HM_IPV6, ty, MSG); + is_int(128, nb, MSG); + + ty = parse_netmask("1:2::7:8/1a", NULL, &nb); + is_int(HM_IPV6, ty, MSG); + is_int(128, nb, MSG); + + ty = parse_netmask_strict("1:2::7:8/1000", NULL, NULL); + is_int(HM_ERROR, ty, MSG); + + ty = parse_netmask_strict("1:2::7:8/1a", NULL, NULL); + is_int(HM_ERROR, ty, MSG); +} + +int main(int argc, char *argv[]) +{ + memset(&me, 0, sizeof(me)); + strcpy(me.name, "me.name."); + + rb_lib_init(NULL, NULL, NULL, 0, 1024, DNODE_HEAP_SIZE, FD_HEAP_SIZE); + rb_linebuf_init(LINEBUF_HEAP_SIZE); + + plan_lazy(); + + plain_hostmask(); + + valid_ipv4(); + invalid_ipv4(); + valid_ipv4_cidr(); + invalid_ipv4_cidr(); + + valid_ipv6(); + invalid_ipv6(); + valid_ipv6_cidr(); + invalid_ipv6_cidr(); + + return 0; +}