diff --git a/help/opers/searchkline b/help/opers/searchkline new file mode 100644 index 000000000..f0da8762b --- /dev/null +++ b/help/opers/searchkline @@ -0,0 +1,5 @@ +SEARCHKLINE [user@]host + +Given a ban mask, report any K/D-lines that match a subset +of that mask. For example, SEARCHKLINE *@*ample* will report +a match for a K-line on foo@example.com. diff --git a/include/hostmask.h b/include/hostmask.h index 33b0e5f61..d53eb5c5c 100644 --- a/include/hostmask.h +++ b/include/hostmask.h @@ -65,6 +65,8 @@ struct ConfItem *find_dline(struct sockaddr *, int); void report_auth(struct Client *); int match_ipv6(struct sockaddr *, struct sockaddr *, int); int match_ipv4(struct sockaddr *, struct sockaddr *, int); +unsigned long hash_ipv6(struct sockaddr *, int); +unsigned long hash_ipv4(struct sockaddr *, int); /* Hashtable stuff... */ #define ATABLE_SIZE 0x1000 /* 2^12 */ diff --git a/ircd/hostmask.c b/ircd/hostmask.c index 1a154130d..3b0999936 100644 --- a/ircd/hostmask.c +++ b/ircd/hostmask.c @@ -32,9 +32,6 @@ #include "send.h" #include "match.h" -static unsigned long hash_ipv6(struct sockaddr *, int); -static unsigned long hash_ipv4(struct sockaddr *, int); - static int _parse_netmask(const char *text, struct rb_sockaddr_storage *naddr, int *nb, bool strict) @@ -140,7 +137,7 @@ init_host_hash(void) * Output: A hash value of the IP address. * Side effects: None */ -static unsigned long +unsigned long hash_ipv4(struct sockaddr *saddr, int bits) { struct sockaddr_in *addr = (struct sockaddr_in *)(void *)saddr; @@ -159,7 +156,7 @@ hash_ipv4(struct sockaddr *saddr, int bits) * Output: A hash value of the IP address. * Side effects: None */ -static unsigned long +unsigned long hash_ipv6(struct sockaddr *saddr, int bits) { struct sockaddr_in6 *addr = (struct sockaddr_in6 *)(void *)saddr; diff --git a/modules/Makefile.am b/modules/Makefile.am index 3ebe628fc..8a58ba5e4 100644 --- a/modules/Makefile.am +++ b/modules/Makefile.am @@ -57,6 +57,7 @@ auto_load_mod_LTLIBRARIES = \ m_resv.la \ m_sasl.la \ m_scan.la \ + m_searchkline.la \ m_services.la \ m_set.la \ m_signon.la \ diff --git a/modules/m_searchkline.c b/modules/m_searchkline.c new file mode 100644 index 000000000..17d23a41a --- /dev/null +++ b/modules/m_searchkline.c @@ -0,0 +1,216 @@ +/* + * Copyright (C) 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 +#include "hash.h" +#include +#include +#include +#include +#include + +static const char searchkline_desc[] = "Provides the ability to search for K/D-lines"; + +static void mo_searchkline(struct MsgBuf *, struct Client *, struct Client *, int, const char **); + +struct Message searchkline_msgtab = { + "SEARCHKLINE", 0, 0, 0, 0, + {mg_unreg, mg_not_oper, mg_ignore, mg_ignore, mg_ignore, {mo_searchkline, 2}} +}; + +mapi_clist_av1 searchkline_clist[] = { &searchkline_msgtab, NULL }; + +DECLARE_MODULE_AV2(searchkline, NULL, NULL, searchkline_clist, NULL, NULL, NULL, NULL, searchkline_desc); + +static void +report_kdline(struct Client *client, int type, struct ConfItem *aconf) +{ + char *puser, *phost, *reason, *operreason, *mask; + char userhost[BUFSIZE]; + char reasonbuf[BUFSIZE]; + char letter; + + switch (type) + { + case CONF_KILL: + letter = 'K'; + break; + case CONF_DLINE: + letter = 'D'; + break; + } + + get_printable_kline(client, aconf, &phost, &reason, &puser, &operreason); + if (operreason != NULL) + { + snprintf(reasonbuf, sizeof reasonbuf, "%s!%s", reason, operreason); + reason = reasonbuf; + } + + if (!EmptyString(aconf->user)) + { + snprintf(userhost, sizeof userhost, "%s@%s", puser, phost); + mask = userhost; + } + else + { + mask = phost; + } + + sendto_one(client, form_str(RPL_TESTLINE), + me.name, client->name, + (aconf->flags & CONF_FLAGS_TEMPORARY) ? tolower(letter) : letter, + (aconf->flags & CONF_FLAGS_TEMPORARY) ? + (long) ((aconf->hold - rb_current_time()) / 60) : 0L, + mask, reason); +} + + +static bool +search_ip_kdlines(struct Client *client, const char *username, struct sockaddr *ip, int blen, int fam) +{ + struct sockaddr_in ip4; + bool found = false; + struct AddressRec *arec; + int masktype = fam == AF_INET ? HM_IPV4 : HM_IPV6; + bool match_dlines = mask_match(username, "*"); + + size_t i; + size_t min = 0; + size_t max = ARRAY_SIZE(atable); + + if (fam == AF_INET && blen == 32) + { + min = max = hash_ipv4(ip, 32); + } + else if (fam == AF_INET6 && blen == 128) + { + min = max = hash_ipv6(ip, 128); + } + + for (i = min; i < max; i++) + { + for (arec = atable[i]; arec; arec = arec->next) + { + if ((arec->type != CONF_DLINE && arec->type != CONF_KILL) || + arec->masktype != masktype || + arec->Mask.ipa.bits < blen || + !comp_with_mask_sock(ip, (struct sockaddr *)&arec->Mask.ipa.addr, blen)) + continue; + + if (arec->type == CONF_KILL && !mask_match(username, arec->username)) + continue; + if (arec->type == CONF_DLINE && !match_dlines) + continue; + + report_kdline(client, arec->type, arec->aconf); + found = true; + } + } + + if (fam == AF_INET6 && blen == 128 && + rb_ipv4_from_ipv6((struct sockaddr_in6 *)ip, &ip4)) + { + found = found || search_ip_kdlines(client, username, (struct sockaddr *)&ip4, 32, AF_INET); + } + + return found; +} + +static void +mo_searchkline(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source_p, int parc, const char *parv[]) +{ + struct rb_sockaddr_storage ip; + const char *username = NULL; + const char *host = NULL; + char *mask; + char *p; + int blen; + int type; + bool found = false; + size_t i; + struct AddressRec *arec; + + if (!HasPrivilege(source_p, "oper:testline")) + { + sendto_one(source_p, form_str(ERR_NOPRIVS), + me.name, source_p->name, "testline"); + return; + } + + mask = LOCAL_COPY(parv[1]); + + if ((p = strchr(mask, '@'))) + { + *p++ = '\0'; + username = mask; + host = p; + + if(EmptyString(host)) + return; + } + else + { + host = mask; + } + + if (username == NULL) + { + username = "*"; + } + + /* parses as an IP, check for IP bans */ + if ((type = parse_netmask(host, &ip, &blen)) != HM_HOST) + { + if(type == HM_IPV6) + found = found || search_ip_kdlines(source_p, username, (struct sockaddr *)&ip, blen, AF_INET6); + else + found = found || search_ip_kdlines(source_p, username, (struct sockaddr *)&ip, blen, AF_INET); + } + + /* all that's left is to check host-like K-lines */ + for (i = 0; i < ARRAY_SIZE(atable); i++) + { + for (arec = atable[i]; arec; arec = arec->next) + { + if (arec->type != CONF_KILL || + (type != HM_HOST && arec->masktype != HM_HOST) || + !mask_match(host, arec->aconf->host) || + !mask_match(username, arec->username)) + continue; + + report_kdline(source_p, arec->type, arec->aconf); + found = true; + } + } + + if (found) + { + sendto_one(source_p, form_str(RPL_TESTLINE), + me.name, source_p->name, + '*', 0l, "*", "End of search results"); + return; + } + + sendto_one(source_p, form_str(RPL_NOTESTLINE), + me.name, source_p->name, parv[1]); +}